Analysis and design of the use of deadzones
This is part of a multi-part series on input in game design. You can find the introduction here, with links to the other articles in the series
This article comes with a code example, if you just want a quick code reference click here.
Analog input is inaccurate. (Yes, I mean inaccurate, it can be imprecise too, but this article won’t help there. Click here to learn about the difference.) Game controllers are physical objects, with their flaws and manufacturing problems, this leads to readings in software that aren’t always the values you’d expect. Even the best-produced, expensive hardware only lessens the inaccuracies, but can never eliminate them.
Between two “high” values, this is rarely a problem, your player probably won’t notice that the 0.5 value isn’t quite half of the way on the travel of the joystick, they just know that they pushed it a bit and the game is reacting a bit. As long as pushing more increases the effect and pushing less decreases, the actual values being read don’t matter (that much).
Where this breaks down though, is at the zero point. If the player isn’t touching the thumbstick, and the camera is slowly panning or the character is moving, then we have a problem.
The solution is the “deadzone”. You’ve probably heard of this solution, most, if not all games that use analog input use deadzones. Most, if not all, small, amateur, and indie games get it wrong. (Unity, in its current implementation, September 2016, gets it wrong).
Single axis deadzone
The single axis deadzone is hard to get wrong. Your analog trigger, throttle lever, or other single-axis analog device, should give you a value between 0 and 1, or in the case of some devices that can move both ways, a value between -1 and 1, with a resting position at 0.
Here the solution is almost brutally simple. Easy mode is to just reject values below some threshold
Here we’ve simply mapped the values from 0 to 0.2 so that they equal 0.
There’s a small problem though, as we slowly push the trigger down, we’ll suddenly hit the threshold and jump to 0.2
This might be okay, depending on the situation, but it might feel janky when precision is needed in the lower values.
What we need to to is map the value of 0.2 to 0 while continuing to have 1 map to 1. To do this we need a little bit of subtraction and division.
Replacing our x with
(x-0.2)/(1–0.2) will do this nicely. (I’ll let the programmer nerds optimise this by storing values and converting divisions to multiplications)
Now we have a nice line that goes through all the values between 0 and 1, with a safe deadzone on the input.
Two axis deadzone
Joysticks and thumbsticks output their data as a pair of numbers, x and y, with numbers ranging from -1 to 1. The “easy” solution is to just apply single axis deadzones to both of these, and call it a day, right?
Well, no… (Although, this is what Unity does, it also doesn’t map the lower values). If we do this, then we run into a problem. Imagine we’re pushing the stick fully forwards to make the character run, and then we want to turn a bit to the right. If we’ve deadzoned both axes independently, then we’ll have to move the stick quite a bit to the right before it picks up. We fall into this weird situation where if we move the stick in a circle, we get a weird “stickyness” in the up, down, left and right positions.
Let’s go back to the intro to remind ourselves why we’re doing the whole deadzone thing again:
Between two “high” values, this is rarely a problem […]. Where this breaks down though, is at the zero point.
Running forwards full-barrel is hardly the “zero point”, is it? And running full-barrel slightly to the right should be taken into account because:
As long as pushing more increases the effect and pushing less decreases, the actual values being read don’t matter.
Moving the stick to the right of the fully forwards position counts as pushing more or less, we want to see a change in effect.
We need to take both the x and y values together. What we’re trying to filter out is the slight wobble when the stick is allowed to stay in its idle position, but any value outside of that needs to be taken into account. What we need is to know the distance of the stick from its zero point.
Remember the Pythagorean theorem? For those of you who don’t, this is quite simple maths, we don’t need to know why it works here, we just need the formula, it’s a really useful (arguably the most useful) formula in game development, so try to memorise it.
On a right angle triangle, the Pythagorean theorem states that the square of the hypotenuse (the long side, opposite the right angle, we’re calling it c), is equal to the sum of the squares of the two other sides (a and b).
We can write that: a²+b²=c²
What we want is a formula to find c so we flip the equation and take the square roots, which gives us:
c=√(a²+b²)
How does this help us? Well if we say that x=a and b=y, then we’ll find that the number c is the distance from zero. In mathematical terms, we can say that c is the magnitude of (x, y)
We’re going to use this value c to filter out unwanted inputs, and to avoid the “jolt” on the threshold, we can also use it to scale x and y without changing the “shape” of the input.
Step one, if c is lower than our threshold (we’ve used 0.2 in the single axis case, so let’s keep using it here), then just use (0, 0) instead of (x, y).
At this point we’ve successfully made a functioning deadzone, but if you use this, there will be a noticeable jank when you cross the 0.2 line.
Step two involves a little math again, but it’s super, super simple. We want to scale the values x and y. First we normalise (x, y), which means we change its size to have a magnitude of 1. We do this by dividing x and y by c, so we get: nx = x/c and ny = y/c
Then we modify c the same way we did in the single axis case:
c’ = (c-0.2)/(1–0.2)
Multiply our new (nx, ny) by c’: (nx×c’, ny×c’)
And send these values to the game…
Phew, that felt like a lot of math. If you’re not sure how to turn this all into code or you’re afraid you’ll get it wrong, you can use my JavaScript implementation, it’s general enough that you shouldn’t have too much trouble converting it to whatever language you use.
Conclusions
We’ve been using 0.2 as the threshold throughout this article, chances are, that’s a good value for you to use in your own game, but it’s not a magic number given to us by the gods. To figure out what number you really want, you’ll need to test the hardware your game will be running on. Ideally, this includes old, worn out, and off-brand hardware too. You want to find the smallest value you can where you never (or rarely) get any unwanted input. Be mean to your devices, shake them, flick the thumbsticks, and if you can afford to, maybe even damage them a bit. Not all of your players have a brand-new controller, and some of your players have little siblings, or worse, animals, that may have played with the controllers too!
This has been a rather mathy and technical episode of Input is hard. It’s not quite a design issue but since most modern games use analog input, it’s important to get it right. There will soon be other articles on analog input, mostly design-oriented though, so they should be a bit more fun to read!