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.
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.
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?
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.
#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
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…
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).
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.
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
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.
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
}
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!