Comment implémenter des fonctionnalités dans un système d'entité?

31

Après avoir posé deux questions sur les systèmes d'entités ( 1 , 2 ), et lu quelques articles à leur sujet, je pense que je les comprends beaucoup mieux qu'avant. J'ai encore quelques incertitudes, principalement sur la construction d'un émetteur de particules, d'un système d'entrée et d'une caméra. J'ai évidemment encore des problèmes pour comprendre les systèmes d'entités, et ils peuvent s'appliquer à une toute autre gamme d'objets, mais j'ai choisi ces trois parce qu'ils sont des concepts très différents, devraient couvrir un terrain assez large et m'aider à comprendre les systèmes d'entités et comment gérer des problèmes comme ceux-ci, moi-même, au fur et à mesure qu'ils surviennent.

Je construis un moteur en JavaScript, et j'ai implémenté la plupart des fonctionnalités de base, qui comprennent: la gestion des entrées, un système d'animation flexible, un émetteur de particules, des classes et des fonctions mathématiques, la gestion des scènes, une caméra et un rendu, et tout un tas d'autres choses que les moteurs prennent généralement en charge. J'ai lu la réponse de Byte56, qui m'a intéressé à faire du moteur un système d'entités. Il resterait toujours un moteur de jeu HTML5, avec la philosophie de base de la scène, mais il devrait prendre en charge la création dynamique d'entités à partir de composants.


Le problème que j'ai, maintenant, est d'adapter mon ancien concept de moteur à ce nouveau paradigme de programmation. Voici quelques définitions des questions précédentes, mises à jour:

  • Une entité est un identifiant. Il n'a pas de données, ce n'est pas un objet, c'est un simple identifiant qui représente un index dans la liste des scènes de toutes les entités (que j'ai l'intention de mettre en œuvre en tant que matrice de composants).

  • Un composant est un détenteur de données, mais avec des méthodes qui peuvent fonctionner sur ces données. Le meilleur exemple est un Vector2Dou un composant "Position". Il a des données: xet y, mais aussi des méthodes qui permettent de manipuler les données sur un peu plus facile: add(), normalize()et ainsi de suite.

  • Un système est quelque chose qui peut fonctionner sur un ensemble d'entités qui répondent à certaines exigences; généralement, les entités doivent avoir un ensemble spécifié de composants, pour être exploitées. Le système est la partie "logique", la partie "algorithme", toutes les fonctionnalités fournies par les composants sont purement pour une gestion des données plus simple.


Caméra

La caméra possède une Vector2Dpropriété de position, une propriété de rotation et quelques méthodes pour la centrer autour d'un point. Chaque image, elle est alimentée dans un moteur de rendu, avec une scène, et tous les objets sont traduits en fonction de sa position. La scène est ensuite rendue.

Comment pourrais-je représenter ce type d'objet dans un système d'entités? La caméra serait-elle une entité, un composant ou une combinaison (selon ma réponse )?

Emetteur de particules

Le problème que j'ai avec mon émetteur de particules est, encore une fois, ce qui devrait être quoi. Je suis à peu près sûr que les particules elles-mêmes ne devraient pas être des entités, car je veux en prendre en charge plus de 10 000, et je pense que créer autant d'entités serait un coup dur pour mes performances.

Comment pourrais-je représenter ce type d'objet dans un système d'entités?

Gestionnaire d'entrées

Le dernier dont je veux parler est de savoir comment gérer les entrées. Dans ma version actuelle du moteur, il existe une classe appelée Input. Il s'agit d'un gestionnaire qui s'abonne aux événements du navigateur, tels que les pressions sur les touches et les changements de position de la souris, et maintient également un état interne. Ensuite, la classe player possède une react()méthode, qui accepte un objet d'entrée comme argument. L'avantage de ceci est que l'objet d'entrée pourrait être sérialisé en .JSON, puis partagé sur le réseau, permettant des simulations multijoueurs fluides.

Comment cela se traduit-il en un système d'entités?

jcora
la source

Réponses:

26
  • Appareil photo: en faire un composant serait assez soigné. Il aurait juste unisRenderingdrapeau et gamme de profondeur comme l'a dit Sean. En plus du "champ de vision" (je suppose que vous pourriez l'appeler échelle en 2D?) Et d'une zone de sortie. La zone de sortie pourrait définir la partie de la fenêtre de jeu vers laquelle cette caméra est rendue. Il n'aurait pas de position / rotation distincte comme vous le mentionnez. L'entité que vous créez qui a un composant de caméra utiliserait les composants de position et de rotation de cette entité. Ensuite, vous auriez un système de caméra qui recherche des entités qui ont une caméra, des composants de position et de rotation. Le système prend cette entité et dessine toutes les entités qu'il peut «voir» depuis sa position, sa rotation, sa profondeur de vue et son champ de vision vers la partie spécifiée de l'écran. Cela vous donne beaucoup d'options pour simuler plusieurs ports de vue, des fenêtres de "vue de personnage", un multijoueur local,

  • Emetteur de particules: cela aussi ne devrait être qu'un composant. Le système de particules rechercherait des entités qui ont une position, une rotation et un émetteur de particules. L'émetteur a toutes les propriétés nécessaires pour reproduire votre émetteur actuel, je ne suis pas sûr de ce que tout cela est, des trucs comme: le taux, la vitesse initiale, le temps de décroissance et ainsi de suite. Vous n'auriez pas à effectuer plusieurs passes. Le système de particules sait quelles entités ont ce composant. J'imagine que vous pourriez réutiliser une bonne partie de votre code existant.

  • Entrées: je dois dire que le transformer en un composant est plus logique étant donné les suggestions que je fais ci-dessus. Votreinput systemserait mis à jour chaque trame avec les événements d'entrée en cours. Ensuite, quand il passe par toutes ses entités qui ont le composant d'entrée, il applique ces événements. Le composant d'entrée aurait une liste des événements clavier et souris, tous les rappels de méthode associés. Je ne suis pas vraiment sûr de l'emplacement des rappels de méthode. Peut-être une classe de contrôleur d'entrée? Tout ce qui a le plus de sens pour une modification ultérieure par les utilisateurs de votre moteur. Mais cela vous donnerait le pouvoir d'appliquer facilement le contrôle d'entrée aux entités de caméra, aux entités de joueur ou à tout ce dont vous avez besoin. Vous voulez synchroniser le mouvement d'un tas d'entités avec le clavier? Donnez-leur simplement tous les composants d'entrée qui répondent aux mêmes entrées et le système d'entrée applique ces événements de déplacement à tous les composants qui les demandent.

Donc, la plupart de cela est juste au-dessus de ma tête, donc cela n'aura probablement aucun sens sans plus d'explications. Alors, faites-moi savoir ce que vous ne savez pas. En gros, je vous ai donné beaucoup de choses à travailler :)

MichaelHouse
la source
Une autre excellente réponse! Merci! Maintenant, mon seul problème est de stocker et de récupérer rapidement des entités, afin que l'utilisateur puisse réellement implémenter une boucle / logique de jeu ... J'essaierai de le comprendre par moi-même, mais je dois d'abord apprendre comment Javascript traite les tableaux, les objets et des valeurs indéfinies en mémoire pour faire une bonne supposition ... Ce sera un problème car différents navigateurs pourraient l'implémenter différemment.
jcora
Cela semble pur sur le plan architectural, mais comment le système de rendu détermine-t-il la caméra active à court d'itération à travers toutes les entités?
Rendez-vous le
@Pace Puisque je voudrais que la caméra active soit trouvée très rapidement, je laisserais probablement le système de caméra garder une référence aux entités qui ont une caméra active.
MichaelHouse
Où placez-vous la logique pour contrôler plusieurs caméras (regarder, faire pivoter, déplacer, etc.)? Comment contrôlez-vous les multiples caméras?
plasmacel
@plasmacel Si vous avez plusieurs objets qui partagent des contrôles, ce sera la responsabilité de votre système de contrôle de déterminer quel objet reçoit les entrées.
MichaelHouse
13

Voici comment je l'ai abordé:

Caméra

Mon appareil photo est une entité comme les autres, qui a des composants attachés:

  1. Transforma Translation, Rotationet des Scalepropriétés, en plus d'autres pour la vitesse, etc.

  2. Pov(Point de vue) a FieldOfView, AspectRatio, Near, Far, et toute autre chose nécessaire pour produire une matrice de projection, en plus d'un IsOrthodrapeau utilisé pour basculer entre perspective et projections orthogonales. Povfournit également une ProjectionMatrixpropriété de chargement différé utilisée par le système de rendu qui est calculée en interne lors de la lecture et mise en cache jusqu'à ce que les autres propriétés soient modifiées.

Il n'y a pas de système de caméra dédié. Le système de rendu conserve une liste de Povfichiers et contient une logique pour déterminer celui à utiliser lors du rendu.

Contribution

Un InputReceivercomposant peut être attaché à n'importe quelle entité. Il dispose d'un gestionnaire d'événements attaché (ou lambda si votre langue le prend en charge) qui est utilisé pour maintenir le traitement d'entrée spécifique à l'entité, qui prend les paramètres pour l'état actuel et précédent de la clé, l'emplacement actuel et précédent de la souris et l'état du bouton, etc. (En fait, il existe des gestionnaires distincts pour la souris et le clavier).

Par exemple, dans un jeu de test de type Asteroids que j'ai créé en m'habituant à Entity / Component, j'ai deux méthodes d'entrée lambda. L'un gère la navigation du navire en traitant les touches fléchées et la barre d'espace (pour le tir). L'autre gère la saisie générale du clavier - touches de sortie, pause, etc., niveau de redémarrage, etc. Je crée deux composants, attache chaque lambda à son propre composant, puis attribue le composant récepteur de navigation à l'entité navire, l'autre à un entité de processeur de commande non visible.

Voici le gestionnaire d'événements pour gérer les clés qui sont maintenues entre les cadres qui sont attachés au InputReceivercomposant du navire (C #):

  void ship_input_Hold(object sender, InputEventArgs args)
    {
        var k = args.Keys;
        var e = args.Entity;

        var dt = (float)args.GameTime.ElapsedGameTime.TotalSeconds;

        var verlet = e.As<VerletMotion>();
        var transform = e.As<Transform>();

        if (verlet != null)
        {

        /// calculate applied force 
            var force = Vector3.Zero;
            var forward = transform.RotationMatrix.Up * Settings.ShipSpeedMax;

            if (k.Contains(Keys.W))
                force += forward;

            if (k.Contains(Keys.S))
                force -= forward;

            verlet.Force += force * dt;
        }

        if (transform != null)
        {
            var theta = Vector3.Zero;

            if (k.Contains(Keys.A))
                theta.Z += Settings.TurnRate;

            if (k.Contains(Keys.D))
                theta.Z -= Settings.TurnRate;

            transform.Rotation += theta * dt;
        }

        if (k.Contains(Keys.Space))
        {
            var time = (float)args.GameTime.TotalGameTime.TotalSeconds - _rapidFireLast;

            if (time >= _rapidFireDelay)
            {
                Fire();
                _rapidFireLast = (float)args.GameTime.TotalGameTime.TotalSeconds;
            }
        }
    }

Si votre appareil photo est mobile, lui donner son propre InputReceiveret Transformcomposant, joindre un lambda ou gestionnaire qui implémente tout type de contrôle que vous voulez, et vous avez terminé.

C'est plutôt bien en ce sens que vous pouvez déplacer le InputReceivercomposant avec le gestionnaire de navigation attaché du vaisseau à un astéroïde, ou autre chose d'ailleurs, et le faire voler à la place. Ou, en affectant un Povcomposant à tout autre élément de votre scène - un astéroïde, un lampadaire, etc. - vous pouvez visualiser votre scène du point de vue de cette entité.

Une InputSystemclasse qui conserve un état interne pour le clavier, la souris, etc. InputSystemfiltre sa collection d'entités internes en entités qui ont un InputReceivercomposant. Dans sa Update()méthode, il parcourt cette collection et appelle les gestionnaires d'entrée attachés à chacun de ces composants de la même manière que le système de rendu dessine chaque entité avec un Renderablecomposant.

Particules

Cela dépend vraiment de la façon dont vous prévoyez d'interagir avec les particules. Si vous avez juste besoin d'un système de particules qui se comporte comme un objet - disons, un feu d'artifice montre que le joueur ne peut pas toucher ou frapper - alors je créerais une seule entité et un ParticleRenderGroupcomposant qui contient toutes les informations dont vous avez besoin pour les particules - désintégration, etc. - qui n'est pas couvert par votre Renderablecomposant. Lors du rendu, le système de rendu verrait si une entité a l' RenderParticleGroupattaché et le gérerait en conséquence.

Si vous avez besoin de particules individuelles pour participer à la détection des collisions, répondre aux entrées, etc., mais que vous souhaitez simplement les rendre sous forme de lot, je créerais un Particlecomposant qui contient ces informations par particule, et les créer comme entités distinctes. Le système de rendu peut toujours les regrouper, mais ils seront traités comme des objets séparés par les autres systèmes. (Cela fonctionne très bien avec l'instanciation.)

Ensuite, soit dans votre MotionSystem(ou quelle que soit votre utilisation qui gère la mise à jour de la position de l'entité, etc.) ou dans dédié ParticleSystem, effectuez le traitement requis pour chaque particule par image. Le RenderSystemserait responsable de la création / mise en lots et de la mise en cache des collections de particules au fur et à mesure qu'elles sont créées et détruites, et les rendrait selon les besoins.

Une bonne chose à propos de cette approche est que vous n'avez pas à avoir de cas particuliers pour les collisions, l'abattage, etc. pour les particules; ils codent que vous écrivez pour tout autre type d'entité peut toujours être utilisé.

Conclusion

Si vous envisagez de passer à plusieurs plates-formes - non super-applicables à JavaScript - tout votre code spécifique à la plate-forme (à savoir, le rendu et la saisie) est isolé dans deux systèmes. Votre logique de jeu reste dans les classes agnositiques à la plateforme (mouvement, collision, etc.), vous ne devriez donc pas avoir à les toucher lors du portage.

Je comprends et suis d'accord avec la position de Sean selon laquelle les éléments de taille de chaussures dans un modèle afin d'adhérer strictement au modèle, plutôt que de modifier le modèle pour répondre aux besoins de votre application, sont mauvais. Je ne vois rien dans Input, Camera ou Particles qui nécessite ce type de traitement.

3Dave
la source
Où placez-vous la logique pour contrôler plusieurs caméras (regarder, faire pivoter, déplacer, etc.)?
plasmacel
7

La logique d'entrée et de jeu sera probablement gérée dans un morceau de code dédié en dehors du système de composants d'entité. Il est techniquement possible de l'intégrer dans la conception, mais il y a peu d'avantages - la logique du jeu et l'interface utilisateur sont hacky et pleins d'abstractions qui fuient quoi que vous fassiez, et essayer de forcer la cheville carrée dans un trou rond juste pour la pureté architecturale est un gaspillage de temps.

De même, les émetteurs de particules sont des bêtes spéciales, surtout si vous vous souciez de la performance. Un composant émetteur est logique, mais les graphiques vont faire de la magie spéciale avec ces composants, mélangés avec la magie pour le reste du rendu.

En ce qui concerne votre caméra, donnez simplement aux caméras un drapeau actif et peut-être un index de "profondeur", et laissez le système graphique rendre toutes celles qui sont activées. C'est en fait pratique pour de nombreuses astuces, y compris les interfaces graphiques (vous voulez que votre interface graphique soit rendue en mode orthographique au-dessus du monde du jeu? Pas de problème, ce ne sont que deux caméras avec des masques d'objets différents et une interface graphique définie sur une couche supérieure). Il est également utile pour les calques d'effets spéciaux et autres.

Sean Middleditch
la source
4

La caméra serait-elle une entité ou simplement un composant?

Je ne sais pas vraiment ce que cette question demande. Étant donné que les seules choses que vous avez dans le jeu sont des entités, les caméras doivent être des entités. La fonctionnalité de la caméra est implémentée via une sorte de composant de caméra. Ne pas avoir de composants "Position" et "Rotation" séparés - c'est un niveau beaucoup trop bas. Ils devraient être combinés en une sorte de composant WorldPosition qui s'appliquerait à toute entité située dans le monde. Quant à savoir lequel utiliser ... vous devez d'une manière ou d'une autre intégrer la logique dans le système. Soit vous le codez en dur dans votre système de gestion de caméra, soit vous joignez des scripts, ou quelque chose. Vous pouvez avoir un indicateur activé / désactivé sur un composant de caméra si cela vous aide.

Je suis sûr que les particules elles-mêmes ne devraient pas être des entités

Moi aussi. Un émetteur de particules serait une entité et le système de particules suivrait les particules associées à une entité donnée. Des choses comme celle-ci sont où vous réalisez que "tout est une entité" est absurdement impossible. En pratique, les seules choses qui sont des entités sont des objets relativement complexes qui bénéficient de combinaisons de composants.

Quant à Input: l'entrée n'existe pas dans le monde du jeu en tant que tel, elle est donc gérée par un système. Pas nécessairement un «système de composants» car tout dans votre jeu ne tourne pas autour des composants. Mais il y aura un système d'entrée. Vous voudrez peut-être marquer l'entité qui répond à l'entrée avec une sorte de composant Player, mais l'entrée va être complexe et complètement spécifique au jeu, il est donc inutile d'essayer de créer des composants pour cela.

Kylotan
la source
1

Voici quelques-unes de mes idées pour résoudre ces problèmes. Ils auront probablement quelque chose qui ne va pas avec eux, et il y aura probablement une meilleure approche, alors s'il vous plaît, dirigez-moi vers ceux dans votre réponse!

Appareil photo :

Il existe un composant "Caméra", qui peut être ajouté à n'importe quelle entité. Je ne peux pas vraiment comprendre quelles données dois-je mettre dans ce composant, cependant: je pourrais avoir des composants séparés "Position" et "Rotation"! La followméthode n'a pas besoin d'être implémentée, car elle suit déjà l'entité à laquelle elle est attachée! Et je suis libre de le déplacer. Le problème avec ce système serait de nombreux objets de caméra différents: comment RendererSystemsavoir lesquels utiliser? Et aussi, je passais juste l'objet caméra autour, mais maintenant il semble que le RendererSystembesoin de répéter deux fois sur toutes les entités: d'abord pour trouver celles agissant comme des caméras, et deuxièmement, pour réellement tout rendre.

Émetteur de particules :

Il y aurait un ParticleSystemqui mettrait à jour toutes les entités qui avaient un composant "Emitter". Les particules sont des objets muets dans un espace de coordonnées relatif, à l'intérieur de ce composant. Il y a un problème de rendu ici: il me faudrait soit faire un ParticleRenderersystème, soit étendre les fonctionnalités de l'existant.

Système d'entrée :

La principale préoccupation pour moi ici était la logique ou la react()méthode. La seule solution que j'ai trouvée est un système séparé pour cela, et un composant pour chaque système, qui indiquerait lequel utiliser. Cela semble vraiment trop hacky, et je ne sais pas comment le gérer correctement. Une chose est que, tant que je suis concerné, le Inputpeut rester implémenté en tant que classe, mais je ne vois pas comment pourrais-je l'intégrer au reste du jeu.

jcora
la source
Il n'y a pas vraiment de raison pour que le RendererSystem itère sur toutes les entités - il devrait déjà avoir une liste de dessinables (et de caméras et de lumières (sauf si les lumières sont dessinables)), ou savoir où se trouvent ces listes. En outre, vous souhaiterez probablement effectuer une élimination des caméras que vous souhaitez rendre, donc votre caméra pourrait peut-être contenir une liste d'ID d'entité pouvant être dessinés qui lui sont visibles. Vous pouvez avoir plusieurs caméras et une caméra active, ou une caméra connectée à différents PDV, qui peuvent être contrôlées par un certain nombre de choses, comme des scripts, des déclencheurs et des entrées
@ melak47, c'est vrai, j'y ai pensé aussi, mais je voulais l'implémenter comme le fait Aremis. Mais ce "système stocke les références aux entités pertinentes" semble être de plus en plus imparfait ...
jcora
Artemis ne stocke-t-il pas chaque type de composant dans sa propre liste? Alors, n'auriez-vous pas exactement ces listes de composants pouvant être dessinés, de composants de caméra, de lumières et quoi pas ailleurs?