DeadZones: Part Four

This part of the deadzones review is a little bit of a sojourn into a mistake I made. Apart from anything else, I feel like developers don’t air their failures enough, even though, for other developers, they must surely be more useful knowledge than the self evident successes: it seems somewhat immoral to try to simply copy successes without working through the hidden problems yourself, but equally immoral to not pass on warnings of danger ahead.

So, indulge me here as I try to see, with a more analytic hat on, what exactly was going on.

Back when working on Brink, I swore blind that this technique of mapping a square input onto a circle was the way to go with regards to dealing with joystick inputs. I had made the (false) assumption that the values being fed in were the full range of a square, between -1 and 1 on two axes, and that, since the XBox controller was clearly a circular gate, it must be magically mapping the inputs from a kind of Eular angle into a square (i.e. Pitch and Yaw between -45 and +45 on each axis). As Part Zero’s analysis of what inputs are actually passed in, I was horribly mistaken.

I had been working on an aim assistance system for the console versions of the game, doing a lot of research into the best-of-breed, and even adding a few novel additions to the mix to reduce the noticeable gap in fidelity between mouse and controller aiming. I had something working in prototype which, for the most part made aiming really rather painless, without being patronizing, to the point that it had to be toned down so that there was at least some skill involved. I was quite pleased with my cleverness. Ah, the hubris.

But mid way through working on it, something intangible felt wrong with the controls… as hard as I tried, I seemed unable to cast my view in a perfect circle just by easing around its physical limits. I wondered if it was something to do with the deltaTime (the time between rendered frames) dipping when looking at the ground vs. the sky, but I was testing in very basic block-out maps. Rolling the stick under my thumb caused some sort of warping effect which made off-cardinal directions just slightly faster to turn with than pure cardinals. It was very hard to put my finger on. Thumb on. Shut up.

I had assumed that something had to be wrong with the aim assistance code I had prototyped: there were something like 3 or 4 contributing factors to the speed at which you turned for the same input, each of them quite involved, modulating and amplifying one another continuously, based on your surroundings. Rather than turn that stuff off and figure it out by knocking down my assumptions, I let it fester in the back of my mind for a while, while no-one called me to task on it.

Eventually, I happened upon this diagram, and thought I had found the answer to the weirdness, and that I was a darn sight cleverer than everyone else who had ever lived.

Warping a Square into a Circle by using science like in that movie "She's All That"

My attempt at re-mapping inputs in (what I thought was) an elegant way assumes that the inputs we’re getting are supposed to be a square shape, coming from a circle. If that were true (and, I’ll reiterate: it’s not) you’d have to deform that square input, so that you aren’t just throwing away those curved corners of detail by choping off any deflection greater than one unit of magnitude.

Luckily, a real programmer caught the issue in time and fixed it, or that game would have been completely mediocre! Ha ha haa! This is real detail we have to work with:

How much input is actually noticed by the XBox controller vs. how much is implied it notices.

Thus, we are more or less warping an octogon into that “circularized” space.

On the left hand side is a “clamped” upper limit, which simply “chops off” the extra detail we have around the corners. In all honesty, this is all I should have used, if any cap at all.

 StickInput = StickInput.normalized * Mathf.Clamp01(StickInput.magnitude);

On the right hand side of the demo is the “Circularized” version of the input. We’ve taken our incompleted roughly octangular inputs and warped them such that the outer perimeter of the square morphs into a circle, with every point inside it distorting with respect to the ratio of the distance to the square edge vs. the distance to the circle edge (err, always 1) along that angle. I think, anyway.

Vector2 CircularizedInput = new Vector2(StickInput.x * Mathf.Sqrt(1.0f - ((StickInput.y * StickInput.y) / 2.0f)),
 StickInput.y * Mathf.Sqrt(1.0f - ((StickInput.x * StickInput.x) / 2.0f)));

This distortion of an incomplete input square from the 360 controller therefore results in a kind of weird “four leaf clover” mapping. Trying to move the character in a circle ends up being kinda wobbly with the circularized version. Again, it’s subtle, but if you’ve got sensitive thumbs, it’s the sort of irritation which will get under your skin.

Clamping simply destroys any input signal whose deflection exceeds 1. Bending the input onto a sphere maintains the entire signal, but because of the hardware, means we never hit a “True” 1.0 magnitude on the diagonals. Unfortunately, because we’re sending in a pre-cauterized signal, we’re only getting out a part of the distorted range, with respect to deflection. Along the diagonals, the equation expects a magnitude of root 2 to be remapped to a maximum value of 1. However, the biggest input we’re giving is just a little over 1.0, but no-where near the magic 1.414 .

We get a rather distorted/chopped off range of the “continuum” along the diagonals versus the cardinal axes. This makes any partial deflection feel badly weighted – as if we gain faster speeds in the mid zones of the diagonals than we do in the mid zones of the cardinals.

So, once again, there’s really no perfect answer to clamping the extremities just right – it’s a case of picking the least worst approach. I think in this demo’s case, we don’t really want to “warp” the linear deflection differently based on what angle we push the stick in. Therefore I’ll continue by using a simple circular clamp.

However, the idea of “warping” inputs rather than just shutting them down altogether does start us thinking down a different track: we can see that it maintains the continuity of the signal (i.e. this sudden jump from zero speed to half speed due to the 0.5 circular deadzone is pretty harsh!), and that it could have a more intentional affect on how the controls feel.

Previous Part | Next Part (soon)

3 thoughts on “DeadZones: Part Four

  1. Hey, right after your unity demo you say “On the left hand side is a “clamped” upper limit, which simply “chops off” the extra detail we have around the corners.”

    Right after that you said “On the right hand side of the demo is the “Circularized” version of the input. We’ve taken our incompleted roughly octangular inputs and warped them around.”

    However, for me, when I fill up the input i get an octagon on the RIGHT hand side of the screen (with the caption “circularized”) and the “clamped” version is completely circular. Did those get switched around or am I not understanding the article correctly?

    • Oops! I have probably made an error. I keep writing and re-writing these things as I go, so I was worried some inconsistencies would fall through.

      Thank you very much for helping! I’ll fix it soon!

    • Actually, you know what? It is correct, but clearly, it could be clearer.

      The circularization doesn’t result in a circular limit since the inputs we are giving are not full squares in the first place (see the Errant Signals post). So, unless my code is wrong (has happened before!) the only reason the circularized version isn’t circular is because the input isn’t square, and if it were, you would get a circle from that code.

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>