Jump to content

[Tutorial] Menu state machine


VeTaL
 Share

Recommended Posts

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.

Working on LeaFAQ :)

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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 :)

Link to comment
Share on other sites

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 :)

Working on LeaFAQ :)

Link to comment
Share on other sites

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 :)

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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 :)

Working on LeaFAQ :)

Link to comment
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...