Personnellement, je recommande de garder la fonction draw hors de la classe Object elle-même. Je recommande même de garder l'emplacement / les coordonnées des objets hors de l'objet lui-même.
Cette méthode draw () va traiter de l'API de rendu de bas niveau d'OpenGL, OpenGL ES, Direct3D, votre couche d'encapsulation sur ces API ou une API de moteurs. Il se peut que vous deviez échanger entre (Si vous vouliez prendre en charge OpenGL + OpenGL ES + Direct3D par exemple.
Ce GameObject devrait simplement contenir les informations de base sur son apparence visuelle, comme un maillage ou peut-être un ensemble plus grand, y compris des entrées de shader, l'état d'animation, etc.
Vous voudrez également un pipeline graphique flexible. Que se passe-t-il si vous souhaitez commander des objets en fonction de leur distance à la caméra. Ou leur type de matériau. Que se passe-t-il si vous souhaitez dessiner un objet «sélectionné» d'une couleur différente. Que se passe-t-il si au lieu de restituer aussi soo que vous appelez une fonction de dessin sur un objet, il la place plutôt dans une liste de commandes d'actions à effectuer pour le rendu (peut être nécessaire pour le thread). Vous pouvez faire ce genre de chose avec l'autre système mais c'est un PITA.
Ce que je recommande, au lieu de dessiner directement, vous liez tous les objets que vous souhaitez à une autre structure de données. Cette liaison n'a vraiment besoin que d'une référence à l'emplacement des objets et aux informations de rendu.
Vos niveaux / morceaux / zones / cartes / hubs / wholeworld / quoi que ce soit reçoivent un index spatial, cela contient les objets et les renvoie en fonction de requêtes de coordonnées et pourrait être une simple liste ou quelque chose comme un Octree. Cela pourrait également être un wrapper pour quelque chose implémenté par un moteur physique tiers en tant que scène physique. Il vous permet de faire des choses comme "Interroger tous les objets qui sont dans la vue de la caméra avec une zone supplémentaire autour d'eux", ou pour des jeux plus simples où vous pouvez simplement tout rendre pour récupérer la liste entière.
Les index spatiaux ne doivent pas contenir les informations de positionnement réelles. Ils fonctionnent en stockant des objets dans des structures arborescentes par rapport à l'emplacement d'autres objets. Ils peuvent être considérés comme une sorte de cache avec perte permettant une recherche rapide d'un objet en fonction de sa position. Il n'est pas vraiment nécessaire de dupliquer vos coordonnées X, Y, Z réelles. Cela dit, vous pouvez si vous le souhaitez
En fait, vos objets de jeu n'ont même pas besoin de contenir leurs propres informations de localisation. Par exemple, un objet qui n'a pas été placé dans un niveau ne doit pas avoir de coordonnées x, y, z, cela n'a aucun sens. Vous pouvez le contenir dans l'index spécial. Si vous devez rechercher les coordonnées de l'objet en fonction de sa référence réelle, vous souhaiterez avoir une liaison entre l'objet et le graphique de la scène (les graphiques de la scène sont destinés au retour d'objets basés sur les coordonnées mais sont lents à renvoyer les coordonnées basées sur les objets) .
Lorsque vous ajoutez un objet à un niveau. Il fera ce qui suit:
1) Créer une structure de localisation:
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
};
Cela pourrait également être une référence à un objet dans un moteur physique tiers. Ou il peut s'agir de coordonnées décalées avec une référence à un autre emplacement (pour une caméra de suivi ou un objet ou un exemple attaché). Avec le polymorphisme, cela peut être selon qu'il s'agit d'un objet statique ou dynamique. En gardant une référence à l'index spatial ici lorsque les coordonnées sont mises à jour, l'index spatial peut aussi l'être.
Si vous êtes préoccupé par l'allocation dynamique de mémoire, utilisez un pool de mémoire.
2) Une liaison / liaison entre votre objet, son emplacement et le graphique de la scène.
typedef std::pair<Object, Location> SpacialBinding.
3) La liaison est ajoutée à l'index spatial à l'intérieur du niveau au point approprié.
Lorsque vous vous préparez à rendre.
1) Obtenez la caméra (ce ne sera qu'un autre objet, sauf que son emplacement suivra le personnage du joueur et que votre moteur de rendu y aura une référence spéciale, en fait c'est tout ce dont il a vraiment besoin).
2) Obtenez le SpacialBinding de la caméra.
3) Obtenez l'index spatial de la liaison.
4) Recherchez les objets qui sont (éventuellement) visibles par la caméra.
5A) Vous devez faire traiter les informations visuelles. Textures téléchargées sur le GPU et ainsi de suite. Il serait préférable de le faire à l'avance (comme au niveau de la charge), mais peut-être de le faire lors de l'exécution (pour un monde ouvert, vous pouvez charger des choses lorsque vous approchez d'un morceau, mais cela devrait toujours être fait à l'avance).
5B) Créez éventuellement un arbre de rendu mis en cache, si vous souhaitez trier la profondeur / le matériau ou suivre les objets à proximité, ils pourraient être visibles ultérieurement. Sinon, vous pouvez simplement interroger l'index spatial à chaque fois que cela dépendra de vos exigences de jeu / performances.
Votre moteur de rendu aura probablement besoin d'un objet RenderBinding qui fera le lien entre l'objet, les coordonnées
class RenderBinding {
Object& object;
RenderInformation& renderInfo;
Location& location // This could just be a coordinates class.
}
Ensuite, lorsque vous effectuez le rendu, exécutez simplement la liste.
J'ai utilisé des références ci-dessus, mais il peut s'agir de pointeurs intelligents, de pointeurs bruts, de poignées d'objets, etc.
ÉDITER:
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
}
}
Quant à rendre les choses «conscientes» les unes des autres. C'est la détection de collision. Il serait probablement mis en œuvre dans l'Octree. Vous devrez fournir un rappel dans votre objet principal. Ce truc est mieux géré par un moteur physique approprié tel que Bullet. Dans ce cas, remplacez simplement Octree par PhysicsScene et Position par un lien vers quelque chose comme CollisionMesh.getPosition ().