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.
