Personalmente, recomiendo mantener la función de dibujar fuera de la clase Object. Incluso recomiendo mantener la ubicación / coordenadas de los objetos fuera del propio objeto.
Ese método draw () tratará con API de renderizado de bajo nivel de OpenGL, OpenGL ES, Direct3D, su capa de ajuste en esas API o una API de motores. Es posible que deba cambiar entre ellos (si desea admitir OpenGL + OpenGL ES + Direct3D, por ejemplo.
Ese GameObject solo debe contener la información básica sobre su apariencia visual, como una malla o tal vez un paquete más grande que incluya entradas de sombreado, estado de animación, etc.
También vas a querer una tubería gráfica flexible. ¿Qué sucede si desea ordenar objetos en función de su distancia a la cámara? O su tipo de material. ¿Qué sucede si desea dibujar un objeto 'seleccionado' de un color diferente? ¿Qué pasa si en lugar de desgarrar realmente como se llama una función de dibujo en un objeto, en su lugar, lo coloca en una lista de comandos de acciones para que el render tome (puede ser necesario para enhebrar)? Puede hacer ese tipo de cosas con el otro sistema, pero es un PITA.
Lo que recomiendo es que en lugar de dibujar directamente, enlace todos los objetos que desee a otra estructura de datos. Ese enlace solo necesita tener una referencia a la ubicación de los objetos y la información de representación.
Sus niveles / fragmentos / áreas / mapas / centros / mundo entero / lo que sea que se les dé un índice espacial, este contiene los objetos y los devuelve en función de consultas de coordenadas y podría ser una lista simple o algo así como un Octree. También podría ser una envoltura para algo implementado por un motor de física de terceros como una escena de física. Le permite hacer cosas como "Consultar todos los objetos que están a la vista de la cámara con un área adicional a su alrededor", o para juegos más simples en los que simplemente puede hacer que todo tome toda la lista.
Los índices espaciales no tienen que contener la información de posicionamiento real. Funcionan almacenando objetos en estructuras de árbol en relación con la ubicación de otros objetos. Pueden considerarse como una especie de caché con pérdida que permite una búsqueda rápida de un objeto en función de su posición. No hay necesidad real de duplicar sus coordenadas X, Y, Z reales. Habiendo dicho eso, podrías si quisieras seguir
De hecho, los objetos de tu juego ni siquiera necesitan contener su propia información de ubicación. Por ejemplo, un objeto que no se ha puesto en un nivel no debe tener coordenadas x, y, z, eso no tiene sentido. Puede contener eso en el índice especial. Si necesita buscar las coordenadas del objeto en función de su referencia real, entonces querrá tener un enlace entre el objeto y el gráfico de escena (los gráficos de escena son para devolver objetos basados en coordenadas pero son lentos para devolver coordenadas basadas en objetos) .
Cuando agrega un objeto a un nivel. Hará lo siguiente:
1) Crear una estructura de ubicación:
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
};
Esto también podría ser una referencia a un objeto en motores de física de terceros. O podría ser un desplazamiento de coordenadas con una referencia a otra ubicación (para una cámara de seguimiento o un objeto adjunto o ejemplo). Con el polimorfismo, podría depender de si es un objeto estático o dinámico. Al mantener una referencia al índice espacial aquí cuando se actualizan las coordenadas, el índice espacial también puede estarlo.
Si le preocupa la asignación dinámica de memoria, use un grupo de memoria.
2) Un enlace / enlace entre su objeto, su ubicación y el gráfico de la escena.
typedef std::pair<Object, Location> SpacialBinding.
3) El enlace se agrega al índice espacial dentro del nivel en el punto apropiado.
Cuando te estés preparando para renderizar.
1) Obtenga la cámara (solo será otro objeto, excepto que su ubicación rastreará al personaje del jugador y su renderizador tendrá una referencia especial, de hecho, eso es todo lo que realmente necesita).
2) Obtenga el enlace espacial de la cámara.
3) Obtenga el índice espacial del enlace.
4) Consultar los objetos que son (posiblemente) visibles para la cámara.
5A) Necesita que se procese la información visual. Texturas cargadas en la GPU y así sucesivamente. Esto se haría mejor de antemano (como en carga nivelada), pero tal vez podría hacerse en tiempo de ejecución (para un mundo abierto, podría cargar cosas cuando se está acercando a un fragmento, pero aún debe hacerse con anticipación).
5B) Opcionalmente, construya un árbol de renderizado en caché, si desea clasificar en profundidad / material o realizar un seguimiento de los objetos cercanos, podría ser visible en un momento posterior. De lo contrario, puede consultar el índice espacial cada vez que dependerá de sus requisitos de juego / rendimiento.
Es probable que su renderizador necesite un objeto RenderBinding que se vincule entre el objeto y las coordenadas.
class RenderBinding {
Object& object;
RenderInformation& renderInfo;
Location& location // This could just be a coordinates class.
}
Luego, cuando renderice, simplemente ejecute la lista.
He usado las referencias anteriores, pero podrían ser punteros inteligentes, punteros sin formato, identificadores de objetos, etc.
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
}
}
En cuanto a hacer las cosas 'conscientes' el uno del otro. Esa es la detección de colisión. Probablemente se implementaría en octubre. Debería proporcionar alguna devolución de llamada en su objeto principal. Estas cosas se manejan mejor con un motor de física adecuado como Bullet. En ese caso, simplemente reemplace Octree con PhysicsScene y Position con un enlace a algo como CollisionMesh.getPosition ().