March 24, 2016 ryanjuckett 25 comments

Interpreting Analog Sticks

When a game supports controllers, the player is likely using an analog stick at all times. Nailing the input processing can have a substantial impact on quality throughout the experience. Let’s walk through some core concepts along with examples from INVERSUS.

Inspection

Before tuning the analog stick, you need to know how it works. What is the hardware actually reporting when you move the stick around? Here’s what the stick inspection for INVERSUS looks like. I don’t claim this to be the best possible display, but all the conveyed information is important.

inspection_view

Before I cover each part in detail, let me present a high level overview. I was using an Xbox 360 controller attached to a PC. The top row represents stages of processing on the left stick. The bottom row is the right stick. You’ll notice that the top row has more data on display than the bottom row. This is because INVERSUS only uses the left stick and it gets special treatment. I’ll only talk about that top row from hear on, but everything still applies to the right stick for games that use it.

Hardware Abstraction

The first two circles represent operations that convert from specific hardware (e.g. an Xbox 360 controller or a DualShock 4 controller) to a common input space. Different hardware is different. It is mechanically different and it feels different. This is a rather obvious fact to state, but it might be one you haven’t considered.

Let’s say you develop your game on console A and later port it to console B. You’ll want to maintain the quality bar of your original tuning. If the analog sticks behave differently, things will feel slightly off. The idea is to convert the x,y values from the supported hardware spaces into a common game space. The game layer can now be tuned agnostic to the controller type.

Radial Dead Zones

radial_dead_zone_demoThe first stage represents radial dead zone processing. The hardware may report bad values due to limitations in construction or wear and tear from usage. This stage removes said unreliable values from the game.

So what are we looking at? The red square bounds a coordinate system with (-1,-1) at the bottom left and (1,1) at the top right. It is divided into quadrants along the X and Y axes. The bright red circle is the unit circle. These guidelines remain the same for each the following stages.

The small green circle is the input value. Because this is the first stage, these are the raw values directly form the hardware. The green trail shows a recent history of inputs (green dots) connected by line segments. The small white circle is the processed output of the stage.

Unique to this stage, we have two more circles: the inner dead zone and the outer dead zone.

Inner Radial Dead Zone

radial_dead_zone_inner

Values within the inner circle cannot be trusted. This is the inner dead zone. In the image, I am gently moving the stick around an area that isn’t receiving any resistance. The stick will not be pulled back to dead center here. With controller age and abuse, this area will grow, shift and distort.

Every value within the inner dead zone is mapped to zero, giving us a reliable origin point for the game logic. You’ll notice that the white output circle remains locked to the center at all times. If the inner dead zone is too tight, motion can drift as the stick comes to rest outside.

Hardware vendors often provide a recommended inner dead zone value. This is a good place to start, but you should do your own testing across as many controllers as you can get your hands on. These recommended values might not be perfect and it never hurts to verify with hard data.

Outer Radial Dead Zone

radial_dead_zone_outer

Values outside the outer circle cannot be trusted. This is the outer dead zone. Most games do an acceptable job with the inner dead zone, but you can likely find errors due to a poor (or nonexistent) outer dead zone.

In the first image, I am moving the stick straight up to the top edge and sliding it counterclockwise around the edge. At the top, it reports values on or outside the unit circle. Life isn’t perfect and this is expected. As I roll the stick left, however, it actually cuts within the unit circle for a bit before extending back out. This behavior isn’t common to all controller types, but it can happen on Xbox 360 controllers.

radial_dead_zone_outer_spin

Based on this data, you might assume the left side is just inaccurate, but it’s not so simple. In this second image, I first move the stick directly left. In this case, it reports as expected. It is only after rotating it in a circle that we see the error.

If the error only happens after rotation, how important is it? It could be very important depending on the game. Imagine a game where the stick magnitude controls player speed. If the player character moves up and banks left, it will be moving slightly under max speed until the stick is released and extended again.

Every value outside the outer dead zone is scaled to have a magnitude of one. This creates a reliable unit circle for game logic. Notice that the output (white circle) remains locked to the edge.

Unfortunately, there likely aren’t any recommended values for the outer dead zone. Once again, perform your own tests and visualize the data. Plug in a bunch of controllers and see how they react. There might be subtle inaccuracies after complex motions that your game needs to account for.

Radial Dead Zone Interpolation

With the inner and outer values snapped to zero and one respectively, what do we do with intermediate values? In order to avoid any discontinuities, we linearly interpolate their scale. The resulting logic might look like this:

void ApplyRadialDeadZone
(
    float* pOutX,      // out: resulting stick x value
    float* pOutY,      // out: resulting stick y value
    float x,           // in: initial stick x value
    float y,           // in: initial stick x value
    float deadZoneLow, // in: distance from zero to ignore
    float deadZoneHigh // in: distance from unit circle to ignore
)
{
    float mag = sqrtf(x*x + y*y);

    if (mag > deadZoneLow)
    {
        // scale such that output magnitude is in the range [0.0f, 1.0f]
        float legalRange = 1.0f - deadZoneHigh - deadZoneLow;
        float normalizedMag = min(1.0f, (mag - deadZoneLow) / legalRange);
        float scale = normalizedMag / mag; 
        *pOutX = x * scale;
        *pOutY = y * scale;
    }
    else
    {
        // stick is in the inner dead zone
        *pOutX = 0.0f;
        *pOutY = 0.0f;
    }
}

Magnitude Remapping

hardware_remap_demo

We’ve culled the bad values and need to align the resulting data into a common feel. Lots of factors affect why the results can feel different across controller types. First off, there are different radial dead zones in play. Second, the physical sticks can have different resistance forces at different angles. Third, the sticks are likely shaped differently: sticks at different heights result in a different amount of torque from your thumb. Finally, they aren’t always located in the same position on the controller.

By remapping the magnitude with a curve, you can make what feels like pushing a stick 50% or 75% report a consistent output regardless of hardware. You’ll never get this perfect, but you can get it better than doing nothing at all.

In the case of INVERSUS, the Xbox 360 controller is my baseline and thus the remap curve is just (output = input) or (y = x). This curve is overlaid on the image as the diagonal line. The input to the curve (the magnitude) is rendered as a vertical line scrolling across the screen. The line intersects the curve at the output value. In this specific case, the input and output are equal, but for alternate controllers I use a cubic spline to make them feel more like an Xbox 360 controller.

We will do another curve remapping later along with an example showing a non-trivial curve.

Shape Remapping

This step isn’t as necessary these days and I don’t actually support it in INVERSUS, but I wanted to mention it. Sometimes a controller gets developed with hardware that doesn’t output a circle of values. It might output something in a square or who knows what. In this case, you need to do extra preprocessing to remap the space back into a circle at the start. Visualize the data, look at what is happening, and act accordingly.

PC Complications

Before we move out of the hardware abstraction layer, I want to discuss complications specific to PC games. Anytime you are dealing with PC hardware – be it for graphics, audio or input – things are an order of magnitude more complicated than on consoles. In the case of controller input, we aren’t actually that far from it just working. Hopefully there is a near future where I can delete this section.

The crux of the problem lies in the fact that XInput will not tell you if an Xbox 360 controller or and Xbox One controller is plugged in. I don’t know of any supported interface for this (and would be very interested in hearing other approaches from anyone that has solved it). This causes multiple problems. First, you can’t automatically adjust your button icons from things like start/back to view/menu. Second, you can’t remap your vibration intensities between different controller types. Third (and more relevant to this article), you can’t automatically adjust your radial dead zones.

The Xbox One controller is a far better device when it comes to input fidelity. It doesn’t need the large outer dead zone of the 360 controller. Unfortunately, without knowing which one is connected, your best option is to restrict the Xbox One controller with the dead zones of the Xbox 360.

If you aren’t interested in my thoughts on hacky PC mitigations, you can skip to the next bit. Otherwise, here are my current thoughts on how to solve this for INVERSUS (fingers crossed).

I initially considered “rewriting” XInput using the Windows Raw Input and HID APIs. Reading data from the devices wouldn’t be that hard to figure out. Sending data for the lights and motors would be more tricky, but doable. Unfortunately, the XBox 360 controller has been built to misbehave as a HID device. The analog triggers can not be read independently. I assume this was done in an attempt to promote XInput, but I’m honestly not sure.

I later considered bypassing the HID interface and talking to the USB directly. I think this means borderline writing a driver. That is going one step too far for my tastes. It might even reach the point where it couldn’t be hidden from the user.

Recently, I thought of a new idea. I haven’t actually implemented this yet and it is a bit crazy, but I do have all the data to make it possible [UPDATE: This totally worked]. You can use Raw Input to identify connected Xbox 360 and Xbox One controllers. You can also use Raw Input to receive HID input data from the controllers. By comparing a subset of data from Raw Input with data from XInput (e.g. button presses and analog sticks), you could map devices from one system to the other. This should allow for classifying the controllers used by XInput. In theory, this would also work in old versions of XInput and thus support older versions of Windows.

Game Intent

We have cleaned up our data and put it into a common space for the game to process. The specifics from here on out can shift from game to game, but the stages used by INVERSUS are applicable to most games. I should also mention that games with multiple contexts (e.g. getting in and out of cars) might toggle settings per context.

Angular Dead Zones

angular_dead_zone_demo

There are common and specific directions that the player will try to point the stick. For INVERSUS, players move around in the two dimensional plane of the monitor.  It is common to try and move directly right or directly up. In 2D games on a rectangular screen, it is easy to visualize vertical and horizontal alignment. The player is more likely to try and accurately walk directly to the left than at five degrees off from left.  Thus, it should be easy to do just this.

We handle this by defining a set of directions bound by angular dead zones. When in this zone, the output is mapped directly onto the specified direction. In the image, there are four dead zones: up, down, left and right. When the input (green circle) is in a dead zone, the output (white circle) is locked to the respective axis. Similar to the radial dead zones, we smoothly interpolate the angle between each adjacent dead zone. The output (white circle) never pops as it moves around. Note that this operation requires first converting to polar coordinates. After operating on the angle value, you can convert back to Cartesian coordinates.

You might also adjust the dead zone’s angular width with a curve based on the distance to origin. If I wanted it to be easier to make subtle axial movements, I could bend out the dead zone angle such that it gets wider at the center.

I’m going to mention some other use cases for angular dead zones because they may not be apparent. What about 3D game navigation? For camera relative motion, the player generally tries to move to/from the camera, or strafe left/right. This applies in both a third and first person scenario. How about 3D camera control? In both third and first person games, lateral stick motion is often used to turn the character. Players don’t want to slightly drift the camera up or down in the process. Make it easy for them. Similar logic applies to looking straight up or straight down. Finally, let’s talk about a case where you might want a simpler setup. In a driving game, the player often wants to accurately drive straight forward or backward. A dead zone at the top and one at the bottom might be sufficient.

Sensitivity Remapping

game_remap_demo

This is the same operation used for the hardware magnitude remapping curve. It also uses the same debug rendering. It is split out such that I can tune the feel of the game in a hardware agnostic manner. I don’t need to redo my work for each supported controller type.

The shape of this curve is very game specific, but it is more than likely going to start low and curve upward. This moves more sensitivity to the outside of the stick where it is easier to make adjustments. Subtle changes to this curve can have a surprisingly significant role in how the game plays. It is worth setting aside some solid time to iterate on it.

In the case of INVERSUS, the curve is a bit steeper than most games likely want. This is due to how useful different move speeds are for the game design. I have also added another small nuance at the start of the curve. It immediately rises very fast. This adjustment is specific to how the physics of the game works. I decided that outputs under that point were not of use to the player and I want to take full advantage of the input range so nothing goes to waste.

Final Output

final_demoThe final display shows the fully processed stick value. Here, I can verify that I’m able to hit my desired directions and magnitudes needed by the game. If it is working well, I can start tuning further up the chain in the realm of character physics. That said, for your game, this might not be the end. Keep asking what could be done to improve the experience.

Understanding the Translation

full_small

Rendering all the stages side by side presents a full understanding of the input pipeline. This helps postulate adjustments and validate results. Players talk to your game through the language of a controller and the translation can be messy. You need to help guide their specific intentions through this fuzzy translation step. If you can make an accurate translation, you can make a tight controlling game.

25 thoughts on “Interpreting Analog Sticks

  1. Great post!

    I didn’t realize that some 360 controllers could hit “max” on the outer radius. I learned something, thanks!

    One of my favorite hacks is players who carve out their controller so they can hit the corners. It’s a huge cheat in games that don’t clamp magnitude.

    Have you messed with an Xbox Elite controller? I believe it lets players adjust all kinds of curves. I’ve never used one. But I wonder if it’s existence changes developer best practices in any way? I can imagine that it might make sense to enable a “raw” mode to let player tuned curves handle everything. (Within bounds of course).

    I suppose the Elite software lets players build per-game curves that can counteract per-game-dev crafted settings. But a raw mode might be an improved user experience.

    1. Thanks!

      Yeah, the Elite lets the player basically re-tune everything on top of the developer tuning. I think you still get less settings if you plug into a PC, though.

      Personally, I’m against the idea. The dev should tune it right for a given hardware spec (and should be able to know the hardware they are building for). They should also know far more about the long term implications of certain control settings on the game than any player will until they’ve lived that game for months on end.

      You can actually ruin your experience with the Elite controller in a bunch of games. No matter how tech savvy you are, being able to accidentally say “I don’t want my right analog trigger to ever report more than 0.7” and then forget to disable it is a very human thing to do. You then end up with a subtle bad taste in some game that is not going to be diagnosed as user error.

      1. I thought Elite let you define settings per game? Maybe it’s less graceful than that.

        I remember Team Fortress 2 adding a “raw” mode for mouse input. Which is where my thought came from.

        I definitely agree that devs should meticulously tune setting for their game and platform. But I also know how damn frustrated I get when a game’s controller settings won’t let me be tune things to the point I desperately crave. Elite settings can probably “counter rotate” so to speak. I still wonder what net adjustments, if any, hardcore/pro players would make with an advanced raw mode option.

        1. It does do a profile assignment per game, but still easy to mess up on accident. There was actually a recent case of that on GiantBomb when they were playing the climb and the stamina/grip system wasn’t working right for them.

          Everything definitely changes per game. I often look to things like Mario games as the devil’s advocate for “what if you just made so there was no need for options”.

      2. This was a very insightful read. I was hoping you might help me with a issue if possible i have a elite controller. a lot of people claim stick drift. in my case my left analog stick at rest sets X @ 35827 roughly and Y@ 32056 roughly and as i’m sure you know 32767 is center X/Y. i don’t feel i have stick drift but more of a less than perfect center at rest. in the game The Crew hands off the sticks controller at rest the wheel is like 3 degrees to the right of top dead center. so my question is can i force the value of 32767 onto my X/Y so that the game see it and not the three degrees difference thinking i’m turning? any help would be great $150 dollar controller will make you look for fixes.

        1. If you’re using an elite, you should have control of the deadzone area. Well, at least on a xbox one you should – I’m not sure if they’ve exposed that on PC yet.

          It sounds like you are resting about 9.3% off center. Unless the game is capable of detecting your connected controller as an XboxOne controller instead of a generic Xbox controller (e.g. one that might be a 360 controller), I’d expect their dead zone to be much larger than that. It sounds like it’s not and the game is requiring a tighter controller :/

          1. thanks for the reply. i did some testing and the same game played from steam with steam support for the controller the wheel snaps right back to top dead center. so i’m not sure how to make this correction apply to the ubisoft version. i am not very tech savvy so a dumbed down version to what if anything i could do to fix this would be great. thank again. also really neat concept on your game INVERSUS.

          2. OK i see what you mean now I read it to fast. need a way to make elite show as xbox and not generic and no there isnt as i know of a way to expose or tighten up the dead zone thank you for you time,.

          3. Ot does show as controller(xbox one for windows) in joy.cpl in windows to calibrate the controller. but as i said when played from the ubisoft version wheel goes off from center. and so does the steam version until i apply the steam support for the controller. then the wheel pops back to center. so is there a way to make the necessary change to apply to the ubisoft version or just buy the steam version and call it a day?

          4. It’s odd that it works different on the Steam version. That said, the why and how of it all really depends on how that game is programmed. You could try reaching out to the studio that made it on their website if they have a support email or twitter (or even just to the ubisoft one). They might have a quick answer for you if you can get to the right person.

          5. Just a follow up . It was a simple fix of increasing the dead zone value. when you pointed out the tolerances may be the root of the game not allow for the amount of offset in my analog stick at rest.. all is well in the force now . thank you for you input.

  2. Have you tried it with the Steam Controller touchpads? I assume players would have better precision with it so large deadzones could be a nuisance.

    1. I haven’t worked in detail with the steam controller yet so I don’t have any concrete thoughts on interpreting the input. I do hope to add support soon and will take the same approach as above in that process (debug render everything, play, iterate, play).

  3. I know this is probably going to be weird, but I guess I should try asking here since you seem to know a lot about the matter. Recently I dropped my brand-new Xbox 360 controller for PC and I didn’t test it out before actually playing with it for a while. I noticed that after the drop (Maybe it was just me, I don’t know?) the left thumbstick handled a bit worse, like it was physically harder, stiffer. So I went to http://html5gamepad.com to see if my thumbstick was working properly and it turns out that it doesn’t manage to perfectly hit all the directions.

    For instance, after meddling with the button placement and the screws for a while, I managed to get it to register a near-perfect “1” on the left, up and right directions (The left one is a bit harder). On the other hand, I can only hit a perfect “1” on left-right on my right thumbstick. Worst offender is the down on the left thumbstick with a great 0,92 on average.

    Is this normal for a new controller or should it hit all the corners from the get-go? Mind you this doesn’t change much for me since it just can’t register a perfect “1” in most cases, it just doesn’t quite get to the perfect ‘corners’…

    1. I’d expect a brand new controller to extend past 0.92 when pushing directly from center to any edge, but I can’t guarantee that.

      1. I agree.

        Oh well… Is there anything I could do about it? Other than carving out the edges with a sandpaper, which should work (Sorry, need some self-reassurance here since the controller was so expensive… At least to me) . All in all, the controller seems fine and the thumbstick seems to be working alright so far, it’s not quite there. I’m thinking maybe the impact loosened up something inside or something like that, maybe even damaged the potentiometers somehow.

        Any suggestions?

  4. We stumbled over here from a different web page and thought
    I may as well check things out. I like what I see
    so i am just following you. Look forward to checking out your web page repeatedly.

Leave a Reply to ryanjuckett Cancel reply

Your email address will not be published. Required fields are marked *