Persönlich empfehle ich, die Zeichenfunktion außerhalb der Object-Klasse selbst zu halten. Ich empfehle sogar, die Position / Koordinaten des Objekts außerhalb des Objekts selbst zu halten.
Diese draw () -Methode wird sich mit der Low-Level-Rendering-API von OpenGL, OpenGL ES, Direct3D, Ihrem Wrapping-Layer auf diesen APIs oder einer Engine-API befassen. Es kann sein, dass Sie zwischen diesen Einstellungen wechseln müssen (wenn Sie beispielsweise OpenGL + OpenGL ES + Direct3D unterstützen möchten).
Dieses GameObject sollte nur die grundlegenden Informationen über das Erscheinungsbild enthalten, z. B. ein Mesh oder ein größeres Bundle, einschließlich Shader-Eingaben, Animationsstatus usw.
Außerdem möchten Sie eine flexible Grafik-Pipeline. Was passiert, wenn Sie Objekte anhand ihres Abstands zur Kamera bestellen möchten? Oder ihre Materialart. Was passiert, wenn Sie ein 'ausgewähltes' Objekt in einer anderen Farbe zeichnen möchten? Was ist, wenn Sie beim Aufrufen einer Zeichenfunktion für ein Objekt nicht das Rendern durchführen, sondern es in eine Befehlsliste mit Aktionen einfügen, die das Rendern ausführen soll (möglicherweise für das Threading erforderlich)? Sie können so etwas mit dem anderen System machen, aber es ist eine PITA.
Ich empfehle, anstatt direkt zu zeichnen, alle gewünschten Objekte an eine andere Datenstruktur zu binden. Diese Bindung muss nur wirklich einen Verweis auf die Position des Objekts und die Rendering-Informationen enthalten.
Ihre Ebenen / Blöcke / Bereiche / Karten / Hubs / ganze Welt / was auch immer einen räumlichen Index erhalten, dieser enthält die Objekte und gibt sie basierend auf Koordinatenabfragen zurück und könnte eine einfache Liste oder so etwas wie ein Octree sein. Es könnte auch ein Wrapper für etwas sein, das von einer Physik-Engine eines Drittanbieters als Physikszene implementiert wird. Es ermöglicht Ihnen, Dinge wie "Alle Objekte abfragen, die sich in der Ansicht der Kamera befinden, mit einem zusätzlichen Bereich um sie herum" oder für einfachere Spiele, bei denen Sie einfach alles rendern können, greifen Sie auf die gesamte Liste zu.
Raumindizes müssen nicht die tatsächlichen Positionsinformationen enthalten. Sie speichern Objekte in Baumstrukturen in Bezug auf die Position anderer Objekte. Sie können jedoch als eine Art verlustbehafteter Cache angesehen werden, der ein schnelles Nachschlagen eines Objekts anhand seiner Position ermöglicht. Es besteht keine wirkliche Notwendigkeit, Ihre tatsächlichen X-, Y- und Z-Koordinaten zu duplizieren. Davon abgesehen könntest du, wenn du behalten wolltest
Tatsächlich müssen Ihre Spielobjekte nicht einmal ihre eigenen Standortinformationen enthalten. Zum Beispiel sollte ein Objekt, das nicht in eine Ebene gelegt wurde, keine x-, y-, z-Koordinaten haben, was keinen Sinn ergibt. Sie können das in dem speziellen Index enthalten. Wenn Sie die Koordinaten des Objekts basierend auf seiner tatsächlichen Referenz nachschlagen müssen, müssen Sie eine Bindung zwischen dem Objekt und dem Szenendiagramm herstellen (Szenendiagramme dienen zum Zurückgeben von Objekten basierend auf Koordinaten, sind jedoch beim Zurückgeben von Koordinaten basierend auf Objekten langsam). .
Wenn Sie einem Level ein Objekt hinzufügen. Es wird folgendes tun:
1) Legen Sie eine Lokationsstruktur an:
class Location {
float x, y, z; // Or a special Coordinates class, or a vec3 or whatever.
SpacialIndex& spacialIndex; // Note this could be the area/level/map/whatever here
};
Dies kann auch ein Verweis auf ein Objekt in einer Physik-Engine eines Drittanbieters sein. Oder es könnte eine Versatzkoordinate mit einem Verweis auf einen anderen Ort sein (für eine Verfolgungskamera oder ein angefügtes Objekt oder ein Beispiel). Beim Polymorphismus kann dies davon abhängen, ob es sich um ein statisches oder ein dynamisches Objekt handelt. Wenn Sie hier beim Aktualisieren der Koordinaten einen Verweis auf den Raumindex behalten, kann dies auch der Raumindex sein.
Wenn Sie sich Sorgen über die dynamische Speicherzuweisung machen, verwenden Sie einen Speicherpool.
2) Eine Bindung / Verknüpfung zwischen Ihrem Objekt, seiner Position und dem Szenengraphen.
typedef std::pair<Object, Location> SpacialBinding.
3) Die Bindung wird an der entsprechenden Stelle zum räumlichen Index innerhalb der Ebene hinzugefügt.
Wenn Sie sich auf das Rendern vorbereiten.
1) Holen Sie sich die Kamera (Es wird nur ein weiteres Objekt sein, außer dass der Ort den Charakter des Spielers verfolgt und Ihr Renderer einen speziellen Verweis darauf hat, das ist alles, was er wirklich benötigt).
2) Holen Sie sich die SpacialBinding der Kamera.
3) Ermitteln Sie den Raumindex aus der Bindung.
4) Fragen Sie die Objekte ab, die (möglicherweise) für die Kamera sichtbar sind.
5A) Sie müssen die visuellen Informationen verarbeiten lassen. Auf die GPU hochgeladene Texturen und so weiter. Dies geschieht am besten im Voraus (z. B. beim Laden auf einer Ebene), kann jedoch möglicherweise zur Laufzeit erfolgen (in einer offenen Welt können Sie Dinge laden, wenn Sie sich einem Block nähern, dies sollte jedoch noch im Voraus erfolgen).
5B) Erstellen Sie optional einen zwischengespeicherten Render-Baum, wenn Sie Objekte in der Nähe sortieren oder nachverfolgen möchten, die möglicherweise zu einem späteren Zeitpunkt sichtbar sind. Andernfalls können Sie den räumlichen Index jedes Mal abfragen, wenn dies von Ihren Spiel- / Leistungsanforderungen abhängt.
Ihr Renderer benötigt wahrscheinlich ein RenderBinding-Objekt, das die Koordinaten zwischen dem Objekt und den Koordinaten verknüpft
class RenderBinding {
Object& object;
RenderInformation& renderInfo;
Location& location // This could just be a coordinates class.
}
Wenn Sie dann rendern, führen Sie einfach die Liste aus.
Ich habe oben Referenzen verwendet, aber sie können intelligente Zeiger, unformatierte Zeiger, Objekthandles usw. sein.
BEARBEITEN:
class Game {
weak_ptr<Camera> camera;
Level level1;
void init() {
Camera camera(75.0_deg, 1.025_ratio, 1000_meters);
auto template_player = loadObject("Player.json")
auto player = level1.addObject(move(player), Position(1.0, 2.0, 3.0));
level1.addObject(move(camera), getRelativePosition(player));
auto template_bad_guy = loadObject("BadGuy.json")
level1.addObject(template_bad_guy, {10, 10, 20});
level1.addObject(template_bad_guy, {10, 30, 20});
level1.addObject(move(template_bad_guy), {50, 30, 20});
}
void render() {
camera->getFrustrum();
auto level = camera->getLocation()->getLevel();
auto object = level.getVisible(camera);
for(object : objects) {
render(objects);
}
}
void render(Object& object) {
auto ri = object.getRenderInfo();
renderVBO(ri.getVBO());
}
Object loadObject(string file) {
Object object;
// Load file from disk and set the properties
// Upload mesh data, textures to GPU. Load shaders whatever.
object.setHitPoints(// values from file);
object.setRenderInfo(// data from 3D api);
}
}
class Level {
Octree octree;
vector<ObjectPtr> objects;
// NOTE: If your level is mesh based there might also be a BSP here. Or a hightmap for an openworld
// There could also be a physics scene here.
ObjectPtr addObject(Object&& object, Position& pos) {
Location location(pos, level, object);
objects.emplace_back(object);
object->setLocation(location)
return octree.addObject(location);
}
vector<Object> getVisible(Camera& camera) {
auto f = camera.getFtrustrum();
return octree.getObjectsInFrustrum(f);
}
void updatePosition(LocationPtr l) {
octree->updatePosition(l);
}
}
class Octree {
OctreeNode root_node;
ObjectPtr add(Location&& object) {
return root_node.add(location);
}
vector<ObjectPtr> getObjectsInRadius(const vec3& position, const float& radius) { // pass to root_node };
vector<ObjectPtr> getObjectsinFrustrum(const FrustrumShape frustrum;) {//...}
void updatePosition(LocationPtr* l) {
// Walk up from l.octree_node until you reach the new place
// Check if objects are colliding
// l.object.CollidedWith(other)
}
}
class Object {
Location location;
RenderInfo render_info;
Properties object_props;
Position getPosition() { return getLocation().position; }
Location getLocation() { return location; }
void collidedWith(ObjectPtr other) {
// if other.isPickup() && object.needs(other.pickupType()) pick it up, play sound whatever
}
}
class Location {
Position position;
LevelPtr level;
ObjectPtr object;
OctreeNote octree_node;
setPosition(Position position) {
position = position;
level.updatePosition(this);
}
}
class Position {
vec3 coordinates;
vec3 rotation;
}
class RenderInfo {
AnimationState anim;
}
class RenderInfo_OpenGL : public RenderInfo {
GLuint vbo_object;
GLuint texture_object;
GLuint shader_object;
}
class Camera: public Object {
Degrees fov;
Ratio aspect;
Meters draw_distance;
Frustrum getFrustrum() {
// Use above to make a skewed frustum box
}
}
Was die gegenseitige Bewusstwerdung betrifft. Das ist Kollisionserkennung. Es würde wahrscheinlich im Octree implementiert werden. Sie müssten in Ihrem Hauptobjekt einen Rückruf bereitstellen. Dieses Zeug wird am besten von einer richtigen Physik-Engine wie Bullet gehandhabt. In diesem Fall ersetzen Sie Octree einfach durch PhysicsScene und Position durch einen Link zu CollisionMesh.getPosition ().