Set mouse position?

Hey, i’ve looked through the forums and google and all i find is 2010-ish answers saying it cant be done.

so, how do we do it?
Unity must have given a solution to this by now or at least a work around, i saw someone talking about getting into to OS itself, but that’s way to far imo.

why can’t i find it, anyone?

Looks like this is the top result when googling for “set cursor position” now.

You have 2 options:

  • Use a software cursor; set the position using the mouse delta and you can move it wherever you want.
  • Use Mouse.WarpCursorPosition from the new input system:
Mouse.current.WarpCursorPosition(Vector2 mousePosition);
2 Likes

Unity uses Mono, not .NET, on all platforms including Windows, and Unity’s version of Mono has not changed since 2010. So that won’t work. (Well, except Windows Store uses .NET, not sure anyone would target just that platform though.) If you use a software cursor, you can do whatever you want with it.

–Eric

2 Likes

@Eric5h5 Hence why I said only if he’s targeting windows exclusively ;p

It’s bodgy though and I see no reason to be controlling the cursor position anyway.

Oh don’t get me wrong, i hate those things too, what I’m after is for an RTS camera, when you right click and drag to rotate i want the cursor to go invisible and then pop back up where it was.

currently i’m making it invisible and on MouseButtonUp i do
LockMode = Locked;
LockMode = None;

that pops it back to the center, but i don’t like it at all.
is there really no way to do this?

1 Like

make the cursor invisible.
set an image to follow the cursor position.
hide/show that image instead.

1 Like

Or alternatively set it invisible/visible again:

Windows and Windows Store are two quite different platforms. “Targetting Windows” isn’t covered by Windows Store. That has specific requirements/limitations. Unity Windows apps don’t use .NET.

Use a software cursor?

–Eric

1 Like

Here, if you’re looking for Windows Standalone:

#if UNITY_STANDALONE_WIN
public class Win32
{
    [DllImport("User32.Dll")]
    public static extern long SetCursorPos(int x, int y);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetCursorPos(out POINT lpPoint);

    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int X;
        public int Y;

        public POINT(int x, int y)
        {
            this.X = x;
            this.Y = y;
        }
    }
}
// You can use like this:
#if UNITY_STANDALONE_WIN
                Win32.POINT pt = new Win32.POINT();
                Win32.GetCursorPos(out pt);
                pos.x = pt.X;
                pos.y = pt.Y;
#endif
// and / or like this:
#if UNITY_STANDALONE_WIN
                Win32.SetCursorPos((int)pos.x, (int)pos.y);
#endif

Hopefully I copied the correct code for you. I once found the proper code for Mac (I think?) But sadly I could not ever find it, again. Not setup properly for Unity in a way that I could understand, sadly…

If anyone knows for Mac (or linux?) I’d love to hear about it :slight_smile: :slight_smile:

To the OP: I actually use this for exactly the reason you described. I love it - I think it’s a great addition. I also lock and hide the cursor, but didn’t include that code, as it’s just regular Unity stuff…

8 Likes

Awesome!

what am i doing wrong?

6314403--699603--upload_2020-9-15_15-31-24.png

The code below line 32 is supposed to be inserted into valid code like within a method in your script that uses this Win32 class. He just provided those snippets of code at the bottom as a few usage examples, they don’t belong below the class definition, that’s not valid code.

Exactly my usecase, homeworld-style move-mode, where you can move your target-x/y-line with during a “move mode”, during this “move mode” you can still RMB-hold to orbit the screen around the targetted ship which then hides the mouse (and keeps the move-line locked in the original place while you are orbitting). On unpressing RMB then the mouse should jump back to the move-location (wherever it is now) so the movement-line stays “in place”.
Might sound like an obscure case, but this is how it actually works in a released game, and it feels pretty good (much better then the mousecursor ending up “wherever” on the screen and your move-line popping wherever the mouse now happens to be the moment you let go of RMB).

Anyway, with the new input system (install via packagemanager) you can use Mouse.current.WarpCursorPosition

https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/Mouse.html:
On desktop platforms (Windows, Mac, Linux, and UWP), you can move the mouse cursor via code. Note that this moves the system’s actual mouse cursor, not just Unity’s internally-stored mouse position. This means that the user sees the cursor jumping to a different position, which is generally considered to be bad UX practice. It’s advisable to only do this if the cursor is hidden (see the Cursor API documentation for more information).

6 Likes

For those wondering why it should ever be needed…

When you Lock the cursor, Unity will center it. This centered Lock cursor will still interfere with UI elements, which is hyper annoying when you are trying to control the UI with a gamepad.

The only solution I ever found was to force the cursor to position (0,0) and to hide it.

Unity has no proper solution for this, which kinda sucks.

No proper solution indeed, but you can get it mostly working like this:

        Vector3 mousepos = Camera.main.WorldToScreenPoint(pos + MoveTarget);
        // 'feature' workaround: https://discussions.unity.com/t/799589
        //InputSystem.QueueDeltaStateEvent(Mouse.current.position,  (Vector2)mousepos);   // required 8 bytes, not 12!
        InputState.Change(Mouse.current.position, (Vector2)mousepos);
#if !UNITY_EDITOR
            // bug workaround : https://forum.unity.com/threads/mouse-y-position-inverted-in-build-using-mouse-current-warpcursorposition.682627/#post-5387577
            mousepos.Set(mousepos.x, Screen.height - mousepos.y, mousepos.z);
#endif
        Mouse.current.WarpCursorPosition(mousepos);

What is especially sad is that the mouse-inverted bug was reported almost 2 years ago… Also I think there’s still another bug when you desktop resolution is smaller than the fullscreen resolution…
If anyone finds another workaround for that (or better: a decent solution) please also share :slight_smile:

Version for Mac:

using UnityEngine;
using System.Runtime.InteropServices;
using System;

public class Mouse : MonoBehaviour
{
    // Mac calculates global screen coordinates from top left corner of screen
    #if (UNITY_EDITOR && UNITY_EDITOR_OSX) || (!UNITY_EDITOR && UNITY_STANDALONE_OSX)

        [DllImport("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics")]
        public static extern int CGWarpMouseCursorPosition(CGPoint point);

        [DllImport("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics")]
        public static extern IntPtr CGEventCreate(IntPtr source);

        [DllImport("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics")]
        public static extern CGPoint CGEventGetLocation(IntPtr evt);

        [DllImport("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics")]
        public static extern void CFRelease(IntPtr cf);

        public struct CGPoint
        {
            public double X { get; set; }
            public double Y { get; set; }
        }

        Vector2 GetCursorPos ()
        {
            IntPtr ptr = CGEventCreate(IntPtr.Zero);
            CGPoint loc = CGEventGetLocation(ptr);
            CFRelease(ptr);
            return new Vector2((float)loc.X, (float)loc.Y);
        }

        void SetCursorPos(float x, float y)
        {
            CGPoint point = new CGPoint() {X = x, Y = y};
            CGWarpMouseCursorPosition(point);
        }

    #endif

    void Update()
    {
        if (Time.time < 12.0f) //test: mouse circular movement through 12 seconds
        {
            SetCursorPos((Mathf.Sin(Time.time) * 0.5f + 0.5f) * 500.0f, (Mathf.Cos(Time.time) * 0.5f + 0.5f) * 500.0f);
            Debug.Log(GetCursorPos());
        }
    }
}
1 Like

If you guys are interested in a paid solution for windows only this will do:

The positives to using a software cursor is that you won’t have to bother with making it work on the different platforms you’re targetting because you’re in control of how it moves.

The downside to using a software cursor is that it will be affected by your game’s fps drops while the hardware cursor is not.

Finally: there is a great solution to this problem IF you use Unity’s new input system.
I haven’t tested it on different platforms but I assume it works just fine.

Mouse.current.WarpCursorPosition(Vector2 mousePosition);
6 Likes

If anyone can’t use the new input system for any reason, here is a safe solution for Windows that doesn’t require it:

#if UNITY_STANDALONE_WIN
public static class User32
{
    [DllImport("user32.dll")]
    public static extern long GetCursorPos(ref POINT point);

    [DllImport("user32.dll")]
    public static extern long SetCursorPos(int x, int y);

    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int x;
        public int y;
    }
}
#endif

public static bool TryWarpCursorPosition(Vector2 unityScreenPosition)
{
#if UNITY_STANDALONE_WIN
    try
    {
        // Convert Unity position to use top-of-screen Y coordinate like Windows
        var newUnityCursorPositionFromTopOfScreen = new Vector2Int((int)unityScreenPosition.x, Screen.height - (int)unityScreenPosition.y);

        // Get Windows cursor position
        User32.POINT _windowsCursorPosition = default;
        User32.GetCursorPos(ref _windowsCursorPosition);
        var currentWindowsCursorPosition = new Vector2Int(_windowsCursorPosition.x, _windowsCursorPosition.y);

        // Get Unity cursor position
        var currentUnityCursorPositionFromTopOfScreen = new Vector2Int
        (
            (int)UnityEngine.Input.mousePosition.x,
            Screen.height - (int)UnityEngine.Input.mousePosition.y
        );

        // Calculate the offset between the absolute Windows position and the local Unity position
        var unityToWindowsOffset = currentWindowsCursorPosition - currentUnityCursorPositionFromTopOfScreen;

        // Calculate the new absolute Windows cursor position based on a local position argument
        var newWindowsCursorPositionX = newUnityCursorPositionFromTopOfScreen.x + unityToWindowsOffset.x;
        var newWindowsCursorPositionY = newUnityCursorPositionFromTopOfScreen.y + unityToWindowsOffset.y;
        User32.SetCursorPos(newWindowsCursorPositionX, newWindowsCursorPositionY);

        return true;
    }
    catch (Exception ex)
    {
        Debug.LogException(ex);
        return false;
    }
#else
    return false;
#endif
}

To call it, simply use:

TryWarpCursorPosition(yourScreenPositionVector2InUnityCoordinates);

The core of the trick is to use the current mouse position to calculate the offset between Unity’s window and Windows coordinates, which makes it work in the editor game window, in player windows, and in full screen players all the same.

This method will gracefully return false on platforms other than Windows, or if the P/Invoke failed (while still logging the error).

Important: I wouldn’t recommend using this method (or any cursor warp) at the same time as Cursor.lockMode, because it’s unclear when exactly Unity moves the system cursor, so they might “fight” for the mouse. But the good news is that you no longer need Cursor.lockMode with this method, as you can fully manage the cursor position yourself (e.g. in Update, centering it on the screen instead of CursorLockMode.Locked, or clamping it to the screen instead of CursorLockMode.Confined).

This was tested on Windows in editor, in build, and on a Steam Deck (works with its touch pad too).

It could probably be easily adapted to work with the Mac solution posted above with a few defines!

For anyone using Unity 6.0+, It appears that you don’t need to explicitly be using the new input system.

I’m using the old input system and was able to plug this into my project without making any input system changes.

EDIT: It will not work on WebGL builds. I think that is a limitation of WebGL though, not Unity.

1 Like