Faire des powerups dans un système basé sur des composants

29

Je commence tout juste à me familiariser avec la conception basée sur les composants. Je ne sais pas quelle est la "bonne" façon de procéder.

Voici le scénario. Le joueur peut équiper un bouclier. Le bouclier est dessiné comme une bulle autour du joueur, il a une forme de collision séparée et réduit les dégâts que le joueur reçoit des effets de zone.

Comment un tel bouclier est-il conçu dans un jeu basé sur des composants?

Là où je suis confus, c'est que le bouclier a évidemment trois composants associés.

  • Réduction / filtrage des dommages
  • Un sprite
  • Un collisionneur.

Pour l'aggraver, différentes variations de bouclier pourraient avoir encore plus de comportements, qui pourraient tous être des composants:

  • augmenter la santé maximale du joueur
  • régénération de la santé
  • déviation du projectile
  • etc

  1. Suis-je trop réfléchir à cela? Le bouclier ne devrait-il être qu'un super composant?
    Je pense vraiment que c'est une mauvaise réponse. Donc, si vous pensez que c'est la voie à suivre, veuillez expliquer.

  2. Le bouclier doit-il être sa propre entité qui suit l'emplacement du joueur?
    Cela pourrait rendre difficile la mise en œuvre du filtrage des dommages. Cela brouille également les lignes entre les composants et les entités attachés.

  3. Le bouclier devrait-il être un composant qui abrite d'autres composants?
    Je n'ai jamais vu ou entendu quelque chose comme ça, mais c'est peut-être commun et je ne suis pas encore assez profond.

  4. Le bouclier ne devrait-il être qu'un ensemble de composants ajoutés au joueur?
    Peut-être avec un composant supplémentaire pour gérer les autres, par exemple afin qu'ils puissent tous être supprimés en tant que groupe. (laissez accidentellement le composant de réduction des dégâts, maintenant ce serait amusant).

  5. Quelque chose d'autre qui est évident pour quelqu'un avec plus d'expérience en composants?

deft_code
la source
J'ai pris la liberté de préciser votre titre.
Tetrad

Réponses:

11

Le bouclier doit-il être sa propre entité qui suit l'emplacement du joueur? Cela pourrait rendre difficile la mise en œuvre du filtrage des dommages. Cela brouille également les lignes entre les composants et les entités attachés.

Edit: Je pense qu'il n'y a pas assez de "comportement autonome" pour une entité séparée. Dans ce cas spécifique, un bouclier suit la cible, fonctionne pour la cible et ne survit pas à la cible. Bien que j'aie tendance à convenir qu'il n'y a rien de mal avec le concept d'un "objet bouclier", dans ce cas, nous avons affaire à un comportement, qui s'intègre très bien dans un composant. Mais je suis également un défenseur des entités purement logiques (par opposition aux systèmes d'entités à part entière dans lesquels vous pouvez trouver des composants de transformation et de rendu).

Le bouclier devrait-il être un composant qui abrite d'autres composants? Je n'ai jamais vu ou entendu quelque chose comme ça, mais c'est peut-être courant et je ne suis pas encore assez profond.

Le voir dans une perspective différente; l'ajout d'un composant ajoute également d'autres composants, et lors de la suppression, les composants supplémentaires ont également disparu.

Le bouclier ne devrait-il être qu'un ensemble de composants ajoutés au joueur? Peut-être avec un composant supplémentaire pour gérer les autres, par exemple afin qu'ils puissent tous être supprimés en tant que groupe. (laissez accidentellement le composant de réduction des dégâts, maintenant ce serait amusant).

Cela pourrait être une solution, cela favoriserait la réutilisation, mais il est également plus sujet aux erreurs (pour le problème que vous avez mentionné, par exemple). Ce n'est pas nécessairement mauvais. Vous pourriez découvrir de nouvelles combinaisons de sorts avec essais et erreurs :)

Quelque chose d'autre qui est évident pour quelqu'un avec plus d'expérience en composants?

Je vais développer un peu.

Je pense que vous avez remarqué que certains composants devraient être prioritaires, peu importe quand ils ont été ajoutés à une entité (cela répondrait également à votre autre question).

Je vais également supposer que nous utilisons la communication basée sur les messages (pour les besoins de la discussion, ce n'est qu'une abstraction sur un appel de méthode pour le moment).

Chaque fois qu'un composant de bouclier est "installé", les gestionnaires de messages de composant de bouclier sont enchaînés avec un ordre spécifique (supérieur).

Handler Stage    Handler Level     Handler Priority
In               Pre               System High
Out              Invariant         High
                 Post              AboveNormal
                                   Normal
                                   BelowNormal
                                   Low
                                   System Low

In - incoming messages
Out - outgoing messages
Index = ((int)Level | (int)Priority)

Le composant "stats" installe un gestionnaire de messages "dommages" à l'index In / Invariant / Normal. Chaque fois qu'un message de "dommage" est reçu, diminuez les HP de sa valeur "value".

Comportement assez standard (insérez une résistance aux dommages naturels et / ou des traits raciaux, peu importe).

Le composant de bouclier installe un gestionnaire de messages "dommages" à l'index In / Pre / High.

Every time a "damage" message is received, deplete the shield energy and substract
the shield energy from the damage value, so that the damage down the message
handler pipeline is reduced.

damage -> stats
    stats
        stats.hp -= damage.value

damage -> shield -> stats
    shield
        if(shield.energy) {
            remove_me();
            return;
        }
        damage.value -= shield.energyquantum
        shield.energy -= shield.energyquantum;

     stats
        stats.hp -= damage.value

Vous pouvez voir que cela est assez flexible, même si cela nécessiterait une planification minutieuse lors de la conception de l'interaction des composants, car vous devrez déterminer dans quelle partie du gestionnaire de messages le gestionnaire d'événements de message du composant de pipeline est installé.

Logique? Faites-moi savoir si je peux ajouter plus de détails.

Edit: concernant plusieurs instances de composants (deux composants d'armure). Vous pouvez soit garder une trace du nombre total d'instances dans une seule instance d'entité (cela tue toutefois l'état par composant) et simplement continuer d'ajouter des gestionnaires d'événements de message, ou vous assurer que vos conteneurs de composants autorisent à l'avance les types de composants en double.

Raine
la source
Vous avez répondu "Non" à la première question sans donner de raison. Enseigner aux autres, c'est les aider à comprendre le raisonnement qui sous-tend toute décision. OMI, le fait que dans RL un champ de force serait une "entité physique" distincte de votre propre corps est suffisant pour qu'il soit une entité distincte dans le code. Pouvez-vous suggérer de bonnes raisons pour expliquer pourquoi emprunter cette route est mauvais?
Ingénieur
@ Nick, je n'essaie nullement d'enseigner quoi que ce soit à quiconque, mais plutôt de partager ce que je sais sur le sujet. Je vais cependant ajouter une justification derrière ce "non" qui, je l'espère, supprimera ce downvote désagréable :(
Raine
Votre point d'autonomie est logique. Mais vous notez: "dans ce cas, nous avons affaire à un comportement". Vrai - comportement impliquant un objet physique entièrement séparé (la forme de collision du bouclier). Pour moi, une entité est liée à un corps physique (ou un ensemble composé de corps reliés par exemple par des articulations). Comment conciliez-vous cela? Pour ma part, je me sentirais mal à l'aise d'ajouter un appareil physique "factice" qui ne s'activerait que si le joueur utilise un bouclier. OMI inflexible, difficile à maintenir dans toutes les entités. Pensez aussi à un jeu où les ceintures de bouclier gardent les boucliers même après la mort (Dune).
Ingénieur
@Nick, la majorité des systèmes d'entités ont des composants à la fois logiques et graphiques, donc, dans ce cas, avoir une entité pour un bouclier est absolument raisonnable. Dans les systèmes d'entités purement logiques, l '"autonomie" est le produit de la complexité d'un objet, de ses dépendances et de sa durée de vie. En fin de compte, l'exigence est reine - et étant donné qu'il n'y a pas de véritable consensus sur ce qu'est un système d'entité, il y a beaucoup de place pour des solutions sur mesure :)
Raine
@deft_code, faites-moi savoir si je peux améliorer ma réponse.
Raine
4

1) Suis-je en train de trop réfléchir à cela? Le bouclier ne devrait-il être qu'un super composant?

Cela dépend peut-être de la réutilisabilité de votre code et de sa pertinence.

2) Le bouclier devrait-il être sa propre entité qui suit l'emplacement du joueur?

Pas à moins que ce bouclier ne soit une sorte de créature qui puisse se promener indépendamment à un moment donné.

3) Le bouclier devrait-il être un composant qui abrite d'autres composants?

Cela ressemble beaucoup à une entité, donc la réponse est non.

4) Le bouclier ne devrait-il être qu'un ensemble de composants ajoutés au joueur?

C'est probable.

"Réduction des dommages / filtrage"

  • fonctionnalité des composants du bouclier central.

"Un sprite"

  • Y a-t-il une raison pour laquelle vous ne pouvez pas ajouter un autre SpriteComponent à votre entité de personnage (en d'autres termes, plus d'un composant d'un certain type par entité)?

"Un collisionneur"

  • êtes-vous sûr d'en avoir besoin d'un autre? Cela dépend de votre moteur physique. Pouvez-vous envoyer un message au ColliderComponent de l'entité personnage et lui demander de changer de forme?

"Augmente la santé maximale du joueur, la régénération de la santé, la déviation du projectile, etc."

  • d'autres artefacts pourraient être en mesure de le faire (épées, bottes, anneaux, sorts / potions / sanctuaires en visite, etc.), ils devraient donc être des composants.
Tanière
la source
3

Un bouclier, en tant qu'entité physique , n'est pas différent de toute autre entité physique , par exemple un drone qui tourne autour de vous (et qui, en fait, pourrait lui-même être un type de bouclier!). Faites donc du bouclier une entité logique distincte (lui permettant ainsi de contenir ses propres composants).

Donnez à votre bouclier quelques composants: un composant physique / spatial pour représenter sa forme de collision et un composant DamageAffector qui contient une référence à une entité à laquelle il appliquera des dommages accrus ou réduits (par exemple, votre personnage de joueur) à chaque fois que l'entité tenir DamageAffector subit des dégâts. Ainsi, votre joueur subit des dégâts "par procuration".

Réglez la position de l'entité bouclier sur la position du joueur à chaque tick. (Écrivez une classe de composants réutilisable qui fait cela: écrire une fois, utiliser plusieurs fois.)

Vous devrez créer l'entité bouclier, par exemple. sur la collecte d'un bonus. J'utilise un concept générique appelé émetteur, qui est un type de composant d'entité qui engendre de nouvelles entités (généralement grâce à l'utilisation d'un EntityFactory auquel il fait référence). L'endroit où vous décidez de localiser l'émetteur dépend de vous - par exemple. mettez-le sur une mise sous tension et faites-le déclencher lorsque la mise sous tension est collectée.


Le bouclier doit-il être sa propre entité qui suit l'emplacement du joueur? Cela pourrait rendre difficile la mise en œuvre du filtrage des dommages. Cela brouille également les lignes entre les composants et les entités attachés.

Il existe une fine ligne entre les sous-composants logiques (spatial, AI, emplacements d'armes, traitement d'entrée, etc., etc.) et les sous-composants physiques. Vous devez décider de quel côté de celui-ci vous vous tenez, car cela définit fortement le type de système d'entité que vous avez. Pour moi, le sous-composant physique de mon entité gère les relations physico-hiérarchiques (telles que les membres dans un corps - pensez aux nœuds scénographiques), tandis que les contrôleurs logiques indiqués ci-dessus sont généralement ceux qui sont représentés par les composants de votre entité - plutôt que ceux qui représentent "agencements" physiques individuels.

Ingénieur
la source
3

Le bouclier devrait-il être un composant qui abrite d'autres composants?

Peut-être ne contient pas d'autres composants, mais contrôle la durée de vie des sous-composants. Donc, dans un pseudo-code approximatif, votre code client ajouterait ce composant "bouclier".

class Shield : Component
{
    void Start() // happens when the component is added
    {
        sprite = entity.add_component<Sprite>( "shield" );
        collider = entity.add_component<Collider>( whatever );
        //etc
    }

    void OnDestroy() // when the component is removed
    {
        entity.remove_component( sprite );
        entity.remove_component( collider );
    }

    void Update() // every tick
    {
        if( ShouldRemoveSelf() ) // probably time based or something
            entity.remove_component( this );
    }
}
Tetrad
la source
On ne sait pas ce que thissignifie votre réponse. Fait thisréférence au composant Bouclier ou vouliez-vous dire l'entité qui utilise le bouclier, son parent? La confusion pourrait être de ma faute. "Composant basé" est un peu vague. Dans ma version des entités basées sur des composants, une entité est simplement un conteneur de composants avec une fonctionnalité minimale qui lui est propre (nom d'objet, balises, messagerie, etc.).
deft_code
Ce serait moins déroutant si j'utilisais gameObjectquelque chose. Il s'agit d'une référence à l'objet / entité de jeu actuel / quel que soit le propriétaire des composants.
Tetrad
0

Si votre système de composants autorise les scripts, le composant bouclier pourrait être presque un super composant qui appelle simplement un script pour son paramètre "effet". De cette façon, vous maintenez la simplicité d'un composant unique pour les boucliers et déchargez toute la logique de ce qu'il fait réellement sur les fichiers de script personnalisés qui sont alimentés aux boucliers par les définitions de votre entité.

Je fais quelque chose de similaire pour mon composant Moveable, il contient un champ qui est du script keyreaction (une sous-classe de script dans mon moteur) ce script définit une méthode qui suit mon message d'entrée. en tant que tel, je peux simplement faire quelque chose comme ça dans mon fichier de définition tempalte

camera template
    moveable
    {
        keyreaction = "scriptforcameramoves"

    }  

player template
    moveable
    {
        keyreaction = "scriptfroplayerkeypress"

    }  

puis dans mon composant mobile lors de l'enregistrement des messages j'enregistre la méthode Do des scripts (code en C #)

Owner.RegisterHandler<InputStateInformation>(MessageType.InputUpdate, kScript.Do);

bien sûr, cela repose sur ma méthode Do suivant le modèle de fonctions que mon RegisterHandler prend. Dans ce cas, son (expéditeur IComponent, argument de type ref)

donc mon "script" (dans mon cas aussi C # juste runime compilé) définit

 public class CameraMoveScript : KeyReactionScript
{
 public override void Do(IComponent pSender, ref InputStateInformation inputState)
 {
    //code here
 }
}

et ma classe de base KeyReactionScript

public class KeyReactionScript : Script
{
      public virtual void Do(IComponent pSender, ref InputStateInformation inputState);
}

puis plus tard, lorsqu'un composant d'entrée envoie un message du type MessageTypes.InputUpdate avec le type en tant que tel

 InputStateInformation myInputState = InputSystem.GetInputState();
 SendMessage<InputStateInformation>(MessageTypes.InputUpdate, ref myInputState);

La méthode du script qui était liée à ce message et à ce type de données sera déclenchée et gérera toute la logique.

Le code est assez spécifique à mon moteur, mais la logique devrait être fonctionnelle dans tous les cas. Je le fais pour de nombreux types afin de garder la structure des composants simple et flexible.

exnihilo1031
la source