Je réfléchissais à la manière d'implémenter les états de jeu dans mon jeu. Les principales choses que je veux pour cela sont:
Les états supérieurs semi-transparents sont capables de voir à travers un menu de pause au jeu derrière
Quelque chose OO-I trouve cela plus facile à utiliser et à comprendre la théorie derrière, ainsi que de rester organisé et d'ajouter plus à.
Je comptais utiliser une liste chaînée et la traiter comme une pile. Cela signifie que je pourrais accéder à l'état ci-dessous pour la semi-transparence.
Plan: la pile d’états doit être une liste chaînée de pointeurs vers IGameStates. L'état supérieur gère ses propres commandes de mise à jour et d'entrée, puis un membre, isTransparent, lui permettant de décider si l'état situé en dessous doit être tracé.
Alors je pourrais faire:
states.push_back(new MainMenuState());
states.push_back(new OptionsMenuState());
states.pop_front();
Pour représenter le chargement du joueur, puis aller aux options, puis au menu principal.
Est-ce une bonne idée ou ...? Devrais-je regarder autre chose?
Merci.
la source
new
comme indiqué dans l'exemple de code, il s'agit simplement de demander des fuites de mémoire ou d'autres erreurs plus graves.Réponses:
J'ai travaillé sur le même moteur que coderanger. J'ai un point de vue différent. :)
Premièrement, nous n'avions pas de pile de FSM - nous avions une pile d'états. Une pile d'états constitue un seul FSM. Je ne sais pas à quoi ressemblerait une pile de FSM. Probablement trop compliqué pour faire quoi que ce soit de pratique.
Mon plus gros problème avec notre machine à états globale était qu'il s'agissait d'une pile d'états et non d'un ensemble d'états. Cela signifie que, par exemple, ... / MainMenu / Le chargement était différent de ... / Loading / MainMenu, selon que le menu principal était activé avant ou après l'écran de chargement (le jeu est asynchrone et le chargement est principalement piloté par le serveur. )
Comme deux exemples de choses rendues laides:
Malgré le nom, ce n'était pas très "global". La plupart des systèmes de jeu internes ne l'utilisaient pas pour suivre leurs états internes, car ils ne voulaient pas que leurs états fouillent avec d'autres systèmes. D'autres, par exemple le système d'interface utilisateur, pourraient l'utiliser, mais uniquement pour copier l'état dans leurs propres systèmes d'état locaux. (Je voudrais tout particulièrement mettre en garde contre le système pour les états d'interface utilisateur. L'état d'interface utilisateur n'est pas une pile, mais bien un groupe de disponibilité de base, et essayer de forcer toute autre structure dessus ne fera que créer des interfaces utilisateur dont l'utilisation est frustrante.)
Ce qui était bien, c’était de séparer les tâches d’intégration de code des programmeurs d’infrastructure qui ne savaient pas comment le flux de jeu était structuré, de sorte que vous puissiez dire au gars qui écrit le correctif "placez votre code dans Client_Patch_Update" et au gars qui écrit les graphiques. chargement "mettez votre code dans Client_MapTransfer_OnEnter", et nous pourrions échanger certains flux logiques sans trop de problèmes.
Sur un projet parallèle, j’ai eu plus de chance avec un ensemble d’ états que avec une pile . juste un moyen compliqué de synchroniser les choses avec des variables globales - Bien sûr, vous allez finir par le faire dans un délai raisonnable, mais ne concevez pas cela comme votre objectif . Fondamentalement, l'état dans un jeu n'est pas une pile et les états dans un jeu ne sont pas tous liés.
Le GSM aussi, comme le font souvent les indicateurs de fonction et les comportements non locaux, a rendu le débogage plus difficile, bien que le débogage de ce type de grandes transitions d'état ne soit pas très amusant avant que nous l'ayons. Les ensembles d'états au lieu de piles d'état n'aident pas vraiment cela, mais vous devriez en être conscient. Les fonctions virtuelles plutôt que les pointeurs de fonctions peuvent atténuer quelque peu cet inconvénient.
la source
Voici un exemple d'implémentation d'une pile de jeu qui m'a semblé très utile: http://creators.xna.com/en-US/samples/gamestatemanagement
Il est écrit en C # et pour le compiler, vous avez besoin du framework XNA. Cependant, vous pouvez simplement consulter le code, la documentation et la vidéo pour vous faire une idée.
Il peut prendre en charge les transitions d'état, les états transparents (tels que les boîtes de message modales) et les états de chargement (qui gèrent le déchargement des états existants et le chargement de l'état suivant).
J'utilise maintenant les mêmes concepts dans mes projets de passe-temps (non-C #) (d'accord, cela pourrait ne pas convenir pour des projets plus importants) et pour les projets de petite taille / hobby, je peux définitivement recommander l'approche.
la source
Ceci est similaire à ce que nous utilisons, une pile de FSM. Fondamentalement, il suffit de donner à chaque état une fonction entrée, sortie et coche et de les appeler dans l’ordre. Fonctionne très bien pour gérer des choses comme le chargement aussi.
la source
L'un des volumes "Game Programming Gems" contenait une implémentation de machine à états destinée aux états de jeu; http://emergent.net/Global/Documents/textbook/Chapter1_GameAppFramework.pdf donne un exemple d'utilisation de ce jeu pour un petit jeu et ne devrait pas être trop spécifique à Gamebryo pour être lisible.
la source
Juste pour ajouter un peu de standardisation à la discussion, le terme CS typique pour ce type de structures de données est un automate à pile .
la source
Je ne suis pas sûr qu'une pile soit entièrement nécessaire et limite les fonctionnalités du système d'état. En utilisant une pile, vous ne pouvez pas "sortir" d'un état vers l'une des nombreuses possibilités. Supposons que vous commenciez dans «Menu principal», puis dans «Charger la partie». Vous souhaiterez peut-être passer à un état «Pause» après avoir chargé avec succès la partie sauvegardée et revenir à «Menu principal» si l'utilisateur annule la charge.
Je voudrais juste que l'état spécifie l'état à suivre lors de sa sortie.
Pour les cas où vous souhaitez revenir à l'état précédant l'état actuel, par exemple "Menu principal-> Options-> Menu principal" et "Pause-> Options-> Pause", laissez simplement comme paramètre de démarrage l'état Etat à retourner.
la source
Une autre solution aux transitions et autres opérations similaires consiste à fournir l’état de destination et de source, ainsi que la machine à états, qui pourraient être liés au "moteur", quel qu’il soit. La vérité est que la plupart des machines d'état devront probablement être adaptées au projet en cours. Une solution pourrait être bénéfique à tel ou tel jeu, d’autres pourraient l’entraver.
Les états sont poussés avec l'état actuel et la machine en tant que paramètres.
Les états sont sautés de la même manière. Que vous appeliez
Enter()
le basState
est une question de mise en œuvre.Lors de la saisie, de la mise à jour ou de la sortie, le
State
obtient toutes les informations dont il a besoin.la source
J'ai utilisé un système très similaire sur plusieurs jeux et j'ai constaté qu'à quelques exceptions près, il constituait un excellent modèle d'interface utilisateur.
Les seuls problèmes que nous avons rencontrés concernaient des cas dans lesquels, dans certains cas, il était souhaitable de revenir dans plusieurs états avant de passer à un nouvel état (nous avons redistribué l'interface utilisateur pour supprimer l'exigence, car il s'agissait généralement d'un signe de mauvaise interface utilisateur) et de la création de style assistant. flux linéaires (résolus facilement en passant les données le long de l'état suivant).
L'implémentation que nous avons utilisée encapsulait la pile et gérait la logique de mise à jour et de rendu, ainsi que les opérations sur la pile. Chaque opération sur la pile a déclenché des événements sur les états pour les avertir de l'opération en cours.
Quelques fonctions d'assistance ont également été ajoutées pour simplifier les tâches courantes, telles que Swap (Pop & Push, pour les flux linéaires) et Reset (pour revenir au menu principal ou mettre fin à un flux).
la source
C’est la démarche que j’adopte pour presque tous mes projets, car cela fonctionne incroyablement bien et est extrêmement simple.
Sharplike , mon projet le plus récent , gère le flux de contrôle de cette manière. Nos états sont tous câblés avec un ensemble de fonctions d'événement appelées lorsque les états changent, et il comporte un concept de "pile nommée" dans lequel vous pouvez avoir plusieurs piles d'états dans la même machine à états et se ramifier parmi eux - un concept outil, et pas nécessaire, mais pratique pour avoir.
Je mettrais en garde contre le paradigme "Dites au contrôleur quel état doit suivre celui-ci quand il se termine" suggéré par Skizz: ce n'est pas structurellement solide, et cela crée des éléments comme des boîtes de dialogue (qui, dans le paradigme standard de l'état de pile, consistent simplement à créer une nouvelle sous-classe d’état avec nouveaux membres, puis lecture de celle-ci lorsque vous revenez à l’état invoquant) beaucoup plus difficile qu’il ne l’est.
la source
J'ai utilisé fondamentalement ce système exact dans plusieurs systèmes orthogonalement; par exemple, les états des menus front-office et in-game (aka "pause") avaient leurs propres piles d’états. L’interface utilisateur du jeu a également utilisé quelque chose comme ceci bien qu’elle ait des aspects «globaux» (comme la barre de santé et la carte / radar) que le changement d’état pourrait colorer mais qui se mettait à jour de manière commune d’un état à l’autre.
Le menu du jeu peut être "mieux" représenté par un DAG, mais avec une machine à états implicite (chaque option de menu qui passe sur un autre écran sait comment s'y rendre, et en appuyant sur le bouton Retour toujours affiché en haut de l'état), l'effet était exactement le même.
Certains de ces autres systèmes avaient également la fonctionnalité "remplacer l'état supérieur", mais celle-ci était généralement implémentée comme
StatePop()
suitStatePush(x);
.La manipulation de la carte mémoire était similaire, car j’ai inséré une tonne d’opérations dans la file d’opérations (qui fonctionnait de la même manière que la pile, tout comme la FIFO plutôt que la LIFO); une fois que vous commencez à utiliser ce type de structure ("il se passe une chose maintenant, et une fois terminé, il apparaît tout seul"), il commence à infecter tous les domaines du code. Même l'IA a commencé à utiliser quelque chose comme ça; l'IA était "désemparée" puis est devenue "méfiante" lorsque le joueur a émis des bruits mais n'a pas été vue, puis a été élevée à "active" lorsqu'elle a vu le joueur (et contrairement aux parties moins importantes de l'époque, vous ne pouviez pas vous cacher dans une boîte en carton et faites que l'ennemi vous oublie! Ce n'est pas que je sois amer ...).
GameState.h:
GameState.cpp:
la source