Pages: [1]   Bas de page
Imprimer
Auteur Fil de discussion: Gestion intelligente des états d'un jeu (Menu, Crédits ...)  (Lu 667 fois)
0 Membres et 1 Invité sur ce fil de discussion.
valeuf Hors ligne
Full Member
***
Messages: 143


Voir le profil WWW
Vive le dev Washable

« 09 Janvier 2010, 17:35:40 »

S'il y a bien quelque chose de très chiant dans un jeu vidéo, c'est de programmer tout ce qui n'est pas vraiment le jeu en lui même : les menus, les crédits etc... De plus, il est possible que ces menus changent assez régulièrement, (ajouts de nouveaux mode de jeu, ajout des High-Score ...).

Il existe différentes solutions pour gérer efficacement cette partie de votre programme, et ne pas perdre trop de temps. L'une des plus élégantes et agréables que j'ai vu jusqu'ici est l'utilisation d'un Patron de Conception (design pattern) dit patron d'état (state pattern). Pas besoin spécialement de savoir ce qu'est un design pattern, il vous suffit juste d'avoir quelques notions en Programmation Orientée Objet pour comprendre ce qui va suivre.

Le "State Pattern" et le four

Ce patron peut s'appliquer à de nombreux cas de figure (qui ne se réduisent pas au jeu vidéo). Je pensais me lancer dans une grande explication théorique, mais essayons de voir plutôt un exemple intitulé : "La patate et le four". Cela sera l'occasion de donner des rudiments de cuisine aux personnes qui mangent trop de plats au micro-onde !

Soit un four, des patates en gratin dauphinois, un poulet rôtie et une tarte au pomme. Votre four est un super four multi-fonction, capable de faire tourne broche, mode gratin, cuisson normal. Bien sur, il est aussi possible de régler la température.

Quel que soit le plat à l'intérieur de votre four, choisir le mode du four, ainsi que régler la température changera l'état de ce qui se trouve à l'intérieur (de pas cuit à cramé), sans que votre four n'est besoin de savoir ce qui se trouve à l'intérieur. En modèle objet cela ressemble un peu à cela :



Appliqué à un programme, ce patron de conception permet donc de changer l'état d'un objet (de pas cuit à cramé) pendant l'exécution (cuisson) du programme. Cela grâce à l'utilisation d'abord de commandes unifiées (comme les boutons d'un four par exemple).

Le "State Pattern" et le jeu vidéo

Si vous avez compris l'idée du four (ce n'est pas normal ... heuh tant mieux). Voyons comment appliquer cela à un jeu vidéo. La question que personne ne se pose c'est : qu'est-ce qui peut bien faire office d'état dans un jeu vidéo. Et bien tout simplement l'état du jeu ! Ou disons plutôt, l'écran en cours. Pour un jeu simple on pourrait avoir les états de jeu suivant :

  • Menu
  • High-Score
  • Crédits
  • Le jeu en lui même

Alors les plus intelligents (les autres peuvent sortir) se demandent sans doute comment on peut avoir des "commandes unifiées" pour tout ces états. Souvenez vous du four, ce n'est pas bien différent. Votre console de jeu ne dispose pas de beaucoup plus de bouton (voir moins) que votre four.

Chaque état sera donc capable de recevoir un évènement (du type "le joueur à appuyé sur tel bouton" et de réagir, ou non, à cet événement).

Cependant, ce n'est pas suffisant, il est souvent bien pratique de rajouter quelques autres "commandes". Entre autre une commande de mise à jour (update) de votre état, pour périodiquement faire évoluer ce dernier. Une méthode d'affichage de l'état peut-être intéressant (sauf si vous avez choisit un modèle MVC).

Au final, vous allez donc avoir une classe "GameState", qui pourra gérer n'importe quel état du jeu. Cette classe servira de courroie de transmission entre votre jeu et le monde extérieure.

Pour aller plus loin

Ce modèle est particulièrement efficace dans le jeu sur le quel j'ai travaillé récemment. En effet, une seule classe "communique" avec le monde extérieure. Ce qui permet (avec d'autres techniques) d'avoir facilement un jeu multi-plateforme (iPhone, Android et PC pour le moment). Alors que c'est trois plateformes utilisent des technologies très différentes. (iPhone : Objective-C, Android : Java et PC : C++).

Si le langage de la cible peut communiquer avec une classe C++ (ce qui est le cas d'Objective-C de façon native, et de JAVA avec JNI), alors il suffit d'écrire un peu de "glue" pour actionner les bonnes commandes de notre classe GameState.

Réalisation

Voici pour finir un squelette d'implémentation (après une petite cure de simplification), sortie de Puzzle Path.

Code: (cpp)
// Code du fichier GameState.h
#ifndef _GAME_STATE_H
#define _GAME_STATE_H

#include "GameApp.h"

class GameState
{
public:
GameState(GameApp *app) { mGameApp = app; }
virtual ~GameState() {}

virtual void Update(float dt) = 0;
virtual void Render() = 0;
virtual bool Create() = 0;
virtual void Destroy() = 0;
virtual void MouseDown(int x, int y) = 0;
virtual void MouseUp(int x, int y) = 0;
virtual void MouseMove(int x, int y,int x2,int y2) = 0;

virtual void Enter() {}
virtual void Exit() {}

protected:
GameApp* mGameApp;
};

#endif

Cette classe GameState est ce qu'on peut appeler interface, pour rajouter un état à notre jeu, il suffit de dériver cette classe en implémentant toutes ces méthodes (impérativement). Pour expliquer rapidement les différentes "commandes de notre four", voici quelques commentaires :

  • Update : Fonction appelée à chaque "frame" pour mettre à jour l'état.
  • Render : Fonction appelée à chaque "frame" pour faire le rendu de l'état (dessin à l'écran).
  • Create : Fonction appelée lors de la création de l'état (permet de faire toutes les allocations mémoires essentiellement).
  • Destroy : Fonction appelée lors de la destruction de l'état (lorsque l'on quitte le jeu ici), pour effacer proprement la mémoire.
  • MouseDown et MouseUp : Fonction appelée respectivement lorsque le joueur appuie (ou relâche) le bouton de sa souris (ou son doigt sur l'écran).
  • MouseMove : Fonction appelée lors d'un mouvement de souris.
  • Enter et exit : Fonction appelée respectivement lorsque l'on rentre et que l'on sort de l'état.

GameApp ensuite est un objet utilisé pour gérer les changements d'états, se souvenir de l'état en cours, et de servir d'interface avec le monde extérieur. Voici une nouvelle fois un extrait qui a subit une cure de minceur :

Code: (cpp)
//Les sources du fichier GameApp.cpp
void GameApp::Create()
{
mGameStates[STATE_LOGO] = new GameStateLogo(this);
mGameStates[STATE_LOGO]->Create();

mGameStates[STATE_HOME] = new GameStateHome(this);
mGameStates[STATE_HOME]->Create();

mGameStates[STATE_ABOUT] = new GameStateAbout(this);
mGameStates[STATE_ABOUT]->Create();

mGameStates[STATE_HI_SCORE] = new GameStateHiScore(this);
mGameStates[STATE_HI_SCORE]->Create();

mGameStates[STATE_MODE_SELECT] = new GameStateModeSelect(this);
mGameStates[STATE_MODE_SELECT]->Create();

mGameStates[STATE_LEVEL_SELECT] = new GameStateLevelSelect(this);
mGameStates[STATE_LEVEL_SELECT]->Create();

mGameStates[STATE_PATHFINDER_INGAME] = new GameStatePathFinderGame(this);
mGameStates[STATE_PATHFINDER_INGAME]->Create();

}

void GameApp::Destroy()
{
for (int i=0;i<MAX_STATE;i++)
{
if (mGameStates[i])
{
mGameStates[i]->Destroy();
SAFE_DELETE (mGameStates[i]);
}
}
}

void GameApp::Start()
{
mCurrentState = mGameStates[STATE_LOGO];
        mCurr = STATE_LOGO;
}

void GameApp::ChangeState(int state)
{
if (mCurr == state)
return;

if (mCurrentState)
mCurrentState->Exit();

mCurrentState = mGameStates[state];
mCurrentState->Enter();
}

void GameApp::Update(float dt)
{
        mCurrentState->Update(dt);
}

void GameApp::Render()
{
if (mCurrentState)
mCurrentState->Render();
}

Code: (cpp)
//Les sources de GameApp.h
#ifndef _GAMEAPP_H_
#define _GAMEAPP_H_

class GameState;

class GameApp:
{
public:
GameApp();
~GameApp();
void Create();
void Destroy();
void Update(float dt);
void Render();

void Start();

void ChangeState(int state);

public:

enum
{
STATE_LOGO,
STATE_HOME,
STATE_HI_SCORE,
STATE_ABOUT,
STATE_MODE_SELECT,
STATE_LEVEL_SELECT,
STATE_PATHFINDER_INGAME,
STATE_POPUP,
MAX_STATE
};


GameState* GetGameState(int nGameState) { return mGameStates[nGameState];}

private:
GameState *mGameStates[MAX_STATE];
GameState *mCurrentState;

int mCurr;

};

#endif

Je vous passe le code relatif à la gestion de la souris (ou de l'écran tactile), il est un peu spécial, et il m'aurait fallut complètement le ré-écrire. Je pense que le code ci-dessus devrait déjà aider à la compréhension. Il faut bien voir que dans notre vrai les fonctions ne sont malheureusement pas aussi vide que cela. (particulièrement pour l'initialisation de GameApp qui fait bien d'autres choses que la gestion des états, ou la fonction de rendu qui gère pas mal d'instruction Open GL ES pour s'assurer que le rendu de l'état sera correct).

On peut voir que l'on sauvegarde assez facilement l'état en cours d'exécution grâce à un tableau et une énumération. C'est une méthode parmi d'autres. Elle à le mérite d'être simple à lire et à comprendre dans le code.

Pour conclure, car il le faut bien, ce patron plutôt simple permet d'apporter un peu de souplesse à vos jeu : il devient plus facile de changer rapidement les états. Vous pouvez même dans certaines conditions ré-utiliser du code entre plusieurs jeu (l'état du logo, ou celui des crédits risque d'être très semblable entre chacun de vos jeux).

Bref, cela risque de rajouter un peu de code la première fois que vous allez l'utiliser, mais vous gagnerez beaucoup par la suite (surtout pour porter le jeu sur une autre plateforme, ou créer un autre jeu).


* LaPatateEtLeFour.png (9.5 Ko, 490x270 - vu 54 fois.)
« Dernière édition: 09 Janvier 2010, 17:39:27 par valeuf » Journalisée

Codons washable !
valeuf Hors ligne
Full Member
***
Messages: 143


Voir le profil WWW
Vive le dev Washable

« Réponse #1 : 09 Janvier 2010, 17:47:18 »

Bon j'ai encore un peu du mal avec l'édition de gros post sur le forum  Angry Mais j'ai vaincu la terrible balise image  whistle

Je finis ça un peu tard (1h30 du matin par ici ...) donc je pense que certaines partis doivent être incompréhensible, truffée de fautes ... J'essaierais de repasser par ici avec les idées claires pour finir les corrections (actuellement, je voulais prévisualiser ... mais je me suis loupé avec un soumettre ...).

En attendant vous avez le droit de vous livrer dans la pure veine communiste façon : "critiquons notre camarade !". Je me promet de me livrer à mon auto-critique par la suite  et je me dirigerais tout seul au goulag Azn

J'espère que cela vous plairas pour un premier petit tuto !
Journalisée

Codons washable !
CaptainSid Hors ligne
Newbie
*
Messages: 21


Voir le profil
« Réponse #2 : 10 Janvier 2010, 00:40:27 »

Bravo, super article, clair et net !
Journalisée
msx64 Hors ligne
Newbie
*
Messages: 6


Voir le profil
« Réponse #3 : 03 Février 2010, 13:40:29 »

Très bien ton article Smiley
Journalisée

Pages: [1]   Haut de page
Imprimer

Aller à: