État du jeu et gestion des entrées dans les systèmes d'entités basés sur les composants

16

Ma question est:

Comment puis-je gérer les états de jeu dans mon système d'entités, sans recourir à la conservation d'une pile d'objets d'état de jeu?

Ainsi, la conception de mon système d'entités signifie que lorsqu'une entité doit s'enregistrer pour des événements d'entrée par exemple, le composant d'entrée appelle le système d'entrée et dit "enregistrer cette entité pour cette entrée". C'est très bien, mais si vous ajoutez à cela le concept d'états de jeu (par exemple un écran de pause), il devient difficile de déterminer si une entité est dans l'état actuel et doit recevoir l'entrée.

Je pourrais augmenter le composant / système d'entrée pour qu'il dise, "enregistrer cette entité pour cette entrée dans ces états de jeu", mais cela nécessite que chaque entité sache dans quels états elle va être utilisée, et cela peut ne pas être évident. De plus, conserver une liste des états du jeu par entrée enregistrée (et d'autres systèmes qui utilisent des rappels) ne semble pas trop efficace.

Une autre idée que j'ai eue est qu'il y aura une entité qui représente l'état du jeu, marquez-la comme étant désactivée, puis lors de la génération de l'événement d'entrée, vérifiez que l'entité n'est pas un descendant d'une entité d'état de jeu désactivée. Semble coûteux de déterminer le parent pour chaque rappel.

Une autre idée est que tous les systèmes stockent leurs données par rapport à l'état actuel, de cette façon lors de la génération de l'entrée, l'entité cible ne sera même pas candidate. Cependant, cela nuit vraiment à la possibilité d'autoriser la communication entre des entités dans des états différents (pas vraiment un problème pour les écrans de pause, mais pensez à la sélection des verrous dans Oblivion / Skyrim).

La seule autre idée que j'ai eue est que tous les composants gèrent les événements de changement d'état et communiquent avec leur système pertinent pour désactiver tout ce qu'ils ont enregistré et le réactiver lors du retour à cet état.

Les deuxièmes (marquer un objet comme désactivé) et les autres (demander à chaque composant de gérer les changements d'état) semblent être les meilleures de mes idées, mais aucune d'entre elles ne me semble particulièrement intéressante.

Quelqu'un d'autre a-t-il d'autres idées sur la façon de procéder?

modifier Bien que je parle d'entrée spécifiquement dans cette question, cela peut signifier tout système capable d'envoyer des messages / événements à des entités, comme des collisions, des événements de minuterie, etc.

elFarto
la source
6
Je le fais comme ceci: j'ai des écrans, MenuScreen PauseScreen GameScreen, chaque écran peut créer son propre monde (conteneur pour les entités) et ses systèmes (comme RenderingSystem), puis dans GameScreen je crée World, Entity with CameraComponent et définit CameraComponent.RenderTarget sur fond d'écran. De cette façon, je peux ajouter InventoryScreen qui aura ses propres entités et systèmes (comme le rendu simplifié). L'entrée peut être passée de l'écran au monde, donc votre interface utilisateur décidera si elle passera l'entrée à l'écran (si elle est focalisée, visible, etc.) et qui passera l'entrée au monde et aux entités
Kikaimaru
2
@ Byte56 Pas vraiment, seul le premier concerne les gamestates (les 2 autres sont des états au sein des entités), et cela ne résout pas vraiment le même problème que moi. Lorsque le jeu est en pause, quelque chose doit arriver au système d'entrée pour l'empêcher d'envoyer des messages de mouvement à l'entité du joueur (par exemple), je n'arrive pas à trouver un bon moyen de le faire.
elFarto
1
OK, considérez-les comme liés alors. Bonne question.
MichaelHouse
1
Autre chose à prendre en compte qui a été une gêne pour mes systèmes basés sur des composants dans le passé: l'interface utilisateur multicouche. Boîte de dialogue apparaissant sur des écrans du monde ou à plusieurs niveaux. Il est apparu jusqu'à présent dans chaque jeu que j'ai fait, je dirais donc de veiller à envisager une approche qui peut résoudre ce problème.
ADB

Réponses:

14

Ce qui est souvent utilisé est un intermédiaire Intent System qui résume l'entrée et garde une trace du contexte et des gamestates pertinents.

Le système d'intention arrêtera de transmettre des entrées lorsque la simulation est interrompue par exemple. Il gère également le mappage entre les événements du contrôleur et les intentions (se déplacer dans la direction, courir, tirer, recharger ...).

De cette façon, vos autres composants ne dépendent pas de manettes / entrées spécifiques (BUTTON_A, BUTTON_B vs BUTTON_X, BUTTON_O ...) mais ils réagissent tous aux mêmes intentions (IntentRun, IntentReload ...).

Un autre avantage est que le système d'intention peut être conscient que des contrôleurs disponibles sont ajoutés / supprimés, car il peut envoyer des intentions à n'importe quel abonné même en dehors de la simulation, vous pouvez gérer des intentions comme AddPlayer(controllerID).

La quantité d'informations sur l'état du jeu que vous fournissez au système via des événements / messages ou directement dépend de vous. Mais le temps investi dans le système Intent en vaut généralement la peine.

Vous pouvez gérer les contextes d'intention qui généreront des intentions lorsqu'ils seront connectés au système.

Le contexte peut être hiérarchisé, c'est-à-dire:

  • SimulationAvailableContext envoie des intentions à la simulation lorsqu'elle est disponible (mais pas en cours d'exécution), par exemple déplacer la caméra, effectuer un zoom avant ou arrière, ajouter / supprimer un lecteur ...
  • SimulationRunningContext envoie des intentions à la simulation alors qu'elle n'est pas en pause, déplacez le joueur, envoyez l'unité en position, tirez ...

De cette façon, vous pouvez ajouter et supprimer les contextes qui sont actuellement pertinents.

Et une chose à propos de l'ensemble des systèmes d'intention est qu'il doit s'exécuter pendant la pause de la simulation.

Une façon qui est souvent utilisée pour jouer / mettre en pause la simulation de jeu sans interrompre les mises à jour non liées à la simulation est d'utiliser un ensemble d'heures différent. ie GenericSystem::onTime(Long time, Long deltaTime, Long simTime, Long simDeltaTime).

Avec cette approche, votre moteur peut simplement bloquer les incréments sur le simTime du jeu qui à son tour bloquera les mises à jour sur les moteurs d'animation et de physique pertinents qui utilisent simTime and simDeltaTimetout en permettant des mises à jour continues de l'effet ressort de votre caméra s'il doit se déplacer même pendant la pause, l'animation de l'effet de chargement sur un panneau d'affichage virtuel dans le jeu pendant le téléchargement des données ...

Coyote
la source
J'aime le fait que cela n'ait pas à appeler un tas de fonctions "State Changed" sur toutes les entités. Vous devez vous inquiéter des mauvaises intentions envoyées au mauvais moment, mais je pense que c'est mieux que l'alternative.
Thomas Marnell
vos entités peuvent ignorer des intentions comme Jump alors que leur état ne leur permet pas de sauter (c'est-à-dire de ne pas toucher le sol). mais ils n'ont pas à se soucier de recevoir de telles intentions lorsque le jeu est en pause.
Coyote
J'avais déjà pensé à laisser l'entité dire au système d'entrée dans quels états transmettre les messages, mais je n'avais pas pensé à mettre les états sur l'entrée elle-même, ce qui est une bonne idée. Il est également agréable de partager le temps et le simTime.
elFarto
Vous devez éviter de gonfler votre état lié à la simulation avec des éléments non liés à la simulation. Déplacez toute l'interface utilisateur et le code associé au joueur aussi loin que possible de la simulation elle-même et dans la simulation, concentrez-vous uniquement sur les intentions.
Coyote
Hé @Coyote, ce système semble très intéressant. Pourriez-vous peut-être fournir plus d'informations en répondant à cette question ? Merci!
pek
2

Que diriez-vous de créer un système d'événement global, puis d'avoir un composant écouteur d'événement pour chaque entité? Après un événement "Game State Change", vous pouvez jouer avec les composants individuellement pour chaque entité particulière.

Disons que vous avez un composant d'entrée. Une fois que le composant écouteur d'événements a reçu l'événement de changement d'état du jeu, il modifie des valeurs très spécifiques pour ce composant d'entrée particulier, de sorte qu'il ne recevrait aucun appel d'entrée ou ne ferait aucun appel de mouvement ou de réponse au système ou à son propriétaire.

Cela fonctionne pour moi car la plupart de mes composants sont scriptés (via Lua). C'est-à-dire que j'ai un composant d'entrée, qui est déclenché une fois lorsqu'une touche est enfoncée et il se déclenche d'un mouvement + direction, puis il est déclenché lorsque la touche est relâchée et il se déclenche d'un arrêt + direction. Il existe également un composant écouteur d'événements qui contacte le composant d'entrée (si le jeu est en pause) pour arrêter le déclenchement de toute fonction et l'arrêter si nécessaire. Je pourrais facilement ensuite ajouter une autre entité avec une réaction différente aux mêmes événements et pressions de touches en utilisant un autre script. De cette façon, vous enregistrez l'interaction entre différentes entités dans différents états et la rendez même beaucoup plus personnalisable. De plus, certaines entités peuvent même ne pas avoir le composant écouteur d'événements en eux.

Ce que je viens d'expliquer est essentiellement un exemple pratique de votre quatrième solution.

karmalis
la source