Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only.

    Please, do not make any changes to your username or email addresses at id.unity.com during this transition time.

    It's still possible to reply to existing private message conversations during the migration, but any new replies you post will be missing after the main migration is complete. We'll do our best to migrate these messages in a follow-up step.

    On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live.


    Read our full announcement for more information and let us know if you have any questions.

Question The joystick changes parameters in Rect Transform at the first touch.

Discussion in 'Input System' started by Richard_Blackwidow, Feb 4, 2023.

  1. Richard_Blackwidow

    Richard_Blackwidow

    Joined:
    Dec 29, 2021
    Posts:
    6
    I'm using a floating joystick from the Asset Store. With the Screen Space - Overlay canvas, everything is fine, but with Screen Space - Camera, the first touch is always problematic. Tell me, please, what to do.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.EventSystems;
    5.  
    6. public class Joystick : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
    7. {
    8.     public float Horizontal { get { return (snapX) ? SnapFloat(input.x, AxisOptions.Horizontal) : input.x; } }
    9.     public float Vertical { get { return (snapY) ? SnapFloat(input.y, AxisOptions.Vertical) : input.y; } }
    10.     public Vector2 Direction { get { return new Vector2(Horizontal, Vertical); } }
    11.  
    12.     public float HandleRange
    13.     {
    14.         get { return handleRange; }
    15.         set { handleRange = Mathf.Abs(value); }
    16.     }
    17.  
    18.     public float DeadZone
    19.     {
    20.         get { return deadZone; }
    21.         set { deadZone = Mathf.Abs(value); }
    22.     }
    23.  
    24.     public AxisOptions AxisOptions { get { return AxisOptions; } set { axisOptions = value; } }
    25.     public bool SnapX { get { return snapX; } set { snapX = value; } }
    26.     public bool SnapY { get { return snapY; } set { snapY = value; } }
    27.  
    28.     [SerializeField] private float handleRange = 1;
    29.     [SerializeField] private float deadZone = 0;
    30.     [SerializeField] private AxisOptions axisOptions = AxisOptions.Both;
    31.     [SerializeField] private bool snapX = false;
    32.     [SerializeField] private bool snapY = false;
    33.  
    34.     [SerializeField] protected RectTransform background = null;
    35.     [SerializeField] private RectTransform handle = null;
    36.     private RectTransform baseRect = null;
    37.  
    38.     private Canvas canvas;
    39.     private Camera cam;
    40.  
    41.     private Vector2 input = Vector2.zero;
    42.  
    43.     protected virtual void Start()
    44.     {
    45.         HandleRange = handleRange;
    46.         DeadZone = deadZone;
    47.         baseRect = GetComponent<RectTransform>();
    48.         canvas = GetComponentInParent<Canvas>();
    49.         if (canvas == null)
    50.             Debug.LogError("The Joystick is not placed inside a canvas");
    51.  
    52.         Vector2 center = new Vector2(0.5f, 0.5f);
    53.         background.pivot = center;
    54.         handle.anchorMin = center;
    55.         handle.anchorMax = center;
    56.         handle.pivot = center;
    57.         handle.anchoredPosition = Vector2.zero;
    58.     }
    59.  
    60.     public virtual void OnPointerDown(PointerEventData eventData)
    61.     {
    62.         OnDrag(eventData);
    63.     }
    64.  
    65.     public void OnDrag(PointerEventData eventData)
    66.     {
    67.         cam = null;
    68.         if (canvas.renderMode == RenderMode.ScreenSpaceCamera)
    69.             cam = canvas.worldCamera;
    70.  
    71.         Vector2 position = RectTransformUtility.WorldToScreenPoint(cam, background.position);
    72.         Vector2 radius = background.sizeDelta / 2;
    73.         input = (eventData.position - position) / (radius * canvas.scaleFactor);
    74.         FormatInput();
    75.         HandleInput(input.magnitude, input.normalized, radius, cam);
    76.         handle.anchoredPosition = input * radius * handleRange;
    77.     }
    78.  
    79.     protected virtual void HandleInput(float magnitude, Vector2 normalised, Vector2 radius, Camera cam)
    80.     {
    81.         if (magnitude > deadZone)
    82.         {
    83.             if (magnitude > 1)
    84.                 input = normalised;
    85.         }
    86.         else
    87.             input = Vector2.zero;
    88.     }
    89.  
    90.     private void FormatInput()
    91.     {
    92.         if (axisOptions == AxisOptions.Horizontal)
    93.             input = new Vector2(input.x, 0f);
    94.         else if (axisOptions == AxisOptions.Vertical)
    95.             input = new Vector2(0f, input.y);
    96.     }
    97.  
    98.     private float SnapFloat(float value, AxisOptions snapAxis)
    99.     {
    100.         if (value == 0)
    101.             return value;
    102.  
    103.         if (axisOptions == AxisOptions.Both)
    104.         {
    105.             float angle = Vector2.Angle(input, Vector2.up);
    106.             if (snapAxis == AxisOptions.Horizontal)
    107.             {
    108.                 if (angle < 22.5f || angle > 157.5f)
    109.                     return 0;
    110.                 else
    111.                     return (value > 0) ? 1 : -1;
    112.             }
    113.             else if (snapAxis == AxisOptions.Vertical)
    114.             {
    115.                 if (angle > 67.5f && angle < 112.5f)
    116.                     return 0;
    117.                 else
    118.                     return (value > 0) ? 1 : -1;
    119.             }
    120.             return value;
    121.         }
    122.         else
    123.         {
    124.             if (value > 0)
    125.                 return 1;
    126.             if (value < 0)
    127.                 return -1;
    128.         }
    129.         return 0;
    130.     }
    131.  
    132.     public virtual void OnPointerUp(PointerEventData eventData)
    133.     {
    134.         input = Vector2.zero;
    135.         handle.anchoredPosition = Vector2.zero;
    136.     }
    137.  
    138.     protected Vector2 ScreenPointToAnchoredPosition(Vector2 screenPosition)
    139.     {
    140.         Vector2 localPoint = Vector2.zero;
    141.         if (RectTransformUtility.ScreenPointToLocalPointInRectangle(baseRect, screenPosition, cam, out localPoint))
    142.         {
    143.             Vector2 pivotOffset = baseRect.pivot * baseRect.sizeDelta;
    144.             return localPoint - (background.anchorMax * baseRect.sizeDelta) + pivotOffset;
    145.         }
    146.         return Vector2.zero;
    147.     }
    148. }
    149.  
    150. public enum AxisOptions { Both, Horizontal, Vertical }
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.EventSystems;
    5.  
    6. public class FloatingJoystick : Joystick
    7. {
    8.  
    9.     void Update()
    10.     {
    11.         if (Input.GetKeyDown(KeyCode.P))
    12.         {
    13.             Debug.Break();
    14.         }
    15.     }
    16.     protected override void Start()
    17.     {
    18.         base.Start();
    19.         background.gameObject.SetActive(false);
    20.     }
    21.  
    22.     public override void OnPointerDown(PointerEventData eventData)
    23.     {
    24.         background.anchoredPosition = ScreenPointToAnchoredPosition(eventData.position);
    25.         background.gameObject.SetActive(true);
    26.         base.OnPointerDown(eventData);
    27.     }
    28.  
    29.     public override void OnPointerUp(PointerEventData eventData)
    30.     {
    31.         background.gameObject.SetActive(false);
    32.         base.OnPointerUp(eventData);
    33.     }
    34. }
     
  2. Richard_Blackwidow

    Richard_Blackwidow

    Joined:
    Dec 29, 2021
    Posts:
    6
    Joystick parameters at the first touch:

    upload_2023-2-4_21-31-30.png

    Joystick options on second touch:

    upload_2023-2-4_21-32-13.png
     
  3. sodarocketstudios

    sodarocketstudios

    Joined:
    Jan 14, 2021
    Posts:
    21
    I suspect you are having issues because of how the two different screen spaces work. As the name would suggest, in Overlay, the UI is overlaying everything else. Meaning that it is rendered essentially directly on the screen after everything else in the scene, So you can think of it as having no z position.

    When you set the screen space to Camera, you are telling Unity to generate a plane at some distance along the z axis away from the camera. This means that the UI is essential rendered within the world and can be affected by things like field of view.

    There is more information here: https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/UICanvas.html

    My assumption as to why it is only happening on the first touch is that something doesn't get initialized properly when you are in camera screen space. I saw that there is a custom space transformation in the joystick class, so I suspect that it is designed to work for overlay mode and it is that transformation combined with some value initialization that is causing the problem.

    It would also help to have a better idea of the hierarchy of the joystick. I see several references to a background in the code, but I don't necessarily see where it is ever initialized which could be the culprit since I see that the anchored position of the background is being directly set by the custom transform function.