2009-03-18 2 views
7

J'écris un jeu, et je veux modéliser ses différents états (l'analogie Game Maker serait des cadres, je suppose) d'une manière propre, orientée objet. Auparavant, je l'ai fait de la façon suivante:Comment modélisez-vous les états d'application?

class Game 
{ 
    enum AppStates 
    { 
    APP_STARTING, 
    APP_TITLE, 
    APP_NEWGAME, 
    APP_NEWLEVEL, 
    APP_PLAYING, 
    APP_PAUSED, 
    APP_ENDED 
    }; 

    typedef AppState(Game::*StateFn)(); 
    typedef std::vector<StateFn> StateFnArray; 

    void Run() 
    { 
    // StateFn's to be registered here 

    AppState lastState(APP_STARTING); 
    while(lastState != APP_ENDED) 
    { 
     lastState = GetCycle_(lastState); 
    } 
    // cleanup 
    } 

protected: 
    // define StateFn's here 

    AppState GetCycle_(AppState a) 
    { 
    // pick StateFn based on passed variable, call it and return its result. 
    } 

    StateFnArray states_; 
}; 

Ce fut à peine manageble pour un petit projet. Toutes les variables que les états utilisaient ont été déversées dans la classe Game, mais je voudrais que l'orientation des objets soit maximale, exposant uniquement les variables partagées par plusieurs états. Je veux également être en mesure d'initialiser un nouvel état lors de la commutation plutôt que d'avoir à le faire dans l'état qui vient de finir (car il pourrait avoir plusieurs résultats - APP_PLAYING peut passer à APP_PAUSED, APP_GAMEOVER, APP_NEWLEVEL, etc.).

Je pensais que quelque chose comme ça (ATTENTION FUZZY STUFF!):

struct AppState 
{ 
    enum { LAST_STATE = -1; } 
    typedef int StateID; 
    typedef std::vector<AppState*> StateArray; 

    static bool Add(AppState *state, StateID desiredID); 
    // return false if desiredID is an id already assigned to 

    static void Execute(StateID state) 
    { 
    while(id != LAST_STATE) 
    { 
     // bounds check etc. 
     states_[id]->Execute(); 
    } 
    } 

    AppState() {}; 
    virtual ~AppState() {}; 

    virtual StateID Execute() =0; // return the ID for the next state to be executed 

protected: 
    static StageArray stages_; 
}; 

Le problème ici est que les niveaux de classe et d'instance sont pêle-mêle se (statique vs virtuel). Les états doivent hériter de AppState, mais - comme j'imagine - la plupart d'entre eux seraient des classes avec des membres tout statiques, ou, au moins, je n'aurai pas besoin de plus d'une instance d'une classe (TitleState, LevelIntroState, PlayingState , GameOverState, EndSequenceState, EditorState ... - la pause ne serait plus un état, plutôt que d'être prise en charge dans les états où cela a du sens).

Comment cela peut-il être fait avec élégance et efficacité?

Répondre

10

L'article suivant donne une belle façon simple de gérer les états de jeu:

http://gamedevgeek.com/tutorials/managing-game-states-in-c/

Fondamentalement, vous maintenez une pile d'états de jeu et exécutez simplement l'état haut. Vous avez raison, de nombreux États n'auraient qu'un seul exemple, mais ce n'est pas vraiment un problème. En fait, cependant, plusieurs des états dont vous parlez pourraient avoir plusieurs instances. .: par exemple

push TitleState 
push MenuState 
push LevelIntroState 
change_to PlayingState 
change_to GameOverState 
pop (back to MenuState) 

... et vous pouvez recommencer avec une nouvelle instance de LevelIntroState, et ainsi de suite.

2

Voici ma solution:

  • Chaque Etat est comme un petit jeu, donc je gère un ensemble de jeux sur une pile.
  • Les événements font bouger la pile jusqu'à ce que quelqu'un les arrête (donc les "jeux" plus haut ne les voient plus). Cela me permet de zoomer sur la carte via plus/moins dans un menu. OTOH, Esc arrête les bulles tôt depuis que le premier menu ouvert l'avale. Chaque "jeu" sur la pile a les mêmes méthodes: handleUserEvent(), keyDown(), keyUp(), mousePressed(), mouseReleased(), mouseMotion(), update() (calculs internes avant le rendu), dessiner() (rendu), préparer() (optimiser le rendu par la mise en cache quelque chose dans une texture qui vient estampillé sur la surface cible tirage())

Pour le rendu, j'utilise des couches avec les priorités. Chaque jeu sera donc rendu sur un canevas transparent et le rendu de calque les rendra dans le bon ordre. De cette façon, chaque jeu peut mettre à jour sa propre couche sans déranger ce que tout le monde fait.

1

J'utilise un gestionnaire d'état de jeu avec une liste de GameStates, où chaque élément de la liste est un "objet GameState" qui implémente IGameState et a deux méthodes .render() et.HandleInput()

Ce GameStateManager est mis en œuvre en tant que singleton de sorte que tout Etat peut sauter à tout autre Etat en appelant

GameStateManager.gi().setState("main menu") 

Et la boucle principale ressemble à quelque chose comme ça

while(isRunning) 
{ 
    GameStateManager.gi().getCurrentState().handleKeyboard(keysobject); 
    GameStateManager.gi().getCurrentState().handleMouse(mouseobject); 

    GameStateManager.gi().getCurrentState().render(screenobject); 

} 

De cette façon, à créez des états, créez simplement une nouvelle classe qui implémente IGameState et ajoutez-la au GameStateManager.

(Note: Ceci est un moyen très pratique pour faire des mini-jeux dans votre jeu principal)

3

J'utilise un certain type de factory pattern combiné avec un state pattern dans mon bientôt à être jeu.

Le code peut être un peu brouillon mais je vais essayer de le nettoyer.

C'est la classe dont vous dérivez tous les états, comme le menu, le jeu ou autre.

class GameState { 
public: 
    virtual ~GameState() { } 

    virtual void Logic() = 0; 
    virtual void Render() = 0; 
}; 

Cette classe sera votre interface pour gérer les différents états. Vous pouvez ajouter dynamiquement et id dynamiquement. J'utilise un foncteur pour gérer la création de différents dérivés GameState.

class BasicStateFunctor { 
public: 
    virtual GameState *operator()() = 0; 
}; 

template<class T> 
class StateFunctor : public BasicStateFunctor { 
public: 
    StateFunctor() { } 
    GameState *operator()() { 
     return new T; 
    } 
    typedef T type; 
}; 

Enfin une usine qui va stocker et gérer les différents états.

class StateFactory { 
public: 
    StateFactory(); 
    virtual ~StateFactory(); 

    bool CheckState(std::string id); 
    GameState *GetState(std::string id); 
    template<class T> void AddState(std::string id); 
private: 
    typedef std::map<std::string, BasicStateFunctor*>::iterator StateIt; 
    std::map<std::string, BasicStateFunctor*> state_map; 
}; 

Dans votre fichier de définition: ici, je ne laisser de côté beaucoup de choses, mais nous espérons que vous aurez l'idée.

bool StateFactory::CheckState(std::string id) 
{ 
    StateIt it = state_map.find(id); 
    if(it != state_map.end()) 
     return true; 
    else 
     return false; 
} 

GameState *StateFactory::GetState(std::string id) 
{ 
    StateIt it = state_map.find(id); 
    if(it != state_map.end()) 
    { 
     return (*(*it).second)(); 
    } else { 
     //handle error here 
} 

template<class T> void StateFactory::AddState(std::string id) 
{ 
    StateFunctor<T> *f = new StateFunctor<T>(); 
    state_map.insert(state_map.end(), std::make_pair(id, f)); 
} 

void State::Init() 
{ 
    state_factory = new StateFactory(); 
    state_factory->AddState<Game>("game"); 
    current_state = state_factory->GetState("game"); 
    is_init = true; 
} 

void State::SetNext(std::string new_state) 
{ 
    //if the user doesn't want to exit 
    if(next_state != "exit") { 
     next_state = new_state; 
    } 
} 

bool State::Change() 
{ 
    //if the state needs to be changed 
    if(next_state != "" && next_state != "exit") 
    { 

     //if we're not about to exit(destructor will call delete on current_state), 
     //call destructor if it's a valid new state 
     if(next_state != "exit" && state_factory->CheckState(next_state)) 
     { 
      delete current_state; 

      current_state = state_factory->GetState(next_state); 

     } 
     else if(next_state == "exit") 
     { 
       return true; 
     } 

     state_id = next_state; 

     //set NULL so state doesn't have to be changed 
     next_state = ""; 
    } 
    return false; 
} 

bool State::Logic() 
{ 
    current_state->Logic(); 
    return Change(); 
} 

Et voici comment vous l'utilisez: et Initialiser ajouter les différents états, je le fais dans le Init().

State.Init(); 

//remember, here's the Init() code: 
state_factory = new StateFactory(); 
state_factory->AddState<Game>("game"); 
current_state = state_factory->GetState("game"); 
is_init = true; 

Pour la fonction de cadre

State.Logic(); //Here I'm returning true when I want to quit 

Et pour la fonction de rendu

State.Render(); 

Cela peut ne pas être parfait, mais il fonctionne très bien pour moi. Pour faire avancer la conception, vous voudrez ajouter Singleton for State et peut-être faire de StateFactory une classe cachée dans l'état.

Questions connexes