Composition OOP lourde vs systèmes de composants d'entité purs? [fermé]

13

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.

Midori Ryuu
la source
2
Tout cela me semble bien. Y a-t-il un exemple spécifique dans votre hiérarchie ou système d'entités qui vous «sent»? En fin de compte, il s'agit de faire le jeu plus que d'un certain sens de la pureté.
drhayes
Je n'en ai pas, mais comme je l'ai dit, je n'ai pratiquement aucune expérience et je suis loin d'être le meilleur juge pour cela.
Midori Ryuu
3
J'ai voté pour clore cette question car elle est subjective et ouverte à une large discussion. J'apprécie que cela ait été demandé dans une position sincère - mais choisir comment procéder ici est entièrement une question d'opinion personnelle. Le seul véritable inconvénient de la fixation de vos composants dans une sous-classe est que la disposition des composants n'est pas modifiable au moment de l'exécution - mais cela doit sûrement être apparent pour vous et c'est un choix que vous faites.
Kylotan
4
J'ai voté pour fermer aussi. C'est une bonne question bien écrite, mais pas appropriée pour GDSE. Vous pouvez essayer de republier cela sur GameDev.net.
Sean Middleditch
Tel qu'écrit, la question étant «Vos opinions?», Elle est en effet ouverte et sans réponse. Cependant, il est possible de répondre si vous y répondez comme si la question était «Y a-t-il des pièges béants dans lesquels je pourrais tomber sans le remarquer? (Au moins, si vous pensez qu'il y a en fait une différence empirique entre les différentes architectures.)
John Calsbeek

Réponses:

8

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, Buildinget Unit. 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 Unitvous 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 qui newest un WeaponComponent, le connecte à un AimingAIComponentet un - ParticleEmitterComponentmerveilleux!

Et bien sûr, car il prend toujours en compte la logique dans les composants, lorsque vous décidez finalement d'ajouter une Towerclasse qui est un bâtiment mais qui a une arme, vous pouvez le gérer. Vous pouvez ajouter un AimingAIComponent, un WeaponComponentet un ParticleEmitterComponentà votre sous-classe de Building. Bien sûr, alors vous devez parcourir et extraire le code d'initialisation de la Unitclasse 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:

if (myGameObject instanceof Unit)
    doSomething(((Unit)myGameObject).getWeaponComponent());

Cela ne fonctionne pas en silence pour votre Towerbâtiment, même si vous vouliez vraiment qu'il fonctionne pour n'importe quoi avec une arme. Maintenant, il doit ressembler à ceci:

WeaponComponent weapon = myGameObject.getComponent(WeaponComponent.class);
if (weapon)
    doSomething(weapon);

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 getComponentméthode, qui peut fonctionner avec n'importe quelle sous-classe. (Alternativement, vous pouvez ajouter tous les types get*Component()à la superclasse GameObjectet les remplacer dans les sous-classes pour renvoyer le composant. Je suppose que le premier, cependant.)

Maintenant, vos classes Buildinget Unitne 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 Unitclasse, vous avez une addUnitComponentsméthode qui fait un AnimationComponentet appelle également addWeaponComponents, qui fait un AimingAIComponent, un WeaponComponentet un ParticleEmitterComponent. 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érifier instanceofou de transtyper en votre type de classe.

John Calsbeek
la source
Bien dit. En plus de cela, je pense que la solution idéale est d'initialiser chaque entité avec un script ou des fichiers descripteurs. J'utilise des fichiers texte pour l'initialisation d'entité. C'est rapide et permet aux non-programmeurs de tester différents paramètres.
Coyote
Wow la lecture de votre message m'a fait un cauchemar! Bien sûr, je veux dire que dans le bon sens, vu que vous m'avez ouvert les yeux sur le genre de cauchemar que je pourrais finir! J'aimerais pouvoir répondre davantage à une réponse aussi détaillée, mais il n'y a pas grand-chose à ajouter vraiment!
Midori Ryuu
D'une manière plus simple. Vous finissez par utiliser l'héritage comme modèle d'usine du pauvre.
Zardoz89
2

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.

Daniel Carlsson
la source
1

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.

Coyote
la source
Merci pour votre réponse même si vous étiez au téléphone! Je comprends ce que tu veux dire. Les choses basées sur les composants de déplacement sont, plus j'ai de flexibilité pour changer les choses. C'est la raison pour laquelle je vais aller de l'avant et essayer une approche de composition avec un héritage minimal. (Encore plus que celui que j'ai proposé.)
Midori Ryuu
@MidoriRyuu évite tout héritage dans les objets d'entité. Vous avez la chance d'utiliser un langage avec réflexion (et introspection) intégré. Vous pouvez utiliser des fichiers (txt ou XML) pour initialiser vos objets avec les bons paramètres. Ce n'est pas trop de travail et cela permettra un réglage fin du jeu amélioré sans recompilation. Mais conservez la classe Entity, au moins pour la communication entre composants et d'autres tâches utilitaires. PS toujours au téléphone :(
Coyote