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
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"]);
});
},
});