313 lines
13 KiB
C#
313 lines
13 KiB
C#
using System.Collections;
|
|
using NUnit.Framework;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.InputSystem.Processors;
|
|
using UnityEngine.TestTools;
|
|
using UnityEngine.TestTools.Utils;
|
|
using UnityEngine.XR.Interaction.Toolkit.Locomotion;
|
|
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Movement;
|
|
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Turning;
|
|
using UnityEngine.XR.Interaction.Toolkit.Utilities;
|
|
|
|
namespace UnityEngine.XR.Interaction.Toolkit.Tests
|
|
{
|
|
[TestFixture]
|
|
class LocomotionInputTests : InputTestFixture
|
|
{
|
|
enum ForwardSource
|
|
{
|
|
Default,
|
|
Camera,
|
|
Controller,
|
|
}
|
|
|
|
[TearDown]
|
|
public override void TearDown()
|
|
{
|
|
TestUtilities.DestroyAllSceneObjects();
|
|
base.TearDown();
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator MoveInDefaultDirection()
|
|
{
|
|
return MoveInDirection(ForwardSource.Default);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator MoveInCameraDirection()
|
|
{
|
|
return MoveInDirection(ForwardSource.Camera);
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator MoveInControllerDirection()
|
|
{
|
|
return MoveInDirection(ForwardSource.Controller);
|
|
}
|
|
|
|
IEnumerator MoveInDirection(ForwardSource forwardSource)
|
|
{
|
|
// Create a stick control to serve as the input action source for the move provider
|
|
var gamepad = InputSystem.InputSystem.AddDevice<Gamepad>();
|
|
|
|
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
|
|
var actionMap = asset.AddActionMap("Locomotion");
|
|
var action = actionMap.AddAction("Move",
|
|
InputActionType.Value,
|
|
"<Gamepad>/leftStick");
|
|
|
|
var inputActionReference = ScriptableObject.CreateInstance<InputActionReference>();
|
|
inputActionReference.Set(action);
|
|
|
|
action.Enable();
|
|
|
|
var xrOrigin = TestUtilities.CreateXROrigin();
|
|
var rigTransform = xrOrigin.Origin.transform;
|
|
var cameraTransform = xrOrigin.Camera.transform;
|
|
|
|
// Rotate the camera to face a different direction than rig forward to test
|
|
// that the move provider will move with respect to a selected forward object.
|
|
cameraTransform.Rotate(0f, 45f, 0f);
|
|
var cameraForward = cameraTransform.forward;
|
|
Assert.That(rigTransform.forward, Is.Not.EqualTo(cameraForward).Using(Vector3ComparerWithEqualsOperator.Instance));
|
|
|
|
// Create a controller object to serve as another forward source
|
|
var controllerGO = new GameObject("Controller");
|
|
controllerGO.transform.SetParent(xrOrigin.CameraFloorOffsetObject.transform, false);
|
|
controllerGO.transform.Rotate(0f, -45f, 0f);
|
|
var controllerForward = controllerGO.transform.forward;
|
|
Assert.That(rigTransform.forward, Is.Not.EqualTo(controllerForward).Using(Vector3ComparerWithEqualsOperator.Instance));
|
|
|
|
// Config continuous move on XR Origin
|
|
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
|
|
mediator.GetComponent<XRBodyTransformer>().xrOrigin = xrOrigin;
|
|
var moveProvider = xrOrigin.gameObject.AddComponent<ContinuousMoveProvider>();
|
|
moveProvider.mediator = mediator;
|
|
moveProvider.leftHandMoveInput.inputActionReference = inputActionReference;
|
|
moveProvider.moveSpeed = 1f;
|
|
|
|
switch (forwardSource)
|
|
{
|
|
case ForwardSource.Default:
|
|
break;
|
|
case ForwardSource.Camera:
|
|
moveProvider.forwardSource = xrOrigin.Camera.transform;
|
|
break;
|
|
case ForwardSource.Controller:
|
|
moveProvider.forwardSource = controllerGO.transform;
|
|
break;
|
|
default:
|
|
Assert.Fail($"Unhandled {nameof(ForwardSource)}={forwardSource}");
|
|
break;
|
|
}
|
|
|
|
// See Script Execution Order diagram https://docs.unity3d.com/Manual/ExecutionOrder.html
|
|
// This test will begin after Update() during the yield null/yield WaitForSeconds/yield StartCoroutine stage.
|
|
// The move provider will process input during Update() of the next frame, and scale the move based on Time.deltaTime.
|
|
// After yielding for 1 second with the stick pushed forward, the stick will be released back to center.
|
|
// The move provider will process the release during Update() of the next frame, and should not apply any more movement.
|
|
|
|
// Partially push stick directly forward.
|
|
// This tests that the move speed will be scaled by the input magnitude.
|
|
var input = new Vector2(0f, 0.5f);
|
|
var processedInput = new StickDeadzoneProcessor().Process(input);
|
|
Set(gamepad.leftStick, input);
|
|
var startTime = Time.time;
|
|
|
|
for (var i = 0; i < 60; ++i) // wait for 60 frames.
|
|
yield return Application.isBatchMode ? null : new WaitForEndOfFrame();
|
|
|
|
var actualPosition = rigTransform.position;
|
|
var actualDistance = Vector3.Distance(Vector3.zero, actualPosition);
|
|
var expectedDistance = processedInput.magnitude * moveProvider.moveSpeed * (Time.time - startTime);
|
|
Assert.That(actualDistance, Is.EqualTo(expectedDistance).Within(1e-5f));
|
|
|
|
switch (forwardSource)
|
|
{
|
|
case ForwardSource.Default:
|
|
case ForwardSource.Camera:
|
|
Assert.That(actualPosition, Is.EqualTo(cameraForward * expectedDistance).Using(Vector3ComparerWithEqualsOperator.Instance));
|
|
break;
|
|
case ForwardSource.Controller:
|
|
Assert.That(actualPosition, Is.EqualTo(controllerForward * expectedDistance).Using(Vector3ComparerWithEqualsOperator.Instance));
|
|
break;
|
|
default:
|
|
Assert.Fail($"Unhandled {nameof(ForwardSource)}={forwardSource}");
|
|
break;
|
|
}
|
|
|
|
// Stop moving
|
|
Set(gamepad.leftStick, Vector2.zero);
|
|
|
|
yield return Application.isBatchMode ? null : new WaitForEndOfFrame();
|
|
|
|
// ReSharper disable Unity.InefficientPropertyAccess -- Property value accessed after yield
|
|
Assert.That(Vector3.Distance(actualPosition, rigTransform.position), Is.EqualTo(0f));
|
|
// ReSharper restore Unity.InefficientPropertyAccess
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator SmoothTurn()
|
|
{
|
|
// Create a stick control to serve as the input action source for the turn provider
|
|
var gamepad = InputSystem.InputSystem.AddDevice<Gamepad>();
|
|
|
|
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
|
|
var actionMap = asset.AddActionMap("Locomotion");
|
|
var action = actionMap.AddAction("Turn",
|
|
InputActionType.Value,
|
|
"<Gamepad>/rightStick");
|
|
|
|
var inputActionReference = ScriptableObject.CreateInstance<InputActionReference>();
|
|
inputActionReference.Set(action);
|
|
|
|
action.Enable();
|
|
|
|
var xrOrigin = TestUtilities.CreateXROrigin();
|
|
var rigTransform = xrOrigin.Origin.transform;
|
|
|
|
// Config continuous turn on XR Origin
|
|
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
|
|
mediator.GetComponent<XRBodyTransformer>().xrOrigin = xrOrigin;
|
|
var turnProvider = xrOrigin.gameObject.AddComponent<ContinuousTurnProvider>();
|
|
turnProvider.mediator = mediator;
|
|
turnProvider.leftHandTurnInput.inputActionReference = inputActionReference;
|
|
turnProvider.turnSpeed = 60f;
|
|
|
|
// Partially push stick directly right.
|
|
// This tests that the turn speed will be scaled by the input magnitude.
|
|
var input = new Vector2(0.5f, 0f);
|
|
var processedInput = new StickDeadzoneProcessor().Process(input);
|
|
Set(gamepad.rightStick, input);
|
|
var startTime = Time.time;
|
|
|
|
for (var i = 0; i < 60; ++i) // wait for 60 frames.
|
|
yield return Application.isBatchMode ? null : new WaitForEndOfFrame();
|
|
|
|
var turnAmount = processedInput.magnitude * turnProvider.turnSpeed * (Time.time - startTime);
|
|
var actualRotation = rigTransform.rotation;
|
|
Assert.That(actualRotation, Is.EqualTo(Quaternion.Euler(0f, turnAmount, 0f)).Using(QuaternionEqualityComparer.Instance));
|
|
|
|
// Stop turning
|
|
Set(gamepad.rightStick, Vector2.zero);
|
|
|
|
yield return Application.isBatchMode ? null : new WaitForEndOfFrame();
|
|
|
|
// ReSharper disable Unity.InefficientPropertyAccess -- Property value accessed after yield
|
|
Assert.That(actualRotation, Is.EqualTo(rigTransform.rotation).Using(QuaternionEqualityComparer.Instance));
|
|
// ReSharper restore Unity.InefficientPropertyAccess
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator TeleportationMonitorDetectsSnapTurns()
|
|
{
|
|
// Create a stick control to serve as the input action source for the turn provider
|
|
var gamepad = InputSystem.InputSystem.AddDevice<Gamepad>();
|
|
|
|
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
|
|
var actionMap = asset.AddActionMap("Locomotion");
|
|
var action = actionMap.AddAction("Turn",
|
|
InputActionType.Value,
|
|
"<Gamepad>/rightStick");
|
|
|
|
var inputActionReference = ScriptableObject.CreateInstance<InputActionReference>();
|
|
inputActionReference.Set(action);
|
|
|
|
action.Enable();
|
|
|
|
var xrOrigin = TestUtilities.CreateXROrigin();
|
|
var rigTransform = xrOrigin.Origin.transform;
|
|
|
|
var mediator = xrOrigin.gameObject.AddComponent<LocomotionMediator>();
|
|
mediator.GetComponent<XRBodyTransformer>().xrOrigin = xrOrigin;
|
|
|
|
// Config snap turn on XR Origin
|
|
var snapTurnProvider = xrOrigin.gameObject.AddComponent<SnapTurnProvider>();
|
|
snapTurnProvider.mediator = mediator;
|
|
snapTurnProvider.rightHandTurnInput.inputActionReference = inputActionReference;
|
|
snapTurnProvider.enableTurnAround = true;
|
|
snapTurnProvider.debounceTime = 0f;
|
|
var snapTurnStepped = false;
|
|
snapTurnProvider.afterStepLocomotion += _ => snapTurnStepped = true;
|
|
|
|
// Config continuous turn on XR Origin
|
|
var continuousTurnProvider = xrOrigin.gameObject.AddComponent<ContinuousTurnProvider>();
|
|
continuousTurnProvider.mediator = mediator;
|
|
continuousTurnProvider.rightHandTurnInput.inputActionReference = inputActionReference;
|
|
continuousTurnProvider.enableTurnAround = true;
|
|
var continuousTurnStepped = false;
|
|
continuousTurnProvider.afterStepLocomotion += _ => continuousTurnStepped = true;
|
|
|
|
// Create interactor under the XR Origin
|
|
var interactor = TestUtilities.CreateMockInteractor();
|
|
interactor.transform.SetParent(rigTransform);
|
|
|
|
var teleported = false;
|
|
var monitor = new TeleportationMonitor();
|
|
monitor.teleported += (_, _, _) => teleported = true;
|
|
monitor.AddInteractor(interactor);
|
|
|
|
// First test with SnapTurnProvider
|
|
snapTurnProvider.enabled = true;
|
|
continuousTurnProvider.enabled = false;
|
|
|
|
// Push stick down to trigger turn around.
|
|
Set(gamepad.rightStick, Vector2.down);
|
|
yield return null;
|
|
|
|
Assert.That(teleported, Is.True);
|
|
Assert.That(snapTurnStepped, Is.True);
|
|
Assert.That(continuousTurnStepped, Is.False);
|
|
teleported = false;
|
|
snapTurnStepped = false;
|
|
|
|
// Stop turning
|
|
Set(gamepad.rightStick, Vector2.zero);
|
|
yield return null;
|
|
|
|
// Turn right to trigger snap right
|
|
Set(gamepad.rightStick, Vector2.right);
|
|
yield return null;
|
|
|
|
Assert.That(teleported, Is.True);
|
|
Assert.That(snapTurnStepped, Is.True);
|
|
Assert.That(continuousTurnStepped, Is.False);
|
|
teleported = false;
|
|
snapTurnStepped = false;
|
|
|
|
// Stop turning
|
|
Set(gamepad.rightStick, Vector2.zero);
|
|
yield return null;
|
|
|
|
// Now test with ContinuousTurnProvider
|
|
snapTurnProvider.enabled = false;
|
|
continuousTurnProvider.enabled = true;
|
|
|
|
// Push stick down to trigger turn around.
|
|
Set(gamepad.rightStick, Vector2.down);
|
|
yield return null;
|
|
|
|
Assert.That(teleported, Is.True);
|
|
Assert.That(snapTurnStepped, Is.False);
|
|
Assert.That(continuousTurnStepped, Is.True);
|
|
teleported = false;
|
|
continuousTurnStepped = false;
|
|
|
|
// Stop turning
|
|
Set(gamepad.rightStick, Vector2.zero);
|
|
yield return null;
|
|
|
|
// Turn right to trigger partial turn right, which should not trigger a teleport
|
|
// since it only turned a few degrees
|
|
Set(gamepad.rightStick, Vector2.right);
|
|
yield return null;
|
|
|
|
Assert.That(teleported, Is.False);
|
|
Assert.That(snapTurnStepped, Is.False);
|
|
Assert.That(continuousTurnStepped, Is.True);
|
|
continuousTurnStepped = false;
|
|
}
|
|
}
|
|
}
|