Cursor.lockState not reflect the actual lockstate of webgl canvas

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

8904204--1218552--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

#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_WEBGL
using System.Runtime.InteropServices;
#endif

using UnityEngine;

public static class CursorLock
{
    public static bool ActualState => pointerLocked && Cursor.lockState != CursorLockMode.None;

#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
    [StructLayout(LayoutKind.Sequential)]
    struct MSRectStruct
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;

        public static implicit operator RectInt(MSRectStruct rect) => new RectInt(rect.Left,rect.Top,rect.Right - rect.Left,rect.Bottom - rect.Top);
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    static extern bool GetClipCursor(out MSRectStruct lprect);

    static bool pointerLocked
    {
        get
        {
            if(!GetClipCursor(out var lprect))
                return false;

            var rect = (RectInt)lprect;
            return rect.width <= Screen.width && rect.height <= Screen.height;
        }
    }
#elif UNITY_WEBGL
    static bool pointerLocked;
    [AOT.MonoPInvokeCallback(typeof(System.Action<bool>))]
    static void OnPointerLockChanged(bool locked)
    {
        pointerLocked = locked;
    }

    [DllImport("__Internal")]
    private static extern void JS_RegisterLockCursorFunction(System.Action<bool> callback);

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
    static void PointerLockChangedRegister()
    {
        JS_RegisterLockCursorFunction(OnPointerLockChanged);
    }
#else
    const bool pointerLocked = true;
#endif
}
 // jslib

mergeInto(LibraryManager.library,{
    JS_RegisterLockCursorFunction: function(onPointerLockChanged) {
        Module['dynCall_vi'](onPointerLockChanged,document.pointerLockElement == Module["canvas"]);
        document.addEventListener("pointerlockchange",function() {
            Module['dynCall_vi'](onPointerLockChanged,document.pointerLockElement == Module["canvas"]);
        });
    },
});
3 Likes

bump

1 Like

Can I change the thread’s tag to input system or something more related?