2009-03-18 18 views
7

Estoy escribiendo un juego, y quiero modelar sus diferentes estados (la analogía de Game Maker sería marcos, supongo) de una manera limpia, orientada a objetos. Previamente, lo he hecho de la siguiente manera:¿Cómo se modelan los estados de aplicación?

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_; 
}; 

Esto apenas era manejable para un proyecto más pequeño. Todas las variables que los estados estaban usando se volcaron en la clase Game, sin embargo, me gustaría mantener la orientación a objetos al máximo, solo exponiendo variables que están compartidas por más de un estado. También quiero poder inicializar un nuevo estado al cambiar a él en lugar de tener que hacerlo en el estado que acaba de finalizar (ya que podría tener múltiples resultados: APP_PLAYING puede pasar a APP_PAUSED, APP_GAMEOVER, APP_NEWLEVEL, etc.).

pensé en algo como esto (PRECAUCIÓN MATERIAL DE FUZZY!):

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_; 
}; 

El problema aquí es que los niveles de clase e instancia están siendo revuelto (estática vs virtual). Los estados deben heredar de AppState, pero, como me imagino, la mayoría de ellos serían clases con miembros totalmente estáticos o, al menos, no necesitaré más de una instancia de una clase (TitleState, LevelIntroState, PlayingState , GameOverState, EndSequenceState, EditorState ... - la pausa ya no sería un estado, en lugar de ser atendido en los estados donde tiene sentido).

¿Cómo se puede hacer de manera elegante y eficiente?

Respuesta

10

El siguiente artículo da una manera agradable, sencilla de gestionar los estados de juego:

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

Básicamente, usted mantiene una pila de estados del juego, y simplemente ejecuta el estado superior. Tiene razón en que muchos estados solo tendrían una instancia, pero esto no es realmente un problema. En realidad, sin embargo, muchos de los estados de los que está hablando podrían tener múltiples instancias. Ej .:

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

... y se puede empezar de nuevo con una nueva instancia de LevelIntroState, y así sucesivamente.

2

Aquí está mi solución:

  • Cada estado es como un pequeño juego, por lo que gestionar un conjunto de juegos en una pila.
  • Los eventos burbujean la pila hasta que alguien los detiene (para que los "juegos" más arriba ya no los vean). Esto me permite hacer zoom en el mapa mediante más/menos en un menú. OTOH, Esc detiene el burbujeo temprano ya que el primer menú abierto se lo traga.
  • Cada "juego" en la pila tiene los mismos métodos: handleUserEvent(), keyDown(), keyUp(), mousePressed(), mouseReleased(), mouseMotion(), update() (cálculos internos antes de la representación), dibujar() (representación), prepare() (optimice la representación almacenando en caché algo en una textura que está estampada en la superficie del objetivo en draw())

Para la representación, estoy usando capas con prioridades. Por lo tanto, cada juego se representará en un lienzo transparente y el renderizador de capas los renderizará en el orden correcto. De esta forma, cada juego puede actualizar su propia capa sin molestar a los demás.

1

Utilizo un administrador de Game State con una lista de GameStates, donde cada elemento de la lista es un "objeto GameState" que implementa IGameState y tiene dos métodos, .render() y.HandleInput()

Este GameStateManager se implementa como un producto único por lo que cualquier estado puede saltar a cualquier otro estado llamando

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

Y el bucle principal se ve algo como esto

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

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

} 

Esa manera de cree estados, solo cree una nueva clase que implemente IGameState y agréguela a GameStateManager.

(Nota: Esta es una manera muy útil para hacer mini-juegos dentro de su juego principal)

3

estoy usando algún tipo de factory pattern combinado con un state pattern en mi pronto-a-ser juego.

El código puede ser un poco complicado pero intentaré limpiarlo.

Esta es la clase de la que obtendrá todos los estados, como el menú, el juego o lo que sea.

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

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

Esta clase será su interfaz para manejar los diferentes estados. Puede agregar dinámicamente e id dinámicamente.

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

    void Init(); 
    void Shutdown(); 
    void SetNext(std::string next_state); 
    void Exit(); 

    bool Logic(); 
    void Render(); 
protected: 
    bool Change(); 

    std::string state_id; 
    std::string next_state; 

    GameState *current_state; 
    std::vector<std::string> state_ids; 

    StateFactory *state_factory; 

    bool is_init; 
}; 

Estoy usando un functor para manejar la creación de diferentes derivados de 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; 
}; 

Por último, una fábrica que almacenará y gestionará los diferentes estados.

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; 
}; 

En el archivo de definición: Aquí me hicieron dejar de lado un montón de cosas, pero es de esperar que obtendrá la idea.

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(); 
} 

Y aquí es cómo lo usa: inicializar y agregar los diferentes estados, lo estoy haciendo en el 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; 

Para la función de marco

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

Y para la función de representación

State.Render(); 

Esto puede no ser perfecto, pero funciona bien para mí. Para seguir avanzando en el diseño, querría agregar Singleton for State y quizás hacer de StateFactory una clase oculta dentro del estado.

Cuestiones relacionadas