Pessoalmente, recomendo manter a função draw fora da própria classe Object. Eu até recomendo manter a localização / coordenadas dos objetos fora do próprio objeto.
Esse método draw () vai lidar com a API de renderização de baixo nível do OpenGL, OpenGL ES, Direct3D, sua camada de quebra automática nessas APIs ou uma API de mecanismos. Pode ser que você tenha que trocar entre eles (se você quisesse dar suporte ao OpenGL + OpenGL ES + Direct3D, por exemplo.
Esse GameObject deve conter apenas as informações básicas sobre sua aparência visual, como uma malha ou talvez um pacote maior, incluindo entradas de sombreador, estado de animação e assim por diante.
Você também vai querer um pipeline gráfico flexível. O que acontece se você deseja solicitar objetos com base na distância da câmera. Ou seu tipo de material. O que acontece se você deseja desenhar um objeto 'selecionado' com uma cor diferente. E se, em vez de realmente renderizar tanto quanto você chamar uma função de desenho em um objeto, ele a colocar em uma lista de comandos de ações a serem executadas pela renderização (pode ser necessário para a segmentação). Você pode fazer esse tipo de coisa com o outro sistema, mas é uma PITA.
O que eu recomendo é que, em vez de desenhar diretamente, você vincule todos os objetos que deseja a outra estrutura de dados. Essa ligação realmente precisa apenas ter uma referência ao local dos objetos e às informações de renderização.
Seus níveis / partes / áreas / mapas / hubs / mundo inteiro / qualquer que seja um índice espacial, ele contém os objetos e os retorna com base em consultas de coordenadas e pode ser uma lista simples ou algo como um Octree. Também poderia ser um invólucro para algo implementado por um mecanismo de física de terceiros como um cenário de física. Ele permite que você faça coisas como "Consultar todos os objetos que estão à vista da câmera com alguma área extra ao seu redor" ou para jogos mais simples, nos quais você pode simplesmente renderizar tudo e pegar a lista inteira.
Os índices espaciais não precisam conter as informações de posicionamento reais. Eles trabalham armazenando objetos em estruturas de árvores em relação à localização de outros objetos. Eles podem ser considerados como um tipo de cache com perdas, permitindo uma pesquisa rápida de um objeto com base em sua posição. Não há necessidade real de duplicar suas coordenadas X, Y, Z reais. Dito isto, você poderia se quisesse manter
Na verdade, seus objetos de jogo nem precisam conter suas próprias informações de localização. Por exemplo, um objeto que não foi colocado em um nível não deve ter coordenadas x, y, z, isso não faz sentido. Você pode conter isso no índice especial. Se você precisar procurar as coordenadas do objeto com base em sua referência real, desejará ter uma ligação entre o objeto e o gráfico de cena (os gráficos de cena são para retornar objetos com base em coordenadas, mas são lentos em retornar coordenadas com base em objetos) .
Quando você adiciona um objeto a um nível. Ele fará o seguinte:
1) Crie uma estrutura de localização:
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
};
Isso também pode ser uma referência a um objeto em mecanismos de física de terceiros. Ou pode ser uma coordenada de deslocamento com uma referência a outro local (para uma câmera de rastreamento ou um objeto ou exemplo anexado). Com o polimorfismo, pode ser dependendo se é um objeto estático ou dinâmico. Mantendo aqui uma referência ao índice espacial, quando as coordenadas são atualizadas, o índice espacial também pode ser.
Se você estiver preocupado com a alocação dinâmica de memória, use um conjunto de memórias.
2) Uma ligação / ligação entre seu objeto, sua localização e o gráfico da cena.
typedef std::pair<Object, Location> SpacialBinding.
3) A ligação é adicionada ao índice espacial dentro do nível no ponto apropriado.
Quando você está se preparando para renderizar.
1) Pegue a câmera (será apenas outro objeto, exceto que sua localização rastreará o personagem dos jogadores e seu renderizador terá uma referência especial a ela, de fato, é tudo o que realmente precisa).
2) Obtenha o SpacialBinding da câmera.
3) Obtenha o índice espacial da ligação.
4) Consulte os objetos que são (possivelmente) visíveis para a câmera.
5A) Você precisa ter as informações visuais processadas. Texturas carregadas na GPU e assim por diante. É melhor fazer isso com antecedência (como na carga nivelada), mas talvez possa ser feito em tempo de execução (para um mundo aberto, você pode carregar coisas quando estiver próximo de um pedaço, mas ainda assim deve ser feito com antecedência).
5B) Opcionalmente, crie uma árvore de renderização em cache, se você quiser classificar em profundidade / material ou acompanhar objetos próximos que possam estar visíveis posteriormente. Caso contrário, você pode simplesmente consultar o índice espacial sempre que ele depender de seus requisitos de jogo / desempenho.
Seu renderizador provavelmente precisará de um objeto RenderBinding que se vincule entre o Object, as coordenadas
class RenderBinding {
Object& object;
RenderInformation& renderInfo;
Location& location // This could just be a coordinates class.
}
Então, quando você renderizar, basta executar a lista.
Eu usei as referências acima, mas elas podem ser ponteiros inteligentes, ponteiros brutos, identificadores de objetos e assim por diante.
EDITAR:
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
}
}
Quanto a tornar as coisas "conscientes" uma da outra. Isso é detecção de colisão. Provavelmente seria implementado no Octree. Você precisaria fornecer algum retorno de chamada em seu objeto principal. Esse material é melhor manipulado por um mecanismo de física adequado, como o Bullet. Nesse caso, substitua Octree por PhysicsScene e Position por um link para algo como CollisionMesh.getPosition ().