Handling Player Input in Cross-Platform Games with LibGDX

LibGDX is an open source Java library for creating cross-platform games and applications. In my last tutorial I covered getting the library setup and ready for development. This tutorial will cover handling player input with LibGDX, bringing interactivity to your cross-platform games.

Different Types of Input States.

Handling input is a simple task. If a key is down, then that key should register as being true and likewise for when a key is up. This simplicity can lead to a lot of problems, especially for games. What you want is a simple way to ask libGDX if a specific key is either pressed, down, or released.

But what is the significance of these three different key states? They describe how a user interacted with their keyboard.

Pressed: A key was activated and it only triggers for one frame.

Down: A key is currently held down.

Released: A key was released and it only triggers for one frame.

If you had logic within the rendering function such as:

if (key.pressed())
print("pressed")
if (key.down())
print("down")
if (key.released())
print("released")

The Input Manager Class

There are many different ways you could implement this class, but I suggest you strive to be as ‘Object Oriented’ as possible which means every key and touch event will be it’s own object.

The first class to declare is the Input Manager itself (which will implement libGDX’s InputProcessor class).

public class InputManager implements InputProcessor {
}

If you’re following along from the previous tutorial and are using IntelliJ, right click within this class and click generate -> override methods. (Due to code bloat, I’m going to leave those generated methods out of these code snippets for now.)

Next, create an inner class called InputState.

NOTE: All three classes below will be inner classes of InputManager.

public class InputState {
public boolean pressed = false;
public boolean down = false;
public boolean released = false;
}

Now create the KeyState and TouchState classes which extend InputState

The TouchState class is more complicated due to not only having Pressed, Down, and Released events, but also storing which finger is in control of this object and storing coordinates and a displacement vector for gesture movements.

Here is the base structure of the entire class (excluding stub override methods):

Since every key/touch event will be it’s own object, you need to store these within an array. Add two new array fields to InputManager.

public Array<KeyState> keyStates = new Array<KeyState>();
public Array<TouchState> touchStates = new Array<TouchState>();

And add a constructor for InputManager to initialize these two objects.

public InputManager() {
//create the initial state of every key on the keyboard.
//There are 256 keys available which are all represented as integers.
for (int i = 0; i < 256; i++) {
keyStates.add(new KeyState(i));
}
//this may not make much sense right now, but I need to create
//atleast one TouchState object due to Desktop users who utilize
//a mouse rather than touch.
touchStates.add(new TouchState(0, 0, 0, 0));
}

Now you’re ready to control the logic of these objects utilizing the override methods generated. Starting with public boolean keyDown(int keycode).

@Override
public boolean keyDown(int keycode) {
//this function only gets called once when an event is fired. (even if this key is being held down)
//I need to store the state of the key being held down as well as pressed
keyStates.get(keycode).pressed = true;
keyStates.get(keycode).down = true;
//every overridden method needs a return value. I won't be utilizing this but it can be used for error handling.
return false;
}

Next is public boolean keyUp(int keycode).

@Override
public boolean keyUp(int keycode) {
//the key was released, I need to set it's down state to false and released state to true
keyStates.get(keycode).down = false;
keyStates.get(keycode).released = true;
return false;
}

Now that you have handled the logic for key state, you need a way to access these keys to check their states. Add these three methods to InputManager:

Everything is taking shape, so now is a good moment to try and explain how everything fits together.

libGDX represents every key as an integer used to grab an element from the keyStates array which returns a single keyState object. You can check the state of that key (Pressed, Down, or Released), which is a boolean.

You’re almost ready to test drive the InputManager but there a few more things to setup. Right now, the only state that is actually functional is the Down state.

When a key is Down. Down is set to true. And when a key is Up, Down is set to false. The other two states, Pressed and Released don’t work properly yet.

These are Trigger keys which should only trigger for one frame and then remain false. Now, once they’re activated, they continue to be True.

You need to implement a new method for InputManager called update which will correctly handle the states of Pressed and Released.

Within the MyGdxGame constructor, instantiate the InputManager. For InputManager to be useful, you need to pass it to libGDX’s InputProcessor and libGDX will use this new object to process input events. Replace the current create method with the below:

The InputManager is almost finished, all that’s left is implementing the logic for handling touch events. Luckily, KeyStates and TouchStates function in the same way. You will now be utilizing the generated touch event methods.

One of the main differences between KeyStates and TouchStates is the fact that all KeyStates get initialized within the constructor of InputManager due to a keyboard being a physical device.

You know all the available keys for use, but a touch event is a cross-platform event. A touch on Desktop means the user has clicked the mouse, but a touch on Android means the user has touched the screen with their finger. On top of that, there can only be one touch event on Desktop (mouse), while there can be multiple touches on Android (finger/s).

To handle this problem, add new TouchStates on-the-fly depending on what the user does.

When a user triggers a touch event, you first want to convert it’s screenX and screenY values to something more usable. The coordinate 0,0 is at the upper-left of the screen, and the Y axis flipped. To accommodate this add two simple methods to InputManager to convert these coordinates to make 0,0 at the center of the screen and the Y axis right-side up which will work better with a SpriteBatch object.

Now you need a way to check if this is a new touch event or if InputManager has already discovered this event and added it to the list of TouchStates. To clarify, the user is holding their device and touches the screen with their right-hand thumb, this will be the first touch event within touchStates and InputManager knows that it can handle at least one touch event. If the user decides to touch the screen with both left-hand and right-hand thumbs, the second touch event will instantiate a new TouchState and add it to touchStates on the fly. InputManager now knows it can process two touch events.

All this works from the pointer variable passed to this method. The pointer variable is an integer that allows you to distinguish between simultaneous touch events.

Every time a user fires a touch event, use pointer to check whether to create a new TouchState or not.

boolean pointerFound = false;

Loop through all available TouchState objects to check if this pointer already exists and set the states of this TouchState.

If one finger touches the screen, pointer is 0 which represents the one finger. Adding that new TouchState object to an array automatically set’s up the correct index value. It’s position in the array is 0 which is the same value as it’s pointer.

If a second finger touches the screen, it’s pointer will be 1. When the new TouchState is added to the array, it’s index value is 1. If you need to find which TouchSate is which, use the given pointer value to find the correct TouchState from touchStates.

Now handle the logic for when a finger is no longer touching the screen.

NOTE: I chose not to remove TouchState objects even if they’re no longer used. If a new finger count has been discovered, I think it should stay discovered and ready to be re-used. If you feel differently about this, within the touchUp method is where you would implement the logic for removing a TouchState.

Now calculate the displacement vector of a TouchState to handle finger gestures.

Game On

This Input Manager was designed for controlling entities within a game environment. I first designed the basics of this class a while back for ‘variable’ jump heights for my game character.

I later extended the class to handle input for Android with multiple touch events which is currently used in a game called Vortel. I needed a way to allow the user to control an entity with their fingers, no matter where their fingers were on the screen, and no matter how many fingers were on the screen at once. I achieved this affect by accumulating all displacement vectors from every TouchState, and then applying this new vector to the entity. If you have some free time, please check it out.

I used the variable jump height feature in another game called Ooze. I used the different states of TouchState (Pressed,Down, and Released) to accurately control how high the character jumps depending on how long the user is touching the screen.

I hope this tutorial was useful to you with your game ideas and look forward to your questions and comments below.