Game development in sge/C++: Part IV (statechart)

Introduction

Welcome back to the fourth “episode” of our journey to create a top down shooter game. Note that there were some minor changes to sge and I’ve updated the repository and the blog code. It’s nothing really noteworthy for us, though.

So, what’s the topic of this post, how do we continue? Well, we definitely need some structure for the game. Something to build upon which won’t change as frequently as the other parts. Again employing a modern C++ library, we’re going to create a “statechart” for the application.

States

Motivation

I’ll first try to motivate the key ideas which make up the boost::statechart library which we’ll be using, then introduce the library itself.

As you can easily imagine, a game “as a whole” almost always consists of a set of states. For example, most games have a main menu. That’s a state, and it is different from the “ingame” state and the “game is over, please enter your name” state. States can form a hierarchy. Below “ingame”, there might be two substates “paused” and “running”. The menu state might consist of a collection of submenu states.

Of course, each state has its own local data attached to it. The menu state stores some gui widgets, the ingame state stores the game’s objects and so on. This data shouldn’t be allocated all the time, just when the state is active. Therefore, it is wise to create one class for each state (or substate, or subsubstate) and make it that when a state is entered, the class’ constructor is called and when it is exited, the destructor is called (following the RAII principle). To make the discussion easier, consider the following very simple hierarchy which we will be using in the game (at least in the beginning):

Our game's first statechart

Our game's first statechart

States are dynamically constructed and destroyed as certain events occur. When the user presses the “pause” button, “running” is destroyed and “paused” is constructed (“ingame”, however, isn’t touched, which makes it a good place to store more persistent game data such as all the game’s objects). These events should be classes, too, since we might want to pass them around and store relevant data in them.

Note that statechart has a lot of capabilities (asynchronous state machines, orthogonal states, history, …) and we will be using a very small subset at first. Later on, we might explore more possibilities.

boost::statechart

Writing a state management system from scratch is a very difficult task. There are some ready-made solutions available, but basically just one sticks out: boost::statechart. You’ll see that we can basically take the picture above and translate it into C++ code. Note that the picture also contains a “machine”. This is really the states’ “engine”. It’s a top-level container which contains the currently active state. Also, the user can store additional data in the machine which is then globally available for all the states.

I’m now going to give you a skeleton for boost::statechart which implements the diagram1. This is just to show you:

  1. how to (syntactically) define a single state, substates and the machine
  2. how the states are interconnected and which class must “know” which one (this is not completely trivial)
#include <boost/statechart/state_machine.hpp>
#include <boost/statechart/state.hpp>

// ----------------- machine begin -----------------
namespace sgeroids { namespace states { namespace ingame { class superstate; } } }

namespace sgeroids {
class machine
:
	public boost::statechart::state_machine<machine,states::ingame::superstate>
{
public:
	machine();

	sge::systems::instance const &
	systems() const;

	void
	run();
private:
	sge::systems::instance systems_;
	bool running_;
};
}
// ----------------- machine end ----------------- 

//  ----------------- superstate begin ----------------- 
namespace sgeroids { namespace states { namespace ingame { class running; } } }

namespace sgeroids { namespace states { namespace ingame {
class superstate
:
	// machine (the "context") must be complete, running (the initial state)
	// doesn't have to be
	public boost::statechart::state<superstate,machine,running>
{
public:
	// Wtf, my_context? See below
	explicit
	superstate(
		my_context);
};
} } }
//  ----------------- superstate end ----------------- 

//  ----------------- running begin ----------------- 
namespace sgeroids { namespace states { namespace ingame {
class running
:
	// ingame now has to be complete
	public boost::statechart::state<running,superstate>
{
public:
	// Wtf, my_context? See below
	explicit
	running(
		my_context);
};
} } }
//  ----------------- running end ----------------- 

//  ----------------- paused begin ----------------- 
namespace sgeroids { namespace states { namespace ingame 
{
class paused
:
	// ingame now has to be complete
	public boost::statechart::state<paused,superstate>
{
public:
	// Wtf, my_context? See below
	explicit
	paused(
		my_context);
};
} } } 
// ----------------- paused end ----------------- 

As you can see, I finally chose a name for the game: “sgeroids”. It’s asteroids for sge on steroids. Thanks to nille for that.

So, we put our states in the “states” namespace and we let the filesystem and namespace hierarchy follow the state hierarchy, which is usually a good idea. You can see three types of objects in the code:

  1. The state machine, derived from boost::statechart::state_machine, which receives itself as the template parameter as well as the initial state.
  2. “leaf” states like ingame::running and ingame::paused which have no further child states. They receive themselves and their “context” or “father” state as a template parameter.
  3. “inner” states like ingame::superstate which receive themselves, the context state and the initial child state as template parameter.

The astute — as well as the unastute — reader might have noticed that all of statechart’s classes are templatized by…themselves! This is called the curiously recurring template pattern and I won’t go into it right now because we probably won’t be using it in our code (except for the statechart declarations). Just keep in mind that the first argument to just about any class in statechart is the name of the class again. It’s a common source of copy-and-paste errors.

My stuff!

There’s another little oddity about the statechart code. The state constructors have exactly one parameter, a my_context object. This looks a little weird, but it’s necessary to pass some data to the states, since we want to be able to access the father state or the machine from within the constructor.

If we receive no parameters in the constructor, that’s not possible (unless the machine is a global object). And, oh well, the statechart author felt it was a good idea to typedef the state’s base class to my_base and the “context” to my_context.

Filling in the blanks

Now that there’s a statechart skeleton we have to think about which state does what and how to transition between states and when. This is usually a pretty easy process. Which variables to put where is largely self-explanatory, so we’ll skip that for now (in the code above I already created some methods and variable names which we’ll use later on).

More interesting is our game loop, since we will base it on statechart events. For now, we will create two events which will be sent every frame:

  1. A tick event which instructs the currently active state to “update the game”. This means stepping the collision engine, updating timers and so on. The event contains no data.
  2. A render event

The definition in C++ is trivial:

#include <boost/statechart/event.hpp>

namespace sgeroids { namespace events {
class tick
:
	public boost::statechart::event<tick>
{
};
} }

namespace sgeroids { namespace events {
class render
:
	public boost::statechart::event<render>
{
};
} }

Notice again the CRTP pattern. Now on to the main loop, which we will initiate inside a member function of the machine:

void
sgeroids::machine::run()
{
	while(running_)
	{
		// Get our input events!
		systems_.window()->dispatch();

		// process_event is derived from state_machine
		process_event(
			events::tick());

		sge::renderer::scoped_block scoped_render_block(
			systems_.renderer());

		process_event(
			events::render());
	}
}

The main loop should seem pretty familiar to you now. The process_event function sends an event “down the state chain”. It will first go as deep as possible in the hierarchy, stopping at states::ingame::running (unless we’re in pause mode). In this state, it’ll look for a “reaction” to the event. If it finds one, it’s done (unless you instructed it to defer/forward the event). If it doesn’t, it’ll go up a level to states::ingame::superstate and look for a reaction there, and so on:

The event chain induced by a call to process_event

The event chain induced by a call to process_event, ordered chronologically with a color coding

pause, running, pause, …

This leaves but one thing to discuss: How to write event reactions! Fortunately, it’s not difficult at all. Let’s learn how to do it by implementing the transition from “running” to “paused”. First, we’re creating a new event:

#include <boost/statechart/event.hpp>

namespace sgeroids { namespace events {
class pause_toggle
:
	public boost::statechart::event<pause_toggle>
{
};
} }
} }

This event will be triggered from ingame::superstate so the states below it can catch it and do the transition.

Transition between running and paused

Transition between running and paused

We add a few things to the superstate:

class superstate
:
	public boost::statechart::state<superstate,machine,running>
{
public
	superstate(my_context _context)
	:
		my_base(
			_context),
		handle_keypress_connection_(
			context<machine>().systems().keyboard_collector().key_callback(
				boost::bind(
					&handle_keypress,
					this,
					_1)))
	{
	}
private:
	fcppt::signal::scoped_connection handle_keypress_connection_;

	void
	handle_keypress(
		sge::input::keyboard::key_event const &)
	{
		context<machine>().process_event(
			events::pause_toggle());
	}
};

There are a few new things here. In the constructor you see how to initialize a state. As I said, the state’s base class is typedeffed to my_base and it expects a my_context as the constructor argument. After the base class is initialized, we can call the context() function to access either a state that’s higher up in the hierarchy or even the machine. In this case, we fetch the machine and use the systems() accessor function to get the keyboard collector.

In our key callback (the last function in the code sample), we “inject” the pause_toggle event to the machine just like we injected tick and render earlier.

Event handlers

On to the transition in running:

#include "superstate.hpp"
#include "paused.hpp"
#include "../../events/pause_toggle.hpp"
#include "../../events/render.hpp"
#include <sge/input/keyboard/key_event_fwd.hpp>
#include <fcppt/signal/scoped_connection.hpp>
#include <boost/statechart/state.hpp>
#include <boost/statechart/custom_reaction.hpp>
#include <boost/mpl/list.hpp>

namespace sgeroids
{
namespace states
{
namespace ingame
{
class running
:
	public boost::statechart::state<running,superstate>
{
public:
	typedef
	boost::mpl::list
	<
		boost::statechart::custom_reaction<events::pause_toggle>
		boost::statechart::custom_reaction<events::render>
	>
	reactions;

	explicit
	running(
		my_context _context)
	:
		my_base(
			_context)
	{
	}

	boost::statechart::result
	react(
		events::pause_toggle const &)
	{
		return transit<paused>();
	}

	boost::statechart::result
	react(
		events::render const &)
	{
		// TODO
		return discard_event();
	}

	~running();
};
}
}
}

Again we use a type container from boost::mpl (last time we used it to store the properties of our sprites). This time, we use it to store the collection of event reactions. custom_reaction is just a wrapper saying “this class has a react member function to react to event x”. This react function returns a result, which has one of the following effects:

  1. discard the event, which just means “move along, nothing to see here”
  2. a transition to a different state (which we are using to get from running to paused and back)
  3. forward the event and let the state above deal with it
  4. defer the event, pushing it into an internal queue so it’s processed later (maybe after a transition?)

You see two types of results in the event handlers: The render event is just discarded. In pause_toggle, we execute the transition to paused. Pretty simple, isn’t it?

What’s next?

I hope you like statechart so far and are not disappointed that there were no “visible” additions in this article. In the next article, we will focus on clocks, durations and time in general, introducing chrono, the time library of the upcoming standard C++0x. This will help us limit the game’s frame rate so it doesn’t consume more CPU time than necessary. Also, we need a way to stop the game time so the “pause” state makes sense.

Note that in the repository you can find the skeleton in the “4” directory. It contains only the files talked about here, so it doesn’t compile/link.

Footnotes

1 Note that I’m breaking sge style a bit here, the namespace declarations are usually one below the other, not next to each other. But I didn’t want to blow up the code in the blog.

Advertisements
This entry was posted in Uncategorized and tagged , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s