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; 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(); var asset = ScriptableObject.CreateInstance(); var actionMap = asset.AddActionMap("Locomotion"); var action = actionMap.AddAction("Move", InputActionType.Value, "/leftStick"); var inputActionReference = ScriptableObject.CreateInstance(); 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(); mediator.GetComponent().xrOrigin = xrOrigin; var moveProvider = xrOrigin.gameObject.AddComponent(); 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(); var asset = ScriptableObject.CreateInstance(); var actionMap = asset.AddActionMap("Locomotion"); var action = actionMap.AddAction("Turn", InputActionType.Value, "/rightStick"); var inputActionReference = ScriptableObject.CreateInstance(); 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(); mediator.GetComponent().xrOrigin = xrOrigin; var turnProvider = xrOrigin.gameObject.AddComponent(); 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 } } }