The Blog

General Game/Engine Structure

Overview

The subject of how one should organize a game has been appearing a lot lately.  Mainly these questions are focused on how to organize all the different parts of the game, such as the menu, the gameplay area, the bonus rounds, the story screens, etc.

The most common proposal I see are switch cases for each state, with an ID assigned to each state since nothing else would really work for a switch case.  It would look something like this:

void UpdateGame() {
    switch (curGameState) {
        case MAINMENU : {
            MainMenu::Update();
        }
        case OPTIONSMENU : {
            OptionsMenu::Update();
        }
        case GAMEPLAY : {
            GamePlay::Update();
        }
        …
    }
}

This is obviously one of the worst possible ways to accomplish this goal, and defeats the purpose of using C++ or any other object-oriented language.

There are many ways to go about this, and this article is intended to provide one which is a much healthier alternative.

 

Goals

Our goal is a clean way to switch between parts of the game to which we refer as “states” from here forth.  Examples of states would be the main menu, options menu, title screen, story screen, credits screen, gameplay screen, bonus rounds, etc.  Any part of the game that can cleanly stand on its own without needing to know about any other section of the game.  In some implementations, changing from the main menu to the options menu won’t cause a fade-out, but simply one set of buttons to move off the screen and another to move on.  In this case the entire menu system would be its own state and each individual menu would still be managed modularly, just not via the state machine.

Some have asked me why we need to break the game into states at all.  The benefits are fairly clear.  Breaking the game into these types of modular components helps with development in that it allows each team member to work inside his or her little black box, reducing the chances of stepping on someone else’s code.

One way in which it does that is to enforce the concept of modularity, which is not only an intuitive and comfortable way to think about each part of the game (as proved by how many people attempt to break their games into states via switch cases—it is just how we humans want to think about the game), but it also gives developers a clear idea where to put the data needed for the functionality they are creating, which will be as regular members of the classes they are making (CMainMenu, for example) rather than as globals tossed randomly about.

It also allows the core engine to enforce (or rather simply expect) some policies that allow better memory management.  If a state has correctly released all memory that it allocates, the memory manager can assume that the RAM before and after the state has run is exactly the same.  Any extra allocations can be reported as leaks, which provides a very helpful early-warning system and isolates the leak to specifically one person’s code, and even more specifically to one part of the game.

Additionally, if the game does release all of its RAM properly for each state, fragmentation is avoided.  Breaking the game into its individual states simply provides a lot of opportunities for organization and debugging, and all with no drawbacks—if you think breaking the game up into states this way is silly, just use one state for your entire game.

 

How?

When new people ask the best way to manage their game states, they aren’t usually concerned with debugging features and being isolated from teammates.  They just want an intuitive way to manage the game states, but just don’t know enough C# or C++ (or what-have-you) to know how to go about it.

My method takes of advantage of polymorphism.  Since this is mainly for beginners I won’t be using such big words.  Polymorphism, in the most beginner-friendly description that applies here (pedantic Nazis be warned), is basically the act of overriding virtual functions on a base class.

Obviously, to do that, we would need to begin with a base class that has a few virtual functions on it that we can override.

// For cross-platforming.
typedef LSVOID void;
typedef LSUINT32 unsigned int;
typedef LSBOOL int; // For alignment.  Could be "bool" for most people.
#define LSENEW new

 

	class CState {
		// Also, the state manager needs to have access to our private parts.
		friend class			CStateMachine;

	public :
		// == Various constructors.
						CState();
		virtual 			~CState();

		// == Functions.
		virtual LSVOID 			Init();

		virtual LSVOID 			Destroy();

		virtual LSBOOL 			Tick();

		virtual LSBOOL 			Draw();

	protected :
		// == Members.
		/**
		 * Local time which keeps track of time since the start of this state.
		 */
		CTime				m_tLocalTime;
		/**
		 * This state ID.
		 */
		LSUINT32			m_ui32StateId;
	};

I felt posting code would be the most helpful since it is extremely obvious what each of the 4 functions do.  Init() is called when the state is created and Destroy() when it is destroyed.  Allocate and delete resources here.  Between these calls, Tick() and Draw() are called repeatedly as the state is active.

I took the liberty of posting a state ID and a local time counter, because again everything’s purpose is fairly intuitive.  The local time is just the time that the state has been running.

What is not intuitive is why we are using Init() and Destroy() instead of constructors and destructors.  I did declare constructors and destructors for completeness, but they are empty.  Why?  Because we need access to a bunch of managers and things to help us load resources.

Beginners are tempted to make those kinds of things into singletons, raw globals, or etc.  This is extremely bad practice in general and limits your game to one instance.  It might be hard to imagine why having more than one instance of a graphics class would ever be desirable since you only have one game running, right?  Just wait until you start making tools with Qt and you realize that each OpenGL View has its own OpenGL context, and the resources you made when one context was activate are no longer applicable in the new context.  It can also help with debugging and it is just conceptually more sound.

There are many topics available online for why singletons are evil, and if you are curious you can look into it on your own.

All that being said, I would rather pass what the state class needs than to have it trying to go looking all over the place and picking things without thought.  But of course I don’t want to pass tons of pointers.  There should be a cleaner way to get that information to the states.

It just so happens there is not only a need for various managers to be passed down, but also game-specific data that is understood only by the game itself (not the engine), and we can kill 2 birds with 1 stone.

		// == Functions.
		virtual LSVOID 			Init( CGame * _pgGame );

		virtual LSVOID 			Destroy( CGame * _pgGame );

		virtual LSBOOL 			Tick( CGame * _pgGame );

		virtual LSBOOL 			Draw( CGame * _pgGame );

The CGame class has the job of masterminding the operation of the game.  It is only natural that that would be the place to put pointers to the keyboard input, touch input, sound manager, etc.

CGame is a class provided by the engine.  Inheriting from CGame class when you make your own game is a way to add new functions and features to the CGame class, all of which will be passed down to all of your states for each of the 4 times they would ever need it.  This means you can keep a list of high scores on your own “CMyGame” class and all parts of the game could access it if they have reason.

class CMyGame : public CGame
class CHighScoreScreenState : public CState
LSVOID CHighScoreScreenState::Init( CGame * _pgGame ) {
    _pgGame->GetSoundMan()->ClearAllSounds();  // Last state should have done this but this is just an example.
    CMyGame * pmgMyGame = static_cast(_pgGame);
    // And now Anything you added to your CMyGame class can be accessed by your state.
}

 

Generating States

The engine provides a base CState class and CGame class, from which both can be inherited.  Since the states you make have meaning only to you and not the engine, you will need to also make a way to associate state ID values with the custom state classes you have created.

I use the factory pattern.  This involves a switch case with enumerated values, but that is nothing new to anyone who has considered the initial switch-case example I presented.

/**
 * Class CStateFactory
 * \brief Generate unique states.
 *
 * Description: Generate unique states.
 */
class CMyStateFactory : public CStateFactory {
    // All is public.  This class has no secrets.
public :
    // == Functions.
    /**
     * Create a state by ID.
     *
     * \param _ui32Id The unique ID of the state to create.
     * \return Returns the created state or NULL if none could be created.
     */
    virtual CState * 		                GetState( LSUINT32 _ui32Id ) const;
};
/**
 * Create a state by ID.
 *
 * \param _ui32Id The unique ID of the state to create.
 * \return Returns the created state or NULL if none could be created.
 */
CState * CMyStateFactory::GetState( LSUINT32 _ui32Id ) const {
    switch ( _ui32Id ) {
        case LST_S_SOUND : {
            return LSENEW CSoundTest();
        }
        case LST_S_BENCHMARK : {
            return LSENEW CBenchMark();
        }
        case LST_S_MODELTEST : {
            return LSENEW CModelTest();
        }
        case LST_S_TERRAINTEST : {
            return LSENEW CTerrainTest();
        }
        case LST_S_SPRITETEST : {
            return LSENEW CSpriteTest();
        }
    }
    return CStateFactory::GetState( _ui32Id );
}

The engine provides a base class called CStateFactory, and CMyStateFactory inherits from it. The CGame class holds a CStateFactory * member.  All we need to do is make it point to a CMyStateFactory object instead.  This can be done anywhere during start-up once you have made an instance of CMyGame (or even CGame if you don’t want to add any features to the CGame class).  In my case, I create the object on the stack inside Main().

INT APIENTRY wWinMain( HINSTANCE /*_hInstance*/, HINSTANCE /*_hPrevInstance*/, LPWSTR /*_lpwsCmdLine*/, INT /*_iCmdShow*/ ) {
    // Do engine initalization.
    …

    CMyGame mgGame;
    mgGame.SetStateFactory( LSENEW CMyStateFactory() );

    …

 

The Runtime Logic

If you are only trying to make a basic game, it is enough to have only one active state at a time.  We have a factory that can turn an ID value from an enumeration into a CState *, and those pointers are to classes that are designed specifically for your game.  You could handle the logic for initializing, destroying, and ticking the current state from the CGame class, but it is more organized to just make a CStateMachine class that performs this job, and add it as a member of CGame.  But the CStateMachine member does not have access to the state factory.  You could organize it so that it does, but I preferred to keep my state machine unaware and uncaring of just how the states come into existence.  All it knows is that it is being told to make a state its current state and it handles the rest.

    /**
     * Set the next state.
     *
     * \param _ui32Id ID of the state to set.
     * \return Returns false if a memory error occurred while trying to create the new state.
     */
    LSBOOL CGame::SetState( LSUINT32 _ui32Id ) {
        CState * psNewState = m_psfStateFactory->GetState( _ui32Id );
        return m_smStateMachine.SetState( psNewState, this, _ui32Id );
    }
    /**
     * Set a new state.  Removes the current state to make room for the new one.
     *
     * \param _psState The new state to make active.
     * \param _pgGame The game class.
     * \param _ui32State The state ID.
     * \return Returns true if the state was set.
     */
    LSBOOL CStateMachine::SetState( CState * _psState, CGame * _pgGame, LSINT32 _ui32State ) {
        if ( !RemoveCurState( _pgGame ) ) { return false; }
        m_psCurState = _psState;
        if ( !_psState ) { return true; }    // NULL states end here.
        m_psCurState->m_ui32StateId = _ui32State;
        m_psCurState->Init( _pgGame );

        return true;
    }
    /**
     * Destroys the current state.
     *
     * \param _pgGame Game object to pass to the states before destroying them.
     * \return Returns true in this simplified version.  The real function could return false.
     */
    LSBOOL CStateMachine::RemoveCurState( CGame * _pgGame ) {
        if ( !m_psCurState ) { return true; }
        m_psCurState->Destroy( _pgGame );
        LSEDELETE m_psCurState;
        m_psCurState = NULL;
        return true;
    }

 

From this point forward it is just a matter of calling Tick() and Draw() on whatever the current state is.  For my engine I have good reason for that to be delegated to the CStateMachine class instead of having the CGame class get the current state and do it itself, but if I stripped away the fat to post an example here, it would be nothing more than the CGame class calling “m_smStateMachine.Tick( this )” and then having the state machine simply pass that pointer on to the current state via “m_psCurState->Tick( _pgGame )”, which would appear redundant.

For a simple game you are free to do it either way, but the idea is simple: Keep calling Tick() and Draw() on the current state for as long as the game is running.  Thanks to the magic of virtual functions, what actually happens and what appears on the screen will be totally different for each state of the game, and states are modular enough to safely jump from any state to any other state simply by telling the _pgGame pointer to set the next state with the given ID.  The catch is that the CGame class must store this information until the beginning of the next frame rather than switch right away.  That would cause the current state to be deleted while it is still inside its Tick() function.

It is trivial to pass information from one state to another, so I leave that as an exercise for the reader.  But rest assured, states can communicate with each other quite easily.

 

Conclusion

Breaking the game into states is a very good way to organize your project intuitively and offers potentials in debugging as well.  But it can become an unmaintanable mess if done improperly and result in more headache than happiness.  Exploiting the features of an object-oriented language can yield a very compact and comprehensive solution to the alternative of endless switch cases, and helps clearly define where the engine ends and the game begins.

 

 

L. Spiro

About L. Spiro

L. Spiro is veteran of the gaming industry and currently makes video games in Tokyo, Japan, as an R&D programmer for tri-Ace (http://research.tri-ace.com/). L. Spiro has worked on Ghost Recon 2 Online, 187 Ride or Die, Catz 5, Dogz 5, Imagine Happy Cooking, Ready Steady Cook, Leisure Suit Larry Beach Volley, HOT PIXEL, After Dark: Flying Toaster and more, for Ubisoft, Atari, Lucas Arts, Eidos Entertainment, Vivendi Universal, Konami, and more.

30 Awesome Comments So Far

Don't be a stranger, join the discussion by leaving your own comment
  1. Bryce
    March 4, 2012 at 4:09 PM #

    Thanks, this has been really helpful!

  2. Burnt_Fyr
    March 6, 2012 at 3:14 AM #

    L Spiro, you should provide a small example of passing info from state to state, as your idea of trivial may not be the same as your readers. Your experience in the industry trivializes those tasks that may seem monumental to those without it.

    • L. Spiro
      March 6, 2012 at 11:18 AM #

      Okay, I will add some more details soon when I have time. It will be in a new topic so that this one can remain short and to-the-point. I expect I can get it written before the weekend.

      L. Spiro

  3. Burnt_Fyr
    March 6, 2012 at 10:25 AM #

    Hey! You should make a short example of passing data from one state to another. What might seem trivial to someone with your experience may not be so for someone who doesn’t have said experience :)

    • L. Spiro
      March 6, 2012 at 11:19 AM #

      Sure, I will have something up in a new topic before or on the weekend.

      L. Spiro

  4. Brian
    March 7, 2012 at 1:50 PM #

    Preface:(am a programming noob), why the LSVOID instead of void?

    • L. Spiro
      March 7, 2012 at 4:54 PM #

      As you may know, for cross-platform compatibility, types such as int, short, etc., are often typedef’ed to ensure consistent sizes across compilers.
      void obviously is not prone to this problem, so you basically have your choice as to whether or not you want to do it. The engine at my office does not.
      I prefer to typedef it for the sake of consistency. If 90% of the types I use are going to begin with LS, be capitol, and not be blue in the editor, the remaining 10% seems out of place and tacky.

      So basically it is up to each person.

      L. Spiro

      • Brian
        March 8, 2012 at 12:09 PM #

        thank you very much for the explanation! :)

  5. Codeslasher
    March 7, 2012 at 8:10 PM #

    Great article,
    I just have a question –
    How is the CGame class linked to the Engine?
    -Does it contain an Engine instance and then drives the engine in its loop?
    OR
    - Does the Engine own the CGame class and calls the right functions during the engine’s loop?

    Basically am asking how this design would be used with an engine?

    Thanks

    • L. Spiro
      March 7, 2012 at 8:39 PM #

      The engine is simply the collection of all the other parts—including an instance of CGame—coming together. The point where those meet could be your CEngine class.
      Its duty is to initialize and destroy all of the parts of the engine that it brings together. This will typically be a static class, not instanced, because no matter how many games you want to run, you still only have one memory manager (if you plan to overload operator new then you have no choice but to make a static memory manager instance).
      Here is how my wWinMain() looks in full:

      INT APIENTRY wWinMain( HINSTANCE /*_hInstance*/, HINSTANCE /*_hPrevInstance*/, LPWSTR /*_lpwsCmdLine*/, INT /*_iCmdShow*/ ) {
      	// Initialize the memory manager.
      	lse::CEngine::LSE_ENGINE_INIT eiInit = {
      		64 * 1024 * 1024,				// Initial size of the heap.
      		true							// If true, the heap is growable.
      	};
      	lse::CEngine::InitEngine( eiInit );
      
      	INT iRet;
      
      	{	// Scope the game class so that it is destroyed before lse::CEngine::DestroyEngine() is called.
      		// We do not need to make a custom game class for this primitive demo.  We would want to make a custom game class
      		//	that inherits from lse::CGame if we wanted to handle any game data not handled by lse::CGame.  Any real game
      		//	will have a custom game class.
      		lse::CGame gGame;
      
      		gGame.SetStateFactory( LSENEW ::CStateFactory() );
      
      		// Before running the "game" we need to tell it where to begin.
      		gGame.SetNextState( LST_S_MODELTEST, 0, true );
      
      		// After creating a game class, we can finish initializing the engine.
      		lse::CEngine::LSE_ENGINE_SECONDARY_INIT esiSecondInit = {
      			&gGame,							// Needed for the window to send input to the game class.
      			800UL,	600UL,					// Windowed resolution.
      			0UL,	0UL,					// Full-screen reoslution.  0 = desktop resolution.
      			"L. Spiro Engine",				// Window title.
      			false							// false = windowed mode, true = full-screen.
      		};
      		lse::CEngine::SecondaryInit( esiSecondInit );
      
      		// From here, simply run the game.  It will handle the ticking of the game object and passing window messages
      		//	to it so it can handle input, etc.
      		iRet = lse::CEngine::Run();
      
      	}
      
      	// The game class and window have been destroyed by this point.  Shut down the rest of the engine.
      	lse::CEngine::DestroyEngine();
      	return iRet;
      }

      Creating the CGame instance is up to you. No other parts of the engine actually call any of the CEngine functions, which means those calls are also up to you, and the 4 calls you see here are the only calls in the class.
      Once each system is initialized, they run independently from that point forward.

      L. Spiro

      • Codeslasher
        March 7, 2012 at 9:21 PM #

        Wow, more information that I expected. Thanks.

  6. Siddu
    April 1, 2012 at 1:03 PM #

    Spiro, Where do you handle state transitions? is it handled by different states or by the state Manager?
    Thanks

    • L. Spiro
      April 1, 2012 at 1:39 PM #

      Anything fancy here would just be over-engineering. States themselves call _pgGame->SetNextState( LSE_OPTIONSMENU /* For example. */ ) directly at any time they choose to move on to the next state.
      States can’t call _pgGame->SetState() directly because that would cause the state to be destroyed while it is still executing code. SetNextState() saves the data until the start of the next frame and then changes to the new state then, when it is safe to delete the current state.

      L. Spiro

  7. Codeslasher
    April 7, 2012 at 7:58 AM #

    “Just wait until you start making tools with Qt and you realize that each OpenGL View has its own OpenGL context….”

    How would this engine setup be used to create tools?

    The CGame class is what drives the game/app, would it then make the 4 other windows/views used in a typical tool/editor?
    How would it manage redirecting window messages to its views?

    Am asking because it would be great to use the same approach for games and tools.

    • L. Spiro
      April 7, 2012 at 2:09 PM #

      In short, this setup won’t be used for tools. You will still need a CGame class probably, but it won’t be running in its own loop. And there probably won’t be any CState classes; they don’t make sense if the CGame is not running its loop.
      For event-driven UI’s you will have no choice but to make a few structural changes, but as long as the rest of your engine is decoupled as much as possible from the overall architecture this won’t impact you so much. For example, if you have a CSceneManager, nothing about how that is used will change. Only how it is updated—one way is via the game loop and the other is by responding to UI events.
      Input messages wouldn’t be sent through the typical CGame input handlers. There is not enough context for that to work. Using Qt for example, your main window will inherit from QGLWidget and in that class just add your CSceneManager object. Input will be retrieved from messages sent to your widget and you will translate them into camera movements etc. on your own.

      It sounds as though a lot changes between tools and games. While there are a lot of differences, they are fairly minor. The of bulk the work is done for you by CSceneManager and other classes, and all of that is the same.

      L. Spiro

  8. A. Turner
    April 12, 2012 at 6:56 PM #

    Very nice article L. Spiro, it would prove a great benefit to those learning about game development. May I offer a suggestion to you that would improve the quality a bit more. It’s a bit tough for many newbies to sometimes follow tutorials that have nothing but code shown at each section.

    1)
    When you use “LSBOOL” or “LSE_CALL” I know what all of this means as I’ve done cross platform programming, but you make the assumption the user does or knows the difference. Better to define these things the first time they appear in your code snippet. I couldn’t tell you how many times I’ve written tutorials, and had people ask me questions related to keywords and their context usage.

    2)
    I’ve been writing tutorials far longer than you have and I’ll share a nice tip for you to consider. If you’ve spent the time to copy in this code, take at least a few minutes and make an image showing the flow of the structure you wish to present.

    It’s less intimidating to visually see some sort of structure defined before jumping into reading your code for a new game programmer. For instance, show how CGame and CState along with the CStateManager flow using arrows; It will make a huge difference in all your future articles discussing these types of concepts.

    Conclusion)
    If you take my advice I think it would greatly improve where your line of thought is before anyone even reads your code. Remember, data without context is noise.

    Kudos on the article anyway.
    P.S, I used to work as an Engine Anti-Cheat specialist, which branch location of UbiSoft were you located?

    A. Turner

    • L. Spiro
      April 12, 2012 at 8:00 PM #

      Hello and thank you.

      #1: People have asked me too, so I guess it is shame on me for being lazy. I will probably start just replacing them with their raw values.
      #2: Actually I would like some advice on any software you would recommend for making the kinds of diagrams suitable for these tutorials. I use Photoshop but that probably isn’t the best tool for this, even if it eventually gets the job done. This is what deters me from adding a lot of images.

      L. Spiro

      P.S.: The work I did for Ubisoft was outsourced from their France branch, where I lived for a short time. On the side, you may also be interested to know that I am the author of MHS (Memory Hacking Software). I guess that means people were using my software to give you a headache. Sorry about that.

  9. Gary McLean Hall
    November 14, 2012 at 9:08 PM #

    Firstly, excellent tutorial, thank you.

    I do have a question, though. When you subclass a state and CGame, you end up breaking LSP in order to use the features of the new CGame:

    class CMyGame : public CGame
    class CHighScoreScreenState : public CState
    LSVOID LSE_CALL CHighScoreScreenState::Init( CGame * _pgGame ) {
    _pgGame->GetSoundMan()->ClearAllSounds(); // Last state should have done this but this is just an example.
    CMyGame * pmgMyGame = static_cast(_pgGame);
    // And now Anything you added to your CMyGame class can be accessed by your state.
    }

    The cast here is a smell for me. Surely this high score should depend directly on CMyGame, rather than pretend to depend on the base class? Of course, this now means states need to be templated or some such, which is probably ugly.

    Any ideas of how this could be avoided?

    • L. Spiro
      November 23, 2012 at 10:54 AM #

      My only idea is to try (I couldn’t test this) to declare the functions inside of CHighScoreScreenState as:
      virtual LSVOID LSE_CALL Init( CMyGame * _pgGame );
      etc.

      Off the top of my head I am not positive the virtual table will point to the correct function but you can try it.

      Otherwise I don’t really have a graceful solution, since wouldn’t want to use templates for this as you said.

      L. Spiro

  10. Suminsky
    April 13, 2013 at 8:34 AM #

    Hey Spiro, I was just thinking that a single game state is composed by layers, sort of.. Say you pause your level state, or the player access the inventory(or pause while in the inventory), youre not going to destroy the level state to bring the inventory state (too heavy, or you even want to display the game behind the inventory/pausemenu popup, etc.), and you also going to need a timer for each “layer”, since your gui stuff need a timer for its own animation or whatever, while the game is frozen behind..
    You think its a good idea to handle this layering system in the engine or the user should handle it himself on the inherited state his going to create?

    • L. Spiro
      April 13, 2013 at 3:40 PM #

      A state should have only one responsibility (be that the game scene, the pause menu, controller disconnected screen, whatever).
      Rather than the states themselves being expanded to handle more than that, the state manager should maintain a stack of states. This is how L. Spiro Engine works. You can push as many states on top of each other as you need and they all have flags such as “pauses lower states” etc.

      Each state has its own timer, so implicitly some states can be paused while others are animated. The main game timer has 2 timers as well anyway (real time that always ticks and virtual time that only ticks when not paused) so it is always possible to have one part of the game paused while another is active even without multiple timers anyway.

      L. Spiro

  11. fredericvo
    June 27, 2014 at 11:19 PM #

    Hello,

    After finishing a lot of DirectX 11 tutorials such as rastertek I decided to finally get my hands dirty at proper game engine design and I had already vaguely read about gamestates and it all seemed to make sense until I really tried it. I initially liked your idea of passing the game class downstream but doing so I learned about a new problem: circular references and/or include hell. Include guards don’t seem to stop this.
    I keep getting errors such as undefined types if I don’t forward declare or can’t access such and such because it is an incomplete type.
    If that wasn’t enough it seems like according to other sources upstream dependencies are an anti-pattern and shouldn’t be done. My world came crushing down, I really wonder how to do this now. I think my workaround will be to simply pass down pointers to all the subsystems or something like that.

    • L. Spiro
      June 28, 2014 at 12:07 AM #

      I can at least assure you that your problem did not arise specifically because you tried to use states. I have no such problems.
      If you have these kinds of circular dependencies then you have an overarching problem with your organization. This isn’t the cause of it, this is its revelation.
      Whether you use states or not you need a better form of global organization. I can’t say anything specific because you haven’t given details, but it’s a problem I had long ago before I ever considered states etc., so it should be treated and fixed as a separate issue.
      Implementing states should never cause up-stream dependencies. I may be able to offer more advice if you give more details on how this happens.

      L. Spiro

  12. fredericvo
    June 28, 2014 at 1:46 AM #

    Spiro, I think I got it to work. Sorry for bothering you.
    I had some issues with incorrect derivation of a base class, having a private rather than protected base member and a simple stupid mistake where I forgot to actually initialise a variable in a constructor. It seems to work now and I’ll probably be back with more questions LOL.

  13. Geoffrey Sangston
    August 17, 2014 at 12:28 PM #

    Why does CStateMachine::SetState pass the state id? Doesn’t passing _psState already give the state id since state id is a member of State?

    Thank you, Geoffrey

    • Geoffrey Sangston
      August 17, 2014 at 12:31 PM #

      Oh, I see it’s a protected member. Why not add a getStateId method instead of passing it separately?

      • L. Spiro
        August 19, 2014 at 2:09 PM #

        The state ID is not connected to the class. The same state class could have a different ID if the state factory wishes it so. Making any hard connection between the state ID and the state class outside of the state factory is to tightly couple the ID and the state, and possibly other things.

        I have never used the state ID in practice, so it is really just optional.

        L. Spiro

    • Geoffrey Sangston
      August 17, 2014 at 12:44 PM #

      Not to spam your comment section, but I have another inquery.

      What is the point of this scheme:

      if ( !RemoveCurState( _pgGame ) ) { return false; }

      As far as I can tell, RemoveCurState always returns true; so the code block of this conditional statement will never get executed. What is the utility of this over just calling RemoveCurState(_pgGame) directly?

      • Geoffrey Sangston
        August 17, 2014 at 12:48 PM #

        inquiry*

        Agh, I skipped over the important comment, “\return Returns true in this simplified version. The real function could return false.”

        Anyways, thanks for this blogpost, it’s very helpful!

Trackbacks/Pingbacks

  1. General Game/Engine Structure by L Spiro « zielprogramming - March 11, 2012

    [...] article I found that gives a good overview on how to set up a state machine for a game engine: http://lspiroengine.com/?p=351 Share this:TwitterFacebookLike this:LikeBe the first to like this post. Categories: [...]

Leave a Comment

Remember to play nicely folks, nobody likes a troll.