Gestion de l'état du jeu (jeu, menu, écran de titre, etc.)

11

Fondamentalement, dans chaque jeu que j'ai fait jusqu'à présent, j'ai toujours une variable comme "current_state", qui peut être "jeu", "titres d'écran", "gameoverscreen", etc.

Et puis sur ma fonction de mise à jour, j'ai un énorme:

if current_state == "game" 
  game stuf
  ...
else if current_state == "titlescreen"
  ...

Cependant, je ne pense pas que ce soit une manière professionnelle / propre de gérer les états. Avez-vous des idées sur la meilleure façon de procéder? Ou est-ce la voie standard?

David Gomes
la source
Quel langage, framework, etc. utilisez-vous?
Petr Abdulin
Habituellement, Lua + LOVE. Je viens également de découvrir que différents cadres ont différentes manières de gérer cela. SFML semble avoir une très bonne classe Screen.
David Gomes
1
Avez-vous étudié les machines à états?
Darcara
1
Vous pouvez également rechercher des gamestates dans la barre de recherche en haut à droite. Devrait donner des résultats.
TravisG
Doit seconder Darcara - cela semble exactement à quoi servent les machines d'État.
balajeerc

Réponses:

14

Puisque vous parlez d'écrans, je pense qu'il est préférable de séparer toute cette logique en différents écrans. Ce que je fais normalement:

Définissez une interface appelée écran et implémentez plusieurs écrans. Comme LoadingScreen, MainMenuScreen, GameScreen, GameOverScreen, HighScoreScreen etc. Dans votre jeu, vous mettez une variable qui contient l'écran actuel. Chaque boucle, vous appelez screen.update () et restituez l'écran actuel. Cela vous fera économiser beaucoup de "si cet état le fait" car votre état est défini par l'écran actuel.

Cela séparera très bien votre logique.

Exemple de code:

### Screen interface ###
public interface Screen {

    public void show();

    public void update(float delta);

    public void render(float delta);

    public void hide ();
}

### An implementation of screen ###
public class MainMenuScreen implements Screen {

    private Game game;

    public MainMenuScreen(Game game) {
        this.game = game;
    }

    public void show() {
        // init stuff
    }

    public void update(float delta) {
        // react to clicks, update animations etc.
        if (buttonwasclicked) {
            game.setScreen(new GameScreen(game)); // change the screen
        }
    }

    public void render(float delta) {
        // draw everything
    }

    public void hide() {
        // release all resources, as the screen is being hidden
    }
}

### Game, drawing the appropriate screen ###
public class Game {

    public Screen screen;

    public void update() {
        screen.update(getDeltaTime);
        screen.render();
    }

    public void setScreen(Screen screen) {
        this.screen.hide();

        this.screen = screen;
        this.screen.show();
    }
}

Ou selon la configuration de votre jeu, vous avez peut-être une boucle infinie comme jeu.

while(true) {
    calculatetimesincelastframe()
    screen.update(time);
    screen.render(time);
}
Matsemann
la source
5

Si vous utilisez déjà Middleclass, il existe une excellente bibliothèque de machines d'état pour l'accompagner appelée Statefull . Il est facile à utiliser et présente les mêmes idées que Matsemann a proposées.

WuTangTan
la source
2

Si votre current_statevariable est une chaîne, alors c'est vraiment facile dans Lua:

game_states = {}
function game_states.game()
    -- game stuff
end
function game_states.titlescreen()
    -- title screen stuff
end

-- then, inside the Update function:
game_states[current_state]()
John Calsbeek
la source
1

Ce que je fais est à peu près comme suit:

J'ai une structure de données de graphique acyclique dirigée , qui est essentiellement juste un tas de nœuds qui pointent les uns vers les autres. Chaque nœud représente un système de jeu. par exemple l'interface utilisateur, le monde, l'entrée, le rendu. Et chaque nœud pointe vers d'autres nœuds qui le précèdent ou le suivent. Une fois que tous les nœuds sont en place, il est facile de les aplatir en une simple liste. La configuration de ce DAG est la première chose que je fais pendant le démarrage du jeu. Chaque fois que je veux ajouter un nouveau système, disons l'IA, je peux simplement dire écrire ce code puis dire à mon jeu de quoi il dépend et ce qui devrait en dépendre.

Ma boucle de jeu principale vient après cela et exécute simplement chaque système dans l'ordre. La première entrée est gérée, puis les mises à jour du monde, puis d'autres choses ... L'interface utilisateur est proche de la fin, et le rendu est le dernier. Lorsque le jeu démarre pour la première fois, il n'y a pas de monde, de physique ou d'IA, donc ces étapes sont essentiellement ignorées et seul l'écran de titre s'affiche. Lorsque vous démarrez le jeu proprement dit, l'interface utilisateur envoie un message au système mondial pour qu'il s'allume, et il prend juste soin de lui-même. Gérer l'état du jeu signifie simplement activer et désactiver les différents systèmes. Chaque système possède son propre ensemble d'informations d'état qui est traité plus ou moins indépendamment de tous les autres (ce n'est pas totalementEn réalité, de nombreux systèmes agissent sur le même ensemble de données - le système d'interface utilisateur, par exemple, récupère des données du monde pour afficher des informations par exemple. Le système d'IA doit également examiner et envoyer des messages à des entités dans le monde).

Alex Ames
la source
Cette réponse est une bonne réponse à une autre question.
Matsemann
Comment? Il a demandé comment configurer ses différents états de jeu, et ma solution n'est pas d'utiliser une machine d'état comme il l'a fait maintenant, mais de diviser les bits en différents systèmes qui ne sont pas une machine d'état mais plutôt un DAG.
Alex Ames
1

Voici comment j'organise mes états dans Lua + Love2d. Cela évite les longues instructions if / then.

Tout d'abord, je crée une classe de base qui contient les méthodes update (dt) et render (). Vous pouvez également lui donner des méthodes de gestion d'événements, comme onKeyDown (clé). J'appelle cette classe Stage, mais tout objet qui implémente les méthodes fonctionnera. Ensuite, je crée une instance de cette classe pour chaque état de jeu, en implémentant les méthodes nécessaires. Je crée ensuite une table clé / valeur avec le nom de l'état et l'instance de l'état. Ensuite, gardez une trace de currentState au niveau global afin que les états puissent le changer lorsqu'une certaine condition est remplie.

states = {}
states["title"] = title   -- Where title implements Stage class.
states["game"] = game     -- You could create the instance of 'game' lazily too.
currentState = "title"

function love.update(dt)
    if states[currentState] ~= nil then
       states[currentState]:update(dt) 
    end
end
h4tch
la source
-1

Eh bien, bien que ce ne soit pas joli, c'est OK pour gérer les états de cette façon, OMI. Vous pouvez le rendre beaucoup plus propre en utilisant des fonctions pour chaque état, comme:

if current_state == "game" 
  game()
else if current_state == "titlescreen"
  titlescreen()

ou quelque chose d'autre vous dérange dans cette approche (je veux dire, sauf que la méthode de mise à jour est très longue)?

Petr Abdulin
la source