On Screen Analogue Stick Circular Boundary

Hi there,

I’m using a version of unity’s on screen joystick and have got it working how I would like, however the boundary for the joystick is a rectangle and I would prefer a Circle.

I’ve looked a bit on the internet and I have tried to implement it myself by trying to set up the boundary as a radius but have failed :frowning:

I was wondering if anyone could point me in the right direction.

here is my code

using UnityEngine;
using System.Collections;

class AxisBoundary
{
    public Vector2 min = Vector2.zero;
    public Vector2 max = Vector2.zero;
}

public class JoystickScript : MonoBehaviour
{
    static private Joystick[] joysticks;
    static private bool enumeratedJoysticks = false;
    static private float tapTimeDelta = 0.3f;

    public bool touchPad;
    public Rect touchZone;
    public Vector2 deadZone = Vector2.zero;
    public bool normalize = false;
    public Vector2 position;
    public int tapCount;

    private int lastFingerId = -1;
    private float tapTimeWindow;
    private Vector2 fingerDownPos;
    private float fingerDownTime;
    private float firstDeltaTime = 0.5f;

    private GUITexture gui;
    private Rect defaultRect;
    private AxisBoundary guiBoundary = new AxisBoundary();
    private Vector2 guiTouchOffset;
    private Vector2 guiCenter;

    private float resRatio;
    public GameObject globalScripts;

    // Use this for initialization
    void Start() 
    {
        //Cache this texture
        gui = gameObject.guiTexture;

        globalScripts = GameObject.FindGameObjectWithTag("Global");
        ScreenResolutionScript resScript = globalScripts.GetComponent<ScreenResolutionScript>();

        resRatio = resScript.GetResRatio();

        gui.pixelInset = new Rect((Screen.width / 6) - (200 * resRatio), (Screen.height / 2 ), (200 * resRatio), (200 * resRatio));

        //Store the default rect for the gui
        defaultRect = gui.pixelInset;

        defaultRect.x += gameObject.transform.position.x * Screen.width;
        defaultRect.y += gameObject.transform.position.y * Screen.height;

        gameObject.transform.position = new Vector3(0.0f, 0.0f, gameObject.transform.position.z);

        if (touchPad)
        {
            //If a texture has been assigned, then use the rect from the gui
            if (gui.texture)
                touchZone = defaultRect;
        }
        else
        {
            //corner of GUI
            guiTouchOffset.x = defaultRect.width * 0.5f;
            guiTouchOffset.y = defaultRect.height * 0.5f;

            //cache center of GUI
            guiCenter.x = defaultRect.x + guiTouchOffset.x;
            guiCenter.y = defaultRect.y + guiTouchOffset.y;

            //Build GUI boundary
            guiBoundary.min.x = defaultRect.x - guiTouchOffset.x;
            guiBoundary.max.x = defaultRect.x + guiTouchOffset.x;
            guiBoundary.min.y = defaultRect.y - guiTouchOffset.y;
            guiBoundary.max.y = defaultRect.y + guiTouchOffset.y;
        }
    }

    void Disable()
    {
        gameObject.SetActive(false);
        enumeratedJoysticks = false;
    }

    void ResetJoystick()
    {
        //Release the finger and reset joystick to default position
        gui.pixelInset = defaultRect;
        lastFingerId = -1;
        position = Vector2.zero;
        fingerDownPos = Vector2.zero;

        if (touchPad)
        {
            gui.color = new Color (gui.color.r, gui.color.g, gui.color.b, 0.025f);
        }
        else
        {
            gui.color = new Color (gui.color.r, gui.color.g, gui.color.b, 0.2f);
        }
    }

    bool IsFingerDown()
    {
        return (lastFingerId != -1);
    }

    void LatchedFinger(int fingerId)
    {
        //If another Joystick latched this finger, release it
        if (lastFingerId == fingerId)
        {
            ResetJoystick();
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (!enumeratedJoysticks)
        {
            //Collect all joysticks
            joysticks = (Joystick[])FindObjectsOfType(typeof(Joystick));
            enumeratedJoysticks = true;
        }

        int count = Input.touchCount;

        //Adjust tap time window
        if (tapTimeWindow > 0)
        {
            tapTimeWindow -= Time.deltaTime;
        }
        else
        {
            tapCount = 0;
        }

        if (count == 0)
            ResetJoystick();
        else
        {
            for (int i = 0; i < count; i++)
            {
                Touch touch = Input.GetTouch(i);
                Vector2 guiTouchPos = touch.position - guiTouchOffset;

                bool shouldLatchFinger = false;

                if (touchPad)
                {
                    if (touchZone.Contains(touch.position))
                    {
                        shouldLatchFinger = true;
                    }
                }
                else if (gui.HitTest(touch.position))
                {
                    shouldLatchFinger = true;
                }

                //Latch finger if its a new touch
                if (shouldLatchFinger && (lastFingerId == -1 || lastFingerId != touch.fingerId))
                {
                    if (touchPad)
                    {
                        gui.color = new Color(gui.color.r, gui.color.g, gui.color.b, 0.15f);

                        lastFingerId = touch.fingerId;
                        fingerDownPos = touch.position;
                        fingerDownTime = Time.time;
                    }

                    lastFingerId = touch.fingerId;

                    //Count taps if they are within the time window
                    if (tapTimeWindow > 0)
                    {
                        tapCount++;
                    }
                    else
                    {
                        tapCount = 1;
                        tapTimeWindow = tapTimeDelta;
                    }

                    //Tell other joysticks finger is latched
                    foreach (Joystick j in joysticks)
                    {
                        if (j != this)
                        {
                            j.LatchedFinger(touch.fingerId);
                        }
                    }
                }

                if (lastFingerId == touch.fingerId)
                {
                    if (touch.tapCount > tapCount)
                    {
                        tapCount = touch.tapCount;
                    }

                    if (touchPad)
                    {
                        //For touchpads set the position directly
                        position.x = Mathf.Clamp((touch.position.x - fingerDownPos.x) / (touchZone.width / 2), -1, 1);
                        position.y = Mathf.Clamp((touch.position.y - fingerDownPos.y) / (touchZone.height / 2), -1, 1);
                    }
                    else
                    {
                        //Change the position of the joystick GUI to match touch position
                        gui.pixelInset = new Rect(Mathf.Clamp(guiTouchPos.x, guiBoundary.min.x, guiBoundary.max.x), Mathf.Clamp(guiTouchPos.y, guiBoundary.min.y, guiBoundary.max.y), 200 * resRatio, 200 * resRatio);
                    }

                    if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
                    {
                        ResetJoystick();
                    }
                }
            }
        }

        if (!touchPad)
        {
            //get a value between -1 and 1 based on joystick gui position
            position.x = (gui.pixelInset.x + guiTouchOffset.x - guiCenter.x) / guiTouchOffset.x;
            position.y = (gui.pixelInset.y + guiTouchOffset.y - guiCenter.y) / guiTouchOffset.y;
        }

        //Adjust for deadzone
        float absoluteX = Mathf.Abs(position.x);
        float absoluteY = Mathf.Abs(position.y);

        if (absoluteX < deadZone.x)
        {
            //Report the joystick as being at the center
            position.x = 0;
        }
        else if (normalize)
        {
            // Rescale the output after taking the dead zone into account
            position.x = Mathf.Sign(position.x) * (absoluteX - deadZone.x) / (1 - deadZone.x);
        }

        if (absoluteY < deadZone.y)
        {
            // Report the joystick as being at the center if it is within the dead zone
            position.y = 0;
        }
        else if (normalize)
        {
            // Rescale the output after taking the dead zone into account
            position.y = Mathf.Sign(position.y) * (absoluteY - deadZone.y) / (1 - deadZone.y);
        }
    }
}

One simple (not necessarily the best) way to do it would be to just compare the distance between your touch zone and the touch.

UPDATED ANSWER

this is a function that will allow you to clamp a point (touchPoint) so that it does not go beyond a maximum radius away from touchZone:

public float maxRad;
	private Vector3 CheckRange(Vector3 touchPoint, Vector3 touchZone)
	{
		if (Vector3.Distance(touchPoint, touchZone) > maxRad)
		{
			touchPoint = touchZone - Vector3.ClampMagnitude((touchZone - touchPoint), maxRad);
		}
		return touchPoint;
	}

ORIGINAL ANSWER:

you could manually compare distances like so:

float touchZoneRadius = 2f; // an arbitrary number i made up. you'd have to figure this part out
if (touchPad)
{
    if (Vector3.Distance(touchZone.position, touch.position) < touchZoneRadius)
    {
        shouldLatchFinger = true;
    }
}