Lock and Hide Mouse Cursor (Windows only)

I couldn’t find a solution to lock and hide the mouse cursor when running in the Editor. The standard Unity methods of UnityEngine.Cursor.visible and Screen.lockCursor, were failing.

So, I developed a solution that will lock the cursor within the Game view and hide it so it can never ever escape unless you want it to.

This has saved me a lot of frustration and so I thought I would share my solution with the Unity community.

Note: Windows only Solution
Note2: If your program isn’t unclipping cursor. The shortcut to stop the player is CTRL+P

Example Method for hiding/displaying cursor

/// <summary>
/// Disable the windows cursor
/// </summary>
/// <param name="mode">True=Disable Cursor   False=Enable Cursor</param>
public void LockAndHideCursor(bool mode)
    {
        if (mode)
        {
            UnityClipCursor.StartClipping();
            //Default Unity Method:
            //UnityEngine.Cursor.visible = false;
            //UnityEngine.Cursor.lockState = CursorLockMode.Locked;
        }
        else
        {
            UnityClipCursor.StopClipping();
            //Default Unity Method:
            //UnityEngine.Cursor.visible = true;
            //UnityEngine.Cursor.lockState = CursorLockMode.None;
        }
    }

Code for lock and hiding cursor

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

public class UnityClipCursor : MonoBehaviour
{

    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool ClipCursor(ref RECT rcClip);
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetClipCursor(out RECT rcClip);
    [DllImport("user32.dll")]
    static extern int GetForegroundWindow();
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GetWindowRect(int hWnd, ref RECT lpRect);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int SetCursorPos(int x, int y);
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetCursorPos(out POINT point);
    [DllImport("user32.dll")]
    static extern int GetWindowText(System.IntPtr hWnd, StringBuilder text, int count);
    [DllImport("user32.dll")]
    static extern int ShowCursor(bool bShow);

    private delegate bool EnumWindowProc(System.IntPtr hwnd, System.IntPtr lParam);

    [DllImport("user32")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool EnumChildWindows(System.IntPtr window, EnumWindowProc callback, System.IntPtr lParam);

    private static RECT currentClippingRect;
    private static RECT originalClippingRect = new RECT();
    private static bool isClipped = false;

    public static int centerX;
    public static int centerY;

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
        public RECT(int left, int top, int right, int bottom)
        {
            Left = left;
            Top = top;
            Right = right;
            Bottom = bottom;
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int x;
        public int y;
        public POINT(int X, int Y)
        {
            x = X;
            y = Y;
        }
    }

    static void SaveOriginal()
    {
        CursorLockMode mode = Cursor.lockState;
        Cursor.lockState = CursorLockMode.None;
        GetClipCursor(out originalClippingRect);
        centerX = originalClippingRect.Left + (originalClippingRect.Right - originalClippingRect.Left) / 2;
        centerY = originalClippingRect.Top + (originalClippingRect.Bottom - originalClippingRect.Top) / 2;
    }

    public static void StartClipping()
    {
        if (isClipped) return;

        SaveOriginal();

        var hndl = GetForegroundWindow();

        var allChildWindows = GetAllChildHandles((System.IntPtr)hndl);
        foreach (var child in allChildWindows)
        {
            const int nChars = 256;
            StringBuilder Buff = new StringBuilder(nChars);
            if (GetWindowText(child, Buff, nChars) > 0)
            {
                if (Buff.ToString().Trim() == "UnityEditor.GameView")
                {
                    hndl = (int)child;
                }
            }
        }

        GetWindowRect(hndl, ref currentClippingRect);
        ClipCursor(ref currentClippingRect);

#if UNITY_EDITOR
        centerX = Screen.width / 2;
        centerY = Screen.height / 2;
#else
  centerX = currentClippingRect.Left + (currentClippingRect.Right - currentClippingRect.Left) / 2;
  centerY = currentClippingRect.Top + (currentClippingRect.Bottom - currentClippingRect.Top) / 2;
#endif
        ShowCursor(false);
        isClipped = true;
        CenterCursor();
    }

    static public void CenterCursor()
    {
        SetCursorPos(centerX, centerY);
    }

    void OnApplicationQuit()
    {
        StopClipping();
    }
    public static void StopClipping()
    {
        if (isClipped)
        {
            CenterCursor();
            isClipped = false;
            ClipCursor(ref originalClippingRect);
            ShowCursor(true);
        }
    }



    public static List<System.IntPtr> GetAllChildHandles(System.IntPtr mainHandle)
    {
        List<System.IntPtr> childHandles = new List<System.IntPtr>();

        GCHandle gcChildhandlesList = GCHandle.Alloc(childHandles);
        System.IntPtr pointerChildHandlesList = GCHandle.ToIntPtr(gcChildhandlesList);

        try
        {
            EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
            EnumChildWindows(mainHandle, childProc, pointerChildHandlesList);
        }
        finally
        {
            gcChildhandlesList.Free();
        }

        return childHandles;
    }

    private static bool EnumWindow(System.IntPtr hWnd, System.IntPtr lParam)
    {
        GCHandle gcChildhandlesList = GCHandle.FromIntPtr(lParam);

        if (gcChildhandlesList == null || gcChildhandlesList.Target == null)
        {
            return false;
        }

        List<System.IntPtr> childHandles = gcChildhandlesList.Target as List<System.IntPtr>;
        childHandles.Add(hWnd);

        return true;
    }
}
3 Likes

Can you specify how UnityEngine.Cursor.visible and Screen.lockCursor were failing for you? Just setting “Screen.lockCursor = false;” should center and hide the cursor in Unity 4.x. In Unity 5.0 Cursor.lockState and Cursor.visible should be used instead. Cursor.lockState can specify cursor locked in the center or confined to the game window. Unity - Scripting API: Cursor

Hi Julius, the default unity method method would work most of the time. But occasionally I was getting odd behavior in the editor. Such as:

  • Cursor flickering in center of screen
  • Cursor not visible over unity window, but could be moved to 2nd monitor and if mouse click would activate application it was over.

Cheers, another workaround I just found was to use custom cursor then switch to software mode before hiding
You can probably omit the lockstate if not required.

public Texture2d cursor;         // specify custom cursor to use

void ShowCursor (bool visible) {
  CursorMode cursorMode = visible ? CursorMode.Auto : CursorMode.ForceSoftware;
  Cursor.SetCursor(cursor, Vector2.zero, cursorMode);
  Cursor.lockState = (visiblel) ? CursorLockMode.None : CursorLockMode.Locked;
  Cursor.visible = (visible);
}

After considerable frustration with Unity 5 myself, I found that chai had the best solution… Here’s what I ended up doing :
I created a BLANK (transparent) blank.png , 16x16 pixels , and used that as the cursor Texture2D.
I found that the Cursor.SetCursor can be run in a Start() function, but the Cursor.lockState needed
to be in a Mouse statement (where I was using it to shoot a zap bullet) … this works fine for me because I don’t want to SEE the mouse and it keeps it in the center of the game window.

var cursorTexture : Texture2D;

function Start() {
    Cursor.SetCursor(cursorTexture, Vector2.zero, CursorMode.ForceSoftware);
 }

function Update() {
   // Cursor.visible = false;   .......... now not needed.
    if(Input.GetMouseButtonDown(0)) {
        Cursor.lockState = CursorLockMode.Locked;

        // your code here
        //var newZap : GameObject;
        //newZap = Instantiate(zapPrefab, gun.position, gun.rotation);
    }

}

Note: I originally made a 9x9 pixel png with a single black dot in the middle. This is how I know that if set as visible=false it shakes but does stay in the center.

1 Like

@Zaddo67 Cheers for the script. I’m still having the problem on 5.2.1 using a multimonitor setup and your script works almost perfect. One problem I have is that when the cursor is unlocked while playing in-game, say for a game menu, it returns to center of primary monitor editor scene and not the center of the game window as I would expect/like. :smile:

@Zaddo67 , Looks like it doesn’t work properly in latest Unity version. My mouse cursor remain hiddent in editor.

WOW seriously nothing works for me on this