Responsive Player Controls: Stack-Based Input Recording

Responsive player controls that react quickly and accurately to user input are very important for any game. They are especially critical for fast-paced action games like BEAT DRIFT, where split second decisions can mean the difference between life or death.

In this post, I will outline a simple stack-based input recording system that was used to give BEAT DRIFT more responsive player controls. Although BEAT DRIFT is currently only available on mobile touchscreen devices (iOS and Android), this technique is applicable to almost any game on any platform.

First, let’s look at a naive approach to capturing player input. For simplicity, we’re using ‘keys’, but the idea is the same on other devices (touchscreen, controllers, etc):

1

2

3

4

5

6

7

8

9

10

voidUpdate(){

// Is left key currently down?

if(IsKeyPressed(Key.Left)){

MoveLeft();

}

// Is right key currently down?

elseif(IsKeyPressed(Key.Right)){

MoveRight();

}

}

There is a major problem with this code: while the left key is down, the right key will never be processed.

Why is this a problem? In fast-paced games where the player is quickly switching between pressing the left/right keys, there will be brief periods of time where both keys are simultaneously pressed. With the above code, the left key will always have priority over the right key. During these times, the right key will feel slightly unresponsive and laggy. This is exactly what we’re trying to avoid.

We can solve this problem by changing how we assign priorities to the inputs. In the above code, the priorities of the inputs are ‘hardcoded’ by the order of the if-statements. Since the Key.Left if-statement is written first, left will always have a higher priority than right.

A better implementation will give priority to the most recent input.

Why the most recent input? The most recent input is the most important input because it’s the one that the player will notice most if it gets ignored.

In BEAT DRIFT, we track not only the most recent input, but all the current inputs and the order they are received. This gives us a very robust system, since we now have a history of the current inputs, and the priority of each of them. This is useful, for example, when the player stops pressing the ‘most recent’ key, since we can now fall back to the ‘next most recent’ key input.

It just so happens that a stack is perfect for implementing this type of system. Stacks are LIFO (Last-In-First-Out) data structures, which means that the last item added (pushed) to the stack will be the first item removed (popped) from the stack.

The code below shows briefly how this could be implemented:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

Stack<Key>_inputStack;

voidRecordInput(Key key){

_inputStack.Push(key);

}

voidUpdate(){

// Record all input to our stack.

{

// - Call RecordInput() for ALL KEYS that are pushed down this frame.

// - Remove all keys from the stack that are released (up) this frame.

// - Processing order does not matter, because the events are starting in the same frame.

}

// Process most recent input (top item on stack).

{

if(!_inputStack.IsEmpty()){

// Get the most recent key press without removing it from the stack.

// It will be removed from the stack when the key is actually released.

Key mostRecentKey=_inputStack.Peek();

// Process the key.

// The order of these if statements doesn't matter, since it is guaranteed

// that we are already processing the highest priority key.

if(mostRecentKey==Key.Left){

MoveLeft();

}

elseif(mostRecentKey==Key.Right){

MoveRight();

}

}

}

}

This system is not limited to just left/right inputs – it can work in games with an arbitrary number of directional inputs.

It also allows us to handle multi-touch input robustly without changing any of the logic. In BEAT DRIFT, even though we only have 2 input types (left and right), our stack has a capacity of 5. This allows us to handle up to 5 fingers on the touchscreen at once, and gives us the order that these fingers touched the screen. When the ‘most recent’ finger is removed from the screen, we remove it from the stack, and process the ‘next most recent’ finger.

Through a simple stack-based input recording system, we’ve outlined how player controls for games can become more responsive, which is especially important for action games such as BEAT DRIFT.

While there are many other elements that go into making a truly good input system, hopefully this provides one technique that can be put to use immediately in other games.

To answer a question I received: in practice, this is often implemented with a simple list that behaves like a stack.

A true stack limits which items you can remove. Each frame, you will remove keys that are released (up) during that frame, but a stack only allows removing (popping) the top item. By using a simple list or array, you are free to remove items at arbitrary positions.

Aside from removal, the rest of the behavior is very similar to a stack, so that is why it is described as such.

That’s all terrific, but I’m just curious where I might find instructions on how to play the iOS version? Or since there doesn’t seem to be a practice mode, I can just keep running into walls for another minute or two? I’d prefer any insight or instructions though.

We will be adding instructions (along with some other features) to an update coming soon!

In the meantime, here is a brief explanation:
– Touch the left side of the screen to move clockwise*.
– Touch the right side of the screen to move counterclockwise*.
– Avoid the solid blocks!
– Survive for 60 seconds to clear each level.

Additional info:
– Levels get harder every 15 seconds.
– You can select levels from the level select screen. Currently there are 6 levels, each with different music and difficulty.
– The game is supposed to be very challenging, and you are supposed to die a lot. Keep practicing

* You can reverse the controls by tapping the U-shaped arrow in the level select menu.