Go to file
Mark Beck e9602140e9 new changes 2025-11-06 14:56:58 +01:00
src new changes 2025-11-06 14:56:58 +01:00
.gitignore feat: add code 2025-11-05 00:07:15 +01:00
README.md new changes 2025-11-06 14:56:58 +01:00
pom.xml feat: add code 2025-11-05 00:07:15 +01:00

README.md

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:

Entity

Entities sind Spielobjekte, sie sind meist nicht mehr als eine fortlaufende ID.

Component

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 meist folgende Operationen erlaubt:

  • addComponent(entity, component) -- Füge einen neuen Component in die verwaltung hinzu.
  • removeComponent(entity, ComponentType) -- Lösche den Component diesen Types für die Entity.
  • withComponents(ComponentTypes[]) -- erstelle eine Liste aller Entities, die alle angegebenen Component Typen besitzen.

Sytem

Systems bilden die Anwendungslogik ab. Sie können die Registry nach Entities abfragen und neue Entities oder Components hinzufügen. Ein System könnte zum Beispiel die Schadensberechnung für alle Einheiten durchführen und daraus Schadens-Entities erstellen und ein anderes System alle Schadens-Entities abfragen und Lebenspunkte abziehen.

Aufgabe

Ihre Aufgabe ist es ein Entity-Component-System zu entwickeln. Das ECS muss mindestens aus folgenden Klassen bestehen:

  • class Entity -- Entities sollten unveränderbar und nach Wert vergleichbar sein.

    • die folgenden beiden Konstruktoren müssen existieren:
    • public Entity()
    • protected Entity(int)
  • interface Component -- Ein interface zur Markierung von Components.

  • class Registry -- Die Registry soll alle Entities und Components verwalten. Sie soll folgende Operationen unterstuützen:

┌─────────────────────────────────────────────────────────────────────────┐
│                                                                         │
│  registerComponentType(componentClass)                                  │
│  └─ Registriert eine neue Art von Component zur Verwaltung.
|     Es dürfen nur Klassen akzeptiert werden, die das Interface Component implementieren        │
│                                                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  addComponent(entity, component)                                        │
│  └─ Fügt der Entity einen neuen Component hinzu.
|     Diese Methode akzeptiert alle Components, die das Interface Component implementieren.
|     Besitzt die Entity bereits einen Component dieser Klasse, wird dieser durch die neue Instanz ersetzt.                         │
│                                                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  removeComponent(entity, componentClass)                                │
│  └─ Löscht den Component der angegebenen Klasse von der Entity.         │
│                                                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  hasComponent(entity, componentClass) → boolean                         │
│  └─ Prüft, ob die Entity einen Component dieser Klasse hat.             │
│                                                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  getComponent(entity, componentClass) → component                       │
│  └─ Gibt den Component der angegebenen Klasse für die Entity zurück.    │
│                                                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  withComponents(componentClass...) → entity[]                           │
│  └─ Gibt alle Entities zurück, die alle angegebenen Components haben.   │
│                                                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  removeEntity(entity)                                                   │
│  └─ Löscht die Entity mit allen ihren Components                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Tipp:

Sie können die Klasse eines Objektes mit der .class Eigenschaft ermitteln. Das damit erzeugte Class-Token kann wie jeder andere Wert in Datenstruckturen gespeichert werden.

class App {}

App app = new App();
Class<App> appClassToken = app.class;