Search Unity

  1. We are migrating the Unity Forums to Unity Discussions by the end of July. Read our announcement for more information and let us know if you have any questions.
    Dismiss Notice
  2. Dismiss Notice

Question Canvas UI Object As Virtual Cursor?

Discussion in 'Scripting' started by Studio_AKIBA, Apr 24, 2024.

  1. Studio_AKIBA

    Studio_AKIBA

    Joined:
    Mar 3, 2014
    Posts:
    1,427
    Hello all,

    Apologies if this is in the wrong topic, but this sort of overlaps with scripting, Input, and UI, but thought this was the best place.

    I am trying to create a virtual cursor in a canvas as I am rendering the canvas to a RenderTexture for other purposes and need to be able to interact with the UI elements in a way similar to a computer (it's an interactable element).

    I found a repo that does this perfectly in Unity 5, but seems to have been largely broken at some point along the line to 2021 (the version my project is using), and can't for the life of me work out how it's broken.

    No errors are thrown, no warnings, no nothing.

    I was wondering if anyone has been able to build a system similar to this

    As a prime example, what this user was searching for is what I am after, but this also seems to no longer work either, and all other examples I can find are using the new input system. Unfortunately I am using a custom controls sub-system built off the legacy input so the new built-in Gamepad Mouse Cursor would also not be a go.

    Basically, I just want to be able to use the pivot point of a UI Image object (or rather it's RectTransform) as the mouse position, and register it as a click location when the actual physical mouse is clicked. I already have the Image object moving around in the Canvas but can't seem to work out how to pass this as a replacement cursor.
     
  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,224
    Try looking into UGUI documentation for all kinds of interfaces which are used to register mouse pointer or touches etc. They work perfectly, I've used it literally a couple of weeks ago in a mobile project for a sliding "virtual joystick".

    Here is the main link, and the scripting reference.

    In your case, you want something like IPointerMoveHandler
    To use it you just implement this interface like so
    Code (csharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3.  
    4. public class MyPointerCapturingClass : MonoBehaviour, IPointerMoveHandler { }
    Now you implement the actual interface by adding this one callback method
    Code (csharp):
    1. void OnPointerMove(PointerEventData eventData) {
    2.   // where you have access to eventData
    3. }
    PointerEventData has a lot of stuff you can grab and take into consideration.
    Now the question remains whether you can put this directly on the canvas object.

    I've used it recently in a context where I stretched an invisible raw image over my game, and this is where the interaction takes place, so this is where I put the script as well. I think it should work with just the canvas, although you should experiment on your own, see what suits you best.

    To use this systematically is a little cumbersome (similar to how physics collisions and triggers are registered), and so if you want to connect this with another part of your logic/engine I recommend you to try implement events (either Unity's if you want them in the inspector, or native C#) so that you can propagate the data you actually need where you need it.

    For example
    Code (csharp):
    1. delegate void CursorHandler(Vector2 position, int button);
    2. // ^ this is just a meta-declaration for a delegate, can be anywhere, even nested, like class or interface
    3.  
    4. public event CursorHandler OnCursorChange; // this is part of the class
    5.  
    6. void OnPointerMove(PointerEventData eventData) {
    7.   OnCursorChange?.Invoke(eventData.position, (int)eventData.button);
    8. }
    Then in your user code, you just hook this up by doing
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class MyUserBehaviour : MonoBehaviour {
    4.  
    5.   [SerializeField] MyPointerCapturingClass _capture;
    6.  
    7.   void OnEnable() => _capture.OnCursorChange += myPointerHandler;
    8.   void OnDisable() => _capture.OnCursorChange -= myPointerHandler;
    9.  
    10.   void myPointerHandler(Vector2 position, int button) {
    11.     Debug.Log($"position is {position} button is {button}");
    12.   }
    13.  
    14. }
     
  3. Studio_AKIBA

    Studio_AKIBA

    Joined:
    Mar 3, 2014
    Posts:
    1,427
    I've copied the current Standalone Input Module and modified it to allow the cursor's position to be the virtual cursor position rather than the mouse, and I've verified this is working using the previewGUI, however the PointerEnter entry never yields any results on any UI elements, nor do any interactive elements respond to interactions.

    I've been over my code a few times, looking at both the default Standalone and my own and can't work out what I'm doing wrong. It's still surprising to me there is no simple way to do this considering how many games have this feature.

    Am I missing something obvious?

    Code (CSharp):
    1. public class VirtualInputModule : PointerInputModule
    2. {
    3.  
    4.     public RectTransform VirtualCursor;
    5.     public Camera CanvasCamera;
    6.  
    7.     private float m_PrevActionTime;
    8.     private Vector2 m_LastMoveVector;
    9.     private int m_ConsecutiveMoveCount = 0;
    10.  
    11.     private Vector2 m_LastMousePosition;
    12.     private Vector2 m_MousePosition;
    13.  
    14.     private GameObject m_CurrentFocusedGameObject;
    15.  
    16.     private PointerEventData m_InputPointerEvent;
    17.  
    18.     /*protected StandaloneInputModule()
    19.     {
    20.     }*/
    21.  
    22.     [Obsolete("Mode is no longer needed on input module as it handles both mouse and keyboard simultaneously.", false)]
    23.     public enum InputMode
    24.     {
    25.         Mouse,
    26.         Buttons
    27.     }
    28.  
    29.     [Obsolete("Mode is no longer needed on input module as it handles both mouse and keyboard simultaneously.", false)]
    30.     public InputMode inputMode
    31.     {
    32.         get { return InputMode.Mouse; }
    33.     }
    34.  
    35.     [SerializeField]
    36.     private string m_HorizontalAxis = "Horizontal";
    37.  
    38.     /// <summary>
    39.     /// Name of the vertical axis for movement (if axis events are used).
    40.     /// </summary>
    41.     [SerializeField]
    42.     private string m_VerticalAxis = "Vertical";
    43.  
    44.     /// <summary>
    45.     /// Name of the submit button.
    46.     /// </summary>
    47.     [SerializeField]
    48.     private string m_SubmitButton = "Submit";
    49.  
    50.     /// <summary>
    51.     /// Name of the submit button.
    52.     /// </summary>
    53.     [SerializeField]
    54.     private string m_CancelButton = "Cancel";
    55.  
    56.     [SerializeField]
    57.     private float m_InputActionsPerSecond = 10;
    58.  
    59.     [SerializeField]
    60.     private float m_RepeatDelay = 0.5f;
    61.  
    62.     [SerializeField]
    63.     [FormerlySerializedAs("m_AllowActivationOnMobileDevice")]
    64.     [HideInInspector]
    65.     private bool m_ForceModuleActive;
    66.  
    67.     [Obsolete("allowActivationOnMobileDevice has been deprecated. Use forceModuleActive instead (UnityUpgradable) -> forceModuleActive")]
    68.     public bool allowActivationOnMobileDevice
    69.     {
    70.         get { return m_ForceModuleActive; }
    71.         set { m_ForceModuleActive = value; }
    72.     }
    73.  
    74.     /// <summary>
    75.     /// Force this module to be active.
    76.     /// </summary>
    77.     /// <remarks>
    78.     /// If there is no module active with higher priority (ordered in the inspector) this module will be forced active even if valid enabling conditions are not met.
    79.     /// </remarks>
    80.  
    81.     [Obsolete("forceModuleActive has been deprecated. There is no need to force the module awake as StandaloneInputModule works for all platforms")]
    82.     public bool forceModuleActive
    83.     {
    84.         get { return m_ForceModuleActive; }
    85.         set { m_ForceModuleActive = value; }
    86.     }
    87.  
    88.     /// <summary>
    89.     /// Number of keyboard / controller inputs allowed per second.
    90.     /// </summary>
    91.     public float inputActionsPerSecond
    92.     {
    93.         get { return m_InputActionsPerSecond; }
    94.         set { m_InputActionsPerSecond = value; }
    95.     }
    96.  
    97.     /// <summary>
    98.     /// Delay in seconds before the input actions per second repeat rate takes effect.
    99.     /// </summary>
    100.     /// <remarks>
    101.     /// If the same direction is sustained, the inputActionsPerSecond property can be used to control the rate at which events are fired. However, it can be desirable that the first repetition is delayed, so the user doesn't get repeated actions by accident.
    102.     /// </remarks>
    103.     public float repeatDelay
    104.     {
    105.         get { return m_RepeatDelay; }
    106.         set { m_RepeatDelay = value; }
    107.     }
    108.  
    109.     /// <summary>
    110.     /// Name of the horizontal axis for movement (if axis events are used).
    111.     /// </summary>
    112.     public string horizontalAxis
    113.     {
    114.         get { return m_HorizontalAxis; }
    115.         set { m_HorizontalAxis = value; }
    116.     }
    117.  
    118.     /// <summary>
    119.     /// Name of the vertical axis for movement (if axis events are used).
    120.     /// </summary>
    121.     public string verticalAxis
    122.     {
    123.         get { return m_VerticalAxis; }
    124.         set { m_VerticalAxis = value; }
    125.     }
    126.  
    127.     /// <summary>
    128.     /// Maximum number of input events handled per second.
    129.     /// </summary>
    130.     public string submitButton
    131.     {
    132.         get { return m_SubmitButton; }
    133.         set { m_SubmitButton = value; }
    134.     }
    135.  
    136.     /// <summary>
    137.     /// Input manager name for the 'cancel' button.
    138.     /// </summary>
    139.     public string cancelButton
    140.     {
    141.         get { return m_CancelButton; }
    142.         set { m_CancelButton = value; }
    143.     }
    144.  
    145.     private bool ShouldIgnoreEventsOnNoFocus()
    146.     {
    147. #if UNITY_EDITOR
    148.         return !UnityEditor.EditorApplication.isRemoteConnected;
    149. #else
    150.             return true;
    151. #endif
    152.     }
    153.  
    154.     public override void UpdateModule()
    155.     {
    156.         if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
    157.         {
    158.             if (m_InputPointerEvent != null && m_InputPointerEvent.pointerDrag != null && m_InputPointerEvent.dragging)
    159.             {
    160.                 ReleaseMouse(m_InputPointerEvent, m_InputPointerEvent.pointerCurrentRaycast.gameObject);
    161.             }
    162.  
    163.             m_InputPointerEvent = null;
    164.  
    165.             return;
    166.         }
    167.  
    168.         m_LastMousePosition = m_MousePosition;
    169.         m_MousePosition = VirtualCursor.anchoredPosition;
    170.     }
    171.  
    172.     private void ReleaseMouse(PointerEventData pointerEvent, GameObject currentOverGo)
    173.     {
    174.         ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
    175.  
    176.         var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
    177.  
    178.         // PointerClick and Drop events
    179.         if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick)
    180.         {
    181.             ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler);
    182.         }
    183.         if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
    184.         {
    185.             ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler);
    186.         }
    187.  
    188.         pointerEvent.eligibleForClick = false;
    189.         pointerEvent.pointerPress = null;
    190.         pointerEvent.rawPointerPress = null;
    191.         pointerEvent.pointerClick = null;
    192.  
    193.         if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
    194.             ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);
    195.  
    196.         pointerEvent.dragging = false;
    197.         pointerEvent.pointerDrag = null;
    198.  
    199.         // redo pointer enter / exit to refresh state
    200.         // so that if we moused over something that ignored it before
    201.         // due to having pressed on something else
    202.         // it now gets it.
    203.         if (currentOverGo != pointerEvent.pointerEnter)
    204.         {
    205.             HandlePointerExitAndEnter(pointerEvent, null);
    206.             HandlePointerExitAndEnter(pointerEvent, currentOverGo);
    207.         }
    208.  
    209.         m_InputPointerEvent = pointerEvent;
    210.     }
    211.  
    212.     public override bool ShouldActivateModule()
    213.     {
    214.         if (!base.ShouldActivateModule())
    215.             return false;
    216.  
    217.         var shouldActivate = m_ForceModuleActive;
    218.         shouldActivate |= input.GetButtonDown(m_SubmitButton);
    219.         shouldActivate |= input.GetButtonDown(m_CancelButton);
    220.         shouldActivate |= !Mathf.Approximately(input.GetAxisRaw(m_HorizontalAxis), 0.0f);
    221.         shouldActivate |= !Mathf.Approximately(input.GetAxisRaw(m_VerticalAxis), 0.0f);
    222.         shouldActivate |= (m_MousePosition - m_LastMousePosition).sqrMagnitude > 0.0f;
    223.         shouldActivate |= input.GetMouseButtonDown(0);
    224.  
    225.         if (input.touchCount > 0)
    226.             shouldActivate = true;
    227.  
    228.         return shouldActivate;
    229.     }
    230.  
    231.     /// <summary>
    232.     /// See BaseInputModule.
    233.     /// </summary>
    234.     public override void ActivateModule()
    235.     {
    236.         if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
    237.             return;
    238.  
    239.         base.ActivateModule();
    240.         m_MousePosition = input.mousePosition;
    241.         m_LastMousePosition = input.mousePosition;
    242.  
    243.         var toSelect = eventSystem.currentSelectedGameObject;
    244.         if (toSelect == null)
    245.             toSelect = eventSystem.firstSelectedGameObject;
    246.  
    247.         eventSystem.SetSelectedGameObject(toSelect, GetBaseEventData());
    248.     }
    249.  
    250.     /// <summary>
    251.     /// See BaseInputModule.
    252.     /// </summary>
    253.     public override void DeactivateModule()
    254.     {
    255.         base.DeactivateModule();
    256.         ClearSelection();
    257.     }
    258.  
    259.     public override void Process()
    260.     {
    261.         if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
    262.             return;
    263.  
    264.         bool usedEvent = SendUpdateEventToSelectedObject();
    265.  
    266.         // case 1004066 - touch / mouse events should be processed before navigation events in case
    267.         // they change the current selected gameobject and the submit button is a touch / mouse button.
    268.  
    269.         // touch needs to take precedence because of the mouse emulation layer
    270.         if (!ProcessTouchEvents() && input.mousePresent)
    271.             ProcessMouseEvent();
    272.  
    273.         if (eventSystem.sendNavigationEvents)
    274.         {
    275.             if (!usedEvent)
    276.                 usedEvent |= SendMoveEventToSelectedObject();
    277.  
    278.             if (!usedEvent)
    279.                 SendSubmitEventToSelectedObject();
    280.         }
    281.     }
    282.  
    283.     private bool ProcessTouchEvents()
    284.     {
    285.         for (int i = 0; i < input.touchCount; ++i)
    286.         {
    287.             Touch touch = input.GetTouch(i);
    288.  
    289.             if (touch.type == TouchType.Indirect)
    290.                 continue;
    291.  
    292.             bool released;
    293.             bool pressed;
    294.             var pointer = GetTouchPointerEventData(touch, out pressed, out released);
    295.  
    296.             ProcessTouchPress(pointer, pressed, released);
    297.  
    298.             if (!released)
    299.             {
    300.                 ProcessMove(pointer);
    301.                 ProcessDrag(pointer);
    302.             }
    303.             else
    304.                 RemovePointerData(pointer);
    305.         }
    306.         return input.touchCount > 0;
    307.     }
    308.  
    309.     /// <summary>
    310.     /// This method is called by Unity whenever a touch event is processed. Override this method with a custom implementation to process touch events yourself.
    311.     /// </summary>
    312.     /// <param name="pointerEvent">Event data relating to the touch event, such as position and ID to be passed to the touch event destination object.</param>
    313.     /// <param name="pressed">This is true for the first frame of a touch event, and false thereafter. This can therefore be used to determine the instant a touch event occurred.</param>
    314.     /// <param name="released">This is true only for the last frame of a touch event.</param>
    315.     /// <remarks>
    316.     /// This method can be overridden in derived classes to change how touch press events are handled.
    317.     /// </remarks>
    318.     protected void ProcessTouchPress(PointerEventData pointerEvent, bool pressed, bool released)
    319.     {
    320.         var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
    321.  
    322.         // PointerDown notification
    323.         if (pressed)
    324.         {
    325.             pointerEvent.eligibleForClick = true;
    326.             pointerEvent.delta = Vector2.zero;
    327.             pointerEvent.dragging = false;
    328.             pointerEvent.useDragThreshold = true;
    329.             pointerEvent.pressPosition = pointerEvent.position;
    330.             pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
    331.  
    332.             DeselectIfSelectionChanged(currentOverGo, pointerEvent);
    333.  
    334.             if (pointerEvent.pointerEnter != currentOverGo)
    335.             {
    336.                 // send a pointer enter to the touched element if it isn't the one to select...
    337.                 HandlePointerExitAndEnter(pointerEvent, currentOverGo);
    338.                 pointerEvent.pointerEnter = currentOverGo;
    339.             }
    340.  
    341.             // search for the control that will receive the press
    342.             // if we can't find a press handler set the press
    343.             // handler to be what would receive a click.
    344.             var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
    345.  
    346.             var newClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
    347.  
    348.             // didnt find a press handler... search for a click handler
    349.             if (newPressed == null)
    350.                 newPressed = newClick;
    351.  
    352.             // Debug.Log("Pressed: " + newPressed);
    353.  
    354.             float time = Time.unscaledTime;
    355.  
    356.             if (newPressed == pointerEvent.lastPress)
    357.             {
    358.                 var diffTime = time - pointerEvent.clickTime;
    359.                 if (diffTime < 0.3f)
    360.                     ++pointerEvent.clickCount;
    361.                 else
    362.                     pointerEvent.clickCount = 1;
    363.  
    364.                 pointerEvent.clickTime = time;
    365.             }
    366.             else
    367.             {
    368.                 pointerEvent.clickCount = 1;
    369.             }
    370.  
    371.             pointerEvent.pointerPress = newPressed;
    372.             pointerEvent.rawPointerPress = currentOverGo;
    373.             pointerEvent.pointerClick = newClick;
    374.  
    375.             pointerEvent.clickTime = time;
    376.  
    377.             // Save the drag handler as well
    378.             pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
    379.  
    380.             if (pointerEvent.pointerDrag != null)
    381.                 ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);
    382.         }
    383.  
    384.         // PointerUp notification
    385.         if (released)
    386.         {
    387.             // Debug.Log("Executing pressup on: " + pointer.pointerPress);
    388.             ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
    389.  
    390.             // Debug.Log("KeyCode: " + pointer.eventData.keyCode);
    391.  
    392.             // see if we mouse up on the same element that we clicked on...
    393.             var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
    394.  
    395.             // PointerClick and Drop events
    396.             if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick)
    397.             {
    398.                 ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler);
    399.             }
    400.  
    401.             if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
    402.             {
    403.                 ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler);
    404.             }
    405.  
    406.             pointerEvent.eligibleForClick = false;
    407.             pointerEvent.pointerPress = null;
    408.             pointerEvent.rawPointerPress = null;
    409.             pointerEvent.pointerClick = null;
    410.  
    411.             if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
    412.                 ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);
    413.  
    414.             pointerEvent.dragging = false;
    415.             pointerEvent.pointerDrag = null;
    416.  
    417.             // send exit events as we need to simulate this on touch up on touch device
    418.             ExecuteEvents.ExecuteHierarchy(pointerEvent.pointerEnter, pointerEvent, ExecuteEvents.pointerExitHandler);
    419.             pointerEvent.pointerEnter = null;
    420.         }
    421.  
    422.         m_InputPointerEvent = pointerEvent;
    423.     }
    424.  
    425.     /// <summary>
    426.     /// Calculate and send a submit event to the current selected object.
    427.     /// </summary>
    428.     /// <returns>If the submit event was used by the selected object.</returns>
    429.     protected bool SendSubmitEventToSelectedObject()
    430.     {
    431.         if (eventSystem.currentSelectedGameObject == null)
    432.             return false;
    433.  
    434.         var data = GetBaseEventData();
    435.         if (input.GetButtonDown(m_SubmitButton))
    436.             ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.submitHandler);
    437.  
    438.         if (input.GetButtonDown(m_CancelButton))
    439.             ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.cancelHandler);
    440.         return data.used;
    441.     }
    442.  
    443.     private Vector2 GetRawMoveVector()
    444.     {
    445.         Vector2 move = Vector2.zero;
    446.         move.x = input.GetAxisRaw(m_HorizontalAxis);
    447.         move.y = input.GetAxisRaw(m_VerticalAxis);
    448.  
    449.         if (input.GetButtonDown(m_HorizontalAxis))
    450.         {
    451.             if (move.x < 0)
    452.                 move.x = -1f;
    453.             if (move.x > 0)
    454.                 move.x = 1f;
    455.         }
    456.         if (input.GetButtonDown(m_VerticalAxis))
    457.         {
    458.             if (move.y < 0)
    459.                 move.y = -1f;
    460.             if (move.y > 0)
    461.                 move.y = 1f;
    462.         }
    463.         return move;
    464.     }
    465.  
    466.     /// <summary>
    467.     /// Calculate and send a move event to the current selected object.
    468.     /// </summary>
    469.     /// <returns>If the move event was used by the selected object.</returns>
    470.     protected bool SendMoveEventToSelectedObject()
    471.     {
    472.         float time = Time.unscaledTime;
    473.  
    474.         Vector2 movement = GetRawMoveVector();
    475.         if (Mathf.Approximately(movement.x, 0f) && Mathf.Approximately(movement.y, 0f))
    476.         {
    477.             m_ConsecutiveMoveCount = 0;
    478.             return false;
    479.         }
    480.  
    481.         bool similarDir = (Vector2.Dot(movement, m_LastMoveVector) > 0);
    482.  
    483.         // If direction didn't change at least 90 degrees, wait for delay before allowing consequtive event.
    484.         if (similarDir && m_ConsecutiveMoveCount == 1)
    485.         {
    486.             if (time <= m_PrevActionTime + m_RepeatDelay)
    487.                 return false;
    488.         }
    489.         // If direction changed at least 90 degree, or we already had the delay, repeat at repeat rate.
    490.         else
    491.         {
    492.             if (time <= m_PrevActionTime + 1f / m_InputActionsPerSecond)
    493.                 return false;
    494.         }
    495.  
    496.         var axisEventData = GetAxisEventData(movement.x, movement.y, 0.6f);
    497.  
    498.         if (axisEventData.moveDir != MoveDirection.None)
    499.         {
    500.             ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, axisEventData, ExecuteEvents.moveHandler);
    501.             if (!similarDir)
    502.                 m_ConsecutiveMoveCount = 0;
    503.             m_ConsecutiveMoveCount++;
    504.             m_PrevActionTime = time;
    505.             m_LastMoveVector = movement;
    506.         }
    507.         else
    508.         {
    509.             m_ConsecutiveMoveCount = 0;
    510.         }
    511.  
    512.         return axisEventData.used;
    513.     }
    514.  
    515.     protected void ProcessMouseEvent()
    516.     {
    517.         ProcessMouseEvent(0);
    518.     }
    519.  
    520.     [Obsolete("This method is no longer checked, overriding it with return true does nothing!")]
    521.     protected virtual bool ForceAutoSelect()
    522.     {
    523.         return false;
    524.     }
    525.  
    526.     /// <summary>
    527.     /// Process all mouse events.
    528.     /// </summary>
    529.     protected void ProcessMouseEvent(int id)
    530.     {
    531.         var mouseData = GetMousePointerEventData(id);
    532.         var leftButtonData = mouseData.GetButtonState(PointerEventData.InputButton.Left).eventData;
    533.  
    534.         m_CurrentFocusedGameObject = leftButtonData.buttonData.pointerCurrentRaycast.gameObject;
    535.  
    536.         // Process the first mouse button fully
    537.         ProcessMousePress(leftButtonData);
    538.         ProcessMove(leftButtonData.buttonData);
    539.         ProcessDrag(leftButtonData.buttonData);
    540.  
    541.         // Now process right / middle clicks
    542.         ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData);
    543.         ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData.buttonData);
    544.         ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData);
    545.         ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData.buttonData);
    546.  
    547.         if (!Mathf.Approximately(leftButtonData.buttonData.scrollDelta.sqrMagnitude, 0.0f))
    548.         {
    549.             var scrollHandler = ExecuteEvents.GetEventHandler<IScrollHandler>(leftButtonData.buttonData.pointerCurrentRaycast.gameObject);
    550.             ExecuteEvents.ExecuteHierarchy(scrollHandler, leftButtonData.buttonData, ExecuteEvents.scrollHandler);
    551.         }
    552.     }
    553.  
    554.     protected bool SendUpdateEventToSelectedObject()
    555.     {
    556.         if (eventSystem.currentSelectedGameObject == null)
    557.             return false;
    558.  
    559.         var data = GetBaseEventData();
    560.         ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.updateSelectedHandler);
    561.         return data.used;
    562.     }
    563.  
    564.     /// <summary>
    565.     /// Calculate and process any mouse button state changes.
    566.     /// </summary>
    567.     protected void ProcessMousePress(MouseButtonEventData data)
    568.     {
    569.         var pointerEvent = data.buttonData;
    570.         var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
    571.  
    572.         // PointerDown notification
    573.         if (data.PressedThisFrame())
    574.         {
    575.             pointerEvent.eligibleForClick = true;
    576.             pointerEvent.delta = Vector2.zero;
    577.             pointerEvent.dragging = false;
    578.             pointerEvent.useDragThreshold = true;
    579.             pointerEvent.pressPosition = pointerEvent.position;
    580.             pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
    581.  
    582.             DeselectIfSelectionChanged(currentOverGo, pointerEvent);
    583.  
    584.             // search for the control that will receive the press
    585.             // if we can't find a press handler set the press
    586.             // handler to be what would receive a click.
    587.             var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
    588.             var newClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
    589.  
    590.             // didnt find a press handler... search for a click handler
    591.             if (newPressed == null)
    592.                 newPressed = newClick;
    593.  
    594.             // Debug.Log("Pressed: " + newPressed);
    595.  
    596.             float time = Time.unscaledTime;
    597.  
    598.             if (newPressed == pointerEvent.lastPress)
    599.             {
    600.                 var diffTime = time - pointerEvent.clickTime;
    601.                 if (diffTime < 0.3f)
    602.                     ++pointerEvent.clickCount;
    603.                 else
    604.                     pointerEvent.clickCount = 1;
    605.  
    606.                 pointerEvent.clickTime = time;
    607.             }
    608.             else
    609.             {
    610.                 pointerEvent.clickCount = 1;
    611.             }
    612.  
    613.             pointerEvent.pointerPress = newPressed;
    614.             pointerEvent.rawPointerPress = currentOverGo;
    615.             pointerEvent.pointerClick = newClick;
    616.  
    617.             pointerEvent.clickTime = time;
    618.  
    619.             // Save the drag handler as well
    620.             pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
    621.  
    622.             if (pointerEvent.pointerDrag != null)
    623.                 ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);
    624.  
    625.             m_InputPointerEvent = pointerEvent;
    626.         }
    627.  
    628.         // PointerUp notification
    629.         if (data.ReleasedThisFrame())
    630.         {
    631.             ReleaseMouse(pointerEvent, currentOverGo);
    632.         }
    633.     }
    634.  
    635.     private readonly MouseState m_MouseState = new MouseState();
    636.  
    637.     protected override MouseState GetMousePointerEventData(int id)
    638.     {
    639.         // Populate the left button...
    640.         PointerEventData leftData;
    641.         var created = GetPointerData(kMouseLeftId, out leftData, true);
    642.  
    643.         leftData.Reset();
    644.  
    645.         var screenSpaceCursorPosition = RectTransformUtility.WorldToScreenPoint(CanvasCamera,
    646.             VirtualCursor.position);
    647.  
    648.         if (created)
    649.             leftData.position = screenSpaceCursorPosition;
    650.  
    651.         Vector2 pos = screenSpaceCursorPosition;
    652.         leftData.delta = pos - leftData.position;
    653.         leftData.position = pos;
    654.         leftData.scrollDelta = Input.mouseScrollDelta;
    655.         leftData.button = PointerEventData.InputButton.Left;
    656.         eventSystem.RaycastAll(leftData, m_RaycastResultCache);
    657.         var raycast = FindFirstRaycast(m_RaycastResultCache);
    658.         leftData.pointerCurrentRaycast = raycast;
    659.         m_RaycastResultCache.Clear();
    660.  
    661.         // copy the apropriate data into right and middle slots
    662.         PointerEventData rightData;
    663.         GetPointerData(kMouseRightId, out rightData, true);
    664.         CopyFromTo(leftData, rightData);
    665.         rightData.button = PointerEventData.InputButton.Right;
    666.  
    667.         PointerEventData middleData;
    668.         GetPointerData(kMouseMiddleId, out middleData, true);
    669.         CopyFromTo(leftData, middleData);
    670.         middleData.button = PointerEventData.InputButton.Middle;
    671.  
    672.         m_MouseState.SetButtonState(PointerEventData.InputButton.Left, StateForMouseButton(0), leftData);
    673.         m_MouseState.SetButtonState(PointerEventData.InputButton.Right, StateForMouseButton(1), rightData);
    674.         m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, StateForMouseButton(2), middleData);
    675.  
    676.         return m_MouseState;
    677.     }
    678.  
    679.     protected GameObject GetCurrentFocusedGameObject()
    680.     {
    681.         return m_CurrentFocusedGameObject;
    682.     }
    683. }
     
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,224
    I'm not sure why you need a custom input module, perhaps I'm not understanding what you need. I gave you a working example in my previous post and it is kind of expected that you provide some feedback. I also have very little experience with UI Toolkit, if that's what you're using, so maybe someone else can share more info on that.
     
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,224
    Now I'm looking at the repo, and it seems to me that, other than just wanting a software cursor, you also want to simulate input? Sure, introducing a custom input module should work, but you can also introduce your own mediator pattern to an existing input, and then everything reacts to that. I presume you want the whole UI system to react to this simulated environment on its own? In that case supplanting a module of your own makes sense.

    Have you tried VirtualMouseInput?
     
  6. Studio_AKIBA

    Studio_AKIBA

    Joined:
    Mar 3, 2014
    Posts:
    1,427
    Oh I'm not using the UI Toolkit, just the regular UI stuff.

    Your post was talking about implementing the pointer input interface, examples I found online talked about modifying the actual input module as this is what handles the cursor position, unless I'm misunderstanding how you're overriding the position?

    I can't see in your post or the links where the actual position is modified. I assume I still need to do this in order to use a canvas image's anchor point as the location.
     
  7. Studio_AKIBA

    Studio_AKIBA

    Joined:
    Mar 3, 2014
    Posts:
    1,427
    I've looked into the new input system, unfortunately everything in my project is using the old input system due to some legacy dependency on a custom control system I built back in the Unity 5 days and have just been building on since.

    The first thing I tried was simply running both systems together but it caused a lot of conflicts and issues with input in general so I decided that probably wasn't the way to go.

    Hence why I've been trying to work out how to implement this myself, it would take far longer to port everything over to the new input system than it would be to get this working.
     
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,224
    I though you just wanted to read the mouse position over canvas in order to provide a software cursor. However if you want to wire this pointer back to event system so that it natively reacts, then check out the second post (edit: sure, it's the new input system, my bad). I must admit I never needed this, so I can't help you more (from the top of my head). Can you explain your use case in more detail?
     
  9. Studio_AKIBA

    Studio_AKIBA

    Joined:
    Mar 3, 2014
    Posts:
    1,427
    Basically I have an interactive element in my level (it's a first-person mystery game) where the player can "dock" with a computer and use it.

    Technically speaking, this "computer" is a Canvas hidden under the map set to Screen Space - Camera and rendered to a RenderTexture which is used for the computer monitor. There are interactable elements within this Canvas (a few buttons and a Scroll View for this one). I have an Image with a cursor sprite with the pivot set to the "click location" I want to use for interacting with the elements.

    As a result of this, the mouse position would obviously not work for this due to the use of a RenderTexture (used so I can apply shader effects to the "monitor").

    Basically, I was hoping to use this cursor image as a replacement cursor to interact with the buttons and scroll view (I can always remove the scroll view if dragging is a no-go). The GitHub repo I linked to was something I'd used way back in the Unity 5 days, but it appears the pointer interactions have been changed somewhere along the line and no longer works in later versions.

    Super edge case I know.