Wie implementiere ich Features in ein Entitätssystem?

31

Nachdem ich zwei Fragen zu Entitätssystemen ( 1 , 2 ) gestellt und einige Artikel darüber gelesen habe, denke ich, dass ich sie viel besser verstehe als zuvor. Ich habe immer noch einige Unsicherheiten, hauptsächlich im Hinblick auf den Bau eines Partikelemitters, eines Eingabesystems und einer Kamera. Ich habe natürlich immer noch einige Probleme beim Verstehen von Entitätssystemen und sie können auf eine ganze Reihe anderer Objekte angewendet werden, aber ich habe diese drei ausgewählt, weil sie sehr unterschiedliche Konzepte sind, einen ziemlich breiten Bereich abdecken und mir helfen, Entitätssysteme und deren Funktionsweise zu verstehen Behandle Probleme wie diese selbst, wenn sie auf dich zukommen.

Ich baue eine Engine in JavaScript und habe die meisten Kernfunktionen implementiert, darunter Eingabehandling, flexibles Animationssystem, Partikelemitter, mathematische Klassen und Funktionen, Szenenhandling, eine Kamera und ein Rendering sowie eine ganze Reihe von Funktionen von anderen Dingen, die Motoren normalerweise unterstützen. Ich habe die Antwort von Byte56 gelesen, die mich daran interessiert hat, die Engine zu einem Entitätssystem zu machen. Es würde immer noch eine HTML5-Spiel-Engine bleiben, mit der Grundphilosophie der Szene, aber es sollte die dynamische Erstellung von Entitäten aus Komponenten unterstützen.


Das Problem, das ich jetzt habe, besteht darin, mein altes Motorkonzept in dieses neue Programmierparadigma einzufügen. Dies sind einige der Definitionen aus den vorherigen Fragen, die aktualisiert wurden:

  • Eine Entität ist eine Kennung. Es hat keine Daten, es ist kein Objekt, es ist eine einfache ID, die einen Index in der Szenenliste aller Entitäten darstellt (die ich eigentlich als Komponentenmatrix implementieren möchte).

  • Eine Komponente ist ein Datenbehälter, jedoch mit Methoden, die diese Daten verarbeiten können. Das beste Beispiel ist eine Vector2Doder eine "Position" -Komponente. Es hat Daten: xund y, aber auch einige Methoden , die auf die Daten ein wenig einfacher machen die Bedienung: add(), normalize(), und so weiter.

  • Ein System kann mit einer Reihe von Entitäten betrieben werden, die bestimmte Anforderungen erfüllen. In der Regel müssen die Entitäten über einen festgelegten Satz von Komponenten verfügen, um betrieben werden zu können. Das System ist der "Logik" -Teil, der "Algorithmus" -Teil, alle von den Komponenten bereitgestellten Funktionen dienen lediglich der einfacheren Datenverwaltung.


Kamera

Die Kamera verfügt über eine Vector2DPositionseigenschaft, eine Rotationseigenschaft und einige Methoden zum Zentrieren um einen Punkt. Jedes Bild wird zusammen mit einer Szene einem Renderer zugeführt, und alle Objekte werden entsprechend ihrer Position verschoben. Die Szene wird dann gerendert.

Wie könnte ich ein solches Objekt in einem Entitätssystem darstellen? Wäre die Kamera eine Entität, eine Komponente oder eine Kombination (gemäß meiner Antwort )?

Teilchenemitter

Das Problem, das ich mit meinem Partikelemitter habe, ist wiederum, was was sein sollte. Ich bin mir ziemlich sicher, dass Partikel selbst keine Entitäten sein sollten, da ich über 10.000 von ihnen unterstützen möchte, und ich glaube, dass das Schaffen so vieler Entitäten meine Leistung stark beeinträchtigen würde.

Wie könnte ich ein solches Objekt in einem Entitätssystem darstellen?

Input Manager

Das letzte, worüber ich sprechen möchte, ist, wie mit Eingaben umgegangen werden soll. In meiner aktuellen Version der Engine gibt es eine Klasse namens Input. Hierbei handelt es sich um einen Handler, der die Ereignisse des Browsers, z. B. das Drücken von Tasten und das Ändern der Mausposition, abonniert und außerdem einen internen Status beibehält. Die Player-Klasse verfügt dann über eine react()Methode, die ein Eingabeobjekt als Argument akzeptiert. Dies hat den Vorteil, dass das Eingabeobjekt in .JSON serialisiert und dann über das Netzwerk gemeinsam genutzt werden kann, um reibungslose Mehrspielersimulationen zu ermöglichen.

Wie übersetzt sich dies in ein Entitätssystem?

jcora
quelle

Antworten:

26
  • Kamera: Eine Komponente daraus zu machen, wäre ziemlich ordentlich. Es hätte einfach eineisRenderingFlagge und Tiefenbereich, wie Sean sagte. Zusätzlich zu "Sichtfeld" (ich nenne es Skalierung in 2D?) Und einer Ausgabezone. Die Ausgabezone könnte den Teil des Spielfensters definieren, in den diese Kamera gerendert wird. Es hätte keine separate Position / Rotation, wie Sie es erwähnen. Die von Ihnen erstellte Entität mit einer Kamerakomponente würde die Positions- und Rotationskomponenten dieser Entität verwenden. Dann hätten Sie ein Kamerasystem, das nach Objekten mit Kamera-, Positions- und Rotationskomponenten sucht. Das System nimmt diese Entität und zeichnet alle Entitäten, die es "sehen" kann, von ihrer Position, Drehung, Sichttiefe und Sichtfeld zu dem angegebenen Teil des Bildschirms. Das gibt Ihnen viele Optionen für die Simulation von mehreren Ansichtsfenstern, "Charakteransicht" -Fenstern, lokalen Mehrspielermodus,

  • Teilchenemitter: Auch dies sollte nur eine Komponente sein. Das Partikelsystem sucht nach Objekten mit Position, Rotation und Partikelemitter. Der Emitter verfügt über alle Eigenschaften, die zur Reproduktion Ihres aktuellen Emitters erforderlich sind. Ich bin mir nicht sicher, was das alles ist, wie zum Beispiel: Rate, Anfangsgeschwindigkeit, Abklingzeit und so weiter. Sie müssten nicht mehrere Durchgänge machen. Das Partikelsystem weiß, welche Entitäten diese Komponente haben. Ich stelle mir vor, Sie könnten einen Großteil Ihres vorhandenen Codes wiederverwenden.

  • Eingaben: Ich muss sagen, dass es angesichts der obigen Vorschläge am sinnvollsten ist, dies in eine Komponente umzuwandeln. Ihreinput systemwürde jedes Bild mit den aktuellen Eingangsereignissen aktualisiert werden. Wenn dann alle Entitäten mit der Eingabekomponente durchlaufen werden, werden diese Ereignisse angewendet. Die Eingabekomponente verfügt über eine Liste der Tastatur- und Mausereignisse aller zugeordneten Methodenrückrufe. Ich bin nicht wirklich sicher, wo die Methodenrückrufe leben würden. Vielleicht eine Input-Controller-Klasse? Was auch immer für spätere Änderungen durch Benutzer Ihrer Engine am sinnvollsten ist. Dies würde Ihnen jedoch die Möglichkeit geben, auf einfache Weise die Eingabesteuerung auf Kamera- und Player-Objekte oder was auch immer Sie benötigen anzuwenden. Möchten Sie die Bewegung einer Reihe von Objekten mit der Tastatur synchronisieren? Geben Sie ihnen einfach alle Eingabekomponenten, die auf dieselben Eingaben reagieren, und das Eingabesystem wendet diese Verschiebungsereignisse auf alle Komponenten an, die nach ihnen fragen.

Das meiste davon liegt mir also auf dem Kopf, sodass es ohne weitere Erklärung wahrscheinlich keinen Sinn ergibt. Also lass mich einfach wissen, worüber du nicht klar bist. Grundsätzlich habe ich dir viel Arbeit gegeben :)

MichaelHouse
quelle
Noch eine tolle Antwort! Vielen Dank! Jetzt ist mein einziges Problem das schnelle Speichern und Abrufen von Entitäten, sodass der Benutzer tatsächlich eine Spielschleife / -logik implementieren kann. Ich werde versuchen, es selbst herauszufinden, aber ich muss erst lernen, wie Javascript mit Arrays, Objekten und Objekten umgeht undefinierte Werte im Speicher, um eine gute Vermutung anzustellen ... Das wird ein Problem sein, da verschiedene Browser es möglicherweise unterschiedlich implementieren.
Jcora
Das fühlt sich architektonisch rein an, aber wie bestimmt das Rendering-System die aktive Kamera, ohne alle Entitäten zu durchlaufen?
Pace
@Pace Da ich möchte, dass die aktive Kamera sehr schnell gefunden wird, würde ich dem Kamerasystem wahrscheinlich erlauben, einen Verweis auf die Entitäten zu behalten, die eine aktive Kamera haben.
MichaelHouse
Wo platzieren Sie die Logik zur Steuerung mehrerer Kameras (Betrachten, Drehen, Bewegen usw.)? Wie steuern Sie die mehreren Kameras?
plasmacel
@plasmacel Wenn Sie mehrere Objekte haben, die Steuerelemente gemeinsam nutzen, liegt es in der Verantwortung Ihres Steuerungssystems, zu bestimmen, welches Objekt die Eingaben empfängt.
MichaelHouse
13

Hier ist, wie ich das angegangen bin:

Kamera

Meine Kamera ist eine Entität wie jede andere, an die Komponenten angeschlossen sind:

  1. Transformhat Translation, Rotationund ScaleEigenschaften, neben anderen für Geschwindigkeit usw.

  2. Pov(Sicht) hat FieldOfView, AspectRatio, Near, Far, und alles , was erforderlich , um eine Projektionsmatrix zu erzeugen, zusätzlich zu einem IsOrthoFlag , um zwischen Perspektive verwendet und orthogonalen Projektionen. PovStellt außerdem eine Lazy-Load- ProjectionMatrixEigenschaft bereit, die vom Rendersystem verwendet wird und die beim Lesen intern berechnet und zwischengespeichert wird, bis eine der anderen Eigenschaften geändert wird.

Es gibt kein spezielles Kamerasystem. Das Rendersystem verwaltet eine Liste von Povund enthält eine Logik zum Bestimmen, welche beim Rendern verwendet werden soll.

Eingang

Eine InputReceiverKomponente kann an eine beliebige Entität angehängt werden. Diesem ist ein Ereignishandler (oder Lambda, wenn Ihre Sprache dies unterstützt) angehängt, der zur Aufnahme der entitätsspezifischen Eingabeverarbeitung verwendet wird und Parameter für den aktuellen und vorherigen Tastenzustand, die aktuelle und vorherige Mausposition und den Tastenzustand usw. enthält. Es gibt separate Handler für Maus und Tastatur.

Zum Beispiel habe ich in einem Asteroids-ähnlichen Testspiel, das ich erstellt habe, als ich mich an Entity / Component gewöhnt habe, zwei Lambda-Eingabemethoden. Die Schiffsnavigation erledigt man mit den Pfeiltasten und der Leertaste (zum Feuern). Die andere behandelt allgemeine Tastatureingaben - Tasten zum Beenden, Anhalten usw., Neustarten usw. Ich erstelle zwei Komponenten, ordne jedes Lambda einer eigenen Komponente zu und ordne dann die Navigationsempfängerkomponente der Schiffseinheit zu, die andere einer nicht sichtbare Befehlsprozessorentität.

Hier ist die Ereignisbehandlungsroutine zum Behandeln von Schlüsseln zwischen Frames, die an die Schiffskomponente angehängt InputReceiverwerden (C #):

  void ship_input_Hold(object sender, InputEventArgs args)
    {
        var k = args.Keys;
        var e = args.Entity;

        var dt = (float)args.GameTime.ElapsedGameTime.TotalSeconds;

        var verlet = e.As<VerletMotion>();
        var transform = e.As<Transform>();

        if (verlet != null)
        {

        /// calculate applied force 
            var force = Vector3.Zero;
            var forward = transform.RotationMatrix.Up * Settings.ShipSpeedMax;

            if (k.Contains(Keys.W))
                force += forward;

            if (k.Contains(Keys.S))
                force -= forward;

            verlet.Force += force * dt;
        }

        if (transform != null)
        {
            var theta = Vector3.Zero;

            if (k.Contains(Keys.A))
                theta.Z += Settings.TurnRate;

            if (k.Contains(Keys.D))
                theta.Z -= Settings.TurnRate;

            transform.Rotation += theta * dt;
        }

        if (k.Contains(Keys.Space))
        {
            var time = (float)args.GameTime.TotalGameTime.TotalSeconds - _rapidFireLast;

            if (time >= _rapidFireDelay)
            {
                Fire();
                _rapidFireLast = (float)args.GameTime.TotalGameTime.TotalSeconds;
            }
        }
    }

Wenn Ihre Kamera mobil ist, geben Sie ihr eine eigene InputReceiverund eine TransformKomponente, schließen Sie ein Lambda oder einen Handler an, der die gewünschte Steuerung implementiert, und fertig.

Dies ist insofern eine nette InputReceiverSache, als Sie die Komponente mit dem Navigations-Handler vom Schiff auf einen Asteroiden oder auf irgendetwas anderes verschieben und stattdessen herumfliegen können. Wenn Sie eine PovKomponente einem anderen Element in Ihrer Szene zuweisen - einem Asteroiden, einer Straßenlampe usw. -, können Sie Ihre Szene aus der Perspektive dieser Entität betrachten.

Eine InputSystemKlasse, die einen internen Status für Tastatur, Maus usw. verwaltet, InputSystemfiltert ihre interne Entitätssammlung nach Entitäten mit einer InputReceiverKomponente. In seiner Update()Methode durchläuft es diese Auflistung und ruft die an jede dieser Komponenten angehängten Eingabehandler auf dieselbe Weise auf, wie das Rendering-System jede Entität mit einer RenderableKomponente zeichnet .

Teilchen

Dies hängt wirklich davon ab, wie Sie mit den Partikeln interagieren möchten. Wenn Sie nur ein Partikelsystem benötigen, das sich wie ein Objekt verhält - beispielsweise ein Feuerwerk, das anzeigt, dass der Spieler weder berühren noch schlagen kann -, erstellen Sie eine einzelne Entität und eine ParticleRenderGroupKomponente, die alle Informationen enthält, die Sie für die Partikel benötigen. Zerfall usw. - das wird von Ihrer RenderableKomponente nicht abgedeckt . Beim Rendern würde das Rendersystem sehen, ob eine Entität die RenderParticleGroupangehängte hat, und es entsprechend behandeln.

Wenn Sie einzelne Partikel benötigen, um an der Kollisionserkennung teilzunehmen, auf Eingaben zu reagieren usw., diese jedoch nur als Stapel wiedergeben möchten, erstellen Sie eine ParticleKomponente, die diese Informationen auf Partikelbasis enthält, und erstellen Sie sie als getrennte Einheiten. Das Rendersystem kann sie weiterhin stapeln, sie werden jedoch von den anderen Systemen als separate Objekte behandelt. (Dies funktioniert sehr gut mit Instanzen.)

MotionSystemFühren Sie dann entweder in Ihrem (oder einem anderen, der die Aktualisierung der Entitätsposition übernimmt usw.) oder in einem dedizierten ParticleSystemModus die für jedes Partikel pro Frame erforderliche Verarbeitung durch. Das RenderSystemwäre verantwortlich für den Aufbau / Dosier- und Caching von Partikel Sammlungen wie sie erzeugt und zerstört, und machen sie je nach Bedarf.

Eine schöne Sache bei diesem Ansatz ist, dass Sie keine speziellen Fälle für Kollisionen, Keulen usw. für Partikel haben müssen. Der Code, den Sie für jede andere Art von Entität schreiben, kann weiterhin verwendet werden.

Fazit

Wenn Sie überlegen, plattformübergreifend zu arbeiten, was für JavaScript nicht besonders geeignet ist, wird Ihr plattformspezifischer Code (Rendering und Eingabe) auf zwei Systeme aufgeteilt. Ihre Spielelogik bleibt in plattformunabhängigen Klassen (Bewegung, Kollision usw.), sodass Sie sie beim Portieren nicht berühren müssen.

Ich verstehe und stimme mit Seans Position überein, dass es schlecht ist, Dinge mit Schuhen in ein Muster zu integrieren, um sich strikt an das Muster zu halten, anstatt das Muster zu optimieren, um die Anforderungen Ihrer Anwendung zu erfüllen. Ich sehe einfach nichts in Input, Kamera oder Partikeln, was eine solche Behandlung erfordert.

3Dave
quelle
Wo platzieren Sie die Logik zur Steuerung mehrerer Kameras (Betrachten, Drehen, Bewegen usw.)?
plasmacel
7

Eingabe- und Spielelogik werden wahrscheinlich in einem dedizierten Codeabschnitt außerhalb des Entitätskomponentensystems verarbeitet. Es ist technisch möglich, es in das Design einzufügen, aber es hat wenig Vorteile - die Spielelogik und die Benutzeroberfläche sind hackig und voller undichter Abstraktionen, egal was Sie tun, und der Versuch, den quadratischen Stift nur aus Gründen der architektonischen Reinheit in ein rundes Loch zu zwingen, ist eine Verschwendung von Zeit.

Ebenso sind Partikelemitter besondere Biester, besonders wenn es Ihnen um Leistung geht. Eine Emitter-Komponente ist sinnvoll, aber Grafiken werden mit diesen Komponenten eine besondere Magie vollbringen, die für den Rest des Renderns mit der Magie vermischt wird.

Geben Sie in Bezug auf Ihre Kamera einfach eine aktive Markierung und möglicherweise einen Tiefenindex an, und lassen Sie das Grafiksystem alle aktivierten rendern. Dies ist praktisch für viele Tricks, einschließlich GUIs (möchten Sie, dass Ihre GUI in einem orthografischen Modus über der Spielwelt gerendert wird? Kein Problem, es sind nur zwei Kameras mit unterschiedlichen Objektmasken und GUI auf einer höheren Ebene). Es ist auch nützlich für Spezialeffektebenen und dergleichen.

Sean Middleditch
quelle
4

Wäre die Kamera eine Einheit oder einfach eine Komponente?

Ich bin mir nicht sicher, was diese Frage wirklich stellt. Da das einzige, was Sie im Spiel haben, Objekte sind, müssen Kameras Objekte sein. Die Kamerafunktionalität wird über eine Art Kamerakomponente implementiert. Keine separaten Komponenten "Position" und "Rotation" - das ist viel zu niedrig. Sie sollten zu einer Art WorldPosition-Komponente zusammengefasst werden, die für jede Entität auf der Welt gilt. Was man verwenden soll ... man muss irgendwie Logik in das System bringen. Entweder Sie codieren es fest in Ihr Kamera-Handlingsystem oder Sie hängen Skripte an oder so. Sie können ein aktiviertes / deaktiviertes Flag für eine Kamerakomponente festlegen, wenn dies hilfreich ist.

Ich bin mir ziemlich sicher, dass Partikel selbst keine Entitäten sein sollten

Ich auch. Ein Partikelemitter wäre eine Entität, und das Partikelsystem würde die Partikel verfolgen, die einer bestimmten Entität zugeordnet sind. In solchen Dingen merkt man, dass "alles eine Einheit ist" absurd unpraktisch ist. In der Praxis sind die einzigen Entitäten relativ komplexe Objekte, die von der Kombination von Komponenten profitieren.

Was Input betrifft: Input existiert in der Spielwelt nicht als solcher, so dass dies von einem System gehandhabt wird. Nicht unbedingt ein 'Komponentensystem', da sich nicht alles in Ihrem Spiel um Komponenten dreht. Es wird aber ein Eingabesystem geben. Möglicherweise möchten Sie die Entität, die auf Eingaben reagiert, mit einer Art Player-Komponente kennzeichnen. Die Eingabe wird jedoch komplex und vollständig spielspezifisch sein, sodass es wenig Sinn macht, Komponenten dafür zu erstellen.

Kylotan
quelle
1

Hier sind einige meiner Ideen zur Lösung dieser Probleme. Sie werden wahrscheinlich etwas falsch mit ihnen haben, und es wird wahrscheinlich einen besseren Ansatz geben. Bitte leiten Sie mich zu denen in Ihrer Antwort!

Kamera :

Es gibt eine "Kamera" -Komponente, die zu jeder Entität hinzugefügt werden kann. Ich kann mir allerdings nicht wirklich vorstellen, welche Daten ich in diese Komponente einfügen soll: Ich könnte getrennte "Position" - und "Rotation" -Komponenten haben! Die followMethode muss nicht implementiert werden, da sie bereits der Entität folgt, an die sie angehängt ist! Und ich kann es frei bewegen. Das Problem mit diesem System wären viele verschiedene Kameraobjekte: Wie können die RendererSystemwissen, welche zu verwenden sind? Früher habe ich das Kameraobjekt nur herumgereicht, aber jetzt scheint es so, als RendererSystemmüsste das Objekt zweimal über alle Entitäten iteriert werden: erstens, um diejenigen zu finden, die sich wie Kameras verhalten, und zweitens, um tatsächlich alles zu rendern.

PartikelEmitter :

Es würde eine geben, ParticleSystemdie alle Entitäten aktualisieren würde, die eine "Emitter" -Komponente hatten. Teilchen sind stumme Objekte in einem relativen Koordinatenraum innerhalb dieser Komponente. Hier gibt es ein Problem beim Rendern: Ich müsste entweder ein ParticleRendererSystem erstellen oder die Funktionalität des vorhandenen Systems erweitern.

Eingabesystem :

Das Hauptanliegen für mich war hier die Logik oder die react()Methode. Die einzige Lösung, die ich gefunden habe, ist ein separates System und eine Komponente für jedes System, die angibt, welches verwendet werden soll. Das scheint wirklich zu abgedreht zu sein und ich weiß nicht, wie ich damit umgehen soll. Eine Sache ist, dass, solange es mich betrifft, das Inputals Klasse implementiert bleiben kann, aber ich sehe nicht, wie ich es in den Rest des Spiels integrieren könnte.

jcora
quelle
Es gibt nicht wirklich einen Grund für das RendererSystem, über alle Entitäten zu iterieren - es sollte bereits eine Liste von Zeichenobjekten (und Kameras und Lichtquellen (außer Lichtquellen sind Zeichenobjekte)) enthalten oder wissen, wo sich diese Listen befinden. Wahrscheinlich möchten Sie auch das Culling für Kameras durchführen, die Sie rendern möchten, sodass Ihre Kamera möglicherweise eine Liste mit ziehbaren Objekt-IDs enthält, die für sie sichtbar sind. Sie könnten viele Kameras und eine aktive haben, oder eine Kamera, die an verschiedene POVs angehängt wird, die beide durch eine beliebige Anzahl von Dingen wie Skripten und Triggern und Eingaben
@ melak47, das stimmt, ich habe auch darüber nachgedacht, aber ich wollte es so implementieren, wie es Aremis tut. Aber dieses "System speichert Verweise auf relevante Entitäten" scheint mehr und mehr fehlerhaft zu sein ...
jcora
Speichert Artemis nicht jeden Komponententyp in einer eigenen Liste? Hätten Sie also nicht genau diese Listen von Zeichenkomponenten, Kamerakomponenten, Lichtern und was nicht irgendwo?