Latest article: Exporter des données depuis Excel

Game State Pattern

Un tutoriel sur le Game State pattern qui permet de sous-diviser son jeu en états indépendants (menu principal, gameplay, écran game over, etc)

Cet article a pour but d’expliquer et illustrer un design pattern très courant dans les jeux vidéo : le game state pattern.

Dans un jeu, on commence par le menu principal, il y a ensuite différents sous-menus, puis l’état du jeu, etc.

Si on est dans un RPG, on aura une phase de jeu où on se déplace sur la carte, une lors des combats, une quand on consulte l’inventaire, …

Plus globalement, tout jeu peut se décomposer en “états”.

Le game state pattern est un pattern très simple, qui consiste à empiler des états dans un gestionnaire d’états.

Comment se définit un état ? Un état a besoin d’être mis à jour, puis d’être affiché. On voudra probablement aussi l’initialiser puis libérer les ressources lors de sa destruction.

Voici un exemple d’une classe abstraite représentant un game state en C++ :

class GameState
{

    public :

        virtual void Initialize () = 0;

        virtual void Update ( float deltatime ) = 0;

        virtual void Draw () = 0;

        virtual void Release() = 0;

    protected :

        GameManager* GameMgr;

};

Initialize sera appelée lorsque l’état sera ajouté à la pile, Release quand il sera retiré. Dans ces méthodes on s’occupera généralement d’initialiser les objets, charger les ressources, etc.

Viennent ensuite Update et Draw, qui servent bien entendu à mettre à jour l’état puis à l’afficher.

Cela reprend les différentes étapes de la boucle infinie.

On pourra aussi garder une référence vers le gestionnaire de jeu, mais cette partie est à adapter à votre application.

Pourquoi les empiler plutôt que de ne garder qu’une référence vers l’état courant ? Imaginons que nous sommes en pleine phase de gameplay, nous voulons configurer les touches. Le joueur appuie sur Esc, on va alors rajouter sur la pile un état Menu qui deviendra l’état courant. Lorsqu’on ré-appuiera sur Esc, l’état Menu va être retiré de la pile, et l’état courant sera celui se situant en dessous : l’état qui correspond à notre phase de jeu. Ainsi on se retrouve au même niveau qu’avant d’avoir ouvert le menu, et on n’a pas besoin de recharger les ressources.

Dans notre gestionnaire d’états (qui peut être directement le game manager), on aura :

public :

    /// Get the current state
    GameState* GetCurrentState() { return ( !States.empty() ) ? States.top() : NULL; }

    /// Add a new state ( become the current one )
    void PushState( GameState& state );

    /// Set the current state
    void SetState( GameState& state );

    /// Delete the current state
    void PopState();

private :

    // State manager for game states
    std::stack<GameState*> States;

Et l’implémentation :

// ===========================================================================================
/// Add a new state (become the current one)
// ===========================================================================================
void GameManager::PushState( GameState& state )
{
    state.SetGameManager ( (*this) );

    // set current state
    States.push( &state );
    States.top()->Initialize();
}

// ===========================================================================================
/// Set the current state
// ===========================================================================================
void GameManager::SetState( GameState& state )
{
    state.SetGameManager ( (*this) );

    // Delete the actual current state (if any)
    PopState ();

    // Add the new state
    PushState( state );
}

// ===========================================================================================
/// Delete the current state
// ===========================================================================================
void GameManager::PopState()
{
    if ( !States.empty() )
    {
        States.top()->Release();
        States.pop();
    }
}

Ensuite dans notre boucle principale on testera si il y a un état courant, et on appellera ses méthodes Update et Draw.

Voilà un bon moyen de séparer les différents états d’un jeu, le principe est universel, mais je vous présente une implémentation parmi qui ne demande qu’a être améliorée et adaptée suivant vos besoins.

Mentions

Discussion

    Reply