Hi all,
I was considering writing a short article on how i've handled my input in my game. The reason for this is simply that ive been told by many people that a lot of newbies approaching the problem of input make what could be classed as the "wrong choice" of either using directinput or GetAsyncKeyState to handle keyboard/mouse input.
Before i start or even spend any time writing an article however i am seeking feedback on the code i have written to see if anyone else here has any feedback about how they might do this differently and if it makes sense.
I am aware there are simpler and easier ways to handle input but after reading up on the subject, many ways shown in tutorials although simple make things such as keyboard remapping and handling of non-keyboard input devices such as gamepads difficult - i believe this approach will simplify such things as i get to them.
Each "command" which may be bound to a key press is defined by an object which inherits its class from a class called KeyCommand:
class KeyCommand
{
private:
bool keydown;
protected:
FireworkFactoryWindow* creator;
public:
KeyCommand(FireworkFactoryWindow* parent);
virtual void Action() = 0;
bool IsPressed();
virtual void Pressed();
virtual void Released();
};
The "Pressed" method is called whenever the key mapped to the command is pressed, e.g. attached directly to the WM_KEYDOWN event, while the "Released" method is mapped directly to the WM_KEYUP event. The default behaviour of the Pressed and Released methods is simply to set and clear the keydown bool.
The Action() method is called repeatedly from the game loop if and only if the key is currently depressed. This allows for either an ordered press/release event on the Pressed/Released method, or a simple edge triggered continually called event on the Action() method, for example for updating movement etc:
for (std::map<WPARAM,KeyCommand*>::iterator k = keymap.begin(); k != keymap.end(); ++k)
{
if (k->second->IsPressed())
k->second->Action();
}
Inside my message loop, just after the PeekMessage() function i check for WM_KEYDOWN and WM_KEYUP messages. As you can see i am also handling mouse events here but am not sure about this as it can introduce numbering conflicts, and might revisit this soon:
WPARAM Window::Loop()
{
MSG msg;
ZeroMemory(&msg, sizeof(MSG));
while (true)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
else if (msg.message == WM_KEYDOWN || msg.message == WM_LBUTTONDOWN)
this->KeyDown(msg.wParam);
else if (msg.message == WM_KEYUP || msg.message == WM_LBUTTONUP)
this->KeyUp(msg.wParam);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
this->Iteration();
}
}
return msg.wParam;
}
You might have noticed that i don't do this inside my wndproc. The main reason for not putting this inside the wndproc is because the Loop function is part of a window class, and the wndproc by its nature must be static, which means that i would be unable to call the KeyUp and KeyDown virtual methods from the static member function.
A std::map holds the current mappings between windows virtual keycodes and commands and is set and updated at changes of state so that different states may have different behaviours of keys:
keymap[VK_LEFT] = new LeftCommand(this);
keymap[VK_RIGHT] = new RightCommand(this);
keymap[VK_UP] = new UpCommand(this);
keymap[VK_DOWN] = new DownCommand(this);
keymap[VK_SPACE] = new SpaceCommand(this);
keymap[MK_LBUTTON] = new ClickCommand(this);
The KeyUp and KeyDown virtual functions handle the keypresses by notifying a class of the change of state by finding them in the map:
void FireworkFactoryWindow::KeyDown(WPARAM key)
{
std::map<WPARAM,KeyCommand*>::iterator k = keymap.find(key);
if (k != keymap.end())
k->second->Pressed();
}
... The KeyUp method is obviously very similar and i will not repeat it here for sake of brevity.
Each of the commands handles the expected behaviour for that key press in a simple and succinct way, for example:
void DownCommand::Action()
{
float speed = 0.08f;
Camera* c = creator->GetCam();
c->SetDistAndPitch(c->GetDist() - speed * 3.0f, c->GetPitch() + (speed / 4.0f));
}
This about sums it up.
I would appreciate firstly any feedback on this code, and how it operates as i am always looking to improve, and secondly if you think this might make useful article material if presented correctly,
I await your replies!