7

我正在编写一个游戏,我想以一种干净的、面向对象的方式对其不同的状态进行建模(我猜 Game Maker 类比是帧)。以前,我是通过以下方式完成的:

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

对于一个较小的项目来说,这很难管理。状态使用的所有变量都转储在 Game 类中,但是我希望最大限度地保持面向对象性,只公开由多个状态共享的变量。我还希望能够在切换到新状态时初始化它,而不是在刚刚完成的状态下进行(因为它可能有多个结果 - APP_PLAYING 可以切换到 APP_PAUSED、APP_GAMEOVER、APP_NEWLEVEL 等)。

我想到了这样的事情(注意!模糊的东西!):

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

这里的问题是类和实例级别变得混乱(静态与虚拟)。状态需要从 AppState 继承,但是 - 我想如何 - 它们中的大多数将是具有全静态成员的类,或者,至少我不需要来自一个类的多个实例(TitleState、LevelIntroState、PlayingState , GameOverState, EndSequenceState, EditorState... - 暂停将不再是一种状态,而不是在有意义的状态中得到照顾)。

如何优雅高效地完成它?

4

4 回答 4

10

下面的文章提供了一种很好、简单的方法来管理游戏状态:

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

基本上,您维护一堆游戏状态,然后只运行顶部状态。你是对的,许多州只有一个实例,但这并不是一个真正的问题。但是,实际上,您所谈论的许多州可能有多个实例。例如:

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

...您可以从 的新实例重新开始LevelIntroState,依此类推。

于 2009-03-18T08:04:47.293 回答
3

我在即将推出的游戏中使用了某种类型的工厂模式状态模式。

代码可能有点乱,但我会尝试清理它。

这是您将从中派生所有状态的类,例如菜单、游戏或其他。

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

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

这个类将是你处理不同状态的接口。您可以动态添加和动态标识。

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

我正在使用函子来处理不同 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;
};

最后是一个工厂,它将存储和管理不同的状态。

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

在您的定义文件中:在这里我确实遗漏了很多东西,但希望您能明白这一点。

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

下面是你如何使用它:初始化并添加不同的状态,我在 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;

对于框架函数

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

而对于渲染功能

State.Render();

这可能并不完美,但对我来说效果很好。为了进一步推进设计,您需要为 State 添加 Singleton,并可能将 StateFactory 作为 State 中的隐藏类。

于 2009-03-18T22:04:05.983 回答
2

这是我的解决方案:

  • 每个状态都像一个小游戏,所以我在堆栈上管理一组游戏。
  • 事件会冒泡堆栈,直到有人阻止它们(因此“游戏”再往上看就看不到它们了)。这允许我在菜单中通过加/减缩放地图。OTOH,由于第一个打开的菜单将其吞下,因此 Esc 提前停止了冒泡。
  • 堆栈上的每个“游戏”都有相同的方法:handleUserEvent()、keyDown()、keyUp()、mousePressed()、mouseReleased()、mouseMotion()、update()(渲染前的内部计算)、draw()(渲染),prepare()(通过缓存纹理中的某些内容来优化渲染,该纹理刚刚在draw()中标记在目标表面上)

对于渲染,我使用具有优先级的图层。所以每个游戏都会在透明画布上渲染,图层渲染器会以正确的顺序渲染它们。这样,每个游戏都可以更新自己的层,而不会打扰其他人正在做的事情。

于 2009-03-18T09:23:26.140 回答
1

我使用带有 GameStates 列表的游戏状态管理器,其中列表中的每个项目都是实现 IGameState 并具有两个方法 .render() 和 .HandleInput() 的“GameState 对象”

此 GameStateManager 实现为单例,因此任何状态都可以通过调用跳转到任何其他状态

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

主循环看起来像这样

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

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

}

那种创建状态的方式,只需要新建一个实现IGameState的类,并将其添加到GameStateManager中即可。

(注意:这是在主游戏中制作小游戏的一种非常方便的方法)

于 2009-03-18T21:49:40.090 回答