Many game engines implement inputs in a more OOP style: you have an object, that represents an input device, and you can query its state. Something like this:
class input_device {};
class mouse : public input_device
{
vector2 position() { /* ... */ }
bool get_key_state(key k) { /* ... */ }
// ...
};
But I don't like OOP so I don't like it.
Another way to do input is to send events to the game like this:
while(have_pending_events())
{
event *e = get_event();
switch(e->type)
{
case EVENT_KEYBOARD_W: // ...
case EVENT_KEYBOARD_A: // ...
case EVENT_KEYBOARD_S: // ...
case EVENT_KEYBOARD_D: // ...
// ...
}
}
I do not like this either. Do you know why? Because I already did it in the platform layer!
For example, this is the Win32 code that does the same thing:
MSG message;
while (PeekMessageA(&message, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&message);
switch (message.message)
{
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP:
{
uint32 virtual_key_code = (uint32) message.wParam;
bool32 alt_down = (message.lParam & (1 << 29)) != 0;
bool32 was_down = (message.lParam & (1 << 30)) != 0;
bool32 is_down = (message.lParam & (1 << 31)) == 0;
switch (virtual_key_code)
{
case 'W': // ...
case 'A': // ...
case 'S': // ...
case 'D': // ...
// ...
So you would just be translating one type of event into another, and that's just not what I want to do.
I like the other approach where you think of the input just as the data. You still store the state of an input device, but you do it in a format that is more like an answer to the question: "what happened during the last frame?"
And the answer to that question is, some buttons went up, some buttons went down, the mouse went in a certain direction, or the stick on the gamepad went in a certain direction.
I store this data like this:
struct button_state
{
uint32 transition_count;
bool32 is_down;
};
struct mouse_device
{
enum key_ {
LMB, MMB, RMB, // ...
KEY_COUNT
};
button_state keys[KEY_COUNT];
};
We can use this data however we want. We can advance our game forward, serialize it, store it as a file, send it over the network and it will be trivial to do, unlike the OOP example above.
The most common operation would be to determine whether a key was pressed or held during the frame. This distinction is very important. Some actions we want to do once, and then stop, like shooting a revolver. Other actions we want to continue after holding a key, such as shooting from a riffle.
I use three functions to do this:
uint32 get_press_count(button_state button)
{
uint32 result = (button.transition_count + (button.is_down > 0)) / 2;
return result;
}
uint32 get_release_count(button_state button)
{
uint32 result = (button.transition_count - (button.is_down > 0) + 1) / 2;
return result;
}
uint32 get_hold_count(button_state button)
{
uint32 result = (button.transition_count + (button.is_down > 0) + 1) / 2;
return result;
}
I don't think I can prove to you that these functions work, but I claim they do!
Let's look at the different situations:
The corresponding button_state
state is:
button_state{
transition_count = 2,
is_down = false
}
So these functions will produce the following results:
get_press_count(mouse[mouse::LMB]); // = (button.transition_count + (button.is_down > 0)) / 2
// = (2 + 0) / 2
// = 1
get_release_count(mouse[mouse::LMB]); // = (button.transition_count - (button.is_down > 0) + 1) / 2
// = (2 - 0 + 1) / 2
// = 1
get_hold_count(mouse[mouse::LMB]); // = (button.transition_count + (button.is_down > 0) + 1) / 2
// = (2 + 0 + 1) / 2
// = 1
Note that I count the one frame click as a hold, so events like shooting from a riffle or walking would also count!
get_press_count(mouse[mouse::LMB]); // = (button.transition_count + (button.is_down > 0)) / 2
// = (1 + 1) / 2
// = 1
get_release_count(mouse[mouse::LMB]); // = (button.transition_count - (button.is_down > 0) + 1) / 2
// = (1 - 1 + 1) / 2
// = 0
get_hold_count(mouse[mouse::LMB]); // = (button.transition_count + (button.is_down > 0) + 1) / 2
// = (1 + 1 + 1) / 2
// = 1
Here we have 0
releases which is makes sense.
get_press_count(mouse[mouse::LMB]); // = (button.transition_count + (button.is_down > 0)) / 2
// = (1 + 0) / 2
// = 0
get_release_count(mouse[mouse::LMB]); // = (button.transition_count - (button.is_down > 0) + 1) / 2
// = (1 - 0 + 1) / 2
// = 1
get_hold_count(mouse[mouse::LMB]); // = (button.transition_count + (button.is_down > 0) + 1) / 2
// = (1 + 0 + 1) / 2
// = 1
Here we have 0
presses, but have 1
release instead. Notice how in all three examples we had hold.
I claim that these functions scale to any number of presses and releases that happened in the frame.
Basically, that's how I do input. This is really easy to use, easy to implement. I just had to spend a little bit of time to figuring out these weird functions, but they work and using them is really easy. In game code I just do this:
if (get_press_count(input->keyboard[keyboard::ESC]))
{
// Code to do when ESC is pressed
}
All credit for this idea of storing input as data goes to Casey Muratori, this is the link you could see him doing this in Handmade Hero: https://handmadehero.org
I just spiced it up a bit with my little functions.