J'avoue, j'ai fait le péché de surutilisation, et même d'abus de l'héritage. Le premier projet de jeu (texte) que j'ai fait lorsque je suivais mon cours de POO allait jusqu'à "Porte verrouillée" et "Porte déverrouillée" de "Porte" et "Chambre avec une porte", "Chambre avec deux portes", et ainsi de suite de "Chambre".
Avec le jeu (graphique) sur lequel j'ai travaillé récemment, je pensais avoir appris ma leçon et limiter l'utilisation de l'héritage. Cependant, j'ai remarqué que les problèmes commençaient à apparaître. Ma classe racine commençait à gonfler de plus en plus, et mes classes de feuilles étaient pleines de codes en double.
Je pensais que je faisais toujours mal les choses, et après l'avoir consulté en ligne, j'ai découvert que je n'étais pas le seul à avoir ce problème. C'est ainsi que j'ai fini par découvrir les systèmes Entity après des recherches approfondies (lire: googlefu)
Quand j'ai commencé à le lire, j'ai pu voir à quel point il était capable de résoudre les problèmes que je rencontrais avec la hiérarchie OOP traditionnelle avec des composants. C'étaient cependant dans les premières lectures. Quand je suis tombé sur des approches ES plus «radicales», comme celle de T-machine .
J'ai commencé à être en désaccord avec les méthodes qu'ils utilisaient. Un système de composants purs semblait soit excessif, soit plutôt peu intuitif, ce qui est probablement la force de la POO. L'auteur va jusqu'à dire que le système ES est l'opposé de la POO, et bien qu'il puisse être utilisable le long de la POO, il ne devrait vraiment pas. Je ne dis pas que c'est faux, mais je ne me sentais pas comme une solution que j'aimerais mettre en œuvre.
Donc pour moi, et pour résoudre les problèmes que je rencontrais au début du billet, sans aller à l'encontre de mes intuitions, c'est toujours utiliser une hiérarchie, mais ce ne sera pas une hiérarchie monolithique comme celles que j'ai utilisées auparavant, mais plutôt un polylithique (je n'ai pas pu trouver un mot opposé à monolithique), qui se compose de plusieurs arbres plus petits.
L'exemple suivant montre ce que je veux dire (il est inspiré d'un exemple que j'ai trouvé dans Game Engine Architecture, chapitre 14).
J'aurais un petit arbre pour les véhicules. La classe de véhicule racine aurait un composant de rendu, un composant de collision, un composant de position, etc.
Ensuite, un char, une sous-classe de véhicule en hériterait et recevrait son propre composant "canon".
Il en va de même pour les personnages. Un personnage aurait ses propres composants, puis la classe Player l'hériterait et recevrait un contrôleur d'entrée, tandis que d'autres classes ennemies hériteraient de la classe Character et recevraient un contrôleur AI.
Je ne vois vraiment aucun problème avec cette conception. Bien qu'il n'utilise pas un système de contrôleur d'entité pur, le problème avec l'effet de bouillonnement et la grande classe racine est résolu en utilisant une hiérarchie multi-arborescente, et le problème des feuilles de duplication de code lourdes a disparu car les feuilles ne le font pas avoir un code pour commencer, juste des composants. Si une modification doit être effectuée au niveau feuille, c'est aussi simple que de changer un seul composant, au lieu de copier-coller le code partout.
Bien sûr, étant aussi inexpérimenté que moi, je n'ai vu aucun problème lorsque j'ai commencé à utiliser la hiérarchie unique, le modèle lourd d'héritage, donc s'il y a des problèmes avec le modèle que je pense actuellement à implémenter, je ne le ferais pas pouvoir le voir.
Tes opinions?
PS: J'utilise Java, il n'est donc pas possible d'utiliser l'héritage multiple pour l'implémenter au lieu d'utiliser des composants normaux.
PPS: les communications entre les composants se feront en reliant les composants dépendants les uns aux autres. Cela conduira à un couplage, mais je pense que c'est un bon compromis.
la source
Réponses:
Considérez cet exemple:
Vous faites un RTS. Dans une logique complète, vous décidez de créer une classe de base
GameObject
, puis deux sous-classes,Building
etUnit
. Cela fonctionne très bien, bien sûr, et vous vous retrouvez avec quelque chose qui ressemble à ceci:GameObject
ModelComponent
CollisionComponent
Building
ProductionComponent
Unit
AimingAIComponent
WeaponComponent
ParticleEmitterComponent
AnimationComponent
Maintenant, chaque sous-classe que
Unit
vous créez possède déjà tous les composants dont vous avez besoin. Et en bonus, vous avez un seul endroit pour mettre tout ce code de configuration fastidieux quinew
est unWeaponComponent
, le connecte à unAimingAIComponent
et un -ParticleEmitterComponent
merveilleux!Et bien sûr, car il prend toujours en compte la logique dans les composants, lorsque vous décidez finalement d'ajouter une
Tower
classe qui est un bâtiment mais qui a une arme, vous pouvez le gérer. Vous pouvez ajouter unAimingAIComponent
, unWeaponComponent
et unParticleEmitterComponent
à votre sous-classe deBuilding
. Bien sûr, alors vous devez parcourir et extraire le code d'initialisation de laUnit
classe et le mettre ailleurs, mais ce n'est pas une grosse perte.Et maintenant, selon la prévoyance dont vous disposiez, vous pouvez ou non vous retrouver avec des bugs subtils. Il s'avère qu'il y avait peut-être un autre code dans le jeu qui ressemblait à ceci:
Cela ne fonctionne pas en silence pour votre
Tower
bâtiment, même si vous vouliez vraiment qu'il fonctionne pour n'importe quoi avec une arme. Maintenant, il doit ressembler à ceci:Bien mieux! Il vous vient à l'esprit après avoir changé cela que l' une des méthodes d'accesseur que vous avez écrites pourrait se retrouver dans cette situation. Donc, la chose la plus sûre à faire est de les supprimer tous et d'accéder à chaque composant via cette seule
getComponent
méthode, qui peut fonctionner avec n'importe quelle sous-classe. (Alternativement, vous pouvez ajouter tous les typesget*Component()
à la superclasseGameObject
et les remplacer dans les sous-classes pour renvoyer le composant. Je suppose que le premier, cependant.)Maintenant, vos classes
Building
etUnit
ne sont quasiment que des sacs de composants, sans même d'accessoires dessus. Et pas d'initialisation de fantaisie non plus, car la plupart de cela devait être déplacé pour éviter la duplication de code avec votre autre objet spécial quelque part.Si vous suivez cela à l'extrême, vous finissez toujours par faire de vos sous-classes des sacs de composants complètement inertes, à quel point il n'y a même aucun point d'avoir les sous-classes autour. Vous pouvez obtenir presque tous les avantages d'avoir une hiérarchie de classes avec une hiérarchie de méthodes qui crée les composants appropriés. Au lieu d'avoir une
Unit
classe, vous avez uneaddUnitComponents
méthode qui fait unAnimationComponent
et appelle égalementaddWeaponComponents
, qui fait unAimingAIComponent
, unWeaponComponent
et unParticleEmitterComponent
. Le résultat est que vous avez tous les avantages d'un système d'entités, toute la réutilisation du code d'une hiérarchie, et aucune tentation de vérifierinstanceof
ou de transtyper en votre type de classe.la source
La composition sur l'héritage est la terminologie de la POO (au moins la façon dont je l'ai apprise / m'a été enseignée). Pour choisir entre composition et héritage, vous dites à voix haute "La voiture est un type de roue" (héritage) et "La voiture a des roues" (composition). L'un devrait sembler plus correct que l'autre (composition dans ce cas), et si vous ne pouvez pas décider, choisissez par défaut la composition jusqu'à ce que vous en décidiez autrement.
la source
Quand il s'agit d'intégrer des systèmes basés sur des composants à des jeux existants basés sur des objets de jeu, il y a un article sur la programmation de cow-boy http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/ qui pourrait vous donner quelques conseils sur la façon dont la transition peut être effectuée.
En ce qui concerne les problèmes, votre modèle devra toujours gérer le joueur différemment d'un autre ennemi. Vous ne pourrez pas changer de statut (c'est-à-dire que le joueur devient contrôlé par l'IA) lorsqu'un joueur se déconnecte ou dans des cas spéciaux sans le traiter différemment des autres personnages.
Outre la réutilisation intensive des composants dans différentes entités, l'idée de jeux basés sur les composants est l'uniformité de l'ensemble du système. Chaque entité a des composants qui peuvent être attachés et détachés à volonté. Tous les composants du même type sont traités exactement de la même manière avec un ensemble d'appels établi par l'interface (composant de base) de chaque type (position, rendu, script ...)
Mélanger l'héritage des objets de jeu avec une approche basée sur les composants vous apporte certains avantages de l'approche basée sur les composants avec les inconvénients des deux approches.
Cela peut fonctionner pour vous. Mais je ne mélangerais pas les deux en dehors d'une période de transition d'une architecture à l'autre.
PS écrit sur un téléphone, j'ai donc du mal à me connecter à des ressources externes.
la source