Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Julién

Alter Hase

  • »Julién« ist der Autor dieses Themas

Beiträge: 717

Wohnort: Bayreuth

Beruf: Student | Hilfswissenschaftler in der Robotik

  • Private Nachricht senden

11

04.01.2015, 23:22

Hi,
ich habe jetzt ein bisschen herumprobiert. Es ist nicht ganz schön, daran arbeite ich noch.
Mir geht es nur darum zu wissen, ob das Prinzip richtig umgesetzt wurde.

Kurze Erläuterung:

Entity sind nur IDs. Zusätzlich hat die Implementation noch einen Pointer auf den EntityManager, der jenes Entity erstellt hat.
Components sind bei mir einfach Interfaces, wie dem auch sei, Components haben eine SysID.
Diese ID ist gleich zum korresponiderendem System, bis jetzt ist es bei mir noch recht statisch.

Der EntityManager hat eine Dictionary-instanz, welche die ID's mit Component-listen verknüpft.

Hier mal mein Code:

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    public class Entity
    {

        private int id;
        private EntityManager mgr;

        public Entity(EntityManager mgr, int id)
        {
            this.mgr = mgr;
            this.id = id;
        }

        public void addComponent(IComponent component)
        {
            mgr.addComponent(this, component);
        }

       // Das ist noch nicht alles, aber ist für einen komfortablen Testlauf genügend

        public int ID
        {
            get
            {
                return this.id;
            }
        }

    }


C#-Quelltext

1
2
3
4
5
6
7
8
9
    public interface IComponent
    {

        uint SysID
        {
            get;
        }

    }


C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
    public interface ISystem
    {

        uint SysID
        {
            get;
        }

        void processComponent(IComponent component);

    }


Jetzt kommt der dicke Schinken:

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
    public class EntityManager
    {

        private Dictionary<int, List<IComponent>>       entitys;
        private List<int>                               openSet;
        private SystemManager                           systemManager;

        public EntityManager(SystemManager systemManager)
        {
            entitys = new Dictionary<int, List<IComponent>>();
            openSet = new List<int>();

            this.systemManager = systemManager;
        }

        public Entity createEntity()
        {
            if(0 == openSet.Count)
            {
                var entity = new Entity(this, entitys.Count);
                entitys[entity.ID] = new List<IComponent>();
                return entity;
            }
            else
            {
                var entity = new Entity(this, openSet[0]);
                entitys[entity.ID] = new List<IComponent>();
                return entity;
            }
        }

        public void destroyEntity(Entity entity)
        {
            entitys.Remove(entity.ID);
            openSet.Add(entity.ID);
        }

        public void addComponent(Entity entity, IComponent component)
        {
            IComponent[] components = entitys[entity.ID].ToArray();

            foreach(IComponent c in components)
            {
                if (c.SysID == component.SysID)
                    // Add some kind of notification
                    // Exception; Debug; PopUpwindow?
                    return;
            }

            entitys[entity.ID].Add(component);
        }

        public void removeComponent(Entity entity, IComponent component)
        {
            entitys[entity.ID].Remove(component);
        }

        public void processEntitys()
        {
            foreach(KeyValuePair<int, List<IComponent>> entry in entitys)
            {
                systemManager.processComponents(entry.Value);
            }
        }

    }


C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    public class SystemManager
    {

        public Dictionary<uint, ISystem> systems;

        public SystemManager()
        {
            systems = new Dictionary<uint, ISystem>();
        }

        public void addSystem(ISystem system)
        {
            systems[system.SysID] = system;
        }

        public void processComponents(List<IComponent> components)
        {
                foreach(IComponent c in components)
                {
                    var sys = systems[c.SysID];
                    if (null != sys)    // Corresponding system exists, else skip this component :P
                        sys.processComponent(c);
                }
        }
    }


Das ist jetzt erstmal meine Vorstellung eines simplen ECS. Es funktioniert, bis jetzt zumindestens.

Ist das vom Prinzip her so richtig?

Gruß Julién
I write my own game engines because if I'm going to live in buggy crappy filth, I want it to me my own - Ron Gilbert

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

12

05.01.2015, 06:43

Ich glaube nicht, dass das mit dem OpenSet funktioniert. Fügst Du zwei Entities ein, entfernst Nummer 1 und fügst danach 9 weitere hinzu, sollten 10 Entities drin sein, es sind aber nur zwei, weil Du beim Einfügen die ID des OpenSets nicht entfernst und somit die ID 1 immer wieder vergibst. Davon abgesehen geht das schon.

Ich würde Dir auch das Schlüsselwort 'var' an's Herz legen. Das ".ToArray()" in Zeile 40 ist überflüssig.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

buggypixels

Treue Seele

Beiträge: 125

Wohnort: Meerbusch

Beruf: Programmierer

  • Private Nachricht senden

13

05.01.2015, 11:15

Also so recht verstehe ich Deinen Ansatz nicht. Du hast eine 1:1 Beziehung zwischen Component und System. Wo ist denn dann der Unterschied zwischen den beiden?
Außerdem ist es gewollt, dass die Art der Komponente das System definiert? Ich kenne das eher, dass ein System mehrere Komponenten verwenden kann für ein Entity.
Aber Du schreibst, dass es so funktioniert. Also scheint es wohl so gewollt zu sein.
Man muß übrigens nicht zwangsläufig ein ECS verwenden, nur um zu einer Komponentenbasierenden Architektur zu wechseln. Es gibt auch Leute, die einen anderen Ansatz verwenden.
Aber Du schreibst nicht zu Deiner Motivation hinter dem Ansatz. Darum kann man nur schwer eine Hilfestellung zu geben.

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

14

05.01.2015, 11:21

Die Komponenten sind in diesem Ansatz nicht weiter definiert. Es ist durchaus möglich, dass eine Komponente weitere Komponenten referenziert, womit ein System auch mehrere Komponenten benutzen kann. Ob das schön ist, sei mal dahingestellt.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Julién

Alter Hase

  • »Julién« ist der Autor dieses Themas

Beiträge: 717

Wohnort: Bayreuth

Beruf: Student | Hilfswissenschaftler in der Robotik

  • Private Nachricht senden

15

05.01.2015, 14:26

Jo, tatsächlich, die IDs werden aus dem OpenSet nicht gelöscht, mein Fehler. Ich wollte, wenn möglich, IDs wiederverwenden.

Bei meinem Ansatz können durchaus verschiedene Components zu einem System gehören, ich muss nur weiter differenzieren, damit ich die Objekt nicht falsch Caste.


Mein Motivation hinter dem ECS ist, dass ich alles schön modular halten möchte.
Jedes System könnte in der Theorie ein eigenes Modul sein, z.B. das RenderSystem ect..
I write my own game engines because if I'm going to live in buggy crappy filth, I want it to me my own - Ron Gilbert

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Julién« (05.01.2015, 14:31)


BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

16

05.01.2015, 14:39

Mein Motivation hinter dem ECS ist, dass ich alles schön modular halten möchte.
Das geht natürlich auch hübsch sauber und modular ohne ein ECS.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Julién

Alter Hase

  • »Julién« ist der Autor dieses Themas

Beiträge: 717

Wohnort: Bayreuth

Beruf: Student | Hilfswissenschaftler in der Robotik

  • Private Nachricht senden

17

05.01.2015, 23:27

Ich habe T=Machines Blog gelesen. Mein Problem, glaube ich, ist es wohl zu verstehen wie eine möglichst simple, doch möglichst flexible, Version aussehen könnte.

EDIT:
Ich konkretisiere mein Problem.
Wie kann ich herausfinden, dass ein Component bzw. ein Set von Components zu einem System gehört, so dass es trotzem flexibel bleibt.
I write my own game engines because if I'm going to live in buggy crappy filth, I want it to me my own - Ron Gilbert

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Julién« (06.01.2015, 01:00)


18

06.01.2015, 04:00

Ich würde dir empfehlen einfach die simpelste Lösung zu nehmen. Also erst mal gar kein ECS oder sowas. Sieht nämlich so aus als wüsstest du gar nicht, was genau du willst. Viele Design-Pattern machen oft nur unnötigen Overhead, wenn sie eigentlich nicht benötigt werden. ECS ist imho eines davon. Du wirst schon merken wenn du refactorn musst und dann kann man sich überlegen was man da so für Pattern nehmen kann.

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

19

06.01.2015, 12:25

Du solltest möglichst gar nicht wollen dass Komponenten sich gegenseitig kennen müssen oder wissen müssen ob andere Komponenten vorhanden sind.
Ein Beispiel. Du hast eine Komponente für die Position, eine zum loggen von Ereignissen und eine Komponente welche die Position beeinflussen möchte. Nennen wir sie mal Bewegungskomponente.
Also haben wir:
MoveComponent, PositionComponent und LogComponent. MoveComponent feuert jetzt ein Event in welchem sie mitteilt dass eine Bewegung durchgeführt werden soll. PositionComponent lauscht auf solche Events und kann jetzt die Position anpassen. LogComponent lauscht auch auf solch ein Event und kann einen Logeintrag anlegen. MoveComponent ist es egal ob es eine PositionComponent gibt oder nicht. MoveComponent ist es auch egal ob es eine LogComponent gibt oder nicht. Jetzt könntest du eine weitere Komponente hinzufügen um Daten über das Netzwerk zu senden. Diese könnte jetzt auch auf dieses Bewegungsevent lauschen und die Daten dann über das Netzwerk übertragen. Möglicherweise soll jetzt die Positionskomponente gar nicht mehr direkt auf das Event lauschen sondern erst später auf ein anderes Event des Netzwerksystems reagieren. Ist den Komponenten egal welche anderen Komponenten es gibt kannst du das ganze mit wenigen Zeilen umsetzen ohne Abhängigkeiten zu ändern. Müssen die Komponenten sich gegenseitig kennen so wird das ganze aufwendiger. Außerdem solltest du dir so deine Casts sparen können.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

20

06.01.2015, 12:38

Ich würde für Move selbst keine Component bauen, sondern ein System, welches die Position beeinflusst - plus eine Data-Component, die die Move-Daten (Start-Position, Start-Zeitpunkt, Richtung/Geschwindigkeit, etc) enthält plus eine Referenz auf die Position, die verändert werden muss. Events würde ich da gar keine werfen. Zur Verarbeitung der Komponenten sind die Systeme da und die nehmen entweder gewisse Typen von Komponenten an oder eben nicht. Und da ist das Problem an seinem Design, ein System kann bisher nicht verschiedene Component-Typen verarbeiten. Wenn es das aber können soll, kommen wir wieder in die Hölle von Double-Dispatch, Instance-Checks und Type-Casts.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Werbeanzeige