Is there a way to create a floating JoyStick using the On-Screen Stick component?

I’m learning the new Input System with On-Screen Stick. It all hooks in with my InputActions.

However, I’m not sure how to build a floating JoyStick, where the JoyStick only shows up when I touch the screen and hide when I release touch.

I find many tutorials on either a fully custom solution without using the built-in On-Screen Stick component or a static one, which is what I have so far.

Below is what I have so far.

I know this is an old subject, but I got the same issue, and found a solution still using the OnScreenStick component.

Basically, I created a FloatingStickAreaController that capture the PointerDown/Up/Drag events, and transmit it to the OnScreenStick.

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.OnScreen;

namespace Assets.Scripts.Controller.Ui
{
    public class FloatingStickAreaController : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler
    {
        [SerializeField]
        GameObject _joystick;

        OnScreenStick _screenStick;
        RectTransform _mainRect;
        RectTransform _joystickRect;

        private void Awake()
        {
            _screenStick = _joystick.GetComponentInChildren<OnScreenStick>();
            _mainRect = GetComponent<RectTransform>();
            _joystickRect = _joystick.GetComponent<RectTransform>();

        }

        public void OnDrag(PointerEventData eventData)
        {
            ExecuteEvents.dragHandler(_screenStick, eventData);
        }

        public void OnPointerDown(PointerEventData eventData)
        {
            Vector2 localPosition;

            RectTransformUtility.ScreenPointToLocalPointInRectangle(_mainRect, eventData.pressPosition, Camera.main, out localPosition);

            _joystickRect.localPosition = localPosition;

            ExecuteEvents.pointerDownHandler(_joystick.GetComponentInChildren<OnScreenStick>(), eventData);
        }

        public void OnPointerUp(PointerEventData eventData)
        {
            ExecuteEvents.pointerUpHandler(_screenStick, eventData);
        }
    }
}

You will need to have a Image on the GameObject that will hold the FloatingStickAreaController (Can put transparent, or UIMask Sprite to hide it).
This will define the zone in which the Floating Stick can go

To implement a Floating On Screen Stick, I had to implement my own OnScreenControl. It was pretty simple and the code is almost the same as OnScreenStick except movement is based on the pointer down position rather than the starting position. Below is a version of my code. The FloatingOnScreenStick component should be attached to a large ray cast target that is the parent of the Joystick GameObject.

GameObject setup:

+ValidLeftThumbAreaGameObject {Image{RayCastTarget:true}, FloatingOnScreenStick{JoystickTransform:ThumbstickGraphicGameObject}}
++ThumbstickGraphicGameObject {Image}

The code is not perfect. It doesn’t account for any transform difference between the parent GameObject and the child Joystick GameObject, you’d have to fix the code to work in that case.

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.OnScreen;

/// <summary>
/// A stick control displayed on screen and moved around by touch or other pointer
/// input. Floats to pointer down position.
/// </summary>
[AddComponentMenu("Input/Floating On-Screen Stick")]
public class FloatingOnScreenStick : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler
{
    public void OnPointerDown(PointerEventData eventData)
    {
        if (eventData == null)
            throw new System.ArgumentNullException(nameof(eventData));

        RectTransformUtility.ScreenPointToLocalPointInRectangle((RectTransform)transform, eventData.position, eventData.pressEventCamera, out m_PointerDownPos);
        m_JoystickTransform.anchoredPosition = m_PointerDownPos;
    }

    public void OnDrag(PointerEventData eventData)
    {
        if (eventData == null)
            throw new System.ArgumentNullException(nameof(eventData));

        RectTransformUtility.ScreenPointToLocalPointInRectangle((RectTransform)transform, eventData.position, eventData.pressEventCamera, out m_DragPos);
        var delta = m_DragPos - m_PointerDownPos;

        delta = Vector2.ClampMagnitude(delta, movementRange);
        m_JoystickTransform.anchoredPosition = m_PointerDownPos + delta;

        var newPos = new Vector2(delta.x / movementRange, delta.y / movementRange);
        SendValueToControl(newPos);
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        m_JoystickTransform.anchoredPosition = m_StartPos;
        SendValueToControl(Vector2.zero);
    }

    private void Start()
    {
        m_StartPos = ((RectTransform) transform).anchoredPosition;
    }

    public float movementRange
    {
        get => m_MovementRange;
        set => m_MovementRange = value;
    }

    [SerializeField]
    private float m_MovementRange = 50;

    [InputControl(layout = "Vector2")]
    [SerializeField]
    private string m_ControlPath;

    [SerializeField]
    private RectTransform m_JoystickTransform;

    private Vector2 m_StartPos;
    private Vector2 m_PointerDownPos;
    private Vector2 m_DragPos;
    

    protected override string controlPathInternal
    {
        get => m_ControlPath;
        set => m_ControlPath = value;
    }
}