Search Unity

Bug Cursor.lockState not reflect the actual lockstate of webgl canvas

Discussion in 'Web' started by Thaina, Mar 26, 2023.

  1. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    In webgl platform browser actually have API and user interaction to override the lockstate of the game, specifically user can manually press escape or alt+tab to force unlock the cursor and do anything else

    And as in current unity API in all version, we can't know this unlock state in webgl. Unity still report that we have Cursor.visible as true and Cursor.lockState as Locked. Every logic depend on this 2 value then didn't work as expected

    Not only that, I have tried to investigate the wasm coe and found out that, it seem unityengine will check the last lockState before calling SetLockCursorInternal, which in turn will call requestPointerLock

    upload_2023-3-26_17-45-15.png

    To have the logic to return to lockstate by clicking on canvas depend on last state is not ideal

    So I would like to propose that, unity should at least cache the last state at the `pointerlockchange` event, and set the value of lockState and visible to be the actual current value. And we should have some way to get that cache value around OnApplicationFocus to consider later lockState ourself

    Or alternatively. We should have new API `Cursor.ActualLockState` and `Cursor.ActualVisible` that report the actual value of Cursor currently in that platform, and not the value that unity will depend on to trigger requestPointerLock again. So we can treat current API as default state that unity would use in the next focus event

    Also it seem this problem is the same behaviour in editor. We are required to click the game screen once but the editor would start Cursor.lockState with the mode we set but not the actual state

    Currently now I need to made my own script to track the actual mode like this

    Code (CSharp):
    1. #if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_WEBGL
    2. using System.Runtime.InteropServices;
    3. #endif
    4.  
    5. using UnityEngine;
    6.  
    7. public static class CursorLock
    8. {
    9.     public static bool ActualState => pointerLocked && Cursor.lockState != CursorLockMode.None;
    10.  
    11. #if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
    12.     [StructLayout(LayoutKind.Sequential)]
    13.     struct MSRectStruct
    14.     {
    15.         public int Left;
    16.         public int Top;
    17.         public int Right;
    18.         public int Bottom;
    19.  
    20.         public static implicit operator RectInt(MSRectStruct rect) => new RectInt(rect.Left,rect.Top,rect.Right - rect.Left,rect.Bottom - rect.Top);
    21.     }
    22.  
    23.     [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    24.     static extern bool GetClipCursor(out MSRectStruct lprect);
    25.  
    26.     static bool pointerLocked
    27.     {
    28.         get
    29.         {
    30.             if(!GetClipCursor(out var lprect))
    31.                 return false;
    32.  
    33.             var rect = (RectInt)lprect;
    34.             return rect.width <= Screen.width && rect.height <= Screen.height;
    35.         }
    36.     }
    37. #elif UNITY_WEBGL
    38.     static bool pointerLocked;
    39.     [AOT.MonoPInvokeCallback(typeof(System.Action<bool>))]
    40.     static void OnPointerLockChanged(bool locked)
    41.     {
    42.         pointerLocked = locked;
    43.     }
    44.  
    45.     [DllImport("__Internal")]
    46.     private static extern void JS_RegisterLockCursorFunction(System.Action<bool> callback);
    47.  
    48.     [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
    49.     static void PointerLockChangedRegister()
    50.     {
    51.         JS_RegisterLockCursorFunction(OnPointerLockChanged);
    52.     }
    53. #else
    54.     const bool pointerLocked = true;
    55. #endif
    56. }
    57.  
    58.  
    59.  
    Code (JavaScript):
    1.  // jslib
    2.  
    3. mergeInto(LibraryManager.library,{
    4.     JS_RegisterLockCursorFunction: function(onPointerLockChanged) {
    5.         Module['dynCall_vi'](onPointerLockChanged,document.pointerLockElement == Module["canvas"]);
    6.         document.addEventListener("pointerlockchange",function() {
    7.             Module['dynCall_vi'](onPointerLockChanged,document.pointerLockElement == Module["canvas"]);
    8.         });
    9.     },
    10. });
     
    Last edited: Apr 2, 2023
    D-Organiq and akareactor like this.
  2. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,166
    bump