Este consejo no es realmente específico para el renderizado, pero debería ayudar a crear un sistema que mantenga las cosas en gran medida separadas. En primer lugar, intente mantener los datos de 'GameObject' separados de la información de posición.
Vale la pena señalar que la simple información posicional de XYZ podría no ser tan simple. Si está utilizando un motor de física, los datos de posición podrían almacenarse en el motor de terceros. Debería sincronizar entre ellos (lo que implicaría una gran cantidad de copia de memoria sin sentido) o consultar la información directamente desde el motor. Pero no todos los objetos necesitan física, algunos se fijarán en su lugar, por lo que un conjunto simple de flotadores funciona bien allí. Algunos incluso pueden estar unidos a otros objetos, por lo que su posición es en realidad un desplazamiento de otra posición. En una configuración avanzada, es posible que tenga una posición almacenada solo en la GPU, la única vez que se necesitaría el lado de la computadora es para secuencias de comandos, almacenamiento y replicación de red. Por lo tanto, es probable que tenga varias opciones posibles para sus datos posicionales. Aquí tiene sentido usar la herencia.
En lugar de un objeto que posee su posición, ese objeto debería ser propiedad de una estructura de datos de indexación. Por ejemplo, un 'Nivel' podría tener un Octree, o tal vez una 'escena' de motor de física. Cuando desea renderizar (o configurar una escena de renderizado), consulta su estructura especial para los objetos que son visibles para la cámara.
Esto también ayuda a administrar bien la memoria. De esta manera, un objeto que no está realmente en un área ni siquiera tiene una posición que tenga sentido en lugar de devolver 0.0 coords o los coords que tenía cuando era el último en un área.
Si ya no mantiene las coordenadas en el objeto, en lugar de object.getX () terminaría teniendo level.getX (objeto). El problema con eso es buscar el objeto en el nivel probablemente será una operación lenta ya que tendrá que mirar a través de todos sus objetos y coincidir con el que está consultando.
Para evitar eso, probablemente crearía una clase especial de 'enlace'. Uno que se une entre un nivel y un objeto. Yo lo llamo una "ubicación". Esto contendría las coordenadas xyz, así como el controlador del nivel y el controlador del objeto. Esta clase de enlace se almacenaría en la estructura / nivel espacial y el objeto tendría una referencia débil (si el nivel / ubicación se destruye, la referencia de los objetos debe actualizarse a nulo). También podría valer la pena tener la clase Ubicación 'posee' el objeto, de esa manera si se elimina un nivel, también lo es la estructura de índice especial, las ubicaciones que contiene y sus objetos.
typedef std::tuple<Level, Object, PositionXYZ> Location;
Ahora la información de posición se almacena solo en un lugar. No se duplica entre el objeto, la estructura de indexación espacial, el renderizador, etc.
Las estructuras de datos espaciales como los octrees a menudo ni siquiera necesitan tener las coordenadas de los objetos que almacenan. Su posición se almacena en la ubicación relativa de los nodos en la estructura misma (podría considerarse como una especie de compresión con pérdida, sacrificando la precisión para tiempos de búsqueda rápidos). Con el objeto de ubicación en Octree, las coordenadas reales se encuentran dentro de él una vez que se realiza la consulta.
O si está utilizando un motor de física para administrar las ubicaciones de sus objetos o una mezcla entre los dos, la clase Ubicación debe manejarlo de manera transparente mientras mantiene todo su código en un solo lugar.
Otra ventaja es que ahora la posición y la referencia al nivel se almacenan en la misma ubicación. Puede implementar object.TeleportTo (other_object) y hacer que funcione en todos los niveles. Del mismo modo, la búsqueda de rutas de IA podría seguir algo en un área diferente.
Con respecto a la representación. Su render puede tener un enlace similar a la ubicación. Excepto que tendría el material específico de representación allí. Probablemente no necesite el 'Objeto' o 'Nivel' para ser almacenado en esta estructura. El Objeto podría ser útil si está tratando de hacer algo como elegir un color o representar una barra de golpe flotando sobre él, etc., pero de lo contrario, el renderizador solo se preocupa por la malla y demás. RenderableStuff sería una malla, también podría tener cuadros delimitadores, etc.
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);
}
Es posible que no necesite hacer esto cada fotograma, puede asegurarse de tomar una región más grande que la que la cámara muestra actualmente. Cachéalo, rastrea los movimientos de objetos para ver si hay un cuadro delimitador dentro del rango, rastrea el movimiento de la cámara, etc. Pero no comiences a jugar con ese tipo de cosas hasta que lo hayas evaluado.
El motor de física en sí podría tener una abstracción similar, ya que tampoco necesita los datos del Objeto, solo la malla de colisión y las propiedades físicas.
Todos los datos del objeto central que contendrían serían el nombre de la malla que usa el objeto. El motor del juego puede continuar y cargar esto en el formato que desee sin cargar su clase de objeto con un montón de cosas específicas de renderizado (que pueden ser específicas de su API de renderizado, es decir, DirectX vs OpenGL).
También mantiene diferentes componentes separados. Esto hace que sea fácil hacer cosas como reemplazar su motor de física, ya que esas cosas son en su mayoría autónomas en una ubicación. También hace que las pruebas unitarias sean mucho más fáciles. Puede probar cosas como consultas de física sin tener que configurar ningún objeto falso real, ya que todo lo que necesita es la clase Ubicación. También puedes optimizar las cosas más fácilmente. Esto hace que sea más obvio qué consultas debe realizar en qué clases y ubicaciones individuales para optimizarlas (por ejemplo, el nivel anterior. GetVisibleObject sería donde podría almacenar en caché las cosas si la cámara no se mueve demasiado).
m_renderable
miembro. De esa manera, puedes separar mejor tu lógica. No aplique la "interfaz" renderizable en objetos generales que también tengan física, ai y otras cosas. Después de eso, puede administrar los renderizables por separado. Necesita una capa de abstracción sobre las llamadas a la función OpenGL para desacoplar las cosas aún más. Por lo tanto, no espere que un buen motor tenga llamadas GL API dentro de sus diversas implementaciones renderizables. Eso es todo, en pocas palabras.