Je suggère de commencer par lire les 3 gros mensonges de Mike Acton, car vous en violez deux. Je suis sérieux, cela va changer la façon dont vous concevez votre code: http://cellperformance.beyond3d.com/articles/2008/03/three-big-lies.html
Alors, que violez-vous?
Mensonge n ° 3 - Le code est plus important que les données
Vous parlez d'injection de dépendance, qui peut être utile dans certains (et seulement certains) cas, mais devrait toujours sonner l'alarme si vous l'utilisez, en particulier dans le développement de jeux! Pourquoi? Parce que c'est une abstraction souvent inutile. Et les abstractions aux mauvais endroits sont horribles. Vous avez donc un jeu. Le jeu a des gestionnaires pour différents composants. Les composants sont tous définis. Faites donc une classe quelque part dans votre code de boucle de jeu principal qui "a" les gestionnaires. Comme:
private CollissionManager _collissionManager;
private BulletManager _bulletManager;
Donnez-lui quelques fonctions getter pour obtenir chaque classe de gestionnaire (getBulletManager ()). Peut-être que cette classe elle-même est un singleton ou qu'elle est accessible à partir d'un (vous avez probablement un singleton de jeu central quelque part de toute façon). Il n'y a rien de mal avec des données et un comportement codés en dur bien définis.
Ne créez pas un ManagerManager qui vous permet d'enregistrer des Managers à l'aide d'une clé, qui peut être récupérée à l'aide de cette clé par d'autres classes qui souhaitent utiliser le Manager. C'est un excellent système et très flexible, mais où l'on parle d'un jeu ici. Vous savez exactement quels systèmes sont en jeu. Pourquoi faire comme si tu ne le fais pas? Parce que c'est un système pour les gens qui pensent que le code est plus important que les données. Ils diront "Le code est flexible, les données le remplissent". Mais le code n'est que des données. Le système que j'ai décrit est beaucoup plus facile, plus fiable, plus facile à entretenir et beaucoup plus flexible (par exemple, si le comportement d'un gestionnaire diffère des autres gestionnaires, vous n'avez qu'à changer quelques lignes au lieu de retravailler l'ensemble du système)
Mensonge n ° 2 - Le code doit être conçu autour d'un modèle du monde
Vous avez donc une entité dans le monde du jeu. L'entité a un certain nombre de composants définissant son comportement. Vous créez donc une classe Entity avec une liste d'objets Component et une fonction Update () qui appelle la fonction Update () de chaque Component. Droite?
Non :) C'est la conception autour d'un modèle du monde: vous avez une balle dans votre jeu, donc vous ajoutez une classe Bullet. Ensuite, vous mettez à jour chaque puce et passez à la suivante. Cela tuera absolument vos performances et vous donnera une horrible base de code alambiquée avec du code en double partout et aucune structuration logique de code similaire. (Consultez ma réponse ici pour une explication plus détaillée des raisons pour lesquelles la conception OO traditionnelle craint, ou recherchez la conception orientée données)
Jetons un œil à la situation sans notre biais OO. Nous voulons ce qui suit, ni plus ni moins (veuillez noter qu'il n'y a pas d'obligation de créer une classe pour une entité ou un objet):
- Vous avez un tas d'entités
- Les entités comprennent un certain nombre de composants qui définissent le comportement de l'entité
- Vous souhaitez mettre à jour chaque composant du jeu à chaque image, de préférence de manière contrôlée
- Outre l'identification des composants comme appartenant ensemble, l'entité elle-même n'a rien à faire. C'est un lien / ID pour quelques composants.
Et regardons la situation. Votre système de composants mettra à jour le comportement de chaque objet du jeu à chaque image. Il s'agit certainement d'un système critique de votre moteur. La performance est importante ici!
Si vous connaissez l'architecture informatique ou la conception orientée données, vous savez comment obtenir les meilleures performances: une mémoire compacte et en regroupant l'exécution du code. Si vous exécutez des extraits de code A, B et C comme ceci: ABCABCABC, vous n'obtiendrez pas les mêmes performances que lorsque vous l'exécutez comme ceci: AAABBBCCC. Ce n'est pas seulement parce que le cache d'instructions et de données sera utilisé plus efficacement, mais aussi parce que si vous exécutez tous les «A» les uns après les autres, il y a beaucoup de place pour l'optimisation: supprimer le code en double, précalculer les données utilisées par tous les "A", etc.
Donc, si nous voulons mettre à jour tous les composants, ne faisons pas d'eux des classes / objets avec une fonction de mise à jour. N'appelons pas cette fonction de mise à jour pour chaque composant de chaque entité. C'est la solution "ABCABCABC". Regroupons toutes les mises à jour de composants identiques. Ensuite, nous pouvons mettre à jour tous les composants A, suivis de B, etc. De quoi avons-nous besoin pour faire cela?
Tout d'abord, nous avons besoin de gestionnaires de composants. Pour chaque type de composant du jeu, nous avons besoin d'une classe de manager. Il a une fonction de mise à jour qui mettra à jour tous les composants de ce type. Il a une fonction de création qui ajoutera un nouveau composant de ce type et une fonction de suppression qui détruira le composant spécifié. Il peut y avoir d'autres fonctions d'aide pour obtenir et définir des données spécifiques à ce composant (par exemple: définir le modèle 3D pour le composant de modèle). Notez que le gestionnaire est en quelque sorte une boîte noire pour le monde extérieur. Nous ne savons pas comment les données de chaque composant sont stockées. Nous ne savons pas comment chaque composant est mis à jour. Peu nous importe, tant que les composants se comportent comme ils le devraient.
Ensuite, nous avons besoin d'une entité. Vous pourriez en faire un cours, mais ce n'est pas vraiment nécessaire. Une entité ne peut être rien de plus qu'un ID entier unique ou une chaîne hachée (donc aussi un entier). Lorsque vous créez un composant pour l'entité, vous transmettez l'ID comme argument au gestionnaire. Lorsque vous souhaitez supprimer le composant, vous transmettez à nouveau l'ID. Il peut y avoir certains avantages à ajouter un peu plus de données à l'entité au lieu de simplement en faire un ID, mais ce ne seront que des fonctions d'assistance car, comme je l'ai indiqué dans les exigences, tout le comportement de l'entité est défini par les composants eux-mêmes. C'est votre moteur, alors faites ce qui a du sens pour vous.
Ce dont nous avons besoin, c'est d'un gestionnaire d'entités. Cette classe génère soit des ID uniques si vous utilisez la solution ID uniquement, soit elle peut être utilisée pour créer / gérer des objets Entity. Il peut également conserver une liste de toutes les entités du jeu si vous en avez besoin. L'Entity Manager peut être la classe centrale de votre système de composants, stockant les références à tous les ComponentManagers de votre jeu et appelant leurs fonctions de mise à jour dans le bon ordre. De cette façon, tout ce que la boucle de jeu doit faire est d'appeler EntityManager.update () et l'ensemble du système est bien séparé du reste de votre moteur.
C'est la vue à vol d'oiseau, regardons comment fonctionnent les gestionnaires de composants. Voici ce dont vous avez besoin:
- Créer des données de composant lorsque create (entityID) est appelé
- Supprimer les données de composant lors de l'appel de remove (entityID)
- Mettre à jour toutes les données des composants (applicables) lorsque update () est appelé (c.-à-d. Que tous les composants n'ont pas besoin de mettre à jour chaque trame)
Le dernier est l'endroit où vous définissez le comportement / la logique des composants et dépend complètement du type de composant que vous écrivez. Le composant Animation mettra à jour les données d'animation en fonction de l'image sur laquelle il se trouve. Le DragableComponent mettra à jour uniquement un composant qui est déplacé par la souris. Le Composant Physique mettra à jour les données du système physique. Cependant, comme vous mettez à jour tous les composants du même type en une seule fois, vous pouvez effectuer certaines optimisations qui ne sont pas possibles lorsque chaque composant est un objet distinct avec une fonction de mise à jour qui peut être appelée à tout moment.
Notez que je n'ai toujours jamais appelé à la création d'une classe XxxComponent pour contenir les données des composants. C'est à toi de voir. Vous aimez la conception orientée données? Structurez ensuite les données dans des tableaux séparés pour chaque variable. Aimez-vous la conception orientée objet? (Je ne le recommanderais pas, cela tuera toujours vos performances dans de nombreux endroits) Ensuite, créez un objet XxxComponent qui contiendra les données de chaque composant.
La grande chose au sujet des gestionnaires est l'encapsulation. Maintenant, l'encapsulation est l'une des philosophies les plus horriblement mal utilisées dans le monde de la programmation. C'est ainsi qu'il devrait être utilisé. Seul le gestionnaire sait quelles données de composant sont stockées, où fonctionne la logique d'un composant. Il y a quelques fonctions pour obtenir / définir des données mais c'est tout. Vous pouvez réécrire l'intégralité du gestionnaire et ses classes sous-jacentes et si vous ne changez pas l'interface publique, personne ne le remarque même. Moteur physique modifié? Réécrivez simplement PhysicsComponentManager et vous avez terminé.
Ensuite, il y a une dernière chose: la communication et le partage de données entre les composants. Maintenant, c'est délicat et il n'y a pas de solution unique. Vous pouvez créer des fonctions get / set dans les gestionnaires pour permettre, par exemple, au composant collision d'obtenir la position du composant position (c'est-à-dire PositionManager.getPosition (entityID)). Vous pouvez utiliser un système d'événements. Vous pouvez stocker des données partagées dans l'entité (la solution la plus laide à mon avis). Vous pourriez utiliser (c'est souvent utilisé) un système de messagerie. Ou utilisez une combinaison de plusieurs systèmes! Je n'ai pas le temps ni l'expérience pour entrer dans chacun de ces systèmes, mais la recherche Google et stackoverflow sont vos amis.