"Visible cursor blocks game mouse input — no raw bypass exists"
▸ SYMPTOM
With a visible cursor (MouseVisibility.Visible, MouseState=UI):
Input.Down("attack2")orInput.Pressed("attack2")never fires when clicking on a UI panel.Input.Keyboard.Down("mouse2")also doesn't work as a bypass.Input.AnalogLookandInput.MouseDeltaare hard-zeroed — a "hold-button-to-look" mode does nothing while the cursor is visible.
▸ CAUSE
Game code's view of the mouse is triply gated when the cursor is visible:
-
Buttons:
InputContext.OnMouseButtonforwards a mouse-button press to game input only whenMouseState == GameOR the panel under the cursor is null / doesn't want pointer events / hasButtonInput == PanelInputType.Game. A press landing on a pointer-events panel is UI-only —Input.Down/Pressedfor that button's actions never fire. Releases always route to game, so a held action can't stick. -
No raw read:
Input.Keyboard.Down("mouse2")readsCurrentContext.KeysCurrent, which is fed byAccumKeysPressedinside the same gatedInput.OnButton. The true raw store (InputRouter.PressedButtons) isinternal static— unreachable from game code. If UI ate the press, game code cannot see the button, full stop. -
Motion:
Input.AnalogLookis hard-zeroed when the cursor is visible (ComputeAnalogLook:if (MouseCursorVisible) AnalogLook = default). The underlying delta feed is also UI-only unlessMouseState == Game || MouseCapture.Mouse.Positiondoes keep updating, so self-diffing it is the only visible-cursor motion source.
▸ FIX
- A press on empty screen (e.g. over a
pointer-events: noneHUD root) does reach game actions even with the cursor visible. Design your HUD overlays withpointer-events: noneon the root if you want click-through. - A "hold-button-to-look" mode must actually lock the cursor (
MouseVisibility.Hidden) for the drag's duration — there is no way to read mouse delta with a visible cursor. - Per-frame Mouse.Visibility write race: if two components both assert
Mouse.Visibilityevery frame (a panel asserting Visible-while-open vs a camera asserting Hidden-while-dragging), they race by component update order — the last writer wins the frame. Fix: publish a static "dragging" flag from the cursor's owner and make every other per-frame asserter skip its write while the flag is up.
▸ WHY IT WORKS
The engine's input routing is designed so that UI panels get exclusive ownership of mouse events when the cursor is visible. This prevents game actions from firing through menus and dialogs. The lack of a raw bypass is intentional — the only way to get game mouse input is to actually be in game mouse state.