Mapping Square Input to Circle in Unity 2017

Moving the player using the input from a keyboard of joystick is easy, though as soon as you start moving in a diagonal direction either using thumb sticks or pressing down two keys at once, you may notice the player will move faster compared to walking forward of sideways.

The reason is the input axis combined results in a longer vector, which can be calculated using simple pythagoras, which shows the combined speed is 1.4142 times faster.

var a = new Vector2(0, 1);
var b = new Vector2(1, 0);
var c = a + b; // Vector2(1, 1)

// pythagoras says length of c = sqrt(a^2 + b^2)
// c = sqrt(1 + 1) = sqrt(2) = 1.4142...
var cMagnitude = Mathf.Sqrt(a.magnitude * a.magnitude + b.magnitude * b.magnitude);

I’ve seen many people suggest to simply normalize the input, which works perfectly if you don’t care about the sensivity. However, using thumb sticks should in my opinion allow the player to move fast and slow.

Based on the formula found at http://mathproofs.blogspot.com/2005/07/mapping-square-to-circle.html, we can remap the square input to a circular one like so:

// get input
var input = new Vector2(
	Input.GetAxisRaw("Horizontal"),
	Input.GetAxisRaw("Vertical")
);

// map square input to circle, to maintain uniform speed in all directions
var inputCircle = new Vector2(
	input.x * Mathf.Sqrt(1 - input.y * input.y * 0.5f),
	input.y * Mathf.Sqrt(1 - input.x * input.x * 0.5f)
);

Philip Nowell also created a nice illustration of what’s happening.

It is worth mentioning that the Xbox Controller does not seem to output a perfect square. More like a rounded square as discussed on the Unreal forums: https://answers.unrealengine.com/questions/226365/analog-stick-input-traces-a-square.html

A possible Unity implementation could look like this

using UnityEngine;

public static class UniformInput
{
    /// <summary>
    /// Remaps the horizontal/vertical input to a perfect circle instead of a square.
    /// This prevents the issue of characters speeding up when moving diagonally, but maintains the analog stick sensivity.
    /// </summary>
    public static Vector2 GetAnalogStick(string horizontal = "Horizontal", string vertical = "Vertical")
    {
        // apply some error margin, because the analog stick typically does not
        // reach the corner entirely
        const float error = 1.1f;

        // clamp input with error margin
        var input = new Vector2(
            Mathf.Clamp(Input.GetAxisRaw(horizontal) * error, -1f, 1f),
            Mathf.Clamp(Input.GetAxisRaw(vertical) * error, -1f, 1f)
        );

        // map square input to circle, to maintain uniform speed in all
        // directions
        return new Vector2(
            input.x * Mathf.Sqrt(1 - input.y * input.y * 0.5f),
            input.y * Mathf.Sqrt(1 - input.x * input.x * 0.5f)
        );
    }
}

Which you would then use like so

Vector2 input = UniformInput.GetAnalogStick();