Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Assets/Tests/InputSystem/Plugins/UITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<InputSystemUIInputModule>();
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<UICallbackReceiver>();

Cursor.lockState = CursorLockMode.Locked;
var mouse = InputSystem.AddDevice<Mouse>();

// 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;
Expand Down
1 change: 1 addition & 0 deletions Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ however, it has to be formatted properly to pass verification tests.
* If, for example, an action was bound to `<Gamepad>/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

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion Packages/com.unity.inputsystem/Documentation~/UISupport.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br><br>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:

Expand Down Expand Up @@ -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).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ public UIPointerBehavior pointerBehavior
set => m_PointerBehavior = value;
}

/// <summary>
/// Where to position the pointer when the cursor is locked.
/// </summary>
/// <remarks>
/// 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 <see cref="PhysicsRaycaster"/> because the raycasts will be sent from the pointer
/// position. By setting the value of <see cref="cursorLockBehavior"/> to <see cref="CursorLockBehavior.ScreenCenter"/>,
/// the raycasts will be sent from the center of the screen. This is useful when trying to interact with world space UI
/// using the <see cref="IPointerEnterHandler"/> and <see cref="IPointerExitHandler"/> interfaces when the cursor
/// is locked.
/// </remarks>
/// <see cref="Cursor.lockState"/>
public CursorLockBehavior cursorLockBehavior
{
get => m_CursorLockBehavior;
set => m_CursorLockBehavior = value;
}

/// <summary>
/// Called by <c>EventSystem</c> when the input module is made current.
/// </summary>
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<InputAction, InputActionReferenceState> s_InputActionReferenceCounts = new Dictionary<InputAction, InputActionReferenceState>();

Expand Down Expand Up @@ -2225,6 +2245,26 @@ private struct InputActionReferenceState

// Navigation-type input.
private NavigationModel m_NavigationState;

/// <summary>
/// Controls the origin point of raycasts when the cursor is locked.
/// </summary>
public enum CursorLockBehavior
{
/// <summary>
/// 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.
/// </summary>
OutsideScreen,

/// <summary>
/// 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 <see cref="IPointerEnterHandler"/> and <see cref="IPointerExitHandler"/>
/// interfaces.
/// </summary>
ScreenCenter
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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."));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably update manual screenshot and doc.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, and include @duckets on the review after the change


if (GUI.changed)
serializedObject.ApplyModifiedProperties();
}
Expand Down