Game development in sge/C++: Part III (input, signals)

Introduction

In the last article we’ve seen how to draw objects on the screen. Now let’s do something completely different. Let’s take our spaceship and move it around using the keyboard. It’s a fairly simple task but, again, I feel like I have to explain some background first. And it’s important background, since to do input, we need to know what kinds of callback mechanisms there are in C++ and we need to know some things about binding.

Input basics

Active and passive event retrieval

There are two approaches to get keyboard events (or any other type of event), one approach is “passive”, the other one is “active”. The active approach would be to ask if there’s a keyboard event and then do something with it, as in:

sge::input::keyboard::key_event e = keyboard_collector.get_event();
std::cout << "The key " << e.key_code() << " was pressed\n";

Technically, you have another pair of choices here: If there’s no keyboard_event currently in the event queue, you could either wait for an event to arrive or you could signal an error. But this discussion will be the subject of a later article.

In the passive approach, you don’t ask for a keyboard event. Instead, you register a callback function which is called whenever there’s an event. If you want to output all the key codes for the keys pressed, you’d write it like this:

namespace
{
void output_keycode(sge::input::keyboard::key_event const &e)
{
  std::cout << "The key " << e.key_code() << " was pressed\n";
}
}

int main()
{
  // Initialization stuff here
  fcppt::signal::scoped_connection connection(
    sys.keyboard_collector().key_callback(
      &output_keycode));
  // Stuff omitted here
  while (running)
  {
    sys.window().dispatch();
  }
}

Ok, the code looks longer and more complicated, but you don’t have to understand all of it, yet. With the key_callback function of the keyboard collector, we tell the input system: call output_keycode(e) whenever you get a key event e. In case you are wondering when the input system itself checks for new key events — that’s the ominous dispatch function which has been lurking in our main loop since the first article.

Function pointer “objects”

When we write &output_keycode, we’re creating a “function pointer”. I’ll explain function pointers a bit differently than you normally read it in books and tutorials: Think of the line &output_keycode as a call to the function named &, passing it the function output_keycode. So it’s more like:

get_function_pointer(output_keycode)

This function get_function_pointer (or the & operator, respectively) returns an object. You can pass this object around, store it in a variable, just like you would with an ordinary int:

void (*myfn)(sge::input::keyboard::key_event const &) = &output_keycode;
myfn = &some_different_function;

Granted, the syntax is a bit eerie, but the code compiles just fine, as long as some_different_function is a function returning void and getting a single parameter of type sge::input::keyboard::key_event const &

But this function pointer object is a special object, because it has a “function call operator”. The code above can be continued:

// Construct an "empty" key event, just for exposition
sge::input::keyboard::key_event e;
myfn(e);

Here, the object’s operator() is invoked, which itself invokes the function output_keycode, passing it e. Objects having at least one operator() are called functors, since they resemble ordinary functions1. Functors have a signature for each operator() they posess. With these new terms, key_callback is a function which takes an arbitrary functor with the signature void (sge::input::keyboard::event const &). Signatures are written just like you would declare a corresponding function, but without the name. We’ll get back to that in a bit.

Function pointer limitations

Now that we know about function pointers and key_callback, back to our problem: let’s write a function that gets as parameters a key event and our spaceship sprite and which moves the sprite around if w,s,a or d are called:

namespace
{
void move(sprite_object &spaceship,sge::input::keyboard::key_event const &key)
{
  // The ship should only move if the corresponding key was pressed, not released
  if (!key.pressed())
    return;

  switch (key.key_code())
  {
    case sge::input::keyboard::key_code::w:
      // move the spaceship 10 pixels upwards
      spaceship.pos(
        // Note that 0 and 10 are of type "int" and we chose our coordinates to 
        // be int (type_choices, remember?) so we can use those literals without 
        // casting them to "point::value_type" first
        spaceship.pos() + sprite_object::point(0,10));
      break;
    // handle s, a and d accordingly
  }
}
}

int main()
{
  // Initialization stuff here
  fcppt::signal::scoped_connection connection(
    sys.keyboard_collector().key_callback(
      &move, ... oh wait

Do you see the problem which occurs now? The &move functor has an operator() with the signature

void (sprite_object &,sge::input::keyboard::key_event const &)

but key_callback expects a functor with the signature

void (sge::input::keybaord::key_event const &)

But how do we pass it the spaceship, which is defined inside our main? It’s not a global variable (and it shouldn’t be) so move has no access to it. As a side note: the spaceship shouldn’t move by a specified amount of pixels each keypress. It should begin to move when you press the key and should stop moving when you release it. But let’s tackle one problem at a time.

To pass the spaceship to move, we need to “glue” the spaceship variable and the move function together and pass the resulting “package” to the key_callback function, which then fills in the key_event parameter when an event occurs. To do that, I’ll introduce an extra class:

class spaceship_move_connection
{
public:
  spaceship_move_connection(
     sge::sprite::object &_spaceship)
  :
    spaceship_(
      _spaceship)
  {
  }

  // Nice, our very own function call operator!
  void operator()(sge::input::keyboard::key_event const &e)
  {
    move(spaceship_,e);
  }
private:
  sge::sprite::object &spaceship_;
};

int main()
{
  // Initialization stuff here
  fcppt::signal::scoped_connection connection(
    sys.keyboard_collector.key_callback(
      spaceship_move_connection(
        spaceship)));
}

Now the signatures of spaceship_move_connection and the parameter of key_callback match and the code runs just fine. You can imagine that you can use this “helper class method” to glue together member functions of a class and an instance of a class (again, by storing a reference to the class and calling the member function in the operator()).

To make our lives easier, boost provides a wrapper which generates the helper classes automatically. It’s called boost::bind and it’s used as follows:

#include <boost/bind.hpp>
#include <boost/ref.hpp>

int main()
{
  // Initialization stuff here
  fcppt::signal::scoped_connection connection(
    sys.keyboard_collector.key_callback(
      boost::bind(
        &move,
        boost::ref(spaceship),
        _1)));
}

Looks pretty straightforward, doesn’t it? We give it the move functor, then a reference to the spaceship and leave the last parameter “open” so key_callback can “fill” it. If you leave out the boost::ref, bind will create a helper class containing a copy of the sprite_object instead of storing just a reference. The _1 is some static object defined in the global namespace and serves as a placeholder.

Signals

This still leaves a few questions unanswered. For one: What’s this fcppt::signal::scoped_connection doing there? And how can key_callback accept arbitrary functors? What if I want to do that, too?

Well, since this is so important, we’re going to implement our own input system! This new input system will not deliver raw keystrokes but “actions” like “move left”, “move right” etc.. We’ll define a map from input::keyboard::key_code to action and translate the keys with that. This map could be read from a configuration file, enabling the users to define their own key bindings — something which is found in most games. But, since parsing configuration files is not the topic of this article, we will hard code the key bindings for now. Let’s look at the code. Take your time to digest it, since it’s a bit longer than the other code snippets:

#include <sge/input/keyboard/device.hpp>
#include <sge/input/keyboard/key_code.hpp>
#include <fcppt/signal/object.hpp>
#include <fcppt/signal/auto_connection.hpp>
#include <fcppt/signal/scoped_connection.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <map>

// Internal stuff goes to the anonymous namespace
namespace 
{

namespace action
{
enum type
{
	move_left,
	move_right,
	move_up,
	move_down
}
}

class action_emitter
{
// For better readability, I'll put the member variable 
// declarations and typedefs here.
private:
	// This is the type of function we accept from the user.
	// The boolean indicates if the action is "on" or "off".
	typedef void callback_type(action::type,bool);

	fcppt::signal::object<callback_type> signal_;
	fcppt::signal::scoped_connection key_connection_;
	std::map<sge::input::keyboard::key_code::type,action::type> key_to_action_;
public:
	// We take a keyboard::device as a parameter. Note that "device" is just an
	// interface name. We can pass our "keyboard_collector" from the second
	// article here, too.
	action_emitter(
		sge::input::keyboard::device &keyboard)
	:
		// No parameters for the signal
		signal_(),
		key_connection_(
			keyboard.key_callback(
				// Our old friend, boost::bind. Here, we bind the member function
				// "key_callback" to the key signal
				boost::bind(
					&action_emitter::key_callback,
					this,
					_1)))
	{
		// Excercise: Read this from file
		key_to_action_[sge::input::keyboard::key_code::left] = action::move_left;
		key_to_action_[sge::input::keyboard::key_code::right] = action::move_right;
		key_to_action_[sge::input::keyboard::key_code::up] = action::move_up;
		key_to_action_[sge::input::keyboard::key_code::down] = action::move_down;
	}

	// This is the function the user calls to register a callback
	fcppt::signal::auto_connection
	action_callback(
		// Here's something new...boost::function. See the explanation below
		boost::function<callback_type> const &f)
	{
		return signal_.connect(f);
	}
private:
	// This can be private, no one should call it from the outside
	void
	key_callback(
		sge::input::keyboard::key_event const &e)
	{
		// No key mapping found, too bad.
		if (key_to_action_.find(e.key_code()) == key_to_action_.end())
			return;

		// Overloaded function call operator
		signal_(
			key_to_action_[e.key_code()],
			e.pressed());
	}
};
}

This is, again, a piece of code that might puzzle you in more than one place. First of all, we see two enumerations here: action::type and sge::input::keycode::key_code::type (damn, a lot of namespaces!). You probably wonder why those have an extra namespace and are both called “type”. This is a workaround for a nuisance in C++: enumerations don’t start a new namespace! This code will fail to compile:

enum action { left,right,up,down };

void take_action(action a)
{
	switch (a)
	{
		case action::left:
			// ...
			break;
	}
}

…simply because “left” is not in the namespace “action” but (in this case) in the global namespace. You’d have to write case left which is…well, “namespace pollution”.

As you can see, we define a signal called signal_ in our action emitter. As template parameter, the signal gets a function signature which tells you what kind of functors can connect to the signal. In our example, we are ready to accept any functor that we give an action. Since we need this function signature in two places, we typedef it (the syntax, again, looks a little strange at first, but it’s the same as defining a function called “callback_type”). We could also have written:

fcppt::signal::object<void (action::type)> signal_;

Moving on, the signal would scream “ACTION EVENT!!?!!?!” and no one would hear it, weren’t it for the action_callback function which connects a functor to the signal. In the signature of this function lies the answer to the question of how to accept arbitrary functors with the correct signature. We use a class called boost::function. Like signal, boost::function gets as template parameter the signature of the functors to accept (we re-use our typedef here). Don’t ask how function works, though, it’s just magic. 😉

Interestingly enough, the connect function has a return value, a connection object. This is basically an object doing bookkeeping. The following code assumes that we don’t need a connection object. It should give you an idea of why it’s important:

// A pretty lame functor
class printer
{
public:
	void operator()() const
	{
		std::cout << "Hello!\n";
	}
};

int main()
{
	// Define a signal with a pretty lame signature
	fcppt::signal::object<void ()> my_signal;

	{
		printer p;
		my_signal.connect(&p);
		// Works just fine...
		my_signal();
	}

	// Shit, the printer object is dead now, this will fail!
	my_signal();
}

We create a signal and attach a temporary object to it with connect. After the printer object’s destruction, we call the signal again. my_signal, however, cannot know that the printer is dead by now. It’s still in the list of connections, so the code above will give a segmentation fault (or something similar). To remedy the situation, we “track” the functor using the connection object, which, on destruction, will un-register the functor.

The astute reader2 might have noticed that there are two connection types, auto_connection and scoped_connection. Don’t worry about that for now, it has something to do with object ownership, a topic for later articles.

Getting the ship moving

Doing it almost right

The last thing we need to do is get the ship moving — but this time, it should move as long a key is pressed and not by a fixed amount of pixels each keypress. But to do that, we need to create a structure holding some state variables for the currently pressed keys (or better, the currently active actions). The structure should also make use of our action_emitter.

Let’s be object-oriented and create a class for our entire spaceship which contains the action_emitter. This way, we can create more than one spaceship, each with a different keyboard::device so you can implement a multiplayer mode featuring two keyboards:

// Some includes omitted here
#include <set>
#include <boost/foreach.hpp>

class ship
{
private:
	action_emitter emitter_;
	// Connection to the emitter
	fcppt::signal::scoped_connection action_connection_;
	// To make the ship as self-sufficient as possible, we have to put the ship's
	// sprite in here, too.
	sprite_object sprite_;
	// Where the ship is heading
	sprite_object::point direction_;

	typedef
	std::set<action::type>
	action_is_active;
	
	action_is_active active_actions_;
public:
	ship(
		sge::input::keyboard::device &keyboard,
		// I didn't want to repeat the sprite initialization code, so I use it as a
		// parameter.
		sprite_object const &_sprite)
	:
		emitter_(keyboard),
		action_connection_(
			emitter_.action_callback(
				boost::bind(
					&ship::action_callback,
					this,
					_1))),
		sprite_(_sprite),
		// Initially, we're going in no direction at all
		direction_(sprite_object::point::null()),
		active_actions_()
	{
	}

	void
	update()
	{
		sprite_.pos(
			sprite_.pos() + direction_);
	}

	// To render the sprite using the sprite system, 
	// we need access to it from outside.
	sprite_object const &
	sprite() const
	{
		return sprite_;
	}
private:
	void
	action_callback(
		action::type const new_action,
		bool const is_on)
	{
		// Update action cache
		if (is_on)
			active_actions_.insert(new_action);
		else
			active_actions_.erase(new_action);

		// Update direction loop
		direction_ = sprite_object::point::null();
		// Meet the neat foreach macro which iterates through all active actions
		// using the "current_action" variable
		BOOST_FOREACH(
			action_type::type const current_action,
			active_actions_)
		{
			// This is inaccurate, actually. It introduces the famous 
			// sqrt(2) bug, but we don't care for now.
			switch (current_action)
			{
				case action::move_left: direction_.x() -= 10; break;
				case action::move_right: direction_.x() += 10; break;
				case action::move_up: direction_.y() -= 10; break;
				case action::move_down: direction_.y() += 10; break;
			}
		}
	}
};

So far, so good. We’ll call update in our main loop and use the sprite getter function and pass it to the sprite system. There is a tiny catch, however. When you run this code and press, the left arrow key, for instance, the ship will vanish!

The problem is that update is called each frame, which means that each frame the ship is moved by one pixel. Since we’re only rendering one sprite as of yet, you’re probably getting about 1000 frames per second3. It would be great if the sprite velocity would be frame-rate–independent. This is only possible if we take the elapsed time into account.

Tempus fugit

Instead of saying “move the ship by 10 pixels a frame” we would like to express “move the ship by 10 pixels a second”. To achieve that, we measure how long each frame takes — let’s say in milliseconds. Denote this time span by ‘d’. By stating:

position += d/1000.0 * point(10.0,10.0)

We now move by 10 pixels each frame — for if ‘d’ is 1 for 1 frame, we get 10, if ‘d’ is 0.5 for 2 frames, we still get 10 and so forth (proof by example, I usually hate that).

To measure the duration of each frame, sge provides some helper functions. Most importantly, there’s sge::time::timer. A timer has an associated duration. You can activate the timer and ask it how much of this duration (in percent) has passed yet. A timer can expire, however, so we need to reset it after asking it how much time has passed.

There’s another complication: As you might recall, we’ve chosen to use “int” for the sprite positions. But the time span between two frames might be infinitesimally small. So we need to switch to a different type. But it wouldn’t be wise to use floating coordinates everywhere. So in the following code, we’ll switch to float only when it’s neccessary:

#include <sge/time/timer.hpp>
#include <sge/time/funit.hpp>
#include <sge/time/second.hpp>
#include <fcppt/math/vector/static.hpp>
#include <fcppt/math/vector/basic_impl.hpp>
#include <fcppt/math/vector/arithmetic.hpp>
#include <fcppt/math/vector/structure_cast.hpp>

class ship
{
private:
	// Our floating vector type for the calculations
	typedef
	fcppt::math::vector::static_<float,2>::type
	float_vector;

	// direction_ is now a floating point vector. 
	// We need to store the position as float, too
	float_vector direction_;
	float_vector position_;

	// ...

	sge::time::timer timer_;
public:
	ship(
		sge::input::keyboard::device &keyboard,
		sprite_object const &_sprite)
	:
		// ...
		direction_(
			float_vector::null()),
		position_(
			// structure_cast => switch from sprite vector to float_vector
			fcppt::math::vector::structure_cast<float_vector>(
				sprite_.pos())),
		timer_(
			sge::time::second(1))
	{
	}

	void
	update()
	{
		// funit for "floating time unit". elapsed_frames will return
		// how much of the specified duration has elapsed. It'll 
		// be 1.0 when one second is over and 0.5 when half a second 
		// is over and so on.
		sge::time::funit const delta = timer_.elapsed_frames();
		timer_.reset();

		position_ += static_cast<float>(delta) * direction_;
		sprite_.pos(
			// Now switch to sprite vector
			fcppt::math::vector::structure_cast<sprite_object::point>(
				position_));
	}

	// ...
};

And that’s it! Our ship moves by 10 pixels a second (pretty slow, you might want to increase the speed a little).

This article was a bit longer and it had no pictures, but I hope it wasn’t too boring. In the next tutorial, we’ll getting some structure into the game — using nice C++ idioms, of course.

Footnotes

1 Note that this has nothing do to with functors in category theory (or the Functor typeclass in Haskell, respectively).
2 I always wanted to say this.
3 This “frame overproduction” is pretty annoying and we’ll fix it in a later article.

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

One Response to Game development in sge/C++: Part III (input, signals)

  1. Pingback: Managing configuration stuff with json – a complete solution? | pimiddy

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