handling input via windows messages - feedback requested

Started by
37 comments, last by Irlan Robson 9 years, 3 months ago

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!

Advertisement


You might have noticed that i don't do this inside my wndproc.

It appears you're now processing messages in 2 different routines. Rather than maintaining two separate sections of code which have the same purpose, an alternative is to stuff a pointer to the class instance into GWL_USERDATA (part of the window structure) when the window is created, and dispatch messages from the static window procedure to a class message procedure - which does all message processing.

I.e.,


// create the window, then ...
SetWindowLongPtr( hWnd, GWL_USERDATA, (LONG)this );
...
// the static procedure then dispatches to the class, allowing class functions to be called
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    MyClass* myClass = (MyClass*)GetWindowLongA(hWnd, GWL_USERDATA);
    if (myClass != NULL) return myClass->MsgProc(hWnd, message, wParam, lParam);

    // else default processing - creation, etc.
    switch (message)
    {
      ...
         default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
...
// define the class message processer
LRESULT MyClass::MsgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
       case WM_KEYDOWN:
            keyDown( ... );
            break;
       case WM_whatever:
            // call MyClass functions and routines as desired
...

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

There are at least 2 major flaws with your approach: You are triggering events on key events.
I describe what is wrong with that here.
As such, KeyCommand shouldn’t exist. WM_KEYDOWN (etc.) events should do nothing but send data to a buffer to be processed later (at a very specific controlled time within your game loop).

The 2nd problem is that you have no log of inputs. Very many games are not purely reactionary to key-downs and key-ups. Fighting games require a log of inputs to scan for valid key combinations to perform moves, for example. You should think of the relationship between the WM_* window messages and the game as client/server. The server gathers the data and then sends (by request) it to the client (the game). The game (client) should log what it needs (as in, don’t complicate the windows message loop or the buffer it should be using with logging—that also has to be done at a certain point in the game, during the hand-off of events).

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.

Use SetWindowLongPtr() to give the Window an instance pointer which the static function can access, then defer all instance-based actions to that.

Buckeye gives more details on this, but it is important to use GetWindowLongPtr(), not GetWindowLong().

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:

Keys should be enumerated and have specific ID’s. A mapping feature only needs to have an array where there is one integer for each key, and that integer is the value of the remapped key. m_uiKeyMap[KK_ENTER_KEY] = KK_0_KEY;.

For much much more information and detailed explanations:

http://www.gamedev.net/topic/641704-help-with-my-input-sytem/#entry5054008

http://www.gamedev.net/topic/641102-frame-independent-movement-and-slowdowns/#entry5049391

http://www.gamedev.net/topic/664362-time-stamped-buffered-input-system/#entry5202649

http://www.gamedev.net/page/resources/_/technical/game-programming/designing-a-robust-input-handling-system-for-games-r2975

L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

I've just written an article (with L. Spiro's idea) about that. You can find here.

A question for Spiro, after re-reading some of the linked posts/topics.
In this reply to Hodgman you mention that on the input thread you should be using WaitMessage() (and i assume you can also use GetMessage()). I read that as saying "instead of peeking all the time and running as fast as possible, use a blocking call and let windows manage how fast the thread runs while still having an accurate enough timestamp". My question here is, what if you have different input devices, such as gamepads, that need to be polled?

The way i did this in my code is, i PeekMessage() in a while loop, and after the loop i poll the gamepads via XInput. If the state for the gamepad changed, i generate input messages that the game can read (and which is the same struct used for mouse/keyboard input, only different data) and put them into an input buffer that the game polls for that frame/logical tick.

Is there a different/better way to handle other input devices that are purely poll based and don't have WM_* events? This question assumes you can't get gamepad/other input device data via WM_* messages, since i'm not sure if WM_INPUT can be used to at least trigger a poll of those devices.

devstropo.blogspot.com - Random stuff about my gamedev hobby

You should use WM_INPUT/raw input for gamepads, touch-screens, etc.

http://www.codeproject.com/Articles/185522/Using-the-Raw-Input-API-to-Process-Joystick-Input

http://msdn.microsoft.com/en-us/library/windows/desktop/ms645543(v=vs.85).aspx

L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid


You might have noticed that i don't do this inside my wndproc.

The solution to this problem it's to use ::GetWindowLongPtr and ::SetWindowLongPtr to change the window procedure after you receive a WM_CREATE message after the window creation:


LRESULT CALLBACK CWindow::OnCreateWndProc(HWND _hWnd, UINT _uiMsg, WPARAM _wParam, LPARAM _lParam) {
	if (_uiMsg == WM_CREATE) {
		::SetWindowLongPtr(_hWnd, GWL_USERDATA, reinterpret_cast<LONG>(reinterpret_cast<LPCREATESTRUCT>(_lParam)->lpCreateParams));
		::SetWindowLongPtr(_hWnd, GWL_WNDPROC, reinterpret_cast<LONG>(WndProc));
		return 0LL;
	}

	return ::DefWindowProc(_hWnd, _uiMsg, _wParam, _lParam);
}

LRESULT CALLBACK CWindow::WndProc(HWND _hWnd, UINT _uiMsg, WPARAM _wParam, LPARAM _lParam) {
	return reinterpret_cast<CWindow*>(::GetWindowLongPtr(_hWnd, GWL_USERDATA))->WindowProc(_hWnd, _uiMsg, _wParam, _lParam);
}

You should use WM_INPUT/raw input for gamepads, touch-screens, etc.

You should use WM_INPUT for high-frequency input devices and you should avoid unnecessary dynamic allocation of its buffer when you poll that. At window initialization after you register what are the devices that need to be polled with WM_INPUT, you can create dynamic array of RAWINPUT with its size being something like sizeof( RAWINPUT) * 4. If you see that you have received a WM_INPUT message buffer size bigger than that, you can re-allocate more memory once instead of re-allocating each time (which can be very consuming) and keep that information on the window (or related) class. Example (for mouse):


case WM_INPUT: {
                /*
                * m_prRawInputBuffer was created at initialization.
                */
		UINT uiRawInputBufferSize;
		::GetRawInputData(reinterpret_cast<HRAWINPUT>(_lParam),
			RID_INPUT,
			NULL,
			&uiRawInputBufferSize,
			sizeof(RAWINPUTHEADER));

		if (uiRawInputBufferSize > m_ui32RawInputBufferSize) {
			delete m_prRawInputBuffer;
			m_ui32RawInputBufferSize = uiRawInputBufferSize / sizeof(RAWINPUT);
			m_prRawInputBuffer = new RAWINPUT[m_ui32RawInputBufferSize];
			::OutputDebugStringA("\nGetRawInputData(...) returned buffer size different of current size. Re-allocating.");
		}

		if (!::GetRawInputData(reinterpret_cast<HRAWINPUT>(_lParam),
			RID_INPUT,
			reinterpret_cast<void*>(m_prRawInputBuffer),
			&m_ui32RawInputBufferSize,
			sizeof(RAWINPUTHEADER))) {
			::OutputDebugStringA("\nGetRawInputData(...) failed.");
		}

		if ( m_prRawInputBuffer->header.dwType == RIM_TYPEMOUSE ) {
			const RAWMOUSE& rmRawMouse = m_prRawInputBuffer->data.mouse;
			if (rmRawMouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN) {
				//Handle it.
			}
			else if (rmRawMouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP) {
				//Handle it.
			}
			else if (rmRawMouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN) {
				//Handle it.
			}
			else if (rmRawMouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP) {
				//Handle it.
			}
			else {
				//Mouse move. Handle it.
			}
		}

There should be another way of handling the problem, but that was more intuitive and faster for me, so, feel free if you want to search for a more general solution.


Keys should be enumerated and have specific ID’s. A mapping feature only needs to have an array where there is one integer for each key, and that integer is the value of the remapped key. m_uiKeyMap[KK_ENTER_KEY] = KK_0_KEY;.

Complementing what L. Spiro said, a window class shoudn't know about keys/buttons that will be used in your game/application. On Windows®, the only thing that it kowns it's VK_* code, which you map that after pass it into the game/application input buffer.

In L. Spiro example the virtual key codes are enumerated (I believe) to KK_ENTER_KEY, KK_SPACEBAR_KEY, etc, and the application/game/engine-side keys are KK_0_KEY, KK_1_KEY, and so on.

Here, I've used the _wParam (which it's the VK_* for a WM_KEY* message) directly as an example, but the window-side input buffer has a buffer of keys which its codes are VK_* codes. After you pass that into the application-side, at specific point, you map into your application-side keyboard keys. Then, you have mapped keys and you can start re-mapping these to the application input-contexts or logging that with it's time of duration, etc (which I'd recommend).

Independent how you manage input (synchronously or asynchronously), you need to have this kind of separation not only for organization but cross-platform support too. Generally people tend to use synchronously input because they only think as FPSs, but today any type of game requires a lot of complexity in its input system, and doing it synchronized with the game simulation it's the only way of measuring input the fastest we can at any time using time-stamped buffered inputs, that allows we hold a button for 100ms at any lag time and compensate that in logical updates for robust input logging.

In L. Spiro example the virtual key codes are enumerated (I believe) to KK_ENTER_KEY, KK_SPACEBAR_KEY, etc, and the application/game/engine-side keys are KK_0_KEY, KK_1_KEY, and so on.

There is only 1 enumeration.
A less-confusing example:
m_uiKeyMap[KK_ENTER_KEY] = KK_SPACE_KEY;

L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

A less-confusing example:


m_uiKeyMap[KK_ENTER_KEY] = KK_SPACE_KEY;
L. Spiro

I am only more confused by this example, since now it appears you're mapping the 'Enter' key to the 'Space' key... unless I'm missing something?

In the input system I just finished refactoring, the mapping flow is: platform-specific "virtual" key (i.e. VK_A, VK_B, etc.), to platform-agnostic "virtual" key (VIRTUAL_KEY_A, VIRTUAL_KEY_B, etc.), to game-specific command (COMMAND_JUMP, COMMAND_CROUCH, etc.). This seems to neatly handle every input scenario we support, and that I can think of.


In the input system I just finished refactoring, the mapping flow is: platform-specific "virtual" key (i.e. VK_A, VK_B, etc.), to platform-agnostic "virtual" key (VIRTUAL_KEY_A, VIRTUAL_KEY_B, etc.), to game-specific command (COMMAND_JUMP, COMMAND_CROUCH, etc.). This seems to neatly handle every input scenario we support, and that I can think of.

I'm using a similar way too. When a input is received I keep its raw key code along with a time-stamp to process it later, then, when it's time to process I map those to a engine-side recognized device key code, that can be mapped later to a game-side key code in the input context or logger.

I believe L. Spiro took a shortcut by enumerating the keys once without changing its overall architecture.

This topic is closed to new replies.

Advertisement