Dieser Rat ist nicht wirklich spezifisch für das Rendern, sollte aber helfen, ein System zu entwickeln, das die Dinge weitgehend getrennt hält. Versuchen Sie zunächst, die 'GameObject'-Daten von den Positionsinformationen zu trennen.
Es ist erwähnenswert, dass einfache XYZ-Positionsinformationen möglicherweise nicht so einfach sind. Wenn Sie eine Physik-Engine verwenden, können Ihre Positionsdaten in der Engine eines Drittanbieters gespeichert werden. Sie müssten entweder eine Synchronisierung zwischen ihnen durchführen (was viel sinnloses Kopieren des Speichers bedeuten würde) oder die Informationen direkt von der Engine abfragen. Aber nicht alle Objekte benötigen Physik, einige werden an Ort und Stelle fixiert, so dass ein einfacher Satz von Schwimmern dort gut funktioniert. Einige können sogar an andere Objekte angehängt sein, sodass ihre Position tatsächlich ein Versatz von einer anderen Position ist. In einer erweiterten Konfiguration ist die Position möglicherweise nur auf der GPU gespeichert. Dies ist auf der Computerseite nur für die Skripterstellung, Speicherung und Netzwerkreplikation erforderlich. Sie haben also wahrscheinlich mehrere Möglichkeiten für Ihre Positionsdaten. Hier ist es sinnvoll, Vererbung zu verwenden.
Anstelle eines Objekts, dessen Position es besitzt, sollte dieses Objekt selbst einer Indexdatenstruktur gehören. Zum Beispiel kann ein Level eine Octree-Szene oder eine Physik-Engine-Szene haben. Wenn Sie eine Renderszene rendern (oder einrichten) möchten, fragen Sie Ihre spezielle Struktur nach Objekten ab, die für die Kamera sichtbar sind.
Dies trägt auch zu einer guten Speicherverwaltung bei. Auf diese Weise hat ein Objekt, das sich nicht in einem Bereich befindet, nicht einmal eine sinnvolle Position, anstatt 0.0-Koordinaten oder die Koordinaten zurückzugeben, die es hatte, als es das letzte Mal in einem Bereich war.
Wenn Sie die Koordinaten nicht mehr im Objekt behalten, erhalten Sie anstelle von object.getX () level.getX (object). Das Problem dabei ist, dass das Objekt in der Ebene gesucht wird, wahrscheinlich ein langsamer Vorgang, da es alle Objekte durchsuchen und mit dem übereinstimmen muss, den Sie abfragen.
Um dies zu vermeiden, würde ich wahrscheinlich eine spezielle 'Link'-Klasse erstellen. Eine, die zwischen einer Ebene und einem Objekt bindet. Ich nenne es einen "Ort". Dies würde die xyz-Koordinaten sowie das Handle für die Ebene und ein Handle für das Objekt enthalten. Diese Verknüpfungsklasse würde in der räumlichen Struktur / Ebene gespeichert und das Objekt würde einen schwachen Verweis darauf haben (wenn die Ebene / Position zerstört wird, muss die Objektreferenz auf null aktualisiert werden. Es könnte sich auch lohnen, die Standortklasse tatsächlich zu haben "besitzen" Sie das Objekt, so dass, wenn eine Ebene gelöscht wird, auch die spezielle Indexstruktur, die darin enthaltenen Positionen und ihre Objekte.
typedef std::tuple<Level, Object, PositionXYZ> Location;
Jetzt werden die Positionsinformationen nur noch an einem Ort gespeichert. Nicht dupliziert zwischen dem Objekt, der räumlichen Indexstruktur, dem Renderer usw.
Räumliche Datenstrukturen wie Octrees müssen oft nicht einmal die Koordinaten der Objekte haben, die sie speichern. Diese Position wird an der relativen Position der Knoten in der Struktur selbst gespeichert (dies könnte als eine Art verlustbehaftete Komprimierung angesehen werden, die Genauigkeit für schnelle Nachschlagezeiten opfert). Mit dem Standortobjekt im Octree werden dann die tatsächlichen Koordinaten darin gefunden, sobald die Abfrage abgeschlossen ist.
Oder wenn Sie eine Physik-Engine zum Verwalten Ihrer Objektpositionen oder einer Mischung aus beiden verwenden, sollte die Location-Klasse dies transparent handhaben und Ihren gesamten Code an einem Ort aufbewahren.
Ein weiterer Vorteil ist nun, dass die Position und die Referenz zum Level am selben Ort gespeichert werden. Sie können object.TeleportTo (other_object) implementieren und über mehrere Ebenen hinweg ausführen. In ähnlicher Weise könnte die KI-Wegfindung etwas in einen anderen Bereich verfolgen.
In Bezug auf das Rendern. Ihr Render kann eine ähnliche Bindung zum Ort haben. Außer, dass es das Rendering-spezifische Zeug gibt. Wahrscheinlich müssen Sie das Objekt oder die Ebene nicht in dieser Struktur speichern. Das Objekt kann nützlich sein, wenn Sie versuchen, etwas wie eine Farbauswahl vorzunehmen oder eine darüber schwebende Trefferleiste usw. zu rendern. Andernfalls kümmert sich der Renderer nur um das Netz und dergleichen. RenderableStuff wäre ein Mesh, könnte auch Rahmen haben und so weiter.
typedef std::pair<RenderableStuff, PositionXYZ> RenderThing;
renderer.render(level, camera);
renderer: object = level.getVisibleObjects(camera);
level: physics.getObjectsInArea(physics.getCameraFrustrum(camera));
for(object in objects) {
//This could be depth sorted, meshes could be broken up and sorted by material for batch rendering or whatever
rendering_que.addObjectToRender(object);
}
Möglicherweise müssen Sie dies nicht für jedes Bild einzeln durchführen. Sie können auch sicherstellen, dass Sie einen größeren Bereich aufnehmen, als von der Kamera aktuell angezeigt wird. Zwischenspeichern, Objektbewegungen verfolgen, um festzustellen, ob der Begrenzungsrahmen in Reichweite ist, Kamerabewegung verfolgen usw. Aber fangen Sie erst an, mit solchen Dingen herumzuspielen, wenn Sie sie getestet haben.
Ihre Physik-Engine selbst verfügt möglicherweise über eine ähnliche Abstraktion, da sie nicht nur die Kollisionsgitter- und Physik-Eigenschaften, sondern auch die Objektdaten benötigt.
Alle Ihre Kernobjektdaten enthalten würde, wäre der Name des Netzes, das das Objekt verwendet. Die Spiel-Engine kann diese dann in jedem beliebigen Format laden, ohne Ihre Objektklasse mit einer Reihe von renderspezifischen Dingen zu belasten (die möglicherweise für Ihre Rendering-API spezifisch sind, z. B. DirectX vs OpenGL).
Es hält auch verschiedene Komponenten getrennt. Dies macht es einfach, Dinge wie das Ersetzen Ihrer Physik-Engine zu tun, da dieses Zeug meist in sich geschlossen an einem Ort ist. Es macht auch das Testen viel einfacher. Sie können Dinge wie physikalische Abfragen testen, ohne dass Sie tatsächlich gefälschte Objekte einrichten müssen, da Sie lediglich die Location-Klasse benötigen. Sie können auch Dinge einfacher optimieren. Es wird klarer, welche Abfragen Sie für welche Klassen und einzelnen Standorte ausführen müssen, um sie zu optimieren (z. B. in der obigen Ebene. GetVisibleObject können Sie Dinge zwischenspeichern, wenn sich die Kamera nicht zu stark bewegt).
m_renderable
Mitglied interagieren zu lassen . Auf diese Weise können Sie Ihre Logik besser trennen. Erzwingen Sie die renderbare "Schnittstelle" nicht für allgemeine Objekte, die auch über Physik, AI und so weiter verfügen. Danach können Sie renderbare Objekte separat verwalten. Sie benötigen eine Abstraktionsebene über OpenGL-Funktionsaufrufe, um die Dinge noch mehr zu entkoppeln. Erwarten Sie also nicht, dass eine gute Engine GL-API-Aufrufe in ihren verschiedenen darstellbaren Implementierungen enthält. Das war's in aller Kürze.