feat: add code
parent
07545d30db
commit
289b635077
|
|
@ -153,3 +153,5 @@ fabric.properties
|
|||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
.mvn/
|
||||
target/
|
||||
136
README.md
136
README.md
|
|
@ -1,2 +1,136 @@
|
|||
# EntityComponentSystem
|
||||
# Entity Component System
|
||||
|
||||
Nachdem das von ihnen entwickelte Spiel Racewars veröffentlicht wurde, bekommen sie einige beschwerden über schlechte Performance. Obwohl sie der Meinung sind, dass sich die Leute einfach einen neuen PC kaufen sollen, will ihr Arbeitgeber, dass sie das Spiel optimieren.
|
||||
|
||||
Nach einiger Recherche stellen sie fest, dass die derzeitige Implementierung der Wesen Klasse zu schlechter Performance führt.
|
||||
|
||||
Ihre Wesen sehen im Arbeitsspeicher etwa so aus:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Wesen Objekt │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ vtable pointer (8 bytes) │
|
||||
│ │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ geschwindigkeit : int (4 bytes) │
|
||||
│ │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ schaden : int (4 bytes) │
|
||||
│ │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ruestung : int (4 bytes) │
|
||||
│ │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ lebenspunkte : double (8 bytes) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Prozessoren laden Daten aus dem Arbeitsspeicher allerdings meist in Blöcken von 64 Byte. Werden nun zum Beipiel bei der Ausgabe der Einheiten nur die Lebenspunkte von jeder Einheit gebraucht, muss der CPU das gesamte Objekt aus dem Arbeitsspeicher laden, obwohl ein Großteil nicht gebraucht wird.
|
||||
|
||||
In vielen modernen Computerspielen wird diese ineffizienz umgangen, indem Spielobjekte nicht als einzelne Java-Objekte, sondern als eine Ansammlung von Komponenten gespeichert werden:
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────────────────┐
|
||||
│ Entity Component System (ECS) Layout │
|
||||
├───────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Entity Table │
|
||||
│ ┌────────────┬────────────┬────────────┬────────────┐ │
|
||||
│ │ Entity 1 │ Entity 2 │ Entity 3 │ Entity 4 │ (int[], 4 bytes) │
|
||||
│ └────────────┴────────────┴────────────┴────────────┘ │
|
||||
│ │
|
||||
├───────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Geschwindigkeit Component │
|
||||
│ ┌────────────┬────────────┬────────────┬────────────┐ │
|
||||
│ │ 25 │ 40 │ 30 │ 35 │ (int[], 4 bytes) │
|
||||
│ └────────────┴────────────┴────────────┴────────────┘ │
|
||||
│ │
|
||||
├───────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Schaden Component │
|
||||
│ ┌────────────┬────────────┬────────────┬────────────┐ │
|
||||
│ │ 10 │ 15 │ 12 │ 18 │ (int[], 4 bytes) │
|
||||
│ └────────────┴────────────┴────────────┴────────────┘ │
|
||||
│ │
|
||||
├───────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Ruestung Component │
|
||||
│ ┌────────────┬────────────┬────────────┬────────────┐ │
|
||||
│ │ 5 │ 8 │ 6 │ 3 │ (int[], 4 bytes) │
|
||||
│ └────────────┴────────────┴────────────┴────────────┘ │
|
||||
│ │
|
||||
├───────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Lebenspunkte Component (Structure of Arrays): │
|
||||
│ ┌────────────┬────────────┬────────────┬────────────┐ │
|
||||
│ │ 100.0 │ 85.5 │ 92.3 │ 78.9 │ (double[], 8 bytes)│
|
||||
│ └────────────┴────────────┴────────────┴────────────┘ │
|
||||
│ │
|
||||
└───────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Werden jetzt wie im vorherigen Beispiel die Lebenspunkte aller Wesen gebraucht, liegen diese im Arbeitsspeicher nebeneinander.
|
||||
|
||||
Ein Entity-Component-System besteht aus vier Konzepten:
|
||||
|
||||
### Entities
|
||||
|
||||
Entities sind Spielobjekte, sie sind meist nicht mehr als eine fortlaufende ID.
|
||||
|
||||
### Components
|
||||
|
||||
Components sind die zum Spielobjekt gehörenden Daten. Wie viele Daten jede Art von Component enthält kann verschieden sein.
|
||||
Zum Beispiel kann ein `Lebenspunkte` Component nur die derzeitigen Lebenspunkte enthalten, während ein `Kampfkraft` Component Schaden, Geschwindigkeit und Rüstung enthält.
|
||||
|
||||
### Registry
|
||||
|
||||
Die Registry ist eine Datenbank, die alle Entities ihren Components zuweist und folgende Operationen Erlaubt:
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ registerComponentType(componentClass) │
|
||||
│ └─ Registriert eine neue Art von Component zur Verwaltung │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ addComponent(entity, component) │
|
||||
│ └─ Fügt der Entity einen neuen Component hinzu │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ removeComponent(entity, componentType) │
|
||||
│ └─ Löscht den angegebenen Component von der Entity │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ hasComponent(entity, componentType) → boolean │
|
||||
│ └─ Prüft, ob die Entity einen Component diesen Types hat │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ getComponent(entity, componentType) → component │
|
||||
│ └─ Gibt den angegebenen Component für die Entity zurück │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ withComponents(componentType...) → entity[] │
|
||||
│ └─ Gibt alle Entities zurück, die alle angegebenen Components haben │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ removeEntity(entity) │
|
||||
│ └─ Löscht die Entity mit allen ihren Components │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Sytems
|
||||
|
||||
Systems bilden die Anwendungslogik ab. Sie können die Registry nach Entities abfragen und neue Entities oder Components hinzufügen
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>racewars</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<name>racewars</name>
|
||||
<!-- FIXME change it to the project's website -->
|
||||
<url>http://www.example.com</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.release>25</maven.compiler.release>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
<version>5.11.0</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Optionally: parameterized tests support -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
|
||||
<plugins>
|
||||
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
|
||||
<plugin>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
</plugin>
|
||||
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.4.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
|
||||
<plugin>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
<version>3.12.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||
<version>3.6.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>1.4.0</version>
|
||||
<configuration>
|
||||
<mainClass>com.example.App</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.example;
|
||||
|
||||
import com.example.controller.GameController;
|
||||
|
||||
/**
|
||||
* Hello world!
|
||||
*/
|
||||
public class App {
|
||||
public static void main(String[] args) {
|
||||
GameController gc = new GameController();
|
||||
gc.runGame();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.example.components;
|
||||
|
||||
import com.example.ecs.Component;
|
||||
import com.example.ecs.Entity;
|
||||
|
||||
public record Attack(
|
||||
Entity source,
|
||||
Entity target,
|
||||
double damage
|
||||
) implements Component {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.example.components;
|
||||
|
||||
import com.example.ecs.Component;
|
||||
import com.example.ecs.Entity;
|
||||
|
||||
public record Battle(Entity squad1, Entity squad2) implements Component {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.example.components;
|
||||
|
||||
import com.example.ecs.Component;
|
||||
import com.example.ecs.Entity;
|
||||
|
||||
public record CombatLog (
|
||||
Entity attacker,
|
||||
Entity defender,
|
||||
double initialDamage,
|
||||
double resultingDamage
|
||||
) implements Component {}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.example.components;
|
||||
|
||||
import com.example.ecs.Component;
|
||||
|
||||
public class Health implements Component {
|
||||
private final double maxHp;
|
||||
private double hp;
|
||||
|
||||
public Health(double maxHp, double hp) {
|
||||
this.maxHp = maxHp;
|
||||
this.hp = hp;
|
||||
}
|
||||
|
||||
public double hp() {
|
||||
return hp;
|
||||
}
|
||||
|
||||
public double maxHp() {
|
||||
return maxHp;
|
||||
}
|
||||
|
||||
public void lowerHp(double amount) {
|
||||
hp -= amount;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.example.components;
|
||||
|
||||
import com.example.ecs.Component;
|
||||
import com.example.model.Element;
|
||||
|
||||
public record Hero(
|
||||
String name,
|
||||
double bonus,
|
||||
Element element
|
||||
) implements Component {
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.example.components;
|
||||
|
||||
import com.example.ecs.Component;
|
||||
|
||||
public record HumanDefense(
|
||||
double defense
|
||||
) implements Component {}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.example.components;
|
||||
|
||||
import com.example.ecs.Component;
|
||||
import com.example.ecs.Entity;
|
||||
import com.example.model.Race;
|
||||
|
||||
public record Investment(
|
||||
int amount,
|
||||
Race race,
|
||||
boolean witLeader,
|
||||
Entity forSquad
|
||||
) implements Component {
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.example.components;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.example.ecs.Component;
|
||||
import com.example.ecs.Entity;
|
||||
|
||||
public class Squad implements Component {
|
||||
private String name;
|
||||
private List<Entity> units;
|
||||
|
||||
public Squad(String name, List<Entity> units) {
|
||||
this.name = name;
|
||||
this.units = units;
|
||||
}
|
||||
|
||||
public void removeUnit(Entity entity) {
|
||||
units.remove(entity);
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<Entity> units() {
|
||||
return units;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.example.components;
|
||||
|
||||
import com.example.ecs.Component;
|
||||
import com.example.model.Race;
|
||||
|
||||
public record Stats(
|
||||
double dmg,
|
||||
double speed,
|
||||
double armor,
|
||||
Race race
|
||||
) implements Component {
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.example.controller;
|
||||
|
||||
|
||||
import com.example.components.Attack;
|
||||
import com.example.components.Battle;
|
||||
import com.example.components.CombatLog;
|
||||
import com.example.components.Health;
|
||||
import com.example.components.Hero;
|
||||
import com.example.components.HumanDefense;
|
||||
import com.example.components.Investment;
|
||||
import com.example.components.Squad;
|
||||
import com.example.components.Stats;
|
||||
import com.example.ecs.Entity;
|
||||
import com.example.ecs.Registry;
|
||||
import com.example.ecs.System;
|
||||
import com.example.model.Race;
|
||||
import com.example.systems.CleanupSystem;
|
||||
import com.example.systems.CombatSystem;
|
||||
import com.example.systems.DamageSystem;
|
||||
import com.example.systems.RenderSystem;
|
||||
import com.example.systems.SquadGenerationSystem;
|
||||
|
||||
public class GameController {
|
||||
|
||||
Registry registry = new Registry();
|
||||
SquadGenerationSystem squadGenerationSystem;
|
||||
System combatSystem;
|
||||
System damageSystem;
|
||||
System renderSystem;
|
||||
CleanupSystem cleanupSystem;
|
||||
|
||||
public GameController() {
|
||||
registry.registerComponentType(Attack.class);
|
||||
registry.registerComponentType(Battle.class);
|
||||
registry.registerComponentType(CombatLog.class);
|
||||
registry.registerComponentType(Health.class);
|
||||
registry.registerComponentType(Hero.class);
|
||||
registry.registerComponentType(HumanDefense.class);
|
||||
registry.registerComponentType(Investment.class);
|
||||
registry.registerComponentType(Squad.class);
|
||||
registry.registerComponentType(Stats.class);
|
||||
|
||||
|
||||
squadGenerationSystem = new SquadGenerationSystem(registry);
|
||||
combatSystem = new CombatSystem(registry);
|
||||
damageSystem = new DamageSystem(registry);
|
||||
renderSystem = new RenderSystem(registry);
|
||||
cleanupSystem = new CleanupSystem(registry);
|
||||
|
||||
Entity squad1 = new Entity();
|
||||
Entity squad2 = new Entity();
|
||||
|
||||
registry.addComponent(new Entity(), new Investment(2000, Race.Human, true, squad1));
|
||||
registry.addComponent(new Entity(), new Investment(2000, Race.Orc, true, squad2));
|
||||
|
||||
squadGenerationSystem.generateSquad(squad1, "Squad1", 2000);
|
||||
squadGenerationSystem.generateSquad(squad2, "Squad2", 2000);
|
||||
|
||||
registry.addComponent(new Entity(), new Battle(squad1, squad2));
|
||||
}
|
||||
|
||||
public void runGame() {
|
||||
while (!cleanupSystem.gameEnded()) {
|
||||
combatSystem.run();
|
||||
damageSystem.run();
|
||||
renderSystem.run();
|
||||
cleanupSystem.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package com.example.ecs;
|
||||
|
||||
public interface Component {
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.example.ecs;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class ComponentManager<T extends Component> {
|
||||
private final Map<Integer, T> entityComponents;
|
||||
|
||||
protected ComponentManager() {
|
||||
entityComponents = new HashMap<>();
|
||||
}
|
||||
|
||||
protected void addComponent(Entity entity, T component) {
|
||||
entityComponents.put(entity.getId(), component);
|
||||
}
|
||||
|
||||
protected void removeComponent(Entity entity) {
|
||||
entityComponents.remove(entity.id);
|
||||
}
|
||||
|
||||
protected T getComponent(Entity entity) {
|
||||
return entityComponents.get(Integer.valueOf(entity.getId()));
|
||||
}
|
||||
|
||||
public boolean hasComponent(Entity entity) {
|
||||
return entityComponents.containsKey(entity.getId());
|
||||
}
|
||||
|
||||
protected Set<Entity> getEntities() {
|
||||
return entityComponents.keySet().stream()
|
||||
.map((Integer id) -> { return new Entity(id); })
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.example.ecs;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Entity {
|
||||
static int count = 0;
|
||||
int id;
|
||||
|
||||
public Entity() {
|
||||
this.id = count++;
|
||||
}
|
||||
|
||||
protected Entity(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof Entity e) {
|
||||
return this.getId() == e.getId();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Entity{" + "id=" + id + '}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.example.ecs;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
public class Registry {
|
||||
HashMap<Class<? extends Component>, ComponentManager<?>> componentManagers = new HashMap<>();
|
||||
|
||||
public <T extends Component> void registerComponentType(Class<T> dataClass) {
|
||||
componentManagers.put(dataClass, new ComponentManager<T>());
|
||||
}
|
||||
|
||||
public <T extends Component> void addComponent(Entity entity, T component) {
|
||||
Class<T> componentClass = (Class<T>) component.getClass();
|
||||
getComponentManager(componentClass).addComponent(entity, component);
|
||||
}
|
||||
|
||||
public <T extends Component> T getComponent(Entity entity, Class<T> componentClass) {
|
||||
return getComponentManager(componentClass).getComponent(entity);
|
||||
}
|
||||
|
||||
public <T extends Component> boolean hasComponent(Entity entity, Class<T> componentClass) {
|
||||
try {
|
||||
var component = getComponentManager(componentClass).getComponent(entity);
|
||||
return component != null;
|
||||
} catch(Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Entity[] getWithComponents(Class<? extends Component>... components) {
|
||||
if(components.length == 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
Set<Entity> entitySet = getComponentManager(components[0]).getEntities();
|
||||
for (int i = 1; i < components.length; i++) {
|
||||
entitySet.retainAll(getComponentManager(components[i]).getEntities());
|
||||
}
|
||||
return entitySet.toArray(new Entity[0]);
|
||||
}
|
||||
|
||||
public void remove(Entity entity) {
|
||||
for (var manager : componentManagers.values()) {
|
||||
manager.removeComponent(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeComponent(Entity entity, Class<? extends Component> component) {
|
||||
getComponentManager(component).removeComponent(entity);
|
||||
}
|
||||
|
||||
private <T extends Component> ComponentManager<T> getComponentManager(Class<T> componentClass) {
|
||||
Object manager = componentManagers.get(componentClass);
|
||||
if (manager == null) {
|
||||
throw new IllegalArgumentException("No component manager for: " + componentClass);
|
||||
}
|
||||
return (ComponentManager<T>) manager;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.example.ecs;
|
||||
|
||||
public abstract class System {
|
||||
protected final Registry registry;
|
||||
|
||||
public System(Registry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
public abstract void run();
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.example.model;
|
||||
|
||||
public enum Element {
|
||||
Fire,
|
||||
Water,
|
||||
Earth,
|
||||
Air
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.example.model;
|
||||
|
||||
import com.example.ecs.Entity;
|
||||
import com.example.ecs.Registry;
|
||||
|
||||
public interface Factory {
|
||||
|
||||
/**
|
||||
* Erzeugt ein neues Wesen der Rasse.
|
||||
* @return das Wesen.
|
||||
*/
|
||||
Entity createUnit(Registry registry);
|
||||
|
||||
/**
|
||||
* Liefert den Anführer der Rasse.
|
||||
* @return Anführer.
|
||||
*/
|
||||
Entity createHero(Registry registry);
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.example.model;
|
||||
|
||||
import com.example.components.Health;
|
||||
import com.example.components.Hero;
|
||||
import com.example.components.HumanDefense;
|
||||
import com.example.components.Stats;
|
||||
import com.example.ecs.Entity;
|
||||
import com.example.ecs.Registry;
|
||||
|
||||
public class HumanFactory implements Factory {
|
||||
|
||||
@Override
|
||||
public Entity createUnit(Registry registry) {
|
||||
Stats stats = new Stats(40, 2, 0.4, Race.Human);
|
||||
Health health = new Health(140, 140);
|
||||
HumanDefense humanDefense = new HumanDefense(0.1);
|
||||
Entity human = new Entity();
|
||||
registry.addComponent(human, stats);
|
||||
registry.addComponent(human, health);
|
||||
registry.addComponent(human, humanDefense);
|
||||
return human;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity createHero(Registry registry) {
|
||||
Stats stats = new Stats(40, 2, 0.4, Race.Human);
|
||||
Health health = new Health(140 * 5, 140 * 5);
|
||||
HumanDefense humanDefense = new HumanDefense(0.1);
|
||||
Hero hero = new Hero("Archmage", 5, Element.Fire);
|
||||
Entity human = new Entity();
|
||||
registry.addComponent(human, stats);
|
||||
registry.addComponent(human, health);
|
||||
registry.addComponent(human, humanDefense);
|
||||
registry.addComponent(human, hero);
|
||||
return human;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.example.model;
|
||||
|
||||
import com.example.components.Health;
|
||||
import com.example.components.Hero;
|
||||
import com.example.components.Stats;
|
||||
import com.example.ecs.Entity;
|
||||
import com.example.ecs.Registry;
|
||||
|
||||
public class OrcFactory implements Factory {
|
||||
|
||||
@Override
|
||||
public Entity createUnit(Registry registry) {
|
||||
Stats stats = new Stats(33, 1, 0.3, Race.Orc);
|
||||
Health health = new Health(140, 140);
|
||||
Entity orc = new Entity();
|
||||
registry.addComponent(orc, stats);
|
||||
registry.addComponent(orc, health);
|
||||
return orc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity createHero(Registry registry) {
|
||||
Stats stats = new Stats(33, 1, 0.3, Race.Orc);
|
||||
Health health = new Health(140 * 1.2, 140 * 1.2);
|
||||
Hero hero = new Hero("Farseer", 1.2, Element.Earth);
|
||||
Entity orc = new Entity();
|
||||
registry.addComponent(orc, stats);
|
||||
registry.addComponent(orc, health);
|
||||
registry.addComponent(orc, hero);
|
||||
return orc;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.example.model;
|
||||
|
||||
public enum Race {
|
||||
Human {
|
||||
@Override
|
||||
public Factory getFactory() {
|
||||
return humanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 110;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeaderCost() {
|
||||
return 220;
|
||||
}
|
||||
},
|
||||
Orc {
|
||||
@Override
|
||||
public Factory getFactory() {
|
||||
return orcFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 150;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeaderCost() {
|
||||
return 300;
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
static Factory humanFactory = new HumanFactory();
|
||||
static Factory orcFactory = new OrcFactory();
|
||||
|
||||
public abstract Factory getFactory();
|
||||
public abstract int getCost();
|
||||
public abstract int getLeaderCost();
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.example.systems;
|
||||
|
||||
import com.example.components.Attack;
|
||||
import com.example.components.Battle;
|
||||
import com.example.components.Health;
|
||||
import com.example.ecs.Registry;
|
||||
import com.example.ecs.System;
|
||||
import com.example.components.Squad;
|
||||
|
||||
public class CleanupSystem extends System {
|
||||
|
||||
private boolean gameEnded = false;
|
||||
|
||||
public CleanupSystem(Registry registry) {
|
||||
super(registry);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
||||
var attacks = registry.getWithComponents(Attack.class);
|
||||
for (var attack : attacks) {
|
||||
registry.remove(attack);
|
||||
}
|
||||
|
||||
var units = registry.getWithComponents(Health.class);
|
||||
for (var unit : units) {
|
||||
var health = registry.getComponent(unit, Health.class);
|
||||
if (health.hp() < 0) {
|
||||
var squads = registry.getWithComponents(Squad.class);
|
||||
for (var squadEntity : squads) {
|
||||
var squad = registry.getComponent(squadEntity, Squad.class);
|
||||
squad.removeUnit(unit);
|
||||
}
|
||||
registry.remove(unit);
|
||||
}
|
||||
}
|
||||
|
||||
var battles = registry.getWithComponents(Battle.class);
|
||||
for (var battleEntity : battles) {
|
||||
var battle = registry.getComponent(battleEntity, Battle.class);
|
||||
var squad1 = registry.getComponent(battle.squad1(), Squad.class);
|
||||
var squad2 = registry.getComponent(battle.squad2(), Squad.class);
|
||||
if (squad1.units().isEmpty() || squad2.units().isEmpty()) {
|
||||
registry.remove(battleEntity);
|
||||
registry.remove(battle.squad1());
|
||||
registry.remove(battle.squad2());
|
||||
}
|
||||
}
|
||||
|
||||
if (registry.getWithComponents(Battle.class).length == 0) {
|
||||
gameEnded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean gameEnded() {
|
||||
return gameEnded;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.example.systems;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import com.example.components.Attack;
|
||||
import com.example.components.Battle;
|
||||
import com.example.components.Hero;
|
||||
import com.example.components.Squad;
|
||||
import com.example.components.Stats;
|
||||
import com.example.ecs.Entity;
|
||||
import com.example.ecs.Registry;
|
||||
import com.example.ecs.System;
|
||||
|
||||
public class CombatSystem extends System {
|
||||
|
||||
public CombatSystem(Registry registry) {
|
||||
super(registry);
|
||||
}
|
||||
|
||||
Random rng = new Random();
|
||||
|
||||
public void run() {
|
||||
for (var battleEntity : registry.getWithComponents(Battle.class)) {
|
||||
var battle = registry.getComponent(battleEntity, Battle.class);
|
||||
|
||||
var squad1 = registry.getComponent(battle.squad1(), Squad.class);
|
||||
var squad2 = registry.getComponent(battle.squad2(), Squad.class);
|
||||
|
||||
attack(squad1, squad2);
|
||||
attack(squad2, squad1);
|
||||
}
|
||||
}
|
||||
|
||||
private void attack(Squad attacker, Squad defender) {
|
||||
for (var entity : attacker.units()) {
|
||||
var unitData = registry.getComponent(entity, Stats.class);
|
||||
boolean isHero = registry.hasComponent(entity, Hero.class);
|
||||
|
||||
var target = defender.units().get(rng.nextInt(defender.units().size()));
|
||||
double damageDealt = unitData.dmg() * unitData.speed();
|
||||
if (isHero) {
|
||||
var heroData = registry.getComponent(entity, Hero.class);
|
||||
damageDealt *= heroData.bonus();
|
||||
}
|
||||
|
||||
registry.addComponent(new Entity(), new Attack(entity, target, damageDealt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.example.systems;
|
||||
|
||||
import com.example.components.Attack;
|
||||
import com.example.components.CombatLog;
|
||||
import com.example.components.Health;
|
||||
import com.example.components.HumanDefense;
|
||||
import com.example.components.Stats;
|
||||
import com.example.ecs.Entity;
|
||||
import com.example.ecs.Registry;
|
||||
import com.example.ecs.System;
|
||||
|
||||
public class DamageSystem extends System {
|
||||
public DamageSystem(Registry registry) {
|
||||
super(registry);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
for (Entity attackEntity : registry.getWithComponents(Attack.class)) {
|
||||
Attack attack = registry.getComponent(attackEntity, Attack.class);
|
||||
Stats targetStats = registry.getComponent(attack.target(), Stats.class);
|
||||
Health targetHealth = registry.getComponent(attack.target(), Health.class);
|
||||
|
||||
double dmgDealt = attack.damage() * (1 - targetStats.armor());
|
||||
if (registry.hasComponent(attack.target(), HumanDefense.class)) {
|
||||
HumanDefense defense = registry.getComponent(attack.target(), HumanDefense.class);
|
||||
dmgDealt -= dmgDealt * defense.defense();
|
||||
}
|
||||
|
||||
targetHealth.lowerHp(dmgDealt);
|
||||
registry.addComponent(attackEntity, new CombatLog(attack.source(), attack.target(), attack.damage(), dmgDealt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.example.systems;
|
||||
|
||||
import com.example.components.CombatLog;
|
||||
import com.example.components.Health;
|
||||
import com.example.components.Hero;
|
||||
import com.example.components.Squad;
|
||||
import com.example.ecs.Entity;
|
||||
import com.example.ecs.Registry;
|
||||
import com.example.ecs.System;
|
||||
import com.example.components.Stats;
|
||||
|
||||
public class RenderSystem extends System {
|
||||
|
||||
public RenderSystem(Registry registry) {
|
||||
super(registry);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
renderCombat();
|
||||
|
||||
var squads = registry.getWithComponents(Squad.class);
|
||||
|
||||
for (var squad : squads) {
|
||||
var squadData = registry.getComponent(squad, Squad.class);
|
||||
renderSquad(squadData);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateName(Entity entity) {
|
||||
if (registry.hasComponent(entity, Hero.class)) {
|
||||
Hero herodata = registry.getComponent(entity, Hero.class);
|
||||
return herodata.name();
|
||||
} else if (registry.hasComponent(entity, Stats.class)) {
|
||||
Stats stats = registry.getComponent(entity, Stats.class);
|
||||
return stats.race().toString();
|
||||
} else {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private void renderCombat() {
|
||||
for (var entity : registry.getWithComponents(CombatLog.class)) {
|
||||
CombatLog log = registry.getComponent(entity, CombatLog.class);
|
||||
IO.println(generateName(log.attacker()) + " ---------[" + log.resultingDamage() + "]-------->" + generateName(log.defender()));
|
||||
}
|
||||
IO.println();
|
||||
}
|
||||
|
||||
private void renderSquad(Squad squad) {
|
||||
IO.println("Squad " + squad.name());
|
||||
for (var unit : squad.units()) {
|
||||
var health = registry.getComponent(unit, Health.class);
|
||||
|
||||
IO.println(generateName(unit) + " [" + health.hp() + "/" + health.maxHp() + "]");
|
||||
}
|
||||
IO.println("-------------------------");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.example.systems;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.example.components.Investment;
|
||||
import com.example.components.Squad;
|
||||
import com.example.ecs.Entity;
|
||||
import com.example.ecs.Registry;
|
||||
|
||||
public class SquadGenerationSystem {
|
||||
private final Registry registry;
|
||||
|
||||
public SquadGenerationSystem(Registry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
public Entity generateSquad(Entity squadEntity, String name, int maxInvestment) {
|
||||
var investmentEntities = registry.getWithComponents(Investment.class);
|
||||
List<Entity> units = new ArrayList<>();
|
||||
for (var investmentEntity : investmentEntities) {
|
||||
var investment = registry.getComponent(investmentEntity, Investment.class);
|
||||
if (investment.forSquad().equals(squadEntity)) {
|
||||
units.addAll(createUnits(investment));
|
||||
registry.remove(investmentEntity);
|
||||
}
|
||||
}
|
||||
|
||||
Squad squad = new Squad(name, units);
|
||||
registry.addComponent(squadEntity, squad);
|
||||
return squadEntity;
|
||||
}
|
||||
|
||||
private List<Entity> createUnits(Investment investment) {
|
||||
int moneyRemaining = investment.amount();
|
||||
List<Entity> leader = new ArrayList<>();
|
||||
|
||||
if (investment.witLeader()) {
|
||||
moneyRemaining -= investment.race().getLeaderCost();
|
||||
leader.add(investment.race().getFactory().createHero(registry));
|
||||
}
|
||||
|
||||
var amount = moneyRemaining / investment.race().getCost();
|
||||
|
||||
List<Entity> squad = Stream.generate(() ->
|
||||
investment.race().getFactory().createUnit(registry))
|
||||
.limit(amount)
|
||||
.toList();
|
||||
var finalSquad = new ArrayList<Entity>();
|
||||
finalSquad.addAll(leader);
|
||||
finalSquad.addAll(squad);
|
||||
|
||||
return finalSquad;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.example;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit test for simple App.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
/**
|
||||
* Rigorous Test :-)
|
||||
*/
|
||||
@Test
|
||||
public void shouldAnswerWithTrue() {
|
||||
assertTrue(true);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue