Fran Tufro

Input system

πŸ—“ 2017-01-21 ⏳ 5m.

Now that I talked about hot-reload and memory architectures, I want to start discussing the subsystems used in the game, starting with the Input system. READMORE Since I split the actual code from the memory layout of my systems, it makes sense to have said layout in header files, while the code is in .cpp files. In that way, I can include the header files both in the hot-reloadable code and in the executable code (this is only for development, I remove the hot-reload stuff when building a release version).

Let’s take a look at how the memory is laid out:

namespace Permanent
  namespace Input
    struct Data
      // Keyboard
      bool keys_down[KEYBOARD_KEY_COUNT];
      bool shift = false;
      bool control = false;
      bool alt = false;
      bool super = false;

      // Mouse
      F64 mouse_x;
      F64 mouse_y;
      F32 mouse_wheel;
      F32 mouse_wheel_buffer;
      bool mouse_down[3];

      // Controllers
      bool controller_connected[CONTROLLER_COUNT];
      bool controller_was_connected[CONTROLLER_COUNT];

      I32 controller_axis_count[CONTROLLER_COUNT];
      F32 controller_axis_status[CONTROLLER_COUNT][CONTROLLER_AXIS_MAX];
      F32 controller_axis_previous_status[CONTROLLER_COUNT][CONTROLLER_AXIS_MAX];
      F32 controller_axis_delta[CONTROLLER_COUNT][CONTROLLER_AXIS_MAX];

      I32 controller_button_count[CONTROLLER_COUNT];
      I32 controller_button_status[CONTROLLER_COUNT][CONTROLLER_BUTTON_MAX];
      I32 controller_button_previous_status[CONTROLLER_COUNT][CONTROLLER_BUTTON_MAX];

So, as you can see, the struct that holds the input is nothing more than a set of arrays that store the state of the input at a given frame.

I think this is pretty much self-explanatory, but just in case you don't follow it, there are three possible inputs: keyboard, mouse, and controllers.

For the keyboard, I just want to know if a key is being pressed (for now, maybe in the future I want other stuff, like for how long it has been pressed) so an array of bools is enough. Each key has a unique number that I assign in a define:

#define KEY_ESCAPE             256
#define KEY_ENTER              257
#define KEY_A                  65
#define KEY_B                  66
#define KEY_C                  67
#define KEY_D                  68
#define KEY_E                  69

I also created shortcuts for Shift, Control, Alt and Super, which are useful for the editor code.

As for mouse and controller, the idea is basically the same, only that for the mouse I care about the position, and state of the three buttons, and for the controller, I care about buttons and axes.

Now, regarding setting these values, this subsystem is a little odd mostly because the update code is not in Input::update as you'd expect, but below the Platform layer, because every platform has its way of dealing with input.

During this first pass on the game I'm using GLFW, which, as soon as I have all the subsystems in place, I'll ditch in favor of platform-specific code.

So here is the update code for the keyboard:

  void key_callback(GLFWwindow*, int key, int, int action, int mods)
    Permanent::Input::Data* input = permanent_memory->input;

    if (action == GLFW_PRESS)
        input->keys_down[key] = true;

    if (action == GLFW_RELEASE)
        input->keys_down[key] = false;

    input->control  = input->keys_down[KEY_LEFT_CONTROL]  || input->keys_down[KEY_RIGHT_CONTROL];
    input->shift    = input->keys_down[KEY_LEFT_SHIFT]    || input->keys_down[KEY_RIGHT_SHIFT];
    input->alt      = input->keys_down[KEY_LEFT_ALT]      || input->keys_down[KEY_RIGHT_ALT];
    input->super    = input->keys_down[KEY_LEFT_SUPER]    || input->keys_down[KEY_RIGHT_SUPER];

This callback is passed to GLFW and it's called every time a key is pressed. What I do here is so simple, I set the value for the specific key in the array to true or false depending on the state of the key. I also set the shortcuts for shift, alt, etc. I have been thinking about moving this last part of the code to Input::update to avoid repeating it in every platform and (as you'll see on a future post) in the input simulator, but for now it works just fine.

If you recall my previous posts, you may have noticed that my subsystems lie in the hot-reloadable code, while my platform layer doesn't. So it's nice to see both of these interacting thanks to the simplicity of the memory layout. permanent_memory is actually a global variable available to all the non-reloadable code, and includes pointers to all the subsystem's ::Data structs. In this case, we use the instance of Input::Data.

So... yeah, the memory is global, everything is one step away from blowing in the air, that's ok for me.

As I mentioned before, the code mouse and controller are pretty much the same, the only difference is in the data it sets. So I don't think it makes sense to show it. If you feel you want to see it please leave a comment and let me know.

All in all, I'm satisfied with this low-level structure for the input system. The only thing I have on top of my head is that for the actual game, I'll want another layer between the game code and the input system, mostly because I know that there are many different layouts for different brands and models of controllers, and I want to provide a unified API to the game, so that the game code doesn't need to think about the button/axis layout.

I'm still not sure how I will implement this, but I'm soon reaching the point where I have to solve this small problem.

I'd love to hear your opinions on the input system... do you have any suggestions? have you done this differently and think I'm screwing up?

The next post will be also related to input: I've created an input simulator system that will allow me to create integration tests and also record and play input from the user. This will be useful when I reach out people to test the game or for when I find a bug and want to write a test for it, so it doesn't happen again.