Fake mouse position in 4.6 UI (ANSWERED)

I’ve been attempting to figure out how to fabricate mouse input on the new UI all day, and feel like I’m stuck in the mud. As the UI is new, it seems there is close to zero in the way of documentation on the scripting end of the EventSystem. :face_with_spiral_eyes:

Problem:
For testing purposes, what I am trying to do is get a scroll bar on a canvas to react to 2 key presses. I have the the scroll bar getting selected, and even firing it’s OnDrag event callback (as well as other various callbacks), yet no reaction on the UI end… The goal is to have the ‘T’ key select the scrollbar, and then while holding the ‘S’ key, have it scroll downwards. The latter part (dragging the scrollbar) being the what’s not working. If anyone has any knowledge of how to accomplish this, or a resource (youtube video, blog post, garbage pale outside Unity HQ for scraps of know-how, anything…) I would greatly appreciate it.

Apologies in advance for the following mess of code… (been tinkering all day, haven’t had time to clean up)

Some of what I have (explained below):

// TESTING
        if( Input.GetKeyDown( KeyCode.T ) )
        {
            //PointerEventData data = new PointerEventData( EventSystem.current );
            //ExecuteEvents.Execute( m_scrollBar, data, ExecuteEvents.beginDragHandler );

            PointerEventData data = new PointerEventData( EventSystem.current );
            data.selectedObject = m_scrollBar;
            //data.pressPosition = new Vector2( Input.mousePosition.x, Input.mousePosition.y );
            m_pressPos = new Vector2( Input.mousePosition.x, Input.mousePosition.y );
            ExecuteEvents.Execute( m_scrollBar, data, ExecuteEvents.selectHandler );
            ExecuteEvents.Execute( m_scrollBar, data, ExecuteEvents.pointerEnterHandler );
            ExecuteEvents.Execute( m_scrollBar, data, ExecuteEvents.initializePotentialDrag );
            ExecuteEvents.Execute( m_scrollBar, data, ExecuteEvents.pointerDownHandler );
        }

if( Input.GetKey( KeyCode.S ) )
        {
            PointerEventData data = new PointerEventData( EventSystem.current );
            //data.position = new Vector2( Input.mousePosition.x, Input.mousePosition.y );
            //data.scrollDelta = new Vector2( Input.mouseScrollDelta.x, Input.mouseScrollDelta.y );
            data.pointerDrag = m_scrollBar;
            data.scrollDelta = new Vector2( 0, -0.1f );
            ExecuteEvents.Execute( m_scrollBar, data, ExecuteEvents.dragHandler );
        }

Both of the above if statements are in an overridden function of the BaseInputModule function, Process(). The script itself inherits from BaseInputModule, and is a component of the EventSystem game object in my scene. I have a second script on the scrollbar itself that simply prints information to the console to determine that the events are being properly sent, callbacks like OnDrag, etc.

Thanks so much, any help is greatly appreciated!

Answered my own question today.

Wow, I was tired last night… There was a much simpler question I could have asked: How to give a fake position for the mouse cursor in the new UI in 4.6.

For anyone who is looking to simulate mouse movement in the new UI (give the mouse a fake position), here’s the code:

using UnityEngine;
using UnityEngine.EventSystems;

public class VRInputModule : StandaloneInputModule {

    private Vector2 m_cursorPos;

    // Hand this function your fake mouse position (in screen coords)
    public void UpdateCursorPosition( Vector2 a_pos )
    {
        m_cursorPos = a_pos;
    }

    // NEXT TWO FUNCTIONS (GetPointerData/CopyFromTo)
    // are copied from PointerInputModule source, next update for Unity this likely won't be necessary (As Tim C has made them protected as of this morning (my current version is 4.6.0f3), thank you!), if you have a later version, remove these two functions to receive updates!
    protected bool GetPointerData(int id, out PointerEventData data, bool create)
    {
        if (!m_PointerData.TryGetValue(id, out data) && create)
        {
            data = new PointerEventData(eventSystem)
            {
                pointerId = id,
            };
            m_PointerData.Add(id, data);
            return true;
        }
        return false;
    }
   
    private void CopyFromTo(PointerEventData @from, PointerEventData @to)
    {
        @to.position = @from.position;
        @to.delta = @from.delta;
        @to.scrollDelta = @from.scrollDelta;
        @to.pointerCurrentRaycast = @from.pointerCurrentRaycast;
    }

    // This is the real function we want, the two commented out lines (Input.mousePosition) are replaced with m_cursorPos (our fake mouse position, set with the public function, UpdateCursorPosition)
    private readonly MouseState m_MouseState = new MouseState();
    protected override MouseState GetMousePointerEventData()
    {
        MouseState m = new MouseState();

        // Populate the left button...
        PointerEventData leftData;
        var created = GetPointerData( kMouseLeftId, out leftData, true );

        leftData.Reset();

        if (created)
            leftData.position = m_cursorPos;
            //leftData.position = Input.mousePosition;

        //Vector2 pos = Input.mousePosition;
        Vector2 pos = m_cursorPos;
        leftData.delta = pos - leftData.position;
        leftData.position = pos;
        leftData.scrollDelta = Input.mouseScrollDelta;
        leftData.button = PointerEventData.InputButton.Left;
        eventSystem.RaycastAll(leftData, m_RaycastResultCache);
        var raycast = FindFirstRaycast(m_RaycastResultCache);
        leftData.pointerCurrentRaycast = raycast;
        m_RaycastResultCache.Clear();

        // copy the apropriate data into right and middle slots
        PointerEventData rightData;
        GetPointerData(kMouseRightId, out rightData, true);
        CopyFromTo(leftData, rightData);
        rightData.button = PointerEventData.InputButton.Right;

        PointerEventData middleData;
        GetPointerData(kMouseMiddleId, out middleData, true);
        CopyFromTo(leftData, middleData);
        middleData.button = PointerEventData.InputButton.Middle;

        m_MouseState.SetButtonState(PointerEventData.InputButton.Left, StateForMouseButton(0), leftData);
        m_MouseState.SetButtonState(PointerEventData.InputButton.Right, StateForMouseButton(1), rightData);
        m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, StateForMouseButton(2), middleData);

        return m_MouseState;
    }

}

Simply attach this script to the EventSystem game object in your scene, and deactivate the StandardInputModule.

Hope this helps someone else!

4 Likes

Thank you for posting!! I wanted to fake a mouse click with another mouse button and tried setting up event triggers on all my buttons, but that solution did not work with scrollbars. I came across your script, swapped middleData for leftData, and success!

Great ,But not working on unity 5.2 above.

for anyone looking for unity 5.2 above
fix “protected override MouseState GetMousePointerEventData()”
to “protected override MouseState GetMousePointerEventData( int id =0)”

2 Likes

also, if you change the first function to the following you don’t need to turn off the other input systems for this to work.

public void UpdateCursorPosition( Vector2 a_pos )
{
ActivateModule();
m_cursorPos = a_pos;
Process();
}

Remembered this post and came back to use my old solution for a new problem (so glad I posted it, never would have found it in my old projects!)

For anyone who also wants to change the button used to select UI elements (instead of the left mouse button), use the following code as an example (I’m using Joystick1Button0, or the A button on an xbox controller, but you can use any input button you want :slight_smile: ). Just use this function in place of the original solution’s GetMousePointerEventData.

// This is the real function we want, the two commented out lines (Input.mousePosition) are replaced with m_cursorPos (our fake mouse position, set with the public function, UpdateCursorPosition)
    private readonly MouseState m_MouseState = new MouseState();
    protected override MouseState GetMousePointerEventData( int id = 0 )
    {
        MouseState m = new MouseState();

        // Populate the left button...
        PointerEventData leftData;
        var created = GetPointerData( kMouseLeftId, out leftData, true );

        leftData.Reset();

        if (created)
            leftData.position = m_cursorPos;
            //leftData.position = Input.mousePosition;

        //Vector2 pos = Input.mousePosition;
        Vector2 pos = m_cursorPos;
        leftData.delta = pos - leftData.position;
        leftData.position = pos;
        leftData.scrollDelta = Input.mouseScrollDelta;
        //leftData.button = PointerEventData.InputButton.Left;
        eventSystem.RaycastAll(leftData, m_RaycastResultCache);
        var raycast = FindFirstRaycast(m_RaycastResultCache);
        leftData.pointerCurrentRaycast = raycast;
        m_RaycastResultCache.Clear();

        // copy the apropriate data into right and middle slots
        PointerEventData rightData;
        GetPointerData(kMouseRightId, out rightData, true);
        CopyFromTo(leftData, rightData);
        rightData.button = PointerEventData.InputButton.Right;

        PointerEventData middleData;
        GetPointerData(kMouseMiddleId, out middleData, true);
        CopyFromTo(leftData, middleData);
        middleData.button = PointerEventData.InputButton.Middle;

        // Use the "A" button on the xbox controller for selection instead of the left mouse button
        PointerEventData.FramePressState selectState = PointerEventData.FramePressState.NotChanged;
        if( Input.GetKeyDown( KeyCode.Joystick1Button0 ) )
            selectState = PointerEventData.FramePressState.Pressed;
        else if( Input.GetKeyUp( KeyCode.Joystick1Button0 ) )
            selectState = PointerEventData.FramePressState.Released;

        m_MouseState.SetButtonState(PointerEventData.InputButton.Left, selectState, leftData);
        m_MouseState.SetButtonState(PointerEventData.InputButton.Right, StateForMouseButton(1), rightData);
        m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, StateForMouseButton(2), middleData);

        return m_MouseState;
    }

Hope this helps someone!

3 Likes

Hi,

The coordinates passed on to the function should be the coordinates within the Unity editor or the system ?
I am trying to use this code for performing mouse key press within Unity Editor while running integration tests.

Okay so I am using this bit of code to get around the odd behaviour of Cursor Locked not setting the cursor to centre screen … this is giving me the click event now like I need however I need to sort out how to simulate the pointer enter and exit events.

Any ideas?

@Loden_Heathen_1 , you may have already found an answer to your question of enter and exit events, but if you haven’t here is what I did.

I found that using the protected method called HandlePointerExitAndEnter I would get the enter and exit events (e.g. button changing color on hover, etc.). The two parameters required are: PointerEventData (I used leftData from the example script that AnimalMother posted), and a GameObject (I used raycast.gameObject, where raycast is what is returned from the FindFirstRaycast method).

After that everything just seemed to work. I hope this helps!

I’m probably going to come across as a little dim here but I’m not sure how to

I thought perhaps that I create a script and use this is the update,

CenterMouseInputModule.UpdateCursorPosition(Vector2(Screen.width/2,Screen.height/2);

But that doesn’t seem to work, can anyone point me in the right direction?

1 Like

To get this code working under 5.6.1 i had to replace the UpdateCursorPosition function with this one

public override void UpdateModule()
{
m_cursorPos = Input.mousePosition;
}

As of the UI code in 2017.x, it seems to be enough to just momentarily unlock the cursor, process the input, and then return the cursor to its original lock state.

Since this thread seems to be where you end up through google, I’ll leave the entirety of my FirstPersonInputModule here:

using UnityEngine;
using UnityEngine.EventSystems;
  
public class FirstPersonInputModule : StandaloneInputModule {
    protected override MouseState GetMousePointerEventData(int id) {
        var lockState = Cursor.lockState;
        Cursor.lockState = CursorLockMode.None;
        var mouseState = base.GetMousePointerEventData(id);
        Cursor.lockState = lockState;
        return mouseState;
    }

    protected override void ProcessMove(PointerEventData pointerEvent) {
        var lockState = Cursor.lockState;
        Cursor.lockState = CursorLockMode.None;
        base.ProcessMove(pointerEvent);
        Cursor.lockState = lockState;
    }

    protected override void ProcessDrag(PointerEventData pointerEvent) {
        var lockState = Cursor.lockState;
        Cursor.lockState = CursorLockMode.None;
        base.ProcessDrag(pointerEvent);
        Cursor.lockState = lockState;
    }
}

Attaching this script instead of the StandaloneInputModule effectively makes the center of the screen work as a UI-interacting cursor, while keeping the actual cursor locked and hidden.

Enter/Exit (hover) events, as well as everything else, seems to work as expected. I haven’t tested the drag functionality, but those three methods above are the only ones that check (and return early) if the cursor is locked, so I imagine the entire UI “just works”.

While this approach seems a little hacky, it utilises the existing PointerInputModule instead of overriding it. Hopefully this means any beneficial changes to the built in Unity UI code won’t break the script above (as seems to have happened a couple of times with the other scripts and suggestions in this thread).

9 Likes

You helped me A LOT!!! Thank you

I’m trying to use this in 2019 version and it’s incredibly jerky. Both in an edit view and the build. Is anyone else having this problem ?

Did you get it to work?

1 Like

I am too, having this problem. I am still new around Unity and I am using the free Modular First Person Controller asset. Any news or update to this?

Same, and the StandaloneInputModule trick is not possible anymore with the new Input System

Thanks, this helped me greatly - I added an in-world computer terminal to my game and was struggling to get the buttons to respond to the hidden locked cursor. I didn’t want the ordinary mouse pointer to be used, like in the pause Menu, but for the player to select via looking at items. This worked perfectly in 2018 using the old input system.

This should have been fixed in a recent InputSystem pull and should no longer need any workaround hopefully.

2 Likes