Ce conseil n'est pas vraiment spécifique au rendu mais devrait aider à trouver un système qui garde les choses largement séparées. Tout d'abord, essayez de séparer les données «GameObject» des informations de position.
Il convient de noter que les informations de position XYZ simples peuvent ne pas être aussi simples. Si vous utilisez un moteur physique, les données de position peuvent être stockées dans le moteur tiers. Vous devrez soit synchroniser entre eux (ce qui impliquerait beaucoup de copie de mémoire inutile) ou interroger les informations directement à partir du moteur. Mais tous les objets n'ont pas besoin de physique, certains seront fixés en place, donc un simple ensemble de flotteurs fonctionne bien là-bas. Certains peuvent même être attachés à d'autres objets, donc leur position est en fait un décalage d'une autre position. Dans une configuration avancée, il se peut que la position ne soit stockée que sur le GPU, la seule fois où l'ordinateur serait nécessaire pour les scripts, le stockage et la réplication réseau. Vous aurez donc probablement plusieurs choix possibles pour vos données de position. Ici, il est logique d'utiliser l'héritage.
Plutôt qu'un objet possédant sa position, cet objet doit lui-même appartenir à une structure de données d'indexation. Par exemple, un «niveau» peut avoir un octree, ou peut-être une «scène» de moteur physique. Lorsque vous souhaitez effectuer un rendu (ou configurer une scène de rendu), vous interrogez votre structure spéciale pour les objets visibles par la caméra.
Cela permet également une bonne gestion de la mémoire. De cette façon, un objet qui n'est pas réellement dans une zone n'a même pas une position qui a du sens plutôt que de renvoyer 0,0 cordes ou les cordes qu'il avait lors de son dernier passage dans une zone.
Si vous ne conservez plus les coordonnées dans l'objet, au lieu de object.getX (), vous finirez par avoir level.getX (object). Le problème avec cela recherche l'objet dans le niveau sera probablement une opération lente car il devra parcourir tous ses objets et correspondre à celui que vous interrogez.
Pour éviter cela, je créerais probablement une classe spéciale «lien». Celui qui lie entre un niveau et un objet. Je l'appelle un "emplacement". Cela contiendrait les coordonnées xyz ainsi que la poignée au niveau et une poignée à l'objet. Cette classe de liens serait stockée dans la structure / niveau spatial et l'objet aurait une faible référence à celui-ci (si le niveau / emplacement est détruit, la réfraction des objets doit être mise à jour à null. Il pourrait également être utile d'avoir la classe Location réellement «posséder» l'objet, de cette façon si un niveau est supprimé, il en est de même de la structure d'index spéciale, des emplacements qu'il contient et de ses objets.
typedef std::tuple<Level, Object, PositionXYZ> Location;
Maintenant, les informations de position sont stockées uniquement au même endroit. Non dupliqué entre l'objet, la structure d'indexation spatiale, le rendu, etc.
Les structures de données spatiales comme Octrees n'ont souvent même pas besoin d'avoir les coordonnées des objets qu'elles stockent. Cette position est stockée à l'emplacement relatif des nœuds dans la structure elle-même (elle pourrait être considérée comme une sorte de compression avec perte, sacrifiant la précision pour des temps de recherche rapides). Avec l'objet de localisation dans l'octree, les coordonnées réelles sont trouvées à l'intérieur une fois la requête terminée.
Ou si vous utilisez un moteur physique pour gérer les emplacements de vos objets ou un mélange entre les deux, la classe Location doit gérer cela de manière transparente tout en conservant tout votre code au même endroit.
Un autre avantage est maintenant que la position et la réfraction au niveau sont stockées au même endroit. Vous pouvez implémenter object.TeleportTo (other_object) et le faire fonctionner sur plusieurs niveaux. De même, la recherche de chemin de l'IA pourrait suivre quelque chose dans une zone différente.
En ce qui concerne le rendu. Votre rendu peut avoir une liaison similaire à l'emplacement. Sauf qu'il y aurait du rendu spécifique là-dedans. Vous n'avez probablement pas besoin que l '«objet» ou le «niveau» soit stocké dans cette structure. L'objet peut être utile si vous essayez de faire quelque chose comme la sélection des couleurs ou le rendu d'une barre de frappe flottant au-dessus, etc., sinon le rendu ne se soucie que du maillage, etc. RenderableStuff serait un maillage, pourrait également avoir des boîtes englobantes et ainsi de suite.
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);
}
Vous n'aurez peut-être pas besoin de le faire à chaque image, vous pouvez vous assurer de prendre une région plus grande que celle que la caméra montre actuellement. Mettez-le en cache, suivez les mouvements des objets pour voir si la boîte englobante se trouve à portée, suivez les mouvements de la caméra, etc. Mais ne commencez pas à jouer avec ce genre de choses avant de l'avoir comparé.
Votre moteur physique lui-même peut avoir une abstraction similaire, car il n'a pas non plus besoin des données d'objet, juste du maillage de collision et des propriétés physiques.
Toutes les données de votre objet principal contiendraient serait le nom du maillage utilisé par l'objet. Le moteur de jeu peut alors aller de l'avant et le charger dans le format qu'il souhaite sans surcharger votre classe d'objets avec un tas de choses spécifiques au rendu (qui pourraient être spécifiques à votre API de rendu, c'est-à-dire DirectX vs OpenGL).
Il garde également différents composants séparés. Cela permet de faire facilement des choses comme remplacer votre moteur physique, car ces éléments sont principalement autonomes au même endroit. Cela facilite également beaucoup le test. Vous pouvez tester des choses comme les requêtes physiques sans avoir à configurer de faux objets réels car tout ce dont vous avez besoin est la classe Location. Vous pouvez également optimiser les choses plus facilement. Cela rend plus évidentes les requêtes que vous devez effectuer sur les classes et les emplacements uniques pour les optimiser (par exemple, le level.getVisibleObject ci-dessus serait l'endroit où vous pourriez mettre en cache les choses si la caméra ne bouge pas trop).
m_renderable
membre. De cette façon, vous pouvez mieux séparer votre logique. N'appliquez pas l '"interface" de rendu sur les objets généraux qui ont également la physique, l'intelligence artificielle et ainsi de suite. Après cela, vous pouvez gérer les rendus séparément. Vous avez besoin d'une couche d'abstraction sur les appels de fonction OpenGL afin de découpler encore plus les choses. Donc, ne vous attendez pas à ce qu'un bon moteur ait des appels à l'API GL dans ses différentes implémentations de rendu. C'est tout, en un mot.