VeTaL Posted July 27, 2011 Share Posted July 27, 2011 As i collected some basic information and even tried to cathegorize it in one place (via LeaFAQ), i decided to contribute something from my own experience. This time it should be an article (i dont know now, how long this post will be) about menu state machine. Additionaly, there would be some information about interfases and inheritance. I didnt wrote atricles for a long time. Last time it was in about 2006, about 3DGameStudio, in Russian language (some states dated 2006-2007 are mine ...Nostalgy... ). Healthy critic is wellcome (even about grammar mistakes), as this is my first article in English. The main idea was taken from Irrlicht forum, when i made a game prototype for Gameloft content. So, lets rock! 1. We have inerface - the abstract class, that can't be instanced. Our game states we will be inherited from this interface. // IGameStates.h interface IGameStates { public: string name; virtual void Activate()=0; virtual void Deactivate()=0; virtual int Update(float deltaTime)=0; virtual void Render(float deltaTime)=0; virtual ~IGameStates() {}; private: }; gameStateName; is optional, for debug reasons. Note that function is written like "Activate()=0", that means that functions must be defined in child class or you'll get an error. 1.1 Also, we should have enumeration of all our game states. Commented code will show some possible usage of this system. // IGameStates.h extern enum GameStatesEnum { //LogoState = 0, //SplashState, //LoadingState, MenuState = 0, GameState, ChatState }; [!!!]We will call states(integer) as MenuState, and states(classes) as StateMenu, so please, be carefull. 2. Next, we will make new class, which will describe game state. First one will be main menu, here we should define those interface functions // StateMenu.h class StateMenu : public IGameStates { public: StateMenu(void); virtual ~StateMenu(void); // IGameStates virtual void Activate(); virtual void Deactivate(); virtual int Update(float deltaTime); virtual void Render(float deltaTime); *** } 2.1. Activate() describes activation of certain state (of cource, you can add Initialize() and call this only first time to load all resourses). In my case it will be // StateMenu.cpp void StateMenu::Activate() { _isLoginChatPressed = false; _isLoginGamePressed = false; _isQuitPressed = false; **** } DeactivateState(), obviously, describes deactivation of that state. Theese both functions will be called each time when game state would be changed to MenuState. 2.2. UpdateState() is one of the most interesting parts. As you may notice, it returns int. [!!!] The main feature: in the main loop we will call this update function and we will check result. If state is the same (user didnt do anything), it should returns its own state. If not - it whould returns next state. I'll explain this little later, while describing main loop. // StateMenu.cpp int StateMenu::Update(float deltaTime) { SingletonOisManager.Update(); if (_isLoginChatPressed) { return ChatState; } if (_isLoginGamePressed) { return GameState; } if (_isQuitPressed) { SingletonGameManager.SetQuit(true); } return MenuState; } So, if player set one of this flags to "true", Update function will return ID of new state. RenderState() is just a simple render of menu of 3d world. //StateMenu.cpp void StateMenu::Render(float deltaTime) { SingletonGuiManager.Render(deltaTime); Flip(1); // 1 = 60, 0 = unlimited } 3. StateGame is the same, but it renders whole world, instead of Gui. StateChat is UI for testing network. 4. In the header of our GameManager class (class, which contains main loop) we should make a container for all states. // GameManager.h IGameStates* _activeState; vector <IGameStates*> _listOfStates; int _stateReturns; int _previousStateReturns; Note, that vector holds IGameStates*, the parent of StateGame, StateChat, StateMenu and so on. 5.1 In the body of our GameManager class, we should create and store all our states to vector // GameManager.cpp _listOfStates.push_back(new StateMenu()); _listOfStates.push_back(new StateGame()); _listOfStates.push_back(new StateChat()); Now, we should select, which state will be the first. [!!!] Note, this is perfect for development: if you wish to work on your menu, you just set first state to load from. If you want to work on your game, you set game state. If you want to show it to your girlfriend (including splashscreen), you set splashscreen first, the next will be menu, and so on . After that, we should activate selected state and run it for the first time. // GameManager.cpp _activeState = _listOfStates.at(0); _activeState->Activate(); _stateReturns = _activeState->Update(0); // if you'll set here 0, you'll get StateMenu, if 1 - ypu'll get StateGame and so on _previousStateReturns = _stateReturns; 5.2 Main loop Its simple. Really // GameManager.cpp // Main loop float CurrentTime = AppTime(); _deltaTime = (CurrentTime - _elapsedTime) * 0.001f; //1 _elapsedTime = CurrentTime; _stateReturns = _activeState->Update(_deltaTime); //2 _activeState->Render(_deltaTime); //3 if (_stateReturns != _previousStateReturns) { // 4 _previousStateReturns = _stateReturns; _activeState->Deactivate(); // 5 _activeState = _listOfStates.at(_stateReturns); // 6 _activeState->Activate(); // 7 } At first (1), we're counting deltatime. Next (2), we run UpdateState() and save results. Result can be the same state or new one. Then (3) we Render current state. Last step: we check (4) the result of UpdateState() with current state. If they are equal - its okay. If they are not equal, we should Deactivate current state(5), set new state (6) and activate it(7)! The loop is over, and it waits for new state changes. PS: if you'll ask me, how i set _isLoginChatPressed in StateMenu::UpdateState(), i'll answer that i'm also using event system, where each entity can be subscripted for a sertain event and be rised in time. This is large article, probably i'll make another one. Also, i'm using imgui, which is perfect for debugging, as its code-driven UI. void StateMenu::RenderGuiEvent( float gameLoopTime ) { imguiBeginScrollArea("Main Menu", 400, 250, 200, 200, &_debugScroll); _isLoginChatPressed = imguiButton("Login to chat",true); _isLoginGamePressed = imguiButton("Login to play",true); _isQuitPressed = imguiButton("Quit",true); imguiEndScrollArea(); } PPS: GameManager contains "Manager" on its end because its singleton. PPPS: "We will call states(integer) as MenuState, and states(classes) as StateMenu, so be carefull." This can disappoint for the first time, but believe me, its really simple. Quote Working on LeaFAQ Link to comment Share on other sites More sharing options...
Roland Posted July 28, 2011 Share Posted July 28, 2011 Nice article. State Machines are very effective but seldom used (at least of what I have seen). Nice approach. Quote Roland Strålberg Website: https://rstralberg.com Link to comment Share on other sites More sharing options...
TheoLogic Posted July 28, 2011 Share Posted July 28, 2011 Your are leaking memory, you must use virtual destructors!!! It is also very interesting to know the previous state on state activation. You may have a back button redirecting to a previous (not defined) state. You also miss some logic: Deactivate a menu: I want a nice tween, like a fade or a slide. You have already activated the second one, which also wants a nice animation. You don't want buttons and so to be enabled during that animation. You may want to render another state during the animation (even at a different position), ... This is a nice start point, but not completed yet. Maybe some remarks, I guess they are copy/paste errors: - Interface in C++? - Protected destructor of your base class? - new StateMenu() and so on - Extern enum? Enum is a custom type... - underscore prefix for function arguments? - You may want to loose that "State" in your methods. C++ is strongly typed, so you know you update a state, no need to say UpdateState and so on. Quote Follow me Link to comment Share on other sites More sharing options...
Rick Posted July 28, 2011 Share Posted July 28, 2011 So for my new project I'm using heavy .NET WinForms for a good number of screens and GUI. I fully enjoy using forms to handle flow. It's just so much easier and I feel like I don't have to worry about this kind of stuff. Of course with WinForms I don't get fancy transitions, but I might play around with WPF because that looks like it has really good potential to add all those fancy things. Also seems it can be skinned much easier so someone wouldn't even really know the difference that they are using windows controls vs a custom game gui, which is exciting! Combining that with klepto's control to embed LE inside these forms can really save a ton of time not messing around with this stuff. Maybe something to consider to save time and pain Quote Link to comment Share on other sites More sharing options...
VeTaL Posted July 28, 2011 Author Share Posted July 28, 2011 Big thanks to TheoLogic: i always knew that writing articles for others hepls myself a lot Your are leaking memory, you must use virtual destructors!!! Yep, fixed. States classes are created one time per launch, but anyway, mistake took a place. It is also very interesting to know the previous state on state activation. You may have a back button redirecting to a previous (not defined) state. Well, you can add one more state-rememberer to "_previousStateReturns = _stateReturns;" But generally, this is designed for MainMenu -> Options button -> OptionsMenu -> Save button -> MainMenu -> Start game button Deactivate a menu: I want a nice tween, like a fade or a slide. You have already activated the second one, which also wants a nice animation. You don't want buttons and so to be enabled during that animation. You may want to render another state during the animation (even at a different position), ... Generally, this system was designed exact for that. You just add new SplashState and FadeState. Or you can add private variable, which will be set to 0 while state Activation, and runs it from 0 to 100 in Update. And menu is unactive if this variable (consider fading) is less than 100. Maybe some remarks, I guess they are copy/paste errors: They are wellcomed - Interface in C++? Yep, why not? Extern enum? Enum is a custom type... It was designed to type "GameStatesEnum." and get the list of all states underscore prefix for function arguments? Typos: usually i use underscore prefix only for private fields. - You may want to loose that "State" in your methods. C++ is strongly typed, so you know you update a state, no need to say UpdateState and so on. Not sure i understand you right. In real project, i have functions *State - they are inherited from IGameStates *Event - they are inherited from IGameObject But that's another story Quote Working on LeaFAQ Link to comment Share on other sites More sharing options...
VeTaL Posted July 28, 2011 Author Share Posted July 28, 2011 2 Rick: you should also look at http://addonstudio.codeplex.com/ Quote Working on LeaFAQ Link to comment Share on other sites More sharing options...
Rick Posted July 28, 2011 Share Posted July 28, 2011 I've looked at that before. That's LUA based stuff and pretty specific to WoW, and isn't so much state related like what you are working on. From looking at what you have you are going from logo to menu, to game, etc and have to handle all that yourself. I'm just offering a different way by using .NET. Seems WPF has some form transitioning techniques as well. These are the things I'm looking into currently, so just thought I'd share with you that it could help you. I know it's 180 degrees from where you are currently but just a thought Quote Link to comment Share on other sites More sharing options...
VeTaL Posted July 28, 2011 Author Share Posted July 28, 2011 Anyway, thanks I'll look into WPF - i'm think about it for some month Quote Working on LeaFAQ Link to comment Share on other sites More sharing options...
TheoLogic Posted July 28, 2011 Share Posted July 28, 2011 Big thanks to TheoLogic: i always knew that writing articles for others hepls myself a lot Yep, fixed. States classes are created one time per launch, but anyway, mistake took a place. Well, you can add one more state-rememberer to "_previousStateReturns = _stateReturns;" But generally, this is designed for MainMenu -> Options button -> OptionsMenu -> Save button -> MainMenu -> Start game button Generally, this system was designed exact for that. You just add new SplashState and FadeState. Or you can add private variable, which will be set to 0 while state Activation, and runs it from 0 to 100 in Update. And menu is unactive if this variable (consider fading) is less than 100. They are wellcomed Yep, why not? It was designed to type "GameStatesEnum." and get the list of all states Typos: usually i use underscore prefix only for private fields. Not sure i understand you right. In real project, i have functions *State - they are inherited from IGameStates *Event - they are inherited from IGameObject But that's another story Regarding previous state: you can for example enter the options screen from the main menu and from the pause. It is handy that the options menu knows what state to go back to. Regarding tweens: you should add states to your MenuStates. State_In, State_Idle, State_Out for example. This can be by design. Interface keyword in C++ is not interface in C# . You should just use class. http://social.msdn.microsoft.com/Forums/en-US/vclanguage/thread/c8a50d60-c4e7-440b-8f27-0939da61d8a2/ I am talking about your abstract class methods like UpdateState, RenderState. In my opinion these can just be Update and Render. You know it's a state object. Quote Follow me Link to comment Share on other sites More sharing options...
VeTaL Posted July 28, 2011 Author Share Posted July 28, 2011 Regarding previous state: you can for example enter the options screen from the main menu and from the pause. It is handy that the options menu knows what state to go back to. That has a sence. Regarding tweens: you should add states to your MenuStates. State_In, State_Idle, State_Out for example. This can be by design. This also has a sence, but States are already designed to do this job Just alternative way. Interface keyword in C++ is not interface in C# . You should just use class. Yes i can, but i like "interface" more I am talking about your abstract class methods like UpdateState, RenderState. In my opinion these can just be Update and Render. You know it's a state object. Also agree. But, as i said, i have RenderState and sometimes i need RenderEvent (which is inherited from another interface). But i'll remove them from this example to be more clear Quote Working on LeaFAQ Link to comment Share on other sites More sharing options...
Laurens Posted July 28, 2011 Share Posted July 28, 2011 Good stuff Vetal. I don't think I can add more to what has already been said. Cheers Quote Link to comment Share on other sites More sharing options...
TheoLogic Posted July 29, 2011 Share Posted July 29, 2011 Yes i can, but i like "interface" more interface is a MS extension, and point to __interface which is an alias for struct. http://msdn.microsoft.com/en-us/library/50h7kwtb(VS.80).aspx It's not about liking it more, it's just not ISO C++ . Quote Follow me Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.