State Manager

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
AlexL
Posts: 184
Joined: Tue Mar 02, 2004 6:06 pm
Location: Washington State

State Manager

Post by AlexL »

Okey, so I recently moved into trying to create a game, or multiple state application; instead of just a single tech scene. This is my first attempt at writing something to handle more then one scene, so instead of this being an error I'm having and looking for help, I am more looking for insight on a correct way to do this.

I spent the last couple of minutes writing this out, it is supposed to be a way to add state pointers into a manager or access later on in the application; it provides a way to register, grab and set a new current state. If someone with some experience in this field can offer some insight on do's and don'ts, can point out what is right or wrong, or even point out a site, tutorial or book that covers this subject in depth; I would very much appreciate it :) Source is below is someone wants to take the time to look it over.

Code: Select all

// Header file.
#ifndef __CSTATEMANAGER_H__
#define __CSTATEMANAGER_H__

#include "ICommon.h"
#include "IState.h"

struct SState
{
	irr::core::stringc strName;
	IState* pState;
};

class CStateManager
{
protected:
	irr::core::array<SState> hList;
	IState* pState;

public:
	void clear();
	bool push(IState* pState,irr::core::stringc strName);
	IState* grab(irr::core::stringc strName);
	void setCurrentState(irr::core::stringc strName);

public:
	IState* getCurrentState() { return pState; };

public:
	CStateManager();
	~CStateManager();
};

#endif // __CSTATEMANAGER_H__

Code: Select all

// Source file.
#include "CStateManager.h"

//--------------------------------------------------------------------------------
/// Method: CStateManager
/// Class: CStateManager
/// Summary: Class constructor.
//--------------------------------------------------------------------------------
CStateManager::CStateManager()
{
	// Set protected member defaults.
	pState = NULL;
}

//--------------------------------------------------------------------------------
/// Method: ~CStateManager
/// Class: CStateManager
/// Summary: Class destructor.
//--------------------------------------------------------------------------------
CStateManager::~CStateManager()
{
	// If items still in list, clear them.
	if(hList.size() > 0)
	{
		// Local variable.
		irr::u32 i;

		// For each state in list, release.
		for(i = 0; i < hList.size(); i++)
		{
			hList[i].pState->release();
		}

		// Clear the list.
		hList.clear();
	}
}

//--------------------------------------------------------------------------------
/// Method: clear
/// Class: CStateManager
/// Summary: Clear and release of all states in the manager.
//--------------------------------------------------------------------------------
void CStateManager::clear()
{
	// Local variable.
	irr::u32 i;

	// For each state in list, release.
	for(i = 0; i < hList.size(); i++)
	{
		hList[i].pState->release();
	}

	// Clear the list.
	hList.clear();
}

//--------------------------------------------------------------------------------
/// Method: push
/// Class: CStateManager
/// Summary: Register a new state with the manager.
//--------------------------------------------------------------------------------
bool CStateManager::push(IState *pState, irr::core::stringc strName)
{
	// Create temporary state structure.
	SState pTemp;

	// Check if state data is null.
	if(pState == NULL)
		return false;

	// Insert data into structure.
	pTemp.pState = pState;
	pTemp.strName = strName;

	// Place temporary state structure into list.
	hList.push_back(pTemp);

	return true;
}

//--------------------------------------------------------------------------------
/// Method: grab
/// Class: CStateManager
/// Summary: Returns the requested state.
//--------------------------------------------------------------------------------
IState* CStateManager::grab(irr::core::stringc strName)
{
	// Local variables.
	irr::u32 i = 0;
	bool bFound = false;

	// For each state in the list, check if names match.
	while(bFound == false && i < hList.size())
	{
		if(hList[i].strName.equals_ignore_case(strName))
			bFound = true;
		else
			i++;
	}
	
	// If state was found, return pointer.
	if(bFound == true)
		return hList[i].pState;
	else
		return 0;
}

//--------------------------------------------------------------------------------
/// Method: setCurrentState
/// Class: CStateManager
/// Summary: Sets a new current state.
//--------------------------------------------------------------------------------
void CStateManager::setCurrentState(irr::core::stringc strName)
{
		// Local variables.
	irr::u32 i = 0;
	bool bFound = false;

	// For each state in the list, check if names match.
	while(bFound == false && i < hList.size())
	{
		if(hList[i].strName.equals_ignore_case(strName))
			bFound = true;
		else
			i++;
	}

	// If exists, release current state.
	if(pState != NULL)
		pState->release();
	
	// If state was found, set as current and initialize.
	if(bFound == true)
	{
		pState = hList[i].pState;
		pState->initialize();
	}
}
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

What you've got above looks very similar to what I've been toying with. My state machine uses templates, for reusability, and I have a global state and a current state. When a state is made active, the previously active state is notified that it is becoming inactive. The global state and the currently active state are both given a chance to update every time through the event loop.

Arggg.. I just went to find the source for my little project I was working on over xmas break and I can't find it!!! I had a great little example that had a settings dialog for choosing window modes and resolutions as well as a paused and playing state. If I find that, I'll post it here.

Anyways, the code I was using used templates a bit. The base game class was a template, and it kept a pointer to an 'owner'. Whenever a state was updated, activated or changed, it would get a pointer to the owner. The owner had all of the application information like the Irrlicht device. This way I could extend the state and graph without having to update the base class code at all.

Travis
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Found it! Never again will I trust windows search to find anything...

Here is the state base class. All states that are used with the state machine will inherit from this state, and the template parameters for the state and the state graph [machine] will be the same.

Here is the code for the state graph. The first template paramater is the type of the 'owner' class, which is usually the class that derives from the graph. The second parameter is the type of event structure that the graph passes to the states. I won't be surprised if you're confused about the whole 'owner' thing right off the bat. It is there to allow the derived state types access to the derived game instance without any ugly casting. It's not really complicated, just a little weird.

Here is some sample code. I'm pretty sure it compiles on Windows/VC8.

I'm not saying that this code is the right way to do it. This is just what I've come up with given the requirements that I had at the time I wrote it. If you find it useful, then Merry Christmas. If you have questions, criticisms or comments, I'll be happy to hear them.

Travis
miko93
Posts: 54
Joined: Sat Nov 12, 2005 5:24 pm
Location: Regensburg, Germany
Contact:

Post by miko93 »

Fiddling with a gamestate manager myself, I found your code very interesting, AlexL. Instead of using a string for the state's name, I did choose an enum, though. Like

Code: Select all

// The game states
typedef enum E_GAMESTATE
{
	GS_STARTUP, GS_NONE, GS_IDLE, GS_LOCALGAME

} GAMESTATE;
This makes the whole thing less universal, I think. But I tried to avoid searching/comparing for strings in a list (thinking of time impact...)

Just my 2 cents...
"Yessir, I'll be happy to make these unnecessary changes to this irrelevant document." (Dilbert)
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Yes, it might seem wasteful to use strings to identify your states, but think about it. Most of your state names are going to be pretty short [less than 16 chars I would expect], and I wouldn't expect to change game states all that often [once every second or two max]. The code I provided uses strings and does a binary search. Even the binary search doesn't even seem necessary as a game probably doesn't have more than 10 states.

Of course if you are using the state machine for something else other than 'game state', then these things may become more important. For instance if you were using a state machine for AI, I'd probably use integers to identify the states. The graph/state system I outlined makes it pretty easy to use any type of identifier for a state 'name' for those special cases.

Travis
AlexL
Posts: 184
Joined: Tue Mar 02, 2004 6:06 pm
Location: Washington State

Post by AlexL »

Vitek,
I have finally gotten the time to actually sit down and take a good look at your state manager, instead of the one or two glance overs that I have had in the past few days. It really amazes me at how different our code is from each others and yet, how very similar they are. At first I had a hard time understanding the usefulness of a global state, but after some more thought on it, it came clear to me.

Templates as a whole are something that are fairly vague for me, so I am wondering if there is another reason behind you using them then just, seemingly, another way of class inheritance? As seen in my state manager, it uses a class called IState, which looks like the following.

Code: Select all

// Header file.
#ifndef __ISTATE_H__
#define __ISTATE_H__

#include "ICommon.h"

class IState
{
public:
	virtual bool initialize() { return false; };
	virtual void release() {};
	virtual void update() {};
	virtual bool processEvents(irr::SEvent hEvent) { return false; };

public:
	virtual ~IState() {};
};

#endif // __ISTATE_H__
I am just trying to figure this all out, the benefits and falls of using one way or another; as I have said, I am fairly new to programming in a multi-scene / manager way. Thanks in advanced :)
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

I chose to use templates for two reasons. The first reason was reusability. I planned on using the state machine framework to manage game state, but I also wanted to be able to use it for AI, and maybe some other things. I didn't want to write several different implementations of the same concept just because the motivation or usage was slightly different. With templates, I could easily change the message type that the states use. This allows me to use the state machine with my entity management system.

The second reason I decided to use templates was so that I could avoid dynamic_casts in the derived state and game classes. By using a template parameter to indicate the type of the 'owner', the states know at compile time what the actual type of the state machine derived class is. And since the type is known at compile time, I can call functions on the derived state machine without any goofy casting.

You can see evidence of this in the sample code that I posted. The following line is an example of where I avoid a dynamic_cast.

Code: Select all

void
MyPausedState::onEnter(MyGame* game)
{
  irr::video::IVideoDriver* driver =
    game->getVideoDriver();

  // <snip>

  irr::gui::IGUIEnvironment* gui =
    game->getUserInterface();

  // <snip>
}
The base class Graph class does not have a getVideoDriver() or getUserInterface() method, but the derived MyGame class does. If the base State class dealt with the base Graph class directly, I wouldn't be able to call those methods without doing some sort of cast or other magic to get the derived class pointer.

I realize that the templates make the code a bit harder to understand. Fortunately you don't have to deal with them much except in the derived class declarations and constructors. I also realize that I could have solved the second problem a few different ways. I chose to use templates because they seemed like the cleanest solution.

As for the global state, you don't absolutely need to have one. The code I posted _should_ work fine if you don't provide a global state, or you set the global state to NULL. I put it there because there are usually some common things that you always need to do. Having the global state gives me a place to do those things independently of the active game state.

Travis
dlangdev
Posts: 1324
Joined: Tue Aug 07, 2007 7:28 pm
Location: Beaverton OR
Contact:

Post by dlangdev »

Vitek,

Just wondering what's the license of the game state code you posted?

I'm planning on setting up a simple multi-user lobby in the next few weeks and will use several code snippets as well.

http://home.comcast.net/~travis_vitek/source/test.cpp

Thanks.
Image
d3jake
Posts: 198
Joined: Sat Mar 22, 2008 7:49 pm
Location: United States of America

Post by d3jake »

I guess I had missed this thread when it was being posted in, but it's interesting how these state machines differ in theory from mine... Certainly is interesting.
The Open Descent Foundation is always looking for programmers! http://www.odf-online.org
"I'll find out if what I deleted was vital here shortly..." -d3jake
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

dlangdev wrote:Just wondering what's the license of the game state code you posted?
There is no license. I posted it without any mention of license, and the sources contain no restrictions. If you decide to use it in a project, it would be cool if you'd send me a pm or e-mail telling me about it, but I don't want to require anyone to do so.

Travis
lymantok
Posts: 67
Joined: Mon Dec 31, 2007 6:13 am

Post by lymantok »

Hi Vitek,

Just came across you state code. Looks like it was done for an earlier version of Irrlicht.

I changed code in MyGlobalState, MyOptionsState, MyPausedState, and MyRunningState to the following so it would compile with Irrlicht 1.6:

virtual bool onEvent(MyGame* entity, const irr::SEvent& event);

However, I'm not getting any keyboard events to work.

I'm not familiar with templates. Do all the files (StateT, GraphT, and main) and all the methods in all files need to change to const irr::SEvent& event to work properly?

Thx!
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

If you just changed the type of the second parameter to onEvent(), that is the problem. The virtual onEvent() function expects the second parameter type that matches the second template parameter type. The only thing that should need to change would be the OnEvent() in class MyGame to take a const irr::SEvent&. If you really wanted to, you could change all references to irr::SEvent to const irr:SEvent&, but it isn't necessary.

If it doesn't work, I'm sorry. You'll have to spend a few minutes in the debugger figuring out where the events are getting dropped. Set breakpoints in the OnEvent() or onEvent() functions and work from there.

Travis
lymantok
Posts: 67
Joined: Mon Dec 31, 2007 6:13 am

Post by lymantok »

Thx Vitek,

That was it. I appreciate the response!
lymantok
Posts: 67
Joined: Mon Dec 31, 2007 6:13 am

Post by lymantok »

Hi Vitek,

I ended up using your state machine code in my project and I like it now that I've spent some time integrating it. Many thanks for posting it!

I made a few trivial adjustments as I wanted to generate a splash screen using an init state and I use textured quads for my target reticle for my HUD and CEGUI for my GUI.

I did a trivial extending of StateT.h with:

virtual void onRender(T* entity)
{
}

I added the following to the MyGlobalState::onUpdate(MyGame* game) method:

game->getCurrentState()->onRender(game);

In my init state, onRender does the following for splash screen:

driver->draw2DImage(m_pSplashScreenTexture, SplashScreenCenterPosition, SplashScreenRect, NULL, SColor(m_Alpha, 255, 255, 255), true);

Not sure if this is the optimal way to do it for splash/HUD/CEGUI, but it seemed reasonable and worked for me.

Just curious if there are other recommended ways to achieve splash screen/HUD with your state machine framework?

Thx!
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

If your target reticle (or splash screen) was part of the scene graph (or gui environment), you wouldn't need this workaround.

This is essentially how the 'paused' state in my example program works. It creates a big button on screen when entering the paused state, and removes it when leaving the paused state. Your splash screen would be the same, except that it wouldn't be an IGUIButton, it would be an IGUIImage, and the state would override onUpdate() to transition to the next state after some amount of time had passed.

You could do the same thing with the target reticle. You just need to create a scene node or gui element that knows where to render an image on screen.

Travis
Post Reply