Search Unity

Lock and Hide Mouse Cursor (Windows only)

Discussion in 'Editor & General Support' started by Zaddo67, Apr 3, 2015.

  1. Zaddo67

    Zaddo67

    Joined:
    Aug 14, 2012
    Posts:
    489
    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
    Code (CSharp):
    1.  
    2. /// <summary>
    3. /// Disable the windows cursor
    4. /// </summary>
    5. /// <param name="mode">True=Disable Cursor   False=Enable Cursor</param>
    6. public void LockAndHideCursor(bool mode)
    7.     {
    8.         if (mode)
    9.         {
    10.             UnityClipCursor.StartClipping();
    11.             //Default Unity Method:
    12.             //UnityEngine.Cursor.visible = false;
    13.             //UnityEngine.Cursor.lockState = CursorLockMode.Locked;
    14.         }
    15.         else
    16.         {
    17.             UnityClipCursor.StopClipping();
    18.             //Default Unity Method:
    19.             //UnityEngine.Cursor.visible = true;
    20.             //UnityEngine.Cursor.lockState = CursorLockMode.None;
    21.         }
    22.     }

    Code for lock and hiding cursor
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. using System.Runtime.InteropServices;
    6.  
    7. public class UnityClipCursor : MonoBehaviour
    8. {
    9.  
    10.     [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    11.     [return: MarshalAs(UnmanagedType.Bool)]
    12.     public static extern bool ClipCursor(ref RECT rcClip);
    13.     [DllImport("user32.dll")]
    14.     [return: MarshalAs(UnmanagedType.Bool)]
    15.     public static extern bool GetClipCursor(out RECT rcClip);
    16.     [DllImport("user32.dll")]
    17.     static extern int GetForegroundWindow();
    18.     [DllImport("user32.dll")]
    19.     [return: MarshalAs(UnmanagedType.Bool)]
    20.     static extern bool GetWindowRect(int hWnd, ref RECT lpRect);
    21.     [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    22.     public static extern int SetCursorPos(int x, int y);
    23.     [DllImport("user32.dll")]
    24.     [return: MarshalAs(UnmanagedType.Bool)]
    25.     public static extern bool GetCursorPos(out POINT point);
    26.     [DllImport("user32.dll")]
    27.     static extern int GetWindowText(System.IntPtr hWnd, StringBuilder text, int count);
    28.     [DllImport("user32.dll")]
    29.     static extern int ShowCursor(bool bShow);
    30.  
    31.     private delegate bool EnumWindowProc(System.IntPtr hwnd, System.IntPtr lParam);
    32.  
    33.     [DllImport("user32")]
    34.     [return: MarshalAs(UnmanagedType.Bool)]
    35.     private static extern bool EnumChildWindows(System.IntPtr window, EnumWindowProc callback, System.IntPtr lParam);
    36.  
    37.     private static RECT currentClippingRect;
    38.     private static RECT originalClippingRect = new RECT();
    39.     private static bool isClipped = false;
    40.  
    41.     public static int centerX;
    42.     public static int centerY;
    43.  
    44.     [StructLayout(LayoutKind.Sequential)]
    45.     public struct RECT
    46.     {
    47.         public int Left;
    48.         public int Top;
    49.         public int Right;
    50.         public int Bottom;
    51.         public RECT(int left, int top, int right, int bottom)
    52.         {
    53.             Left = left;
    54.             Top = top;
    55.             Right = right;
    56.             Bottom = bottom;
    57.         }
    58.     }
    59.  
    60.     [StructLayout(LayoutKind.Sequential)]
    61.     public struct POINT
    62.     {
    63.         public int x;
    64.         public int y;
    65.         public POINT(int X, int Y)
    66.         {
    67.             x = X;
    68.             y = Y;
    69.         }
    70.     }
    71.  
    72.     static void SaveOriginal()
    73.     {
    74.         CursorLockMode mode = Cursor.lockState;
    75.         Cursor.lockState = CursorLockMode.None;
    76.         GetClipCursor(out originalClippingRect);
    77.         centerX = originalClippingRect.Left + (originalClippingRect.Right - originalClippingRect.Left) / 2;
    78.         centerY = originalClippingRect.Top + (originalClippingRect.Bottom - originalClippingRect.Top) / 2;
    79.     }
    80.  
    81.     public static void StartClipping()
    82.     {
    83.         if (isClipped) return;
    84.  
    85.         SaveOriginal();
    86.  
    87.         var hndl = GetForegroundWindow();
    88.  
    89.         var allChildWindows = GetAllChildHandles((System.IntPtr)hndl);
    90.         foreach (var child in allChildWindows)
    91.         {
    92.             const int nChars = 256;
    93.             StringBuilder Buff = new StringBuilder(nChars);
    94.             if (GetWindowText(child, Buff, nChars) > 0)
    95.             {
    96.                 if (Buff.ToString().Trim() == "UnityEditor.GameView")
    97.                 {
    98.                     hndl = (int)child;
    99.                 }
    100.             }
    101.         }
    102.  
    103.         GetWindowRect(hndl, ref currentClippingRect);
    104.         ClipCursor(ref currentClippingRect);
    105.  
    106. #if UNITY_EDITOR
    107.         centerX = Screen.width / 2;
    108.         centerY = Screen.height / 2;
    109. #else
    110.   centerX = currentClippingRect.Left + (currentClippingRect.Right - currentClippingRect.Left) / 2;
    111.   centerY = currentClippingRect.Top + (currentClippingRect.Bottom - currentClippingRect.Top) / 2;
    112. #endif
    113.         ShowCursor(false);
    114.         isClipped = true;
    115.         CenterCursor();
    116.     }
    117.  
    118.     static public void CenterCursor()
    119.     {
    120.         SetCursorPos(centerX, centerY);
    121.     }
    122.  
    123.     void OnApplicationQuit()
    124.     {
    125.         StopClipping();
    126.     }
    127.     public static void StopClipping()
    128.     {
    129.         if (isClipped)
    130.         {
    131.             CenterCursor();
    132.             isClipped = false;
    133.             ClipCursor(ref originalClippingRect);
    134.             ShowCursor(true);
    135.         }
    136.     }
    137.  
    138.  
    139.  
    140.     public static List<System.IntPtr> GetAllChildHandles(System.IntPtr mainHandle)
    141.     {
    142.         List<System.IntPtr> childHandles = new List<System.IntPtr>();
    143.  
    144.         GCHandle gcChildhandlesList = GCHandle.Alloc(childHandles);
    145.         System.IntPtr pointerChildHandlesList = GCHandle.ToIntPtr(gcChildhandlesList);
    146.  
    147.         try
    148.         {
    149.             EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
    150.             EnumChildWindows(mainHandle, childProc, pointerChildHandlesList);
    151.         }
    152.         finally
    153.         {
    154.             gcChildhandlesList.Free();
    155.         }
    156.  
    157.         return childHandles;
    158.     }
    159.  
    160.     private static bool EnumWindow(System.IntPtr hWnd, System.IntPtr lParam)
    161.     {
    162.         GCHandle gcChildhandlesList = GCHandle.FromIntPtr(lParam);
    163.  
    164.         if (gcChildhandlesList == null || gcChildhandlesList.Target == null)
    165.         {
    166.             return false;
    167.         }
    168.  
    169.         List<System.IntPtr> childHandles = gcChildhandlesList.Target as List<System.IntPtr>;
    170.         childHandles.Add(hWnd);
    171.  
    172.         return true;
    173.     }
    174. }
    175.  
     
    Last edited: Apr 4, 2015
    Gua, terravires and mgear like this.
  2. JuliusM

    JuliusM

    Unity Technologies

    Joined:
    Apr 17, 2013
    Posts:
    836
    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. http://docs.unity3d.com/ScriptReference/Cursor.html
     
  3. Zaddo67

    Zaddo67

    Joined:
    Aug 14, 2012
    Posts:
    489
    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.
     
  4. chai

    chai

    Joined:
    Jun 3, 2009
    Posts:
    84
    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.

    Code (csharp):
    1. public Texture2d cursor;         // specify custom cursor to use
    2.  
    3. void ShowCursor (bool visible) {
    4.   CursorMode cursorMode = visible ? CursorMode.Auto : CursorMode.ForceSoftware;
    5.   Cursor.SetCursor(cursor, Vector2.zero, cursorMode);
    6.   Cursor.lockState = (visiblel) ? CursorLockMode.None : CursorLockMode.Locked;
    7.   Cursor.visible = (visible);
    8. }
     
  5. Michio-Magic

    Michio-Magic

    Joined:
    Dec 25, 2014
    Posts:
    18
    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.

    Code (JavaScript):
    1.  
    2. var cursorTexture : Texture2D;
    3.  
    4. function Start() {
    5.     Cursor.SetCursor(cursorTexture, Vector2.zero, CursorMode.ForceSoftware);
    6.  }
    7.  
    8. function Update() {
    9.    // Cursor.visible = false;   .......... now not needed.
    10.     if(Input.GetMouseButtonDown(0)) {
    11.         Cursor.lockState = CursorLockMode.Locked;
    12.  
    13.         // your code here
    14.         //var newZap : GameObject;
    15.         //newZap = Instantiate(zapPrefab, gun.position, gun.rotation);
    16.     }
    17.  
    18. }
    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.
     
    Zaddo67 likes this.
  6. terravires

    terravires

    Joined:
    Mar 27, 2013
    Posts:
    103
    @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. :D
     
  7. Gua

    Gua

    Joined:
    Oct 29, 2012
    Posts:
    455
    @Zaddo67, Looks like it doesn't work properly in latest Unity version. My mouse cursor remain hiddent in editor.
     
  8. GiDesign

    GiDesign

    Joined:
    Aug 27, 2015
    Posts:
    5
    WOW seriously nothing works for me on this