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.
quelle
Antworten:
Betrachten Sie dieses Beispiel:
Du machst ein RTS. Aus logischen Gründen entscheiden Sie sich, eine Basisklasse
GameObject
und dann zwei Unterklassen zu erstellen,Building
undUnit
. 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
Unit
Ihnen bereits alle Komponenten, die Sie benötigen. Und als Bonus, Sie an einem einzigen Ort haben alle , dass langwierige Setup - Code zu setzen , dienew
ein s upWeaponComponent
, verbindet er sich einAimingAIComponent
und einParticleEmitterComponent
Wonderful!Und natürlich können Sie es verwalten, wenn Sie sich schließlich dazu entschließen, eine
Tower
Klasse 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 einAimingAIComponent
, einWeaponComponent
und 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.ParticleEmitterComponent
Building
Unit
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:
Das funktioniert stillschweigend nicht für Ihr
Tower
Gebäude, obwohl Sie wirklich wollten, dass es für irgendetwas mit einer Waffe funktioniert. Jetzt muss es ungefähr so aussehen: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 Sieget*Component()
der Superklasse jede einzelne Art von hinzufügenGameObject
und sie in Unterklassen überschreiben, um die Komponente zurückzugeben. Die erste wird jedoch angenommen.)Jetzt sind Ihre
Building
undUnit
Klassen 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
Unit
Klasse zu haben, haben Sie eineaddUnitComponents
Methode, die einAnimationComponent
und auch AufrufeaddWeaponComponents
macht, die einAimingAIComponent
, einWeaponComponent
und ein machtParticleEmitterComponent
. Das Ergebnis ist, dass Sie alle Vorteile eines Entitätssystems, die Wiederverwendung von Code in einer Hierarchie und keine Versuchung habeninstanceof
, Ihren Klassentyp zu überprüfen oder in ihn umzuwandeln.quelle
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.
quelle
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.
quelle