Bitsets abgleichen

Leon_2001

Aktives Mitglied
Hallo,

ich bin gerade dabei ein Entity Component System zu implementieren (falls wem das etwas sagt, wenn nicht auch nicht schlimm).
Jedenfalls habe ich Objekte, die alle eine bestimmte Auswahl an Komponenten haben (z.B Physik Komponente, Position Komponente usw.) und Systeme, die sich um alle Objekte kümmern, die eine gewisse Kombination an Komponenten haben.
D.h. PhysikSystem soll sich um alle Objekte kümmern, die eine Physik und Position Komponente besitzen.

Ich dachte als Lösung daran ein Bitfeld zu nehmen. Jedes System hat ein Bitfeld, wo eingetragen ist, welche Komponenten das System haben möchte und jedes Objekt hat ein Bitfeld mit den Komponenten, welche es hat.
ObjectBitfeld & SystemBitfeld == SystemBitfeld ... sollte dann überprüfen, ob das Objekt die erforderlichen Komponenten hat.

Mein Problem liegt vor allem darin, diese Bitfelder zu kreiren.

1. Ich habe eine Oberklasse Componente, jede Unterklasse soll eine ID haben, ab 0 angefangen zu zählen ... diese zuweisung solllte nach Möglichkeit automatisch geschehen, da später der Nutzer eigene Komponenten hinzufügen soll und sich nicht damit plaggen soll, welche Id er diesem manuell eintragen muss.

2. Wie erreiche ich diese Id? Beim hinzufügen neuer Komponenten zu dem Objekt, sollte es kein Problem sein, da ich eine Instanz von dem Objekt habe (Typ Component) und könnte einfach eine Methode getId() von Component aufrufen.
Schwieriger wird das Preferenzen setzen bei den Systemen. Dort habe ich aktuell eine Methode:
Java:
protected void requireComponent(Class<? extends EntityComponent> type) {}
sodass man sagen kann in den Konstruktoren der einzelnen Systeme: requireComponent(PhysicComponent.class); ... auf ein Class Objekt kann ich ja schlecht eine member function aufrufen.
Eine Überlegung von mir war, die Methode generisch zu machen : requireComponent<PhysicComponent>(), dann könnte man zumindest statische Methoden aufrufen.

Vielen Dank für eure Hilfe :)

Grüße Leon
 

temi

Top Contributor
Sorry, deine Frage kann ich nicht beantworten, aber ich kann was zu ECS sagen und hatte dazu auch einen sehr guten Artikel gefunden. Leider bin ich nicht sicher ob es dieser hier ist: http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/

Aber soweit ich kurz überflogen habe, werden einige relevante Punkte erläutert.
Grundlegende Erkenntnis daraus ist, dass ein Entity nichts ist als eine ID:

Java:
public class Entity
{
    private final UUID id;

    public Entity(final UUID id)
    {
        this.id = id;
    }
}

Und eine Komponente ist nichts als eine pure Datenklasse:
Java:
public interface Component {}

public class PositionComponent implements Component
{
    public int x, y;
}

Zusammengehalten werden die einzelnen Teile von einem EntityManager:
Java:
public interface EntityManager
{
    Entity createEntity();

    <T extends Component> Set<Entity> getEntities(Class<T> componentType);

    <T extends Component> void addComponent(Entity entity, T component);

    <T extends Component> T getComponent(Entity entity, Class<T> componentType);

    <T extends Component> List<T> getComponents(Entity entity);

    <T extends Component> List<T> getComponents(Class<T> componentType);

    <T extends Component> void removeComponent(Entity entity, T component);

    <T extends Component> boolean hasComponent(Entity entity, Class<T> componentType);
}

public class SimpleEntityManager implements EntityManager
{
    private final Set<Entity> entities = new HashSet<>(); // hier sind alle vorhandenen Entities gepeichert
    private final Map<Class, HashMap<Entity, ? extends Component>> components = new HashMap<>(); // und hier die dazu vorhandenen Components

    @Override
    public Entity createEntity()
    {
        Entity entity;
        entity = new Entity(UUID.randomUUID());
        entities.add(entity);
        return entity;
    }
  
    @Override
    public <T extends Component> void addComponent(final Entity entity, final T component)
    {
        HashMap<Entity, ? extends Component> componentsOfType = components.get(component.getClass());
        if (componentsOfType == null)
        {
            componentsOfType = new HashMap<Entity, T>();
            components.put(component.getClass(), componentsOfType);
        }
        ((HashMap<Entity, T>) componentsOfType).put(entity, component);
    }

    // Rest weggelassen...
}

Das MoveSystem holt sich dann vom EntityManager die Daten, die es benötigt (sofern das Entity diese Komponente enthält).

Im Grunde ist das so eine Art relationale DB mit der Entity (= ID) als Schlüssel. Damit bist du sehr flexibel und kannst beliebige neue Komponenten erstellen und hinzufügen und ebenso einfach die Systeme für die neuen Komponenten anpassen.

Du kannst dir auch mal Ashley anschauen, dass ist auch so ähnlich: https://github.com/libgdx/ashley
 
Zuletzt bearbeitet:

Leon_2001

Aktives Mitglied
Hey temi,

vielen Dank für die Inspiration , so in etwa habe ich es tatsächlich implementiert.
Größter Unterschied wird wohl sein, dass ich statt interfaces immer Klassen genommen haben. Aus C++ bin ich gewöhnt, dass Interfaces immer abstrakte Basisklassen sind ... demenstprechen ist bei bei Component nun auch eine abstrakte Basisklasse.
Ich hatte bereits daran gedacht, es zu einem Interface zu machen, jedoch waren nach meinem Verständnis Interfaces in Java eher Fähigkeiten ... zum Beispiel "Comparable" ... ein Objekt, dass dieses Interface implementiert, kann verglichen werden.
Aus meinem Verständnis heraus, sind PhysikKomponente und Komponente in einer "ist-Beziehung", daher abstrakte Oberklasse (wobei Interfaces ja auch abstrakte Klassen sind).

Mögicherweise habe ich diese ganze interface Sache in Java aber auch falsch verstanden.


Für die Systeme gibt es nach meinem Verständnis zwei Möglichkeiten:

1. Event basiert: Die Systeme führen ihre Aufgabe auf, wenn ein gewisses event auftritt z.B. die Position eines Objektes wurde verändert -> PositionChangedEvent -> Rendersystem zeichnet Objekt neu

2. Frame basiert: Jedes System hat quasi seine eigene Gameloop (diese können auch in mehreren Threads laufen) und führt seine Aufgabe z.B. zeichnen der Objekte jeden Frame aus. Dazu braucht es z.B. eine ArrayList mit Objekten (Entities), die im Falle des RenderSystem überhaupt gezeichnet werden können d.h. sie brauchen eine GrafikKomponente.

Ich habe mich erstmal für Variante 2 entschieden. Um diese ArrayList mit den Entities zu kreieren, muss beim hinzufügen eines Objektes (Entity) erstmal geprüft werden, stimmen die Komponenten des Entity mit einem System überein. Sprich hat z.B das RenderSystem Interesse an diesem Entity.
Dazu suche ich eben eine schöne Lösung ... hab zwar eine, aber die ist grauenvoll:

1. Man gibt jeder Komponentenklasse eine Id (static variable) -> unschön, da der Nutzer der "Game Engine" später beim eigenen hinzufügen von Komponenten, sich selbst darum kümmern muss, welche Id seine Komponente bekommen soll.

2. In System sagt man... requireComponent(Component.class) -> Methode der abstrakten System Oberklasse

Java:
protected void requireComponent(Class<? extends EntityComponent> type) {
        if(type.getSimpleName().equals("GraphicsComponent")) {
            this.componentMask.set(0);
        }
        else if(type.getSimpleName().equals("HealthComponent")) {
            this.componentMask.set(1);
        }
        else if(type.getSimpleName().equals("PhysicsComponent")) {
            this.componentMask.set(2);
        }
        else if(type.getSimpleName().equals("PositionComponent")) {
            this.componentMask.set(3);
        }
        else if(type.getSimpleName().equals("InputComponent")) {
            this.componentMask.set(4);
        }
    }

--> absolut grauenvoll, bei jedem hinzufügen einer neuen Komponentenklasse, muss man diese Methode erweitern (kann auf keinen Fall so bleiben, da dieser Teil ja nicht vom Nutzer der gameEngine verändert werden soll)

3. In EntityManager wird beim hinzufügen einer neuen Komponente zu einem Entity dessen Komponentenmaske verändert (die in einer Hashmap gespeichert ist)

Java:
public void addComponent(Entity entity, EntityComponent component) {
        this.entities.get(entity).add(component);
        this.componentMasks.get(entity).set(component.getIndex());
    }

-> vertretbar wie ich finde ... schön wäre, wenn man in 2. die Id auch über das ClassObjekt auslesen könnte

4. Im System Manager wird geprüft, ob ein Entity mit einem System übereinstimmt (bzw. mindestens die erforderlichen Komponenten besitzt )
Java:
public void addToSystem(Entity entity, BitSet componentMask) {
        for(EntitySystem system: systems) {
            BitSet bitset = (BitSet) componentMask.clone();
            bitset.and(system.getComponentMask());
            if(bitset.equals(system.getComponentMask())) {
                system.addEntity(entity);
            }
        }
    }


Wichtig ist vor allem, dass der Nutzer nichts an den Klassen der engine verädern muss ... sprich die requireComponent Methode muss definit verändert werden. Schön wäre, wenn die Id zuweisung an neue Komponentenklassen auch automatisch erfolgt, aber mit dieser manuellen Zuweisung könnte ich vermutlich noch leben.

Über Hilfe würde ich mich sehr freuen! :)

Grüße Leon
 

temi

Top Contributor
Ergänzend f
muss beim hinzufügen eines Objektes (Entity) erstmal geprüft werden, stimmen die Komponenten des Entity mit einem System überein.
Beim Hinzufügen kannst du doch einfach z.B. "entityManager.hasComponent(entity, PositionComponent.class)" aufrufen und das Entity verwerfen, wenn es die Voraussetzungen nicht erfüllt. Jedes System weiß ja ganz genau welche Komponenten es benötigt, um damit arbeiten zu können.
Oder das System holt sich selbst die passenden Entities, wie es bei Ashley gemacht wird:
Java:
public class MovementSystem extends EntitySystem {
    private ImmutableArray<Entity> entities;
    // ...
    public void addedToEngine(Engine engine) {
        entities = engine.getEntitiesFor(Family.all(PositionComponent.class, VelocityComponent.class).get());
    }
    // ...
}
 

mrBrown

Super-Moderator
Mitarbeiter
Ich hatte bereits daran gedacht, es zu einem Interface zu machen, jedoch waren nach meinem Verständnis Interfaces in Java eher Fähigkeiten ... zum Beispiel "Comparable" ... ein Objekt, dass dieses Interface implementiert, kann verglichen werden.
Aus meinem Verständnis heraus, sind PhysikKomponente und Komponente in einer "ist-Beziehung", daher abstrakte Oberklasse (wobei Interfaces ja auch abstrakte Klassen sind).

Mögicherweise habe ich diese ganze interface Sache in Java aber auch falsch verstanden.
Zu einem Interface gehört genauso eine "ist ein" Beziehung (bzw, bei *able abgekürzt zu "ist", was daran aber nichts ändert).

Die meisten Interfaces in Java sind nicht als Fähigkeit aufzufassen, zb alle Collections.

Zwischen Interface und abstrakter Klasse gibt es aber durchaus einige Unterschiede ;)
 

Ähnliche Java Themen

Neue Themen


Oben