#if ENABLE_INPUT_SYSTEM using System.Collections.Generic; using System.Linq; using System.Reflection; using Invector.vItemManager; using NUnit.Framework; using UnityEditor; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.UI; namespace Invector.vCharacterController.InputSystemEditor { public class vInputSystemRegressionTests { private const string Root = "Assets/Invector-3rdPersonController/"; private static readonly FieldInfo ActionField = typeof(GenericInput).GetField("inputAction", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly FieldInfo InstanceField = typeof(vInput).GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic); private readonly List created = new List(); private InputActionAsset actions; private Gamepad pad; private Keyboard keyboard; private object previousInstance; private object fixture; [SetUp] public void Setup() { // The predefined editor assembly cannot reference the package's test-only asmdef directly. var fixtureType = System.Type.GetType("UnityEngine.InputSystem.InputTestFixture, Unity.InputSystem.TestFramework", true); fixture = System.Activator.CreateInstance(fixtureType); fixtureType.GetMethod("Setup").Invoke(fixture, null); previousInstance = InstanceField.GetValue(null); var manager = Track(new GameObject("Input regression manager")); InstanceField.SetValue(null, manager.AddComponent()); pad = InputSystem.AddDevice(); keyboard = InputSystem.AddDevice(); actions = Track(InputActionAsset.FromJson(System.IO.File.ReadAllText(Root + "Basic Locomotion/InputSystem/InvectorPlayerInputs.inputactions"))); actions.Enable(); } [TearDown] public void TearDown() { try { foreach (var obj in created.AsEnumerable().Reverse()) if (obj) Object.DestroyImmediate(obj); created.Clear(); InstanceField.SetValue(null, previousInstance); } finally { if (fixture != null) fixture.GetType().GetMethod("TearDown").Invoke(fixture, null); } } private T Track(T obj) where T : Object { created.Add(obj); return obj; } private void Bind(GenericInput input) { if (input.inputActionReference == null) return; var reference = Track(InputActionReference.Create(actions.FindAction(input.inputActionReference.action.id))); ActionField.SetValue(input, reference); input.GetButtonDown(); } private vInventory LoadInventory(string name) { var prefab = AssetDatabase.LoadAssetAtPath(Root + "ItemManager/Prefabs/" + name + ".prefab"); var root = Track(Object.Instantiate(prefab)); root.SetActive(false); var inventory = root.GetComponentInChildren(true); inventory.canEquip = true; foreach (var control in inventory.changeEquipmentControllers) { control.useItemInput.useInput = false; Bind(control.previousItemInput); Bind(control.nextItemInput); control.equipArea.onChangeEquipSlot = new OnChangeEquipmentEvent(); control.equipArea.onEquipItem = new OnChangeEquipmentEvent(); control.equipArea.onUnequipItem = new OnChangeEquipmentEvent(); } foreach (var selector in root.GetComponentsInChildren(true)) { selector.inventory = inventory; foreach (var slot in selector.slotsSelectors) Bind(slot.input); } return inventory; } private static void Tick(vInventory inventory) { foreach (var selector in inventory.GetComponentsInChildren(true)) typeof(vControlAreaByInput).GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(selector, null); typeof(vInventory).GetMethod("ChangeEquipmentInput", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(inventory, null); } [Test] public void EquipmentDirectionsCycleOnlyTheirArea( [Values("Inventory_ShooterMelee", "Inventory_MeleeOnly")] string prefab, [Values(false, true)] bool gamepad, [Values(0, 1, 2)] int area) { var inventory = LoadInventory(prefab); var target = inventory.changeEquipmentControllers[area].equipArea; var validSlots = Enumerable.Range(0, target.equipSlots.Count).Where(i => target.equipSlots[i].isValid).ToArray(); Assert.That(validSlots.Length, Is.GreaterThan(1)); foreach (var control in inventory.changeEquipmentControllers) control.equipArea.indexOfEquippedItem = 0; var directions = new[] { GamepadButton.DpadRight, GamepadButton.DpadLeft, GamepadButton.DpadDown }; var keys = new[] { Key.RightArrow, Key.LeftArrow, Key.DownArrow }; for (int press = 0; press < validSlots.Length + 1; press++) { InputSystem.QueueStateEvent(pad, new GamepadState()); InputSystem.QueueStateEvent(keyboard, new KeyboardState()); InputSystem.Update(); Tick(inventory); if (gamepad) InputSystem.QueueStateEvent(pad, new GamepadState().WithButton(directions[area])); else InputSystem.QueueStateEvent(keyboard, new KeyboardState(keys[area])); InputSystem.Update(); Tick(inventory); int expected = validSlots[(press + 1) % validSlots.Length]; Assert.That(target.indexOfEquippedItem, Is.EqualTo(expected), "Each press must advance exactly one available slot."); foreach (var other in inventory.changeEquipmentControllers.Where((c, i) => i != area)) Assert.That(other.equipArea.indexOfEquippedItem, Is.Zero, "Another equipment area consumed the same input."); InputSystem.Update(); Tick(inventory); Assert.That(target.indexOfEquippedItem, Is.EqualTo(expected), "Holding a direction must not select another slot."); } } [TestCase(-1f, 1f, 0f)] [TestCase(0f, 0f, 0f)] [TestCase(1f, 0f, 1f)] public void LockOnUsesRightStickHorizontal(float x, float previous, float next) { InputSystem.QueueStateEvent(pad, new GamepadState { rightStick = new Vector2(x, 0f) }); InputSystem.Update(); Assert.That(actions.FindAction("Previous Target").ReadValue(), Is.EqualTo(previous)); Assert.That(actions.FindAction("Next Target").ReadValue(), Is.EqualTo(next)); } [TestCase("Previous Target", "Z")] [TestCase("Next Target", "X")] public void LockOnAxisFiresOncePerDeflectionAndKeepsKeyboard(string actionName, string key) { var input = JsonUtility.FromJson("{\"useInput\":true,\"inputSource\":1,\"inputSystemAxis\":true,\"inputSystemRawThreshold\":0.1}"); ActionField.SetValue(input, Track(InputActionReference.Create(actions.FindAction(actionName)))); float x = actionName == "Previous Target" ? -1f : 1f; Assert.That(input.GetButtonDown(), Is.False); for (int i = 0; i < 2; i++) { InputSystem.QueueStateEvent(pad, new GamepadState { rightStick = new Vector2(x, 0f) }); InputSystem.Update(); Assert.That(input.GetButtonDown(), Is.True); InputSystem.Update(); Assert.That(input.GetButtonDown(), Is.False); InputSystem.QueueStateEvent(pad, new GamepadState()); InputSystem.Update(); Assert.That(input.GetButtonDown(), Is.False); } InputSystem.QueueStateEvent(keyboard, new KeyboardState((Key)System.Enum.Parse(typeof(Key), key))); InputSystem.Update(); Assert.That(input.GetButtonDown(), Is.True); } [Test] public void ShooterOnlyRetainsItsAuthoredDirectSlotShortcuts() { var inventory = LoadInventory("Inventory_ShooterOnly"); foreach (var selector in inventory.GetComponentsInChildren(true)) foreach (var slot in selector.slotsSelectors) { Assert.That(slot.input.useInput, Is.True); Assert.That(slot.input.inputActionReference, Is.Not.Null); Assert.That(slot.input.inputActionReference.action.bindings.Any(b => b.path.StartsWith("/dpad/")), Is.True); } } [Test] public void EventSystemSelectsOnlyNewBackendAndPreservesLegacyConfiguration() { var root = Track(new GameObject("UI backend regression", typeof(EventSystem), typeof(StandaloneInputModule))); var legacy = root.GetComponent(); legacy.horizontalAxis = "CustomHorizontal"; var backend = root.AddComponent(); backend.Configure(); backend.Configure(); Assert.That(legacy.enabled, Is.False); Assert.That(legacy.horizontalAxis, Is.EqualTo("CustomHorizontal")); Assert.That(root.GetComponents().Length, Is.EqualTo(1)); Assert.That(root.GetComponent().enabled, Is.True); } } } #endif