#Himalaya: Need Input

A few weeks have passed and the Himalaya game engine has grown a lot. Sorry for not posting earlier but I was busy with so many things at once that I didn’t really know what to write about. However, from the many features that were added to the framework, let’s focus on the one that handles user input.

c308d88f9e24a0e87e70925ad19c39c2cae411a95aa5312b9f0e3c671739ccfe

Just like Johnny 5 in the late 1980s movie, most games need input to function. Luckily, XNA/Monogame already has us covered with a few classes that handle keyboard, mouse and gamepad input. Using only the vanilla Monogame framework, you’d typically do something like this to make a character jump:


// Get the current state of the keyboard.
KeyboardState currentKeyboardState = Keyboard.GetState();

// Get the current state of the first gamepad.
GamePadState currentGamePadState = GamePad.GetState(PlayerIndex.One);

/* Check if the spacebar is pressed in this frame and was not pressed
in the previous frame or if the "A"-Button is pressed in this frame
and was not pressed in the previous frame. */
if((currentKeyboardState.IsKeyDown(Keys.Space) &&
    previousKeyboardState.IsKeyUp(Keys.Space)) ||
    (currentGamePadState.IsButtonDown(Buttons.A) &&
    previousGamePadState.IsButtonUp(Buttons.A))
{
    // Call the jump method we defined somewhere.
    Jump();
}

/* Update the variable that stores the previous keyboard state,
which we declared as a field of this class for example. */
previousKeyboardState = currentKeyboardState;

// Same thing goes for the gamepad's state
previousGamePadState = currentGamePadState;

And this works just fine, of course. But in many cases, we don’t want the controls to be static. We’d rather have a generic “Jump”-button that substitutes for some button that can be changed at runtime. Also, we don’t want to keep track of the state of our input devices all the time. We want to simply be able to tell, if a certain button is pressed, released or held down. Let’s assign all the dirty work to someone else.

So I figured I’d design a system, that makes it really simple to create controls for a game. My idea was to enable the developer to define game controls by a name and sort of map them to different keys or buttons. So in our example, the dev could create a game control named “Jump” and map it to the spacebar and the “A”-button on the gamepad alternatively.  Well, sometimes code is worth a thousand words, so here you go:

public GameControl RegisterControl(string name, Keys key, Buttons button, Keys alternativeKey, Buttons alternativeButton, bool doRepeat = false, float repeatInterval = GameControl.DEFAULT_REPEAT_INTERVAL)
{
    // do magic
}

This method is part of the class “ControlSettings” which basically holds a map of all game controls registered. You can use this class to set up a complete set of controls and then request their state from somewhere else in your code. When you call this method to register a game control, you pass it the name of the new control and the key(s) and/or button(s) it should be mapped to. Optionally, you can also configure the control to be retriggered at a certain time interval. There are multiple overloads of this method for when you only need to assign one key or one button and so on. An instance of ControlSettings needs to be attached to a “ControlListener”, which will internally use XNA’s input system to update the state of each “GameControl”. For checking the state of a control, the ControlListener provides the following methods:


public bool GetButtonDown(string controlName) => KnowsControl(controlName) ? Settings.ControlMap[controlName].IsDown : false;
public bool GetButtonUp(string controlName) => KnowsControl(controlName) ? !Settings.ControlMap[controlName].IsDown : false;
public bool GetButtonPress(string controlName) => KnowsControl(controlName) ? Settings.ControlMap[controlName].IsPressed : false;
public bool GetButtonRelease(string controlName) => KnowsControl(controlName) ? Settings.ControlMap[controlName].IsReleased : false;

KnowsControl, as you might have guessed, simply checks whether a GameControl with the given name was registered to the ControlSettings attached to this ControlListener. So if we were to check if a control, that does not exist, is pressed for example, it will always return false. Aside from these methods, there are also events that are raised when a control is pressed and released respectively.

When you create a ControlListener, you need to define which gamepad (1 – 4) it should process the input of. This way, you can create different control settings for each individual gamepad. So let’s get back to the example from earlier and use this system. First we need to create the settings and the listener:


// Create a new instance of ControlSettings
ControlSettings settings = new ControlSettings();

// Register a control named "Jump" that responds to space and the "A"-Button
settings.RegisterControl("Jump", Keys.Space, Buttons.A);

// Create a control listener and pass it the control settings
ControlListener listener = new ControlListener(PlayerIndex.One, settings);

Then, provided we call the Update method of our listener each frame, we can do this:


if(listener.GetButtonPress("Jump"))
{
    Jump();
}

Or, for an event-based approach, we could subscribe to the “ButtonPressed” event and go:


private void OnButtonPressed(object sender, GameControlEventArgs e)
{
    if(e.ControlName == "Jump")
    {
        Jump();
    }
}

giphy

But, wait! There’s more!

Aside from those simple game controls that keep track of whether they are up, down, pressed or released, there are also a special kind of game controls that store a value ranging from -1 to 1. The class is called “GameControlAxis”. A GameControlAxis is registered in a similar manner to a GameControl:


public GameControlAxis RegisterControlAxis(string name, AxisDirection direction,Keys positiveKey, Keys negativeKey, Buttons positiveButton, Buttons negativeButton, Keys alternativePositiveKey, Keys alternativeNegativeKey, Buttons alternativePositiveButton, Buttons alternativeNegativeButton, GamePadAxes gamePadAxis, float deadzone = GameControlAxis.DEFAULT_DEADZONE)
{
    // sorcery and wizardry
}

Instead of just mapping one key/button to the control (not counting the alternatives), we can map two keys and/or buttons to it; one that increases the axis’ value and one that decreases it. The parameter direction can be either “AxisDirection.Horizontal” or “AxisDirection.Vertical”. It is needed for when we map the control to a control stick on a gamepad to determine whether to pick up its horizontal or vertical offset. This is also what the optional deadzone is for. Usually, you don’t want to pick up a value from a stick that is only very slightly off-center or a shoulder button that is only marginally pressed down. Deadzone defines a threshold that has to be reached in order for a value to be picked up.
To access the value of a GameControlAxis, we simply call listener.GetAxisValue(“someName”). A GameControlAxis is especially handy when it comes to movement controls. We could register a GameControlAxis called “Horizontal” with the keys “A” and “D” or the left and right arrow keys mapped to it. Then when we want to move our character horizontally, we just add to their x-coordinate the character’s speed multiplied by what “listener.GetAxisValue(“Horizontal”) returns.

This whole system draws some inspiration from the Unity game engine, which I’m sure you already noticed if you’re familiar with it. I just think this is a very handy and intuitive way to handle user input.

So, that’s it for today, guys! As you know, the source code of Himalaya is available in my GitHub repo! Next time we’ll look into some of the other features that I’ve been working on during the last couple weeks.