diff --git a/Assets/Tests/InputSystem/Plugins/UITests.cs b/Assets/Tests/InputSystem/Plugins/UITests.cs index 2f14a49133..5dfb0ecf04 100644 --- a/Assets/Tests/InputSystem/Plugins/UITests.cs +++ b/Assets/Tests/InputSystem/Plugins/UITests.cs @@ -24,6 +24,7 @@ using Is = UnityEngine.TestTools.Constraints.Is; using MouseButton = UnityEngine.InputSystem.LowLevel.MouseButton; using UnityEngine.Scripting; +using Cursor = UnityEngine.Cursor; #if UNITY_EDITOR using UnityEditor; @@ -3667,6 +3668,38 @@ public IEnumerator UI_WhenAppLosesAndRegainsFocus_WhileUIButtonIsPressed_UIButto Assert.That(clicked, Is.EqualTo(canRunInBackground)); } + [UnityTest] + [Category("UI")] + public IEnumerator UI_WhenCursorIsLockedToScreenCenter_PointerEnterAndExitEventsFire() + { + var eventSystem = new GameObject("EventSystem", typeof(TestEventSystem), typeof(InputSystemUIInputModule)); + var inputModule = eventSystem.GetComponent(); + inputModule.m_CursorLockBehavior = InputSystemUIInputModule.CursorLockBehavior.ScreenCenter; + inputModule.actionsAsset.Enable(); + + new GameObject("Raycaster", typeof(PhysicsRaycaster)) + { + transform = { position = new Vector3(0, 0, -1) } + }; + var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); + var callbackReceiver = cube.AddComponent(); + + Cursor.lockState = CursorLockMode.Locked; + var mouse = InputSystem.AddDevice(); + + // first yield is to enable the input system ui input module + yield return null; + Press(mouse.leftButton); + + // second yield is to actually process events in the module + yield return null; + Assert.That(callbackReceiver.events.Any(e => e.type == EventType.PointerEnter), Is.True); + + cube.transform.position = new Vector3(1000, 0, 0); + yield return null; + Assert.That(callbackReceiver.events.Any(e => e.type == EventType.PointerExit), Is.True); + } + public class MyButton : UnityEngine.UI.Button { public bool receivedPointerDown; diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md index 88ca44d75a..8015a46b5a 100755 --- a/Packages/com.unity.inputsystem/CHANGELOG.md +++ b/Packages/com.unity.inputsystem/CHANGELOG.md @@ -37,6 +37,7 @@ however, it has to be formatted properly to pass verification tests. * If, for example, an action was bound to `/buttonSouth` and was enabled, adding a second `Gamepad` would lead to the action being temporarily disabled, then updated, and finally re-enabled. * This was especially noticeable if the action was currently in progress as it would get cancelled and then subsequently resumed. * Now, an in-progress action will get cancelled if the device of its active control is removed. If its active control is not affected, however, the action will keep going regardless of whether controls are added or removed from its `InputAction.controls` list. +- Added the 'Cursor Lock Behavior' setting to InputSystemUIInputModule to control the origin point of UI raycasts when the cursor is locked. This enables the use of PhysicsRaycaster when the cursor is locked to the center of the screen ([case 1395281](https://issuetracker.unity3d.com/product/unity/issues/guid/1395281/)). ### Fixed diff --git a/Packages/com.unity.inputsystem/Documentation~/Images/InputSystemUIInputModule.png b/Packages/com.unity.inputsystem/Documentation~/Images/InputSystemUIInputModule.png index f27104fd7d..021170f210 100644 Binary files a/Packages/com.unity.inputsystem/Documentation~/Images/InputSystemUIInputModule.png and b/Packages/com.unity.inputsystem/Documentation~/Images/InputSystemUIInputModule.png differ diff --git a/Packages/com.unity.inputsystem/Documentation~/UISupport.md b/Packages/com.unity.inputsystem/Documentation~/UISupport.md index 61f79e8de9..b08d5f4855 100644 --- a/Packages/com.unity.inputsystem/Documentation~/UISupport.md +++ b/Packages/com.unity.inputsystem/Documentation~/UISupport.md @@ -32,6 +32,7 @@ You can use the following properties to configure [InputSystemUIInputModule](../ |[Actions Asset](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html#UnityEngine_InputSystem_UI_InputSystemUIInputModule_actionsAsset)|An [Input Action Asset](ActionAssets.md) containing all the Actions to control the UI. You can choose which Actions in the Asset correspond to which UI inputs using the following properties.

By default, this references a built-in Asset named *DefaultInputActions*, which contains common default Actions for driving UI. If you want to set up your own Actions, [create a custom Input Action Asset](ActionAssets.md#creating-input-action-assets) and assign it here. When you assign a new Asset reference to this field in the Inspector, the Editor attempts to automatically map Actions to UI inputs based on common naming conventions.| |[Deselect on Background Click](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html#UnityEngine_InputSystem_UI_InputSystemUIInputModule_deselectOnBackgroundClick)|By default, when the pointer is clicked and does not hit any `GameObject`, the current selection is cleared. This, however, can get in the way of keyboard and gamepad navigation which will want to work off the currently selected object. To prevent automatic deselection, set this property to false.| |[Pointer Behavior](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html#UnityEngine_InputSystem_UI_InputSystemUIInputModule_pointerBehavior)|How to deal with multiple pointers feeding input into the UI. See [pointer-type input](#pointer-type-input).| +|[Cursor Lock Behavior](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html#UnityEngine_InputSystem_UI_InputSystemUIInputModule_cursorLockBehavior)|Controls the origin point of UI raycasts when the cursor is locked. | You can use the following properties to map Actions from the chosen [__Actions Asset__](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html#UnityEngine_InputSystem_UI_InputSystemUIInputModule_actionsAsset) to UI input Actions. In the Inspector, these appear as foldout lists that contain all the Actions in the Asset: @@ -63,7 +64,7 @@ For each of these types of input, input is sourced and combined from a specific To the UI, a pointer is a position from which clicks and scrolls can be triggered to interact with UI elements at the pointer's position. Pointer-type input is sourced from [point](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html#UnityEngine_InputSystem_UI_InputSystemUIInputModule_point), [leftClick](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html#UnityEngine_InputSystem_UI_InputSystemUIInputModule_leftClick), [rightClick](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html#UnityEngine_InputSystem_UI_InputSystemUIInputModule_rightClick), [middleClick](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html#UnityEngine_InputSystem_UI_InputSystemUIInputModule_middleClick), and [scrollWheel](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html#UnityEngine_InputSystem_UI_InputSystemUIInputModule_scrollWheel). >[!NOTE] ->The UI input module does not have an association between pointers and cursors. In general, the UI is oblivious to whether a cursor exists for a particular pointer. However, for mouse and pen input, the UI input module will respect [Cusor.lockState](https://docs.unity3d.com/ScriptReference/Cursor-lockState.html) and pin the pointer position at `(-1,-1)` whenever the cursor is locked. +>The UI input module does not have an association between pointers and cursors. In general, the UI is oblivious to whether a cursor exists for a particular pointer. However, for mouse and pen input, the UI input module will respect [Cusor.lockState](https://docs.unity3d.com/ScriptReference/Cursor-lockState.html) and pin the pointer position at `(-1,-1)` whenever the cursor is locked. This behavior can be changed through the [Cursor Lock Behavior](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html#UnityEngine_InputSystem_UI_InputSystemUIInputModule_cursorLockBehavior) property of the [InputSystemUIInputModule](../api/UnityEngine.InputSystem.UI.InputSystemUIInputModule.html). Multiple pointer Devices may feed input into a single UI input module. Also, in the case of [Touchscreen](../api/UnityEngine.InputSystem.Touchscreen.html), a single Device can have the ability to have multiple concurrent pointers (each finger contact is one pointer). diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs index 2d88ec11ea..9349b1fd8b 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs @@ -82,6 +82,24 @@ public UIPointerBehavior pointerBehavior set => m_PointerBehavior = value; } + /// + /// Where to position the pointer when the cursor is locked. + /// + /// + /// By default, the pointer is positioned at -1, -1 in screen space when the cursor is locked. This has implications + /// for using ray casters like because the raycasts will be sent from the pointer + /// position. By setting the value of to , + /// the raycasts will be sent from the center of the screen. This is useful when trying to interact with world space UI + /// using the and interfaces when the cursor + /// is locked. + /// + /// + public CursorLockBehavior cursorLockBehavior + { + get => m_CursorLockBehavior; + set => m_CursorLockBehavior = value; + } + /// /// Called by EventSystem when the input module is made current. /// @@ -249,7 +267,9 @@ private void ProcessPointer(ref PointerModel state) var pointerType = eventData.pointerType; if (pointerType == UIPointerType.MouseOrPen && Cursor.lockState == CursorLockMode.Locked) { - eventData.position = new Vector2(-1, -1); + eventData.position = m_CursorLockBehavior == CursorLockBehavior.OutsideScreen ? + new Vector2(-1, -1) : + new Vector2(Screen.width / 2f, Screen.height / 2f); ////REVIEW: This is consistent with StandaloneInputModule but having no deltas in locked mode seems wrong eventData.delta = default; } @@ -341,8 +361,7 @@ private void ProcessPointerMovement(ref PointerModel pointer, ExtendedPointerEve var currentPointerTarget = // If the pointer is a touch that was released the *previous* frame, we generate pointer-exit events // and then later remove the pointer. - (eventData.pointerType == UIPointerType.Touch && !pointer.leftButton.isPressed && !pointer.leftButton.wasReleasedThisFrame) || - (eventData.pointerType == UIPointerType.MouseOrPen && Cursor.lockState == CursorLockMode.Locked) + eventData.pointerType == UIPointerType.Touch && !pointer.leftButton.isPressed && !pointer.leftButton.wasReleasedThisFrame ? null : eventData.pointerCurrentRaycast.gameObject; @@ -2193,6 +2212,7 @@ public InputActionAsset actionsAsset [SerializeField] private bool m_DeselectOnBackgroundClick = true; [SerializeField] private UIPointerBehavior m_PointerBehavior = UIPointerBehavior.SingleMouseOrPenButMultiTouchAndTrack; + [SerializeField, HideInInspector] internal CursorLockBehavior m_CursorLockBehavior = CursorLockBehavior.OutsideScreen; private static Dictionary s_InputActionReferenceCounts = new Dictionary(); @@ -2225,6 +2245,26 @@ private struct InputActionReferenceState // Navigation-type input. private NavigationModel m_NavigationState; + + /// + /// Controls the origin point of raycasts when the cursor is locked. + /// + public enum CursorLockBehavior + { + /// + /// The internal pointer position will be set to -1, -1. This short-circuits the raycasting + /// logic so no objects will be intersected. This is the default setting. + /// + OutsideScreen, + + /// + /// Raycasts will originate from the center of the screen. This mode can be useful for + /// example to check in pointer-driven FPS games if the player is looking at some world-space + /// object that implements the and + /// interfaces. + /// + ScreenCenter + } } } #endif diff --git a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModuleEditor.cs b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModuleEditor.cs index 7015ce1741..415ea83d67 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModuleEditor.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModuleEditor.cs @@ -68,6 +68,7 @@ private static InputActionReference[] GetAllAssetReferencesFromAssetDatabase(Inp private SerializedProperty m_ActionsAsset; private InputActionReference[] m_AvailableActionReferencesInAssetDatabase; private string[] m_AvailableActionsInAssetNames; + private bool m_AdvancedFoldoutState; private string MakeActionReferenceNameUsableInGenericMenu(string name) { @@ -167,6 +168,14 @@ public override void OnInspectorGUI() EditorGUI.EndDisabledGroup(); } + m_AdvancedFoldoutState = EditorGUILayout.Foldout(m_AdvancedFoldoutState, new GUIContent("Advanced"), true); + if (m_AdvancedFoldoutState) + EditorGUILayout.PropertyField(serializedObject.FindProperty("m_CursorLockBehavior"), + EditorGUIUtility.TrTextContent("Cursor Lock Behavior", + $"Controls the origin point of UI raycasts when the cursor is locked. {InputSystemUIInputModule.CursorLockBehavior.OutsideScreen} " + + $"is the default behavior and will force the raycast to miss all objects. {InputSystemUIInputModule.CursorLockBehavior.ScreenCenter} " + + $"will cast the ray from the center of the screen.")); + if (GUI.changed) serializedObject.ApplyModifiedProperties(); }