Kompositionslastiger OOP vs. reine Entitätskomponentensysteme? [geschlossen]

13

Ich gebe zu, ich habe die Sünde gemacht, das Erbe zu überbeanspruchen und sogar zu missbrauchen. Das erste (Text-) Spielprojekt, das ich gemacht habe, als ich meinen OOP-Kurs absolvierte, ging bis zu "verschlossene Tür" und "entriegelte Tür" von "Tür" und "Raum mit einer Tür", "Raum mit zwei Türen" und so weiter von "Zimmer".

Mit dem (grafischen) Spiel, an dem ich kürzlich gearbeitet habe, dachte ich, ich hätte meine Lektion gelernt und die Verwendung von Vererbung begrenzt. Ich bemerkte jedoch, dass die Probleme bald auftauchten. Meine Wurzelklasse begann sich immer mehr aufzublähen, und meine Blattklassen waren voller doppelter Codes.

Ich dachte, ich mache immer noch etwas falsch und als ich online nachschaute, stellte ich fest, dass ich nicht der einzige mit diesem Problem war. So habe ich nach gründlichen Recherchen Entity-Systeme entdeckt (siehe: googlefu)

Als ich anfing, es zu lesen, konnte ich sehen, wie deutlich es die Probleme lösen konnte, die ich mit der traditionellen OOP-Hierarchie mit Komponenten hatte. Dies waren jedoch in den ersten Lesungen. Als ich auf weitere… „radikale“ ES-Ansätze stieß, wie den bei T-machine .

Ich begann mit den Methoden, die sie verwendeten, nicht einverstanden zu sein. Ein reines Komponentensystem schien entweder übertrieben oder eher nicht intuitiv zu sein, was wahrscheinlich die Stärke von OOP ist. Der Autor geht so weit zu sagen, dass das ES-System das Gegenteil von OOP ist, und obwohl es möglicherweise zusammen mit OOP verwendet werden kann, sollte es dies wirklich nicht tun. Ich sage nicht, dass es falsch ist, aber ich hatte einfach keine Lust auf eine Lösung, die ich gerne implementieren würde.

Für mich und um die Probleme zu lösen, die ich zu Beginn des Beitrages hatte, ohne gegen meine Intuition zu verstoßen, ist es dennoch, eine Hierarchie zu verwenden, die jedoch keine monolithische Hierarchie wie die zuvor verwendeten ist, sondern ein polylithischer (ich konnte kein Wort im Gegensatz zu monolithischen finden), der aus mehreren, kleineren Bäumen besteht.

Das folgende Beispiel zeigt, was ich meine (dies ist inspiriert von einem Beispiel, das ich in Game Engine Architecture, Kapitel 14, gefunden habe).

Ich hätte einen kleinen Baum für Fahrzeuge. Die Wurzelfahrzeugklasse würde eine Renderkomponente, eine Kollisionskomponente, eine Positionskomponente usw. aufweisen.

Dann würde ein Panzer, eine Unterklasse von Fahrzeugen, diese Komponenten erben und eine eigene "Kanonenkomponente" erhalten.

Gleiches gilt für die Charaktere. Ein Charakter hätte seine eigenen Komponenten, dann würde die Spielerklasse ihn erben und einen Input-Controller erhalten, während andere feindliche Klassen von der Charakterklasse erben und einen AI-Controller erhalten würden.

Ich sehe keine wirklichen Probleme mit diesem Design. Obwohl kein reines Entity Controller-System verwendet wird, wird das Problem mit dem Sprudeleffekt und der großen Stammklasse durch die Verwendung einer Mehrbaumhierarchie gelöst, und das Problem der schweren, Code duplizierenden Blätter ist weg, da die Blätter dies nicht tun Ich habe jeden Code am Anfang, nur Komponenten. Wenn eine Änderung an der Blattebene vorgenommen werden muss, ist dies so einfach wie das Ändern einer einzelnen Komponente, anstatt den Code überall einzufügen.

Da ich so unerfahren wie ich bin, sah ich natürlich keine Probleme, als ich anfing, das vererbungslastige Modell mit einer einzelnen Hierarchie zu verwenden. Wenn es also Probleme mit dem Modell gibt, an dessen Implementierung ich gerade denke, würde ich das nicht tun in der Lage sein, es zu sehen.

Deine meinungen?

PS: Ich verwende Java, daher ist die Verwendung von Mehrfachvererbung zur Implementierung dieser Funktion anstelle von normalen Komponenten nicht möglich.

PPS: Die Kommunikation zwischen Komponenten erfolgt durch Verknüpfung abhängiger Komponenten. Dies wird zu einer Kopplung führen, aber ich denke, es ist ein guter Kompromiss.

Midori Ryuu
quelle
2
Das scheint mir alles in Ordnung zu sein. Gibt es ein bestimmtes Beispiel in Ihrer Hierarchie oder Ihrem Entitätssystem, das für Sie "riecht"? Am Ende geht es darum, das Spiel mehr als nur ein Gefühl der Reinheit zu schaffen.
Drhayes
Ich weiß nicht, aber wie gesagt, ich habe kaum Erfahrung und bin weit davon entfernt, dies zu beurteilen.
Midori Ryuu
3
Ich habe dafür gestimmt, diese Frage zu schließen, da sie subjektiv und offen für eine breite Diskussion ist. Ich schätze, es wurde von einer aufrichtigen Position aus gefragt - aber die Entscheidung, wie hier vorzugehen ist, ist ganz und gar eine Frage der persönlichen Meinung. Der einzige wirkliche Nachteil der Fixierung Ihrer Komponenten in einer Unterklasse besteht darin, dass die Anordnung der Komponenten zur Laufzeit nicht geändert werden kann. Dies muss jedoch für Sie offensichtlich sein und ist eine Entscheidung, die Sie treffen.
Kylotan
4
Ich habe auch dafür gestimmt, zu schließen. Es ist eine gute und gut geschriebene Frage, aber für die GDSE nicht geeignet. Sie können versuchen, dies bei GameDev.net erneut zu veröffentlichen.
Sean Middleditch
Wie geschrieben, mit der Frage "Ihre Meinung?", Ist es in der Tat unbefristet und unbeantwortbar. Es ist jedoch beantwortbar, wenn Sie antworten, als ob die Frage lautete: "Gibt es klaffende Fallen, in die ich geraten könnte, ohne es zu merken?" (Zumindest, wenn Sie glauben, dass es tatsächlich einen empirischen Unterschied zwischen verschiedenen Architekturen gibt.)
John Calsbeek

Antworten:

8

Betrachten Sie dieses Beispiel:

Du machst ein RTS. Aus logischen Gründen entscheiden Sie sich, eine Basisklasse GameObjectund dann zwei Unterklassen zu erstellen, Buildingund Unit. Das funktioniert natürlich einwandfrei und am Ende haben Sie etwas, das so aussieht:

  • GameObject
    • ModelComponent
    • CollisionComponent
  • Building
    • ProductionComponent
  • Unit
    • AimingAIComponent
    • WeaponComponent
    • ParticleEmitterComponent
    • AnimationComponent

Jetzt hat jede Unterklasse von UnitIhnen bereits alle Komponenten, die Sie benötigen. Und als Bonus, Sie an einem einzigen Ort haben alle , dass langwierige Setup - Code zu setzen , die newein s up WeaponComponent, verbindet er sich ein AimingAIComponentund ein ParticleEmitterComponentWonderful!

Und natürlich können Sie es verwalten, wenn Sie sich schließlich dazu entschließen, eine TowerKlasse hinzuzufügen , die ein Gebäude ist, aber über eine Waffe verfügt , da sie die Logik immer noch in Komponenten zerlegt . Sie können Ihrer Unterklasse von ein AimingAIComponent, ein WeaponComponentund ein hinzufügen . Natürlich müssen Sie dann den Initialisierungscode aus der Klasse heraussuchen und an einer anderen Stelle ablegen, aber das ist kein großer Verlust.ParticleEmitterComponentBuildingUnit

Und jetzt, je nachdem, wie viel Weitsicht Sie hatten, können Sie mit subtilen Fehlern enden oder auch nicht. Es stellte sich heraus, dass es an anderer Stelle im Spiel möglicherweise Code gegeben hat, der so aussah:

if (myGameObject instanceof Unit)
    doSomething(((Unit)myGameObject).getWeaponComponent());

Das funktioniert stillschweigend nicht für Ihr TowerGebäude, obwohl Sie wirklich wollten, dass es für irgendetwas mit einer Waffe funktioniert. Jetzt muss es ungefähr so ​​aussehen:

WeaponComponent weapon = myGameObject.getComponent(WeaponComponent.class);
if (weapon)
    doSomething(weapon);

Viel besser! Nachdem Sie dies geändert haben, können alle von Ihnen geschriebenen Zugriffsmethoden in dieser Situation enden. Am sichersten ist es also, sie alle zu entfernen und über diese eine Methode auf jede Komponente zuzugreifen getComponent, die mit jeder Unterklasse arbeiten kann. (Alternativ können Sie get*Component()der Superklasse jede einzelne Art von hinzufügen GameObjectund sie in Unterklassen überschreiben, um die Komponente zurückzugeben. Die erste wird jedoch angenommen.)

Jetzt sind Ihre Buildingund UnitKlassen so ziemlich nur Taschen mit Komponenten, auf denen noch nicht einmal Zugriffsmethoden vorhanden sind. Und auch keine ausgefallene Initialisierung, da das meiste davon herausgezogen werden musste, um eine Codeduplizierung mit Ihrem einen speziellen anderen Objekt irgendwo zu vermeiden.

Wenn Sie dies auf die Spitze folgen, werden Sie immer am Ende Ihre Subklassen völlig inert Taschen von Komponenten bilden, wobei an diesem Punkt gibt es nicht einmal jeden Punkt die Unterklassen mit der Umgebung. Sie können fast alle Vorteile einer Klassenhierarchie mit einer Methodenhierarchie nutzen , die die richtigen Komponenten erstellt. Anstatt eine UnitKlasse zu haben, haben Sie eine addUnitComponentsMethode, die ein AnimationComponentund auch Aufrufe addWeaponComponentsmacht, die ein AimingAIComponent, ein WeaponComponentund ein macht ParticleEmitterComponent. Das Ergebnis ist, dass Sie alle Vorteile eines Entitätssystems, die Wiederverwendung von Code in einer Hierarchie und keine Versuchung haben instanceof, Ihren Klassentyp zu überprüfen oder in ihn umzuwandeln.

John Calsbeek
quelle
Gut gesagt. Hinzu kommt meiner Meinung nach die ideale Lösung, jede Entität mit einem Skript oder mit Deskriptordateien zu initialisieren. Ich verwende Textdateien für die Entitätsinitialisierung. Es ist schnell und ermöglicht es Nicht-Programmierern, verschiedene Parameter zu testen.
Coyote
Wow, das Lesen deines Beitrags hat mir einen Albtraum beschert! Natürlich meine ich das auf eine gute Art und Weise, als Sie mir die Augen geöffnet haben, mit was für einem Albtraum ich enden könnte! Ich wünschte, ich könnte mehr auf eine so ausführliche Antwort antworten, aber es gibt nicht wirklich viel hinzuzufügen!
Midori Ryuu
Auf einfachere Weise. Sie beenden die Verwendung von Inheritance als Man-Factory-Muster für Arme.
Zardoz89
2

Komposition über Vererbung ist die OOP-Terminologie (zumindest so, wie ich sie gelernt habe / gelernt habe). Um zwischen Komposition und Vererbung zu wählen, sagen Sie laut "Auto ist eine Art Rad" (Vererbung) und "Auto hat Räder" (Komposition). Einer sollte korrekter klingen als der andere (in diesem Fall die Komposition). Wenn Sie sich nicht entscheiden können, wählen Sie standardmäßig die Komposition, bis Sie eine andere Entscheidung treffen.

Daniel Carlsson
quelle
1

Wenn es darum geht, komponentenbasierte Systeme in bestehende Spiele zu integrieren, die auf Spielobjekten basieren, gibt es einen Artikel bei Cowboy Programming unter http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/, der Ihnen einige Hinweise geben könnte Art und Weise kann der Übergang erfolgen.

In Bezug auf Probleme muss Ihr Modell den Spieler dennoch anders als einen anderen Feind behandeln. Sie können den Status nicht ändern (dh der Spieler wird von der KI kontrolliert), wenn ein Spieler die Verbindung trennt oder in besonderen Fällen, ohne ihn anders als die anderen Charaktere zu behandeln.

Abgesehen von der starken Wiederverwendung von Komponenten über verschiedene Entitäten hinweg ist die Idee von komponentenbasierten Spielen die Einheitlichkeit des gesamten Systems. Jede Entität verfügt über Komponenten, die nach Belieben hinzugefügt und entfernt werden können. Alle Komponenten derselben Art werden mit einer Reihe von Aufrufen, die von der Schnittstelle (Basiskomponente) jeder Art (Position, Rendering, Skript ...) erstellt wurden, genauso behandelt.

Das Mischen der Vererbung von Spielobjekten mit einem komponentenbasierten Ansatz bringt Ihnen einige Vorteile des komponentenbasierten Ansatzes mit den Nachteilen beider Ansätze.

Es kann für Sie arbeiten. Aber ich würde nicht beides außerhalb einer Übergangsphase von einer Architektur zur anderen mischen.

PS geschrieben auf einem Telefon, also habe ich eine harte Zeit, zu externen Betriebsmitteln zu verbinden.

Kojote
quelle
Vielen Dank für Ihre Antwort, obwohl Sie telefoniert haben! Ich verstehe, was du meinst. Je bewegungskomponentenbasierter die Dinge sind, desto flexibler muss ich die Dinge ändern. Aus diesem Grund werde ich einen Kompositionsansatz mit minimaler Vererbung ausprobieren. (Noch mehr als die, die ich vorgeschlagen habe.)
Midori Ryuu
@MidoriRyuu Vermeiden Sie jede Vererbung in den Entitätsobjekten. Sie haben das Glück, eine Sprache mit eingebauter Reflexion (und Selbstbeobachtung) zu verwenden. Sie können Dateien (txt oder XML) verwenden, um Ihre Objekte mit den richtigen Parametern zu initialisieren. Es ist nicht zu viel Arbeit und ermöglicht eine verbesserte Feinabstimmung des Spiels, ohne es neu zu kompilieren. Behalten Sie jedoch die Entity-Klasse bei, zumindest für die Kommunikation zwischen Komponenten und andere Dienstprogramme. PS immer noch am Telefon :(
Coyote