Par exemple, avoir une classe de base GameObject avec une hiérarchie d'héritage profonde pourrait être bon pour la maintenance ...
En fait, les hiérarchies profondes sont généralement pires pour la maintenabilité que celles peu profondes, et le style architectural moderne des objets de jeu tend vers des approches superficielles basées sur l'agrégation .
Cependant, je pense que cette approche peut créer des problèmes de performances. D'un autre côté, toutes les données et fonctions des objets de jeu pourraient être globales. Ce qui serait un casse-tête de maintenance mais pourrait être plus proche d'une boucle de jeu performante.
La boucle que vous avez montrée présente potentiellement des problèmes de performances, mais pas, comme l'implique votre instruction suivante, car vous avez des données d'instance et des fonctions membres dans la GameObject
classe. Au contraire, le problème est que si vous traitez chaque objet du jeu exactement de la même manière, vous ne regroupez probablement pas ces objets de manière intelligente - ils sont probablement dispersés au hasard dans cette liste. Potentiellement, alors, chaque appel à la méthode de mise à jour pour cet objet (que cette méthode soit une fonction globale ou non, et que cet objet ait des données d'instance ou des "données globales" flottant dans une table dans laquelle vous indexez ou autre) est différent de l'appel de mise à jour dans les dernières itérations de boucle.
Cela peut exercer une pression accrue sur le système car vous devrez peut-être paginer la mémoire avec la fonction appropriée dans et hors de la mémoire et remplir plus fréquemment le cache d'instructions, ce qui entraînera une boucle plus lente. Que cela soit ou non observable à l'œil nu (ou même dans un profileur) dépend exactement de ce qui est considéré comme un «objet de jeu», du nombre d'entre eux qui existent en moyenne et de ce qui se passe dans votre application.
Les systèmes d'objets orientés composants sont une tendance populaire en ce moment, tirant parti de la philosophie selon laquelle l' agrégation est préférable à l'héritage . De tels systèmes vous permettent potentiellement de diviser la logique de «mise à jour» des composants (où «composant» est grossièrement défini comme une unité de fonctionnalité, comme la chose qui représente la partie physiquement simulée d'un objet, qui est traitée par le système physique ) sur plusieurs threads - différenciés par type de composant - si possible et souhaité, ce qui pourrait avoir un gain de performances. À tout le moins, vous pouvez organiser les composants de telle sorte que tous les composants d'un type donné se mettent à jour ensemble , en utilisant de manière optimale le cache du processeur. Un exemple d'un tel système orienté composant est discuté dans ce fil .
Ces systèmes sont souvent très découplés, ce qui est également une aubaine pour la maintenance.
La conception orientée données est une approche connexe - il s'agit de vous orienter autour des données requises des objets en priorité, afin que ces données puissent être traitées efficacement en masse (par exemple). Cela signifie généralement une organisation qui essaie de garder les données utilisées pour le même cluster ensemble et exploitées en même temps. Ce n'est pas fondamentalement incompatible avec la conception OO, et vous pouvez trouver quelques discussions sur le sujet ici à GDSE dans cette question .
En effet, une approche plus optimale de la boucle de jeu serait, au lieu de votre original
foreach(GameObject g in gameObjects) g.update();
quelque chose de plus comme
ProcessUserInput();
UpdatePhysicsForAllObjects();
UpdateScriptsForAllObjects();
UpdateRenderDataForAllObjects();
RenderEverything();
Dans un tel monde, chacun GameObject
peut avoir un pointeur ou une référence à son propre PhysicsData
ou Script
ou RenderData
, pour les cas où vous pourriez avoir besoin d'interagir avec des objets sur une base individuelle, mais le réel PhysicsData
, Scripts
, RenderData
, et ainsi de suite seraient tous la propriété de leurs sous - systèmes respectifs (simulateur physique, environnement d'hébergement de scripts, moteur de rendu) et traité en masse comme indiqué ci-dessus.
Il est très important de noter que cette approche n'est pas une solution miracle et ne produira pas toujours une amélioration des performances (bien qu'il s'agisse généralement d'une meilleure conception qu'un arbre d'héritage profond). Vous êtes particulièrement susceptible de remarquer essentiellement aucune différence de performances si vous avez très peu d'objets, ou de très nombreux objets pour lesquels vous ne pouvez pas paralléliser efficacement les mises à jour.
Malheureusement, il n'y a pas une telle boucle magique qui soit la plus optimale - chaque jeu est différent et peut nécessiter un réglage des performances de différentes manières. Il est donc très important de mesurer (profil) les choses avant d'aller aveuglément suivre les conseils d'un gars au hasard sur Internet.