Gestion des entrées dans la conception basée sur les composants

12

Je sais que cette question a été posée plusieurs fois, mais je ne sais toujours pas comment implémenter la gestion des entrées dans un moteur basé sur des composants.

La conception basée sur les composants que j'ai utilisée était basée sur la série de blogs de T = Machine et sur Artemis dans lequel les entités ne sont que des identifiants.

Il y a trois idées principales que j'ai dans la mise en œuvre de la gestion des entrées:

  1. Le composant d'entrée contiendra les événements qui l'intéressent. Le système d'entrée traduira les événements de touches et de souris en événements de jeu et parcourra les entités avec le composant d'entrée et s'ils sont intéressés par l'événement, une action appropriée sera prise par le système d'entrée. Cette action serait codée en dur dans le système d'entrée.
  2. Aucun composant d'entrée. Vous devez enregistrer des entités avec des événements spécifiques dans le système d'entrée. Le système d'entrée enverrait alors des messages (avec l'ID d'entité et le type d'événement) à d'autres systèmes afin que ceux-ci puissent prendre les mesures appropriées. Ou comme dans le premier cas, les actions seraient codées en dur dans le système d'entrée.
  3. Semblable à la première méthode, mais au lieu de coder en dur l'action sur le système d'entrée, le composant contiendrait une carte des événements aux fonctions (c'est-à-dire std::map<std::function>) qui serait appelée par le système d'entrée. Cela a pour effet supplémentaire de pouvoir coupler le même événement à différentes actions.

Recommanderiez-vous l'une des méthodes ci-dessus ou avez-vous des suggestions qui pourraient m'aider à mettre en œuvre un système de gestion des entrées flexible? De plus, je ne suis pas encore familier avec le multi-threading mais toutes les suggestions qui rendraient l'implémentation compatible avec les threads sont également les bienvenues.

Remarque: Une exigence supplémentaire que j'aimerais que l'implémentation remplisse est que je puisse transmettre la même entrée à de nombreuses entités, comme par exemple déplacer une entité caméra et le lecteur en même temps.

Grieverheart
la source
2
Habituellement (lorsque la caméra suit le lecteur), vous ne voulez pas recevoir d'entrée dans la caméra, mais faites en sorte que la caméra vérifie la position du lecteur et la suive.
Luke B.
1
Peu importe conceptuellement si la caméra suit le joueur ou "elle-même". Néanmoins, je ne sais pas comment votre suggestion serait mise en œuvre dans une conception basée sur les composants sans enfreindre les principes de conception.
Grieverheart
1
@Luke B .: Après y avoir réfléchi, je vois que vous pouvez également faire de l'appareil photo une classe distincte, en prenant un pointeur vers une entité à suivre.
Grieverheart

Réponses:

8

Je pense que, tout comme ma réponse concernant les matériaux dans un système de composants , vous rencontrez un problème où vous essayez de tout mettre dans un "composant". Vous n'avez pas besoin de le faire et, ce faisant, vous créez probablement une interface vraiment encombrante en essayant de placer un tas de chevilles carrées dans des trous ronds.

Il semble que vous disposiez déjà d'un système qui gère l'acquisition des entrées du lecteur. J'opterais pour une approche qui traduirait ensuite cette contribution en actions («avancer» ou «reculer») ou en événements et les envoyer aux parties intéressées. Dans le passé, j'ai des composants non autorisés d'enregistrer eux - mêmes pour ces événements, préférant une approche où le système de niveau supérieur sélectionné explicitement la « entité contrôlée ». Mais cela pourrait fonctionner de cette autre manière si vous préférez, surtout si vous allez réutiliser les mêmes messages pour entreprendre des actions qui n'ont pas été directement stimulées par la saisie.

Je ne suggérerais pas nécessairement de mettre en œuvre un comportement de suivi de caméra en faisant en sorte que l'entité caméra et l'entité joueur répondent au message "aller de l'avant" (et cetera). Cela crée une connexion extrêmement rigide entre les deux objets qui ne se sentira probablement pas bien pour le joueur, et cela rend également un peu plus difficile de gérer des choses comme avoir la caméra en orbite autour du joueur lorsque le joueur tourne à gauche ou à droite: vous avez une entité répondre à "tourner à gauche" en supposant qu'il est asservi au joueur, mais cela signifie qu'il ne peut pas répondre correctement s'il a jamais été asservi ... à moins que vous n'introduisiez ce concept comme un état que vous pouvez vérifier. Et si vous voulez faire cela, vous pouvez aussi bien mettre en œuvre un système approprié pour asservir deux objets physiques ensemble, avec des ajustements d'élasticité appropriés, etc.

En ce qui concerne le multi-threading, je ne vois pas vraiment la nécessité de l'utiliser ici car cela entraînerait probablement plus de complications que cela en vaut la peine, et vous avez affaire à un problème intrinsèquement série, il vous suffit donc d'impliquer beaucoup de thread primitives de synchronisation.

Communauté
la source
Je réfléchis à ma question et j'allais y répondre moi-même. Je suis également arrivé à la conclusion que je devrais être mieux de découpler la gestion des entrées du système EC, donc c'est agréable de voir une confirmation de cela. J'ai pensé à faire cela en utilisant des signaux et en associant plusieurs entités à un type d'événement. J'ai également décidé de découpler la caméra, bien que ce ne soit pas vraiment nécessaire et l'avoir comme entité serait tout aussi viable. Je pense que lorsque vous êtes encore un débutant avec les EC, vous devez vraiment penser aux avantages de faire de quelque chose un composant ou une entité.
Grieverheart
4

Mon expérience peut être biaisée, mais dans les projets multi-plateformes, les périphériques d'entrée ne sont pas directement exposés au système d'entité.

Les périphériques d'entrée sont gérés par un système de niveau inférieur qui reçoit les événements des touches, boutons, axe, souris, surfaces tactiles, accéléromètres ...

Ces événements sont ensuite envoyés via une couche de générateurs d'intentions dépendant du contexte.

Chaque générateur enregistre les changements d'état des composants, des entités et des systèmes qui sont pertinents pour ses fonctions.

Ces générateurs envoient ensuite des messages / intentions pour le routage vers le système d'intentions où les entités ont un composant ou directement vers les bons composants.

De cette façon, vous pouvez simplement compter sur "toujours" la même entrée, c'est-à-dire JUMP_INTENT (1), JUMP_INTENT (0), AIM_INTENT (1) ...

Et «tout» le travail d'entrée dépendant de la plate-forme sale reste en dehors de votre système d'entité.


En ce qui concerne la caméra, si vous souhaitez la déplacer dans le lecteur, elle peut enregistrer son propre composant d'intention et écouter les intentions que vous enverrez.

Sinon, si suit le lecteur, il ne doit jamais écouter les entrées destinées au lecteur. Il doit écouter les changements d'état émis par le lecteur (ENTITY_MOVED (transform)) ... et se déplacer en conséquence. Si vous utilisez un système physique, vous pouvez même attacher la caméra au lecteur en utilisant l'une des différentes articulations.

Coyote
la source
Coyote, merci pour votre réponse. J'ai également lu votre autre article ici . Ma plus grande préoccupation n'est pas de savoir comment résumer l'entrée. J'ai déjà une construction de niveau inférieur qui gère les pressions de touches et autres, et ajouter un niveau d'indirection supplémentaire ne serait pas difficile. Mon problème est de gérer les événements générés par exemple par votre système d'intention. Si je comprends bien, vous n'avez aucun composant d'entrée dans votre méthode. Comment savez-vous quelles entités ont besoin de contributions et comment les gérez-vous? Pourriez-vous donner des exemples plus concrets?
Grieverheart
2

Quel est l'avantage d'un InputComponent? C'est certainement la prérogative de la commande d'entrée de décider sur quelles entités elle exécute une action. L'exemple classique est celui de faire sauter le joueur. Au lieu d'avoir un InputComponent sur chaque entité à l'écoute des événements "Jump", pourquoi ne pas demander à la commande jump de rechercher l'entité marquée "player" et d'effectuer elle-même la logique nécessaire?

Action jump = () =>
{
    entities["player"].Transform.Velocity.Y += 5;
};

Un autre exemple, du PO:

Action moveRight = () =>
{
    foreach (var entity in entities.Tagged("player", "camera"))
        entity.Transform.Position.X += 5;
};
AlexFoxGill
la source