#7 Framework 3 – Input

Wait, haven’t we already sorted input out? Why are we covering it now?

Well, quite simply, what we have was a hack. We don’t really want to have our input handling spread over the whole game really. What happens if we want to have configurable controls for example? Basically, we want something a bit more flexible which we can work with. So we’ll make a little input handler.

I won’t bother drawing a design for this, it will be pretty simple. The idea will be a handler, contained within our “game” class, which updates every frame. It will contain a list (or, more probably an std::map) of commands. That’s right, we’re going to be using our good friend the command pattern. A small difference in this one will be that we will want to specify a default key to bind. In future, we can add things like loading the keymaps from a file, but that will then just require a change in a centralised place. Of course, we can’t stop polling the input entirely, because the Maratis input system expects to be polled, but we should be able to clean up a lot of the game code if we don’t have to poll everywhere. So let’s have a look at some code. There’s not much at all, so I’ll just dump it. As usual, add the new files and re-run

premake4 vs2010

(we’ll add this to a script soon)

InputManager.h
#ifndef __INPUT_MANAGER_H__
#define __INPUT_MANAGER_H__

//----------------------------------------
// InputCommand
//
// Interface for input commands, will send
// a OnKeyPressed and OnKeyReleased signal
//----------------------------------------
class InputCommand
{
public:
	InputCommand();

	virtual const char* GetCommandName() = 0;
	virtual void		OnKeyPressed() {}
	virtual void		OnKeyReleased() {}

	const char*			GetKeyName() { return m_KeyName; }
	void				SetKeyName(const char* keyName);

private:
	const char* m_KeyName;
};

#include 

//----------------------------------------
// InputManager
//
// Simple input manager wrapper to send
// commands when a key is pressed.
// This provides a nice, central place for
// when we might want to write modifiable
// keys
//----------------------------------------
class InputManager
{
public:
	InputManager();
	~InputManager();

	// Command interface
	void RegisterCommand(InputCommand* command);
	void UnregisterCommand(InputCommand* command);

	void Update();
private:
	//------------------------------------
	// commandDef
	//
	// Tiny little struct just to hold the
	// callback command, and the current
	// state of that key
	//------------------------------------
	struct commandDef
	{
		enum commandState
		{
			eUp,
			ePressed,
			eDown,
			eReleased,
		};

		InputCommand*	command;
		commandState	state;
	};

	typedef std::vector	commandVec;
	typedef commandVec::iterator		commandVecIter;

	commandVec	m_Commands;
};

#endif /*__INPUT_MANAGER_H__*/

There really shouldn’t be much here at all that surprises you at this stage, it’s pretty much the same as things like the Timer/ITimerAction relationship. At some point I will possibly go through this code and consolidate naming schemes (Add vs Register etc). The main difference in the command is that it’s not entirely virtual, we have the Get/SetKeyName which are implemented in the base class. I’ve also added a small wrapper struct into the InputManager in order to wrap the command with extra data. In this situation it’s not actually needed, and you could argue it’s better to put the state of the command in the command itself, but there are a couple of times where this might be useful, so I left it in as an example.

InputManager.cpp
#include "InputManager.h"

#include 

//----------------------------------------
InputCommand::InputCommand()
: m_KeyName(0)
{
}
//----------------------------------------
void InputCommand::SetKeyName(const char* keyName)
{
	m_KeyName = keyName;
}
//----------------------------------------
InputManager::InputManager()
{
	// for now, we don't really
	// care much for constructing
	// anything
}
//----------------------------------------
InputManager::~InputManager()
{
	// just make sure our command list is properly
	// empty. We don't care about the commands
	// themselves, because we don't own them
	for(commandVecIter iCommand = m_Commands.begin();
		iCommand != m_Commands.end();
		iCommand++)
	{
		delete *iCommand;
	}
	m_Commands.clear();
}
//----------------------------------------
void InputManager::RegisterCommand(InputCommand* command)
{
	// first thing's first. We don't want to
	// be adding duplicate commands
	for(commandVecIter iCommand = m_Commands.begin();
		iCommand != m_Commands.end();
		iCommand++)
	{
		// this command is already registered
		if((*iCommand)->command == command)
			return;
	}
	// all is a-ok
	// create a new command and set it up
	commandDef* newCommand = new commandDef;
	newCommand->command = command;
	newCommand->state = commandDef::eUp;
	m_Commands.push_back(newCommand);
}
//----------------------------------------
void InputManager::UnregisterCommand(InputCommand* command)
{
	// unfortunately we can't do an std::find here
	// as we aren't adding the command to the vector
	// itself
	for(commandVecIter iCommand = m_Commands.begin();
		iCommand != m_Commands.end();
		iCommand++)
	{
		if((*iCommand)->command == command)
		{
			delete *iCommand;
			m_Commands.erase(iCommand);
			// we should never have duplicate commands
			// so there's no reason why we can't just
			// drop out here
			return;
		}
	}
}
//----------------------------------------
void InputManager::Update()
{
	MEngine* engine = MEngine::getInstance();

	if(MInputContext* input = engine->getInputContext())
	{
		// this bit is simple, just loop through the
		// commands we have registered
		for(commandVecIter iCommand = m_Commands.begin();
			iCommand != m_Commands.end();
			iCommand++)
		{
			commandDef::commandState state = (*iCommand)->state;
			// find out what state the key is which is bound to
			// this command
			if(input->isKeyPressed((*iCommand)->command->GetKeyName()))
			{
				// if it's down, check the previous state
				switch(state)
				{
				case commandDef::eUp:
				case commandDef::eReleased:
					// we only just pressed it, so send the key pressed
					// command
					(*iCommand)->command->OnKeyPressed();
					(*iCommand)->state = commandDef::ePressed;
					break;
				case commandDef::ePressed:
				case commandDef::eDown:
					// it was already down, just make sure it's now
					// saved as down
					(*iCommand)->state = commandDef::eDown;
					break;
				}
			}
			else
			{
				// same deal for up, but in reverse
				switch(state)
				{
				case commandDef::eUp:
				case commandDef::eReleased:
					(*iCommand)->state = commandDef::eUp;
					break;
				case commandDef::ePressed:
				case commandDef::eDown:
					(*iCommand)->command->OnKeyReleased();
					(*iCommand)->state = commandDef::eReleased;
					break;
				}
			}
		}
	}
}

I couple of things to mention here. Firstly, because we are wrapping the command up in the struct, registering and unregistering becomes a little more complex than usual, but it’s fairly self explanitory. As usual, we don’t own the command which are being registered, but we do own the wrapper, so we’re going to clean it up properly on destruction. That much is pretty simple though. That just leaves the update. There’s not really much to do here, we just loop through all the registered commands and check if the button is down. We then update the state in the command. There are 4 states:

  • up (not pressed)
  • pressed (only the first frame of being pressed)
  • down
  • released (only the first frame)

As well as setting the state, which we might want to poll at a later date, we also call OnKeyPressed and OnKeyReleased on the command. It is pretty simple to extend this to call, for example, OnKeyDown every frame that the key is held down. So, shall we get this thing working? First of all, we need to add it to our game.

TutorialGame.h
class TutorialGame : public MGame, public Subject
{
public:
	TutorialGame();
	~TutorialGame();

	//----------------------------------------
	// MGame virtuals
	//----------------------------------------
	virtual void update();

	//----------------------------------------
	// Public interface
	//----------------------------------------
	GameClock*		GetGameClock();
	MessageSystem*	GetMessageSystem();
	InputManager*	GetInputManager();

private:
	GameClock		m_Clock;

	MessageSystem	m_MessageSystem;

	InputManager	m_InputManager;
};
TutorialGame.cpp
void TutorialGame::update()
{
	// tick the game clock
	m_Clock.Update();

	m_InputManager.Update();

	MGame::update();

	// quick message system test
	static Timer testTimer(0);

	if(testTimer.HasExpired())
	{
		testTimer.SetTimeMs(500);
		testTimer.Start();

		// also, if we've expired, send the message
		SendMessage(MESSAGE_TEST);
	}

	testTimer.Update(m_Clock.GetDeltaMs());
}
//----------------------------------------
MessageSystem* TutorialGame::GetMessageSystem()
{
	return &m_MessageSystem;
}

That’s pretty simple. Nothing out of the ordinary there. Now we want to change the tutorial behaviour to use the new input manager. I have to apologise because, for simplicity, right now, I’m just going to forward the call onto the message system, at a later date, we might wrap the whole thing into a macro to make this more pleasant. We don’t have to use the message system, and it makes it seem a redundant to have the command and the message system, but it will allow for a bit more flexibility as we can now use both systems at the same time, depending which is easier. As the command class will be so small, I’m just going to stick it into the source file.

TutorialBehaviour.cpp
REGISTER_MESSAGE(MESSAGE_SLIDE_START);
REGISTER_MESSAGE(MESSAGE_SLIDE_END);
class SlideCommand : public IInputCommand, public Subject
{
public:
	const char* GetKeyName() { return "SPACE"; }
	const char* GetCommandName() { return "BoxSlide"; }
	void		OnKeyPressed() { SendMessage(MESSAGE_SLIDE_START); }
	void		OnKeyReleased() { SendMessage(MESSAGE_SLIDE_END); }
};
SlideCommand slideCmd;
//...
void TutorialBehaviour::Setup(float step, float delta, float range)
{
	//...

	MEngine* engine = MEngine::getInstance();
	if(MGame* game = engine->getGame())
	{
		// make a new timer and start it ticking
		TutorialGame* tutGame = (TutorialGame*)game;
		//...

		tutGame->GetInputManager()->RegisterCommand(&slideCmd);
		slideCmd.AttachObserver(this);
	}
}
//----------------------------------------
TutorialBehaviour::~TutorialBehaviour(void)
{
	MEngine* engine = MEngine::getInstance();
	if(MGame* game = engine->getGame())
	{
		TutorialGame* tutGame = (TutorialGame*)game;
		//...
		tutGame->GetInputManager()->UnregisterCommand(&slideCmd);
		slideCmd.DetachObserver(this);
	}
}
//...
void TutorialBehaviour::update()
{
	//...
	// don't forget to add this to the class definition
	if(m_IsSliding)
	{
		m_CurrDeltaX += m_StepX;

		if( abs(m_CurrDeltaX) >= abs(m_RangeX) )
			m_StepX = -m_StepX;
	}
	//...
}
//...
void TutorialBehaviour::OnMessage(Message message, int param1)
{
	//...
	else if(message == MESSAGE_SLIDE_START)
	{
		m_IsSliding = true;
	}
	else if(message == MESSAGE_SLIDE_END)
	{
		m_IsSliding = false;
	}
}

There we go, all done. This is pretty simple really. I made the actual command as a global, purely for simplicity. The rest is pretty simple. When we get the Key Pressed, it gets translated to a SLIDE_START message, which sets the flag, and when we get the Key Released we send the SLIDE_END message, which unsets it. The actual logic, from then on, is practically the same as it was before. Compile and test it. If it all works like before, congratulations, it works. A bit disappointing when you do work and it behaves exactly as before, but hopefully you can see we have a more flexible solution now to continue.

Next time, we’ll make some more bahaviours and add some more functionality to the behaviours in order to make things a bit nicer. Then we’re almost ready to start making our game in earnest. Jump on the Maratis forums to let me know what you think so far.

<-Previous Next->

Discuss

Leave a Reply

Your email address will not be published. Required fields are marked *

Diary of a Slacker