It seems like Unity has disabled UI interaction while the cursor is locked, which is frustrating because the store in my game is an actual in-world one, and thus means the player has to look at the buttons and click. What are some ways around this?
-
1\$\begingroup\$ You might be able to make a custom input module (as demo'd here) that fires pointer events through the first person reticle, rather than through a cursor position that roams around the screen. \$\endgroup\$– DMGregory ♦Commented Jul 21, 2021 at 12:11
4 Answers
the player has to look at the buttons and click
So you have a fixed reticule and you need to aim and click at buttons? Add colliders and raycast to detect hits on the buttons. You can put all of your UI on another layer (maybe name it InWorldUI) and limit your raycasts to that layer and another layer that blocks visibility (so you can't click buttons through walls).
Your UI is essentially the same as shooting a gun at buttons to click them, so you can set up your logic the same way.
To support hover, do your raycast regularly instead of only on click.
You need to override StandaloneInputModule.GetMousePointerEventData(int id)
and remove the lines that prevent this from happening:
if (Cursor.lockState == CursorLockMode.Locked)
{
// We don't want to do ANY cursor-based interaction when the mouse is locked
leftData.position = new Vector2(-1.0f, -1.0f);
leftData.delta = Vector2.zero;
}
You can interact with the UI with the cursor locked, but cannot use the mouse, only the keyboard, which is indeed not ideal. We all manage the locking/unlocking in code using a state machine approach so that you lock/unlock the cursor depending on the game state. When the player enters a store you unlock the cursor and lock it again when the player leaves the store.
To riff off of @nicholas-zhang's answer, replacing the StandaloneInputModule component on EventSystem with this new component seemed to do the trick:
using UnityEngine;
using UnityEngine.EventSystems;
public class CustomInputModule : StandaloneInputModule {
protected override MouseState GetMousePointerEventData(int id) {
if (Cursor.lockState == CursorLockMode.Locked) {
try {
Cursor.lockState = CursorLockMode.Confined;
return base.GetMousePointerEventData(id);
} finally {
Cursor.lockState = CursorLockMode.Locked;
}
}
return base.GetMousePointerEventData(id);
}
}
Cursor.lockState
looks like it's a native call, so it's probably not the most performant, but also...seems to work fine.