Wenn Sie Spiele erstellen, erstellen Sie häufig das folgende Spielobjekt, von dem alle Entitäten erben:
public class GameObject{
abstract void Update(...);
abstract void Draw(...);
}
In Ihrer Aktualisierungsschleife iterieren Sie also über alle Spielobjekte und geben ihnen die Möglichkeit, den Status zu ändern. In der nächsten Ziehschleife durchlaufen Sie erneut alle Spielobjekte und geben ihnen die Möglichkeit, sich selbst zu zeichnen.
Obwohl dies in einem einfachen Spiel mit einem einfachen Forward-Renderer ziemlich gut funktioniert, führt es oft zu einigen gigantischen Spielobjekten, die ihre Modelle speichern müssen, mehreren Texturen und am schlimmsten zu einer Fat Draw-Methode, die eine enge Kopplung zwischen dem Spielobjekt schafft. die aktuelle Renderstrategie und alle Rendering-bezogenen Klassen.
Wenn ich die Renderstrategie von vorwärts auf verzögert ändern würde, müsste ich viele Spielobjekte aktualisieren. Und die Spielobjekte, die ich mache, sind nicht so wiederverwendbar, wie sie sein könnten. Natürlich können Vererbung und / oder Komposition mir helfen, die Duplizierung von Code zu bekämpfen und die Änderung der Implementierung ein wenig zu vereinfachen, aber es scheint immer noch mangelhaft zu sein.
Ein besserer Weg wäre vielleicht, die Draw-Methode vollständig aus der GameObject-Klasse zu entfernen und eine Renderer-Klasse zu erstellen. Das GameObject müsste noch einige Daten über seine Grafik enthalten, z. B. mit welchem Modell es dargestellt werden soll und welche Texturen auf das Modell gemalt werden sollen. Wie dies gemacht wird, bleibt jedoch dem Renderer überlassen. Es gibt jedoch oft viele Grenzfälle beim Rendern. Obwohl dies die enge Kopplung vom GameObject zum Renderer aufheben würde, müsste der Renderer immer noch alles über alle Spielobjekte wissen, die es fett machen würden, alles wissen und eng verbunden. Dies würde gegen einige gute Praktiken verstoßen. Vielleicht könnte datenorientiertes Design den Trick machen. Spielobjekte wären sicherlich Daten, aber wie würde der Renderer davon angetrieben? Ich bin mir nicht sicher.
Ich bin also ratlos und kann mir keine gute Lösung vorstellen. Ich habe versucht, die Prinzipien von MVC anzuwenden, und in der Vergangenheit hatte ich einige Ideen, wie man das in Spielen verwendet, aber in letzter Zeit scheint es nicht so anwendbar zu sein, wie ich dachte. Ich würde gerne wissen, wie Sie alle dieses Problem angehen.
Wie auch immer, ich bin daran interessiert, wie die folgenden Designziele erreicht werden können.
- Keine Renderlogik im Spielobjekt
- Lose Kopplung zwischen Spielobjekten und Render-Engine
- Kein allwissender Renderer
- Vorzugsweise Laufzeitumschaltung zwischen Render-Engines
Das ideale Projekt-Setup wäre ein separates 'Spiellogik'- und Renderlogik-Projekt, das nicht aufeinander verweisen muss.
Dieser Gedankengang begann, als ich John Carmack auf Twitter sagen hörte, dass er ein System hat, das so flexibel ist, dass er Render-Engines zur Laufzeit austauschen und sein System sogar anweisen kann, beide Renderer (einen Software-Renderer und einen hardwarebeschleunigten Renderer) zu verwenden. Gleichzeitig kann er Unterschiede untersuchen. Die Systeme, die ich bisher programmiert habe, sind nicht einmal annähernd so flexibel
quelle
Was ich für meine eigene Engine getan habe, ist, alles in Module zu gruppieren. Also habe ich meine
GameObject
Klasse und sie hat einen Griff zu:Ich habe also eine
Player
Klasse und eineBullet
Klasse. Beide stammen vonGameObject
und werden dem hinzugefügtScene
. HatPlayer
aber folgende Module:Und
Bullet
hat diese Module:Diese Art, Dinge zu organisieren, vermeidet den "Diamanten des Todes", in dem Sie ein
Vehicle
, einVehicleLand
und ein habenVehicleWater
und jetzt ein wollenVehicleAmphibious
. Stattdessen hast du einVehicle
und es kann einModuleWater
und ein habenModuleLand
.Zusätzlicher Bonus: Sie können Objekte mit einer Reihe von Eigenschaften erstellen. Alles, was Sie wissen müssen, ist der Basistyp (Player, Enemy, Bullet usw.) und dann Handles für Module erstellen, die Sie für diesen Typ benötigen.
In meiner Szene mache ich Folgendes:
Update
für alleGameObject
Griffe auf.ModuleCollision
Handle haben.UpdatePost
für alleGameObject
Griffe an, um ihre endgültige Position nach der Physik zu erfahren.m_ObjectsCreated
Liste neue Objekte aus derm_Objects
Liste hinzu.Und ich könnte es weiter organisieren: nach Modulen statt nach Objekten. Dann würde ich eine Liste von rendern, eine Reihe von
ModuleSprite
aktualisierenModuleScriptingBase
und Kollisionen mit einer Liste von durchführenModuleCollision
.quelle
GameObject
(zum Beispiel eine Möglichkeit, eine "Schlange" von Sprites zu rendern), müssen Sie entweder ein untergeordnetes ElementModuleSprite
für diese bestimmte Funktionalität erstellen (ModuleSpriteSnake
) oder ein neues Modul hinzufügen (ModuleSnake
). Zum Glück sind sie nur Zeiger, aber ich habe Code gesehen, in demGameObject
buchstäblich alles getan wurde, was ein Objekt tun konnte.