Esse conselho não é realmente específico da renderização, mas deve ajudar a criar um sistema que mantenha as coisas em grande parte separadas. Primeiro, tente manter os dados 'GameObject' separados das informações de posição.
Vale a pena notar que informações posicionais simples de XYZ podem não ser tão simples. Se você estiver usando um mecanismo de física, os dados de posição poderão ser armazenados no mecanismo de terceiros. Você precisaria sincronizar entre eles (o que envolveria muita cópia inútil da memória) ou consultar as informações diretamente do mecanismo. Mas nem todos os objetos precisam de física, alguns serão fixados no local, então um simples conjunto de carros alegóricos funciona bem lá. Alguns podem até ser anexados a outros objetos, portanto, sua posição é na verdade um deslocamento de outra posição. Em uma configuração avançada, você pode ter a posição armazenada apenas na GPU; o único momento em que seria necessário no lado do computador é para scripts, armazenamento e replicação de rede. Portanto, você provavelmente terá várias opções possíveis para seus dados posicionais. Aqui faz sentido usar herança.
Em vez de um objeto possuir sua posição, ele deve pertencer a uma estrutura de dados de indexação. Por exemplo, um 'Nível' pode ter um Octree, ou talvez uma 'cena' de um mecanismo de física. Quando você deseja renderizar (ou configurar uma cena de renderização), consulta sua estrutura especial em busca de objetos visíveis para a câmera.
Isso também ajuda a fornecer um bom gerenciamento de memória. Dessa forma, um objeto que não está realmente em uma área nem sequer tem uma posição que faça sentido, em vez de retornar 0,0 ou mais cordões que ele tinha na última vez em uma área.
Se você não mantiver mais as coordenadas no objeto, em vez de object.getX (), você acabará tendo level.getX (objeto). O problema com isso é procurar o objeto no nível provavelmente será uma operação lenta, pois terá que examinar todos os seus objetos e corresponder ao que você está consultando.
Para evitar isso, eu provavelmente criaria uma classe 'link' especial. Um que se liga entre um nível e um objeto. Eu chamo isso de "Localização". Isso conteria as coordenadas xyz, bem como a alça para o nível e uma alça para o objeto. Essa classe de link seria armazenada na estrutura / nível espacial e o objeto teria uma referência fraca a ela (se o nível / localização for destruído, a atualização dos objetos precisará ser atualizada para nula. Também pode valer a pena ter a classe Location realmente 'possua' o objeto, assim, se um nível for excluído, a estrutura especial do índice, os locais que ele contém e seus Objetos.
typedef std::tuple<Level, Object, PositionXYZ> Location;
Agora, as informações de posição são armazenadas apenas em um único local. Não duplicado entre o objeto, a estrutura de indexação espacial, o renderizador e assim por diante.
Estruturas de dados espaciais como Octrees geralmente nem precisam ter as coordenadas dos objetos que armazenam. Essa posição é armazenada na localização relativa dos nós na própria estrutura (pode ser considerada como uma espécie de compactação com perdas, sacrificando a precisão por tempos de pesquisa rápidos). Com o objeto de localização no Octree, as coordenadas reais são encontradas dentro dele depois que a consulta é concluída.
Ou, se você estiver usando um mecanismo de física para gerenciar as localizações dos objetos ou uma mistura entre as duas, a classe Location deve lidar com isso de forma transparente, mantendo todo o seu código em um único local.
Outra vantagem é agora a posição e a referência ao nível são armazenadas no mesmo local. Você pode implementar object.TeleportTo (other_object) e fazê-lo funcionar em vários níveis. Da mesma forma, a descoberta de caminhos da IA pode seguir algo em uma área diferente.
Com relação à renderização. Sua renderização pode ter uma ligação semelhante ao local. Exceto que teria o material específico de renderização lá. Você provavelmente não precisa que o 'Objeto' ou 'Nível' seja armazenado nessa estrutura. O objeto pode ser útil se você estiver tentando fazer algo como escolher cores ou renderizar uma barra de hit flutuando acima dele, mas, caso contrário, o renderizador se preocupa apenas com a malha e tal. RenderableStuff seria uma malha, também poderia ter caixas delimitadoras e assim por diante.
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);
}
Talvez você não precise fazer isso em todos os quadros, pode garantir uma região maior do que a câmera atualmente mostra. Coloque em cache, rastreie os movimentos do objeto para ver se a caixa delimitadora está ao alcance, rastreie o movimento da câmera e assim por diante. Mas não comece a mexer com esse tipo de coisa até que você o compare.
O próprio mecanismo de física pode ter uma abstração semelhante, pois também não precisa dos dados do objeto, apenas da malha de colisão e das propriedades físicas.
Todos os dados do objeto principal conteriam o nome da malha que o objeto usa. O mecanismo de jogo pode então prosseguir e carregá-lo no formato que desejar, sem sobrecarregar sua classe de objeto com várias coisas específicas de renderização (que podem ser específicas à sua API de renderização, por exemplo, DirectX vs OpenGL).
Também mantém diferentes componentes separados. Isso facilita fazer coisas como substituir o seu mecanismo de física, já que essas coisas são praticamente independentes em um único local. Isso também facilita muito o desestímulo. Você pode testar itens como consultas de física sem ter que configurar objetos falsos reais, pois tudo o que você precisa é da classe Location. Você também pode otimizar as coisas mais facilmente. Isso torna mais óbvias as consultas que você precisa executar em quais classes e locais únicos para otimizá-las (por exemplo, o level.getVisibleObject acima seria o local onde você poderia armazenar em cache as coisas se a câmera não se mover muito).
m_renderable
membro. Dessa forma, você pode separar melhor sua lógica. Não imponha a "interface" renderizável em objetos gerais que também possuem física, ai e outros enfeites. Depois disso, você pode gerenciar os renderizáveis separadamente. Você precisa de uma camada de abstração nas chamadas de função do OpenGL para dissociar ainda mais as coisas. Portanto, não espere que um bom mecanismo tenha chamadas de API GL em suas várias implementações renderizáveis. É isso, em poucas palavras.