At first though, an analog stick seems trivial to handle, the harder the player presses it in one direction, the faster we go in that direction. Even if that approach might be sufficient in some cases, when we want comfort, responsiveness and precision it gets a bit harder.
For another view on the same topic you should read Pat Wilson’s fine post “Analog Input Processing” here: /2011/06/16/analog-input-processing/
1. Circular motion
I will discuss noise and dead zones a bit further, let’s leave that aside for now.
A stick will return positions along the X and Y axes, and if we only want a direction we won’t need much more than atan2. But we might also need amplitude, and its maximum won’t be constant for all directions, so how do we normalize that? First, let’s plot raw stick coordinates for two consoles I got here, let’s name them A and B (I might be a bit overzealous about my NDA here, but it’s not really relevant anyway):
So stick B hits the maximum of both axes in the corners, and it also seems to clamp earlier because I wasn’t pushing the stick any harder yet the edges of stick A are rather fuzzy. The fuzziness of stick A is also explained because circling in one direction created an oval along one diagonal, and circling in the opposite direction created an oval along the other diagonal.
Reverting the distortion seems like the correct approach, but clamping on a circle is a lot simpler and given the short movement range of such sticks, it seems good enough to me. Note that if the stick range was going inside the circle we might have to scale it up first.
if squared_length( x, y ) > 1 then normalize( x, y ) end
2. Dead zones
The only realistic positions where we can expect the player to steadily hold the stick are in the center and on the edges. The previous point took care of the edges, but the oversensitivity in the center is annoying (especially since some controllers tend to loose the precision of their centering mechanism as they wear out).
Simple enough, let’s discard an axis when its amplitude is below a certain threshold…
Oops, we cleaned out the noise around the center but we also lost four rather large sections along the axes (much larger on console B, as the recommenced dead zones are bigger). One might argue that this behavior might actually be desirable, when are trying to move in a straight horizontal line it would be nice if the game would help you do so. But I would retort that this belongs in a high level of input processing, we should try to get something clean before cutting parts of it.
A better way of dealing with the dead zone is to consider both axes together, discard both if the amplitude of the vector is below the threshold, and keep everything otherwise. The result is much better, but we are still left with a hole in the middle.
So we still need to remap the range [ dead_zone; 1 ] to [ 0; 1 ], but once again considering both axes together. So we need a square root, but only one per stick per frame, sounds reasonable.
len = ( sqrt( x * x + y * y ) - dead_zone ) / ( 1.0f - dead_zone ) x *= len y *= len
Moving a cursor around with the stick now that we have properly filtered input can be as simple as using the position as speed. Of course, we will have to ensure that we keep the coordinates at a sufficiently high resolution to guarantee a smooth acceleration.
That might work just fine with a high precision stick, like stick A. But with something where progressive control is harder like stick B (because of limited range, awkward grip or non-linear spring response), adding some acceleration into the mix might help.
Since the acceleration will vary with the position of the stick, and since the sampling frequency might not be constant, it will be hard to have a proper acceleration curve. So instead, I suggest to dampen towards the target velocity that would be defined by the current position of the stick if there was no acceleration.
And although the same principle could work when both speeding up and slowing down, I recommend to not dampen anything when slowing down, so that a complete release of the stick will immediately stop the cursor, but it depends on the kind of feeling you are trying to accomplish.
4. What else?
Those are nothing but some simple observations and remarks, and tuning the feeling of stick control in a game goes a long way from there. I am sure there are many people around which a lot of experience on the topic, so I would like to ask: “what did you wish you knew before starting the implementation of the input processing for your game or engine?”.
(thanks to Tom Gaulton and James Podesta for their help)