Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
September 2, 2014
arrowPress Releases
September 2, 2014
PR Newswire
View All
View All     Submit Event





If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 
Doing Thumbstick Dead Zones Right
by Josh Sutphin on 04/16/13 12:40:00 pm   Expert Blogs   Featured Blogs

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

(This article originally appeared at third-helix.com)

As OUYA Kickstarter backers begin receiving their dev units, I’ve seen several discussions pop up about thumbstick dead zones. Unfortunately most of the advice I’ve seen is pretty bad, so I thought I’d share some simple techniques I’ve learned over the last six years working on major PS3 titles Warhawk and Starhawk.

(Note: The following code samples are in C# and based on Unity, but the basic principle should be clear enough to adapt to whatever language/API you’re working within.)

What’s A Dead Zone?

Skip this section if you already know. For the rest of you, here’s a quick primer!

Analog thumbsticks typically send input to your code in the form of two numbers: one for the X (horizontal) axis, and one for the Y (vertical) axis. Usually the number ranges from −1 (fully extended one direction) to +1 (fully extended the opposite direction), where 0 is dead-center. The assumption is that if you’re not touching the stick, it’ll return (0, 0).

In reality, though, thumbsticks vary in quality and wear out over time. You’ve probably used a gamepad at some point that had a loose or “wiggly” stick; in that case, the neutral position is just a little bit off from (0, 0), even though you’re not touching the stick. To your code, that’s indistinguishable from the player pushing the stick just a tiny, tiny bit.

Dead zones are simply a minimum input threshold, often somewhere between 0.1 to 0.2. If the input received from the stick is smaller than that, it’s ignored.

Have you ever played a game where the camera moved or rotated very slowly of its own accord, even though you weren’t touching the stick at all? That’s a case of a missing (or too-small) dead zone. (Curiously, I see this issue in a lot of Xbox 360 first-person shooters.)

So to sum up: dead zones prevent unexpected input from loose thumbsticks, which makes players happy. :)

The Naïve Way — Axial Dead Zone

Okay, let’s start with a look at the naïve implementation of a dead zone. This is the method everyone jumps to first, because it’s the most immediately intuitive:

float deadzone = 0.25f;
Vector2 stickInput = new Vector2(Input.GetAxis(“Horizontal”), Input.GetAxis(“Vertical”));
if(Mathf.Abs(stickInput.x) < deadzone)
    stickInput.x = 0.0f;
if(Mathf.Abs(stickInput.y) < deadzone)
    stickInput.y = 0.0f;

Simple enough: if our input magnitude in either direction is less than our dead zone, we simply zero out the input in that direction, and that’s all there is to it… right?

Well, here’s a diagram of what this kind of dead zone looks like. The circle represents rotation space of the thumbstick (it’s the same as circular opening in your controller that the stick is seated in), and the red shaded area represents where the dead zone will kick in and cancel out your input:

axial-deadzone

In practice, this implementation feels very bad, and you’ll notice it whenever you try to rotate the stick in a sweeping motion (which is a really common gesture in first-person shooters). What happens is, as you rotate the stick across one of the cardinal directions — anywhere within the red shaded area — you’ll feel it “snap” to the cardinal. If you’re making a game that’s all about 2D four-directional movement (maybe a Bomberman clone or something) then that’s great, but for anything requiring analog precision (like a first-person or twin-stick shooter) this is nowhere near accurate enough.

A Better Way — Radial Dead Zone

Fortunately it’s really easy to get rid of the cardinal-direction snap. We simply test the magnitude of the entire input vector, rather than testing each axis separately:

float deadzone = 0.25f;
Vector2 stickInput = new Vector2(Input.GetAxis(“Horizontal”), Input.GetAxis(“Vertical”));
if(stickInput.magnitude < deadzone)
    stickInput = Vector2.zero;

This is much better. For many games, you could probably ship with this; in fact, this method is the most common one I’ve seen people propose recently. Here’s what that dead zone looks like on the stick:

radial-deadzone

When we think about dead zones, this is usually the kind of thing we’re envisioning: a very small area in the center of the stick within which input is ignored. The size of the area is simply our best guess at how far a loose, worn-out stick is likely to wiggle on its own, without physical input.

The High-Precision Problem

If you’re making a first- or third-person shooter, odds are you need all the input precision you can get. The previous method covers you for large movements, but you’ll find a flaw when you try to make very fine, low-magnitude adjustments (like aiming a sniper rifle). As you slowly push the stick away from neutral, you’ll feel the edge of the dead zone as your aim suddenly “kicks” into motion. This doesn’t feel smooth, and can make high-precision gameplay feel extremely tedious in a way that can be hard to define.

The problem with the previous method is that it’s clipping the input vector below the dead zone, which means all the precision that exists inside the dead zone is completely lost. In other words, you can’t smoothly ramp your input from 0 to +1 any more; instead you snap from 0 to +0.2 (or whatever your dead zone is), and then you ramp from +0.2 to +1.

Here’s an illustration:

precision-problem

The gradient indicates the strength of the resulting input (after the dead zone is applied). Note that the edge of the dead zone is clearly visible: as you push the stick away from the center, the gradient value changes suddenly, not smoothly, at that edge.

The Right Way — Scaled Radial Dead Zone

Fortunately, the high-precision problem is also very easy to fix. We just need to rescale the clipped input vector into the non-dead zone space:

float deadzone = 0.25f;
Vector2 stickInput = new Vector2(Input.GetAxis(“Horizontal”), Input.GetAxis(“Vertical”));
if(stickInput.magnitude < deadzone)
    stickInput = Vector2.zero;
else
    stickInput = stickInput.normalized * ((stickInput.magnitude - deadzone) / (1 - deadzone));

Here’s what the adjusted dead zone looks like:

scaled-radial-deadzone

Notice that there’s no longer a visible edge: as you push the stick away from the center, the gradient value changes smoothly while the dead zone is still preserved. This feels buttery-smooth, just as God intended. ;)

Dead Zones For Fun and Profit

I called it the “right” way but that doesn’t mean you’ll never use any other method, ever. The most important thing is to use the method that makes sense for your particular project. Here are a few scenarios:

  • Tile-based (4-way) movement: The Axial Dead Zone actually works well here since it snaps analog input to the only four input vectors that are actually relevant.
  • Twin-stick shooter: In these games the magnitude of input rarely matters — all you care about is direction — so the simple Radial Dead Zone should be perfectly suitable here.
  • Super-polished FPS: Sometimes you need to sweep your aim through a line, and keep the crosshair on or close to the line. In this case you might want to blend a Scaled Radial with a modified Axial Dead Zone, such that the stronger your input in one axis, the larger the dead zone gets for the other axis. (At LightBox we called this the “bowtie” because the dead zone diagram looks like… a bowtie. I’ll leave the implementation of this one as an exercise for the reader!)

Now go forth and implement your dead zones properly! It’s easy, and your players will appreciate it. :)

P.S. For what it’s worth, I’ve noticed that the OUYA controller seems to require a larger dead zone than the Xbox 360 controller. I had to go up as high as 0.25 to get a new, unworn OUYA controller to sit reliably at neutral, while a new, unworn Xbox 360 controller was fine around 0.1.

(Josh Sutphin is an indie game developer, former lead designer of Starhawk (PS3), and creator of the Ludum Dare-winning RTS/tower-defense hybrid Fail-Deadly. He blogs at third-helix.com and tweets nonsense at @invicticide.)


Related Jobs

Vicarious Visions / Activision
Vicarious Visions / Activision — Albany, New York, United States
[09.02.14]

VFX Artist-Vicarious Visions
Vicarious Visions / Activision
Vicarious Visions / Activision — Albany, New York, United States
[09.02.14]

Animator-Temporary-Vicarious Visions
Cloud Imperium Games
Cloud Imperium Games — Santa Monica, California, United States
[09.01.14]

Technical Animator
Blizzard Entertainment
Blizzard Entertainment — Irvine, California, United States
[09.01.14]

Heroes of the Storm - User Interface Artist






Comments


Dan Porter
profile image
Impressive and succinct! Thanks for including the math & images. This one is definitely going on the cheatsheet.

Dave Long
profile image
Great article/blog, thanks :).

Steven Christian
profile image
"So to sum up: dead zones prevent unexpected input from loose thumbsticks, which makes players happy."

Actually, deadzones make me very upset. Like having a loose steering wheel, or a mouse with gunk stuck on the little wheels next to the mouseball, or a mouse that goes to sleep and doesn't register the first second of input.

I would prefer no deadzone, and if my character/view starts drifting, it means that i need a better controller ;)

Andy Wallace
profile image
Any analog input is going to be a bit noisy. Smoothing that data is almost always a good thing, and that's what dead zones do. As long as the design isn't over zealous with it, dead zones are helpful even with good controllers.

Kenneth Blaney
profile image
This conversation along with the given implementation inspires an interesting feature in a test build... come up with a scoring mechanism of some kind (whatever the metric you want to measure is be it score, time to completion, self reported fun, etc) and then give testers randomized deadzones upon starting the game. Correlate scores with deadzone sizes, run a regression analysis and then use that to optimize the size of your deadzone for a final release.

I suppose, given that we generally know how much precision will be needed in certain situations, one could also programmatically change the size of the dead zone. This would be useful to keep a good feel between navigating menus, walking around a town, running through a battle zone, aiming different types of guns, etc or could also give a feeling of weight to heavy vehicles vs light vehicles.

Sort of thinking out loud, but thanks for the inspiration.

David Klingler
profile image
Thanks for this article! Should be helpful to many, including me.

James Coote
profile image
Heh, well I'm probably one of the people spreading round the bad advice since I've been using the radial deadzone method. Fortunately my game doesn't require high precision, but might just give the scaled method a go for rotating the scene.

However, I don't know if you've noticed, but the OUYA controller (at least the devkit ones) seem to do a certain amount of snapping the movement to the up/down and left/right axes. It's very annoying as I use one stick to switch the tile selection between neighbouring hexagonally arranged tiles. Since they are not orientated to the x/y axes, this can quite often lead to the selection being move to the "wrong" tile / to the one the user didn't intend

Josh Sutphin
profile image
I have noticed that, but I haven't dug around yet to find out a) where it's coming from (I suspect the SDK since it feels like a naive axial dead zone and I have a hard time believing that would exist at the hardware level) or b) precisely how severe it is.

Also, I noticed this on a prototype controller. Haven't tried a retail controller yet.

Bernardo Del Castillo
profile image
Ok so.... good Advice.. in general..
buuut one thing....this is probably unnecessary, but your "ideal" solution uses magnitude TWICE! Per frame!
Magnitude is a pretty expensive call because well square root is a pretty expensive operation.

You could should probably at least store it in a temp variable OR.. use sqrMagnitude! because that's much faster... but then you find yourself in the issue of positives and negatives... But you could solve that by figuring out if the individual original components of the vector are positive or negative.

Makes sense?

Karl Schmidt
profile image
I would measure before coming to conclusions like that. Using an interpreted language is likely going to have enough overhead that math like that will get completely drowned out.

Josh Sutphin
profile image
Yes, absolutely! But I wanted to keep the examples as simple as possible and focused only on the dead zone logic, for the sake of clarity. Performance optimizations are best addressed on a case-by-case basis anyway.

Bernardo Del Castillo
profile image
Karl, I have tested and measured, a code calling magnitude 2 times will have around 2000% longer execution time than one only using SqrMagnitude. Of course everything is relative to your particular machine but it is still VERY considerable.
I'm not whistling dixie, calling 2 sqrt calls will be expensive. It is NOT negligible when working on mobile apps, and it should simply never be done if avoidable.
And although I understand for simplicity, this is not a thing of case by case optimisation, it is simply better practice -_-...

Curtis Turner - IceIYIaN
profile image
I'd like to chime in and say, let the players decide. And please don't give bars, I like looking at actual numbers when it comes to input.

Christian Philippe Guay
profile image
Thank you for sharing, gamasutra.com needs more article like this.

Benoit Brind'Amour
profile image
Great informative article, even for the plain old gamers reading Gamasutra. I'm just a dumb game consumer myself, but I just had to sign up and ask a followup on this article.

I noticed some games a while back that seemed to calibrate the dead zones during loading screens. I can't remember any specific titles but I do remember playing fps's that I could screw with if I cranked the joysticks to their maximum positions (or any positions I'm guessing) and held it still during the entire loading screens.

Is sampling the inputs several times during a loadscreen and modifying the dead zone accordingly (as long as there is no variance) something standard in games or even built in? and Is it a good idea?

Mathieu MarquisBolduc
profile image
Haaaa, filtering thumbstick inputs. Fond memories. Fun exercise: Ask 10 gamers to go left-right-left without pause on 10 different controllers, and compare the raw data from the controller driver. Nothing will look alike.

Another fun one is to ask the users to go right then let go. Remember, those things have *springs* inside.

John Turner
profile image
I have a quick question that it seems like would be perfect for you. I am currently learning to use Unity, after mapping all the controls to the xbox controller I found that I had a really bad case of analog stick drift. I turned up the deadzone incrementally up to the ridiculous amount of 4 (obviously I started at around .01). It seems to have no effect on the drift whatsoever. I can't find any resources online that explains this to me. If you know what I am doing wrong then I would be greatfull for an answer. (I also took the controller apart and cleaned out the components, deleted the fps controller and set it up three additional times, etc.)

Josh Sutphin
profile image
Sorry I missed this! I wish Gama would email me when people respond to my article. :(

A dead zone value of 1 should prevent all input (1 means 100% stick extension). Anything above 1 should be effectively meaningless. Since you're still getting drift, that makes me wonder about the code you're using to read the stick. Is it possible that code is accidentally introducing some drift *after* pulling the stick input, maybe in some custom smoothing math or anything like that?

Alternatively, you might look at the input manager's "sensitivity" and "gravity" settings; they can cause inputs to change over time when your thumb isn't on the stick/button, and if they're set to outlandish values you might see such a disconnect that it feels like a drift.

It's hard to say without seeing your code/project/setup directly but I'd look at those two things first.

Aubrey Hesselgren
profile image
Damn your succintness! I did a 6 part article on this :D

http://bezzy.net/

Aubrey Hesselgren
profile image
One useful thing you missed was adding a pow to the final magnitude of the stick throw: this allows a more tangential transition from "dead" to "reacting".

See part 6: http://bezzy.net/?p=198

Ferdinand Joseph Fernandez
profile image
FYI all the images are 404 now.


none
 
Comment: