Saturday, 29 October 2011

The Bouncy Stick Problem

Awesomenauts has really fast controls: if you push the stick in a direction, your character immediately faces in that direction. Many games have smoothing animations in between, making the game look more realistic, and adding a bit more weight to the character's movement. However, this also slows down the game a bit and adds some delay to your input, since the character isn't immediately doing what you tell it to. For Awesomenauts we chose to have really quick controls and not do those in-between animations too much.

However, we discovered an interesting problem with reacting so quickly. It turned out that quite often, when a player walked to the right and then let go of the stick, he ended up facing to the left. Even though he was pressing right. Depending on the controller and the way the player handled the stick, this may never happen, or happen 30% of the times. When this happened it was quite annoying, especially when it happened in the shop: the player would accidentally buy a different item than he wanted once in a while.

So we investigated this issue and my colleague Machiel (of Paper Cakes fame) discovered what was happening: whenever you let go of the stick, it bounces back to its centre position. But the spring that causes this bounce is so strong, that the stick actually bounces beyond the centre position for a very short amount of time. This GIF animation shows what I am talking about:

Note that this doesn't only happen on the Playstation's controller: it also happens on the Xbox 360 controller.

So this explains why the player sometimes ends up looking in the wrong direction: for just one frame, the stick bounces back beyond the neutral position. Awesomenauts reacts to input so quickly, that this results in the player immediately turning around. In slower games, this would not be a problem, but in Awesomenauts, it is.

So I made some measurements on the exact output that the stick is giving each frame. In some cases when you let go of the stick, it just springs back to the neutral position. In other cases, it indeed bounces beyond that.

This doesn't always happen, though. So why is that? As you can see in the measurement, the spring back happens extremely quickly, usually within one frame. My guess is that the bounce beyond neutral actually always happens, but is so fast that it often happens in between two frames. So I can't always measure it and thus the game quite often isn't influenced by this bounce.

I tried this a lot of times, and it turns out that the stick can bounce quite extremely. When in the neutral stick position, the stick has position 0. All the way to the left is -1 and all the way to the right equals 1. Now when I let go with the stick all the way to the right (1), I have seen it bounce back to as far as -0.72 before getting back to 0. So that bounce is almost entirely to the left! This greatly depends on the controller, though: it seems like the bounce becomes worse when the controller is older, so apparently it increases with use.

The solution

So, how to solve this? The first thing that comes to mind is to increase the deadzone: ignore small stick input and hope the bounce falls within the deadzone and thus isn't used. However, since I sometimes measured a bounce of up to 0.72, I would have to increase the deadzone to almost the entire range of the stick, which means the game would only react to stick input when the stick is entirely to the right or left. Not cool.

Another solution also seemed reasonable: since the bounce happens for only one frame, an easy solution would be to ignore input that takes only one frame. However, the game cannot know this until one frame later. So to ignore single-frame input, I would have to introduce a one-frame delay in handling the player's controls. That sucks, because it takes away a little bit from the instant controls that make Awesomenauts play so well.

Now if you look at the graphs, you can see that the user never moves the stick all the way to the right in a single frame: he takes several frames to do this, even on 30fps. Users think they react instantly, but in practice they usually take around 0.1 seconds to move the stick in a certain direction. I tried this a lot and even when I tried to move the stick extremely fast, I am still only very rarely able to move the stick all the way to the right in a single frame. During normal use, this never seems to happen.

The evil bounce, however, always happens practically within a single frame. It is much faster than any real user input. So Machiel came up with a solution that uses this. I implemented his solution and it which works great:

When the change in the stick's position is bigger than 1.05, ignore it for one frame. *

This eliminates all the bounces that I have seen. And in my measurements, it practically never ignores or delays actual real player input. When the player moves from neutral to all the way to the right, that is a movement from 0 to 1. That is smaller than 1.05, and thus always used immediately. When the user moves all the way from the left to the right, so from -1 to 1, then he takes several frames for this. So the movement per frame is still less than 1.05, and is thus not ignored.

So, I implemented this, and we have not been able to reproduce the Bouncy Stick Problem in Awesomenauts ever since. Win! :)

* Edit: As Ben suggested in the comments below this post, it is probably best to use stick position 0 when the output is large, since using the previous frame adds some delay to stopping walking.