问题描述
我正在写游戏,我想以一种干净,面向对象的方式对其不同状态进行建模(游戏制造商类比是框架).以前,我以下方式完成了此操作:
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_; };
对于一个较小的项目来说,这几乎无法管理.各州使用的所有变量均已倾倒在游戏类中,但是我想将面向对象的态度保持在最大值,只揭示了多个状态共享的变量.我还希望在切换到它时能够初始化一个新状态,而不必在刚刚完成的状态下进行(因为它可能具有多个结果 - app_playing可以切换到app_pause,app_gameover,app_newlevel等)./p>
我想到了这样的事情(请注意!模糊的东西!):
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,latecintrostate,Playstate) ,GameOverState,EndSequencestate,EditorState ...-暂停将不再是一种状态,而不是在有意义的州受到照顾).
如何优雅,有效地进行?
推荐答案
以下文章提供了一种管理游戏状态的好方法:
http://gamedevgeek.com/tutorials/managing-game-game-states -in-c/
基本上,您要保持一堆游戏状态,并且只能运行最高状态.您是对的,许多州只有一个实例,但这并不是一个问题.但是,实际上,您所谈论的许多州都可能有多个实例.例如:
push TitleState push MenuState push LevelIntroState change_to PlayingState change_to GameOverState pop (back to MenuState)
...您可以从LevelIntroState的新实例开始,等等.
其他推荐答案
我正在使用某种工厂模式合并与a 状态模式在我的即将到来的游戏中.
代码可能有点混乱,但我会尝试清理它.
这是您将从菜单,游戏或其他任何状态中得出所有状态的类.
class GameState { public: virtual ~GameState() { } virtual void Logic() = 0; virtual void Render() = 0; };
此类将是您处理不同状态的界面.您可以动态添加和ID.
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; };
我正在使用函子来处理不同的游戏衍生物的创建.
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();
这可能不是完美的,但对我来说很好. 为了进一步推进设计,您想为状态添加Singleton,并将状态因素作为状态内的隐藏类.
其他推荐答案
这是我的解决方案:
- 每个状态都像一个小型游戏,所以我在堆栈上管理一组游戏.
- 事件将堆叠起来直到有人阻止它们为止(因此,"游戏"进一步不再看到它们).这使我可以在菜单中通过Plus/Minus缩小地图. OTOH,ESC自第一个打开菜单吞下它以来就停止起泡.
- 堆栈上的每个"游戏"都具有相同的方法:handleuserevent(),keydown(),keyup(),mousepressed(),mouseRealease(),mousemotion(),update(),update()(呈现之前的计算),绘制绘制,绘制()(渲染),准备()()(通过在纹理中缓存的东西来优化渲染,该纹理仅在draw())()) 中刻在目标表面上
对于渲染,我正在使用具有优先级的层.因此,每个游戏都会在透明的画布上渲染,并且层渲染器将以正确的顺序渲染.这样,每个游戏都可以更新自己的图层而不会打扰其他所有人在做什么.
问题描述
I'm writing a game, and I want to model its different states (the Game Maker analogy would be frames, I guess) in a clean, object-oriented way. Previously, I've done it in the following way:
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_; };
This was hardly manageble for a smaller project. All the variables that the states were using were dumped in the Game class, however I'd want to keep object-orientedness to a maximum, only exposing variables that are shared by more than one state. I also want to be able to initialize a new state when switching to it rather than having to do it in the state that's just finishing (as it might have multiple outcomes - APP_PLAYING can switch to APP_PAUSED, APP_GAMEOVER, APP_NEWLEVEL, etc.).
I thought of something like this (CAUTION! 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_; };
The problem here is that the class and instance levels are getting jumbled up (static vs virtual). The states need to inherit from AppState, but - how I'd imagine - most of them would be classes with all-static members, or, at least I won't need more than one instance from one class (TitleState, LevelIntroState, PlayingState, GameOverState, EndSequenceState, EditorState... - pausing would no longer be a state, rather than being taken care of in the states where it makes sense).
How can it be done elegantly and efficiently?
推荐答案
The following article gives a nice, simple way to manage game states:
http://gamedevgeek.com/tutorials/managing-game-states-in-c/
Basically, you maintain a stack of game states, and just run the top state. You're right that many states would only have one instance, but this isn't really a problem. Actually, though, many of the states you're talking about could have multiple instances. E.g.:
push TitleState push MenuState push LevelIntroState change_to PlayingState change_to GameOverState pop (back to MenuState)
... and you can start over with a new instance of LevelIntroState, and so on.
其他推荐答案
I'm using some type of factory pattern combined with a state pattern in my soon-to-be game.
The code might be a bit messy but I'll try to clean it up.
This is the class you'll derive all states from, like the menu, the game or whatever.
class GameState { public: virtual ~GameState() { } virtual void Logic() = 0; virtual void Render() = 0; };
This class will be your interface for handling the different states. You can dynamically add and id dynamically.
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; };
I'm using a functor to handle the creation of different GameState derivatives.
class BasicStateFunctor { public: virtual GameState *operator ()() = 0; }; template<class T> class StateFunctor : public BasicStateFunctor { public: StateFunctor() { } GameState *operator ()() { return new T; } typedef T type; };
Lastly a factory which will store and manage the different states.
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; };
In your definition file: Here I did leave out a lot of stuff, but hopefully you'll get the 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(); }
And here's how you use it: Initialize and add the different states, I'm doing it in the 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;
For the frame function
State.Logic(); //Here I'm returning true when I want to quit
And for the rendering function
State.Render();
This may not be perfect but it works fine for me. To further advance the design you'd want to add Singleton for State and maybe make the StateFactory as a hidden class inside State.
其他推荐答案
Here is my solution:
- Every state is like a small game, so I manage a set of games on a stack.
- Events bubble the stack up until someone stops them (so "games" further up don't see them anymore). This allows me to zoom the map via plus/minus while in a menu. OTOH, Esc stops the bubbling early since the first open menu swallows it.
- Each "game" on the stack has the same methods: handleUserEvent(), keyDown(), keyUp(), mousePressed(), mouseReleased(), mouseMotion(), update() (internal calculations before rendering), draw() (rendering), prepare() (optimize rendering by caching something in a texture that's just stamped on the target surface in draw())
For rendering, I'm using layers with priorities. So each game will render on a transparent canvas and the layer renderer will render them in the correct order. This way, each game can update its own layer without bothering what everyone else is doing.