feat: add reusable virtual hud input
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using NUnit.Framework;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using XGame;
|
||||
using XGame.VirtualInput;
|
||||
|
||||
public sealed class LobbyWorldControllerVirtualJoystickTests
|
||||
{
|
||||
[Test]
|
||||
public void MapMoveInputForTest_UsesJoystickVectorWithCameraAxes()
|
||||
{
|
||||
Vector3 dir = LobbyWorldController.MapMoveInputForTest(
|
||||
new Vector2(1f, 0f),
|
||||
Vector3.forward,
|
||||
Vector3.right,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
|
||||
Assert.That(dir, Is.EqualTo(Vector3.right));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MapMoveInputForTest_CombinesKeyboardAndJoystick()
|
||||
{
|
||||
Vector3 dir = LobbyWorldController.MapMoveInputForTest(
|
||||
new Vector2(0f, 1f),
|
||||
Vector3.forward,
|
||||
Vector3.right,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
|
||||
Assert.That(dir, Is.EqualTo(Vector3.forward * 2f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReleaseJoystickUIForTest_ClearsVirtualJoystickReferences()
|
||||
{
|
||||
GameObject controllerObj = new GameObject("LobbyWorldControllerTest");
|
||||
GameObject joystickObj = new GameObject("UI_VirtualJoystick", typeof(RectTransform));
|
||||
try
|
||||
{
|
||||
LobbyWorldController controller = controllerObj.AddComponent<LobbyWorldController>();
|
||||
VirtualJoystick joystick = joystickObj.AddComponent<VirtualJoystick>();
|
||||
SetPrivateField(controller, "joystickRoot", joystickObj.GetComponent<RectTransform>());
|
||||
SetPrivateField(controller, "virtualJoystick", joystick);
|
||||
|
||||
InvokePrivate(controller, "ReleaseJoystickUI", false);
|
||||
|
||||
Assert.That(GetPrivateField(controller, "joystickRoot"), Is.Null);
|
||||
Assert.That(GetPrivateField(controller, "virtualJoystick"), Is.Null);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(joystickObj);
|
||||
Object.DestroyImmediate(controllerObj);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPrivateField(object target, string name, object value)
|
||||
{
|
||||
FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(field, Is.Not.Null);
|
||||
field.SetValue(target, value);
|
||||
}
|
||||
|
||||
private static object GetPrivateField(object target, string name)
|
||||
{
|
||||
FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(field, Is.Not.Null);
|
||||
return field.GetValue(target);
|
||||
}
|
||||
|
||||
private static void InvokePrivate(object target, string name, params object[] args)
|
||||
{
|
||||
MethodInfo method = target.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(method, Is.Not.Null);
|
||||
method.Invoke(target, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2bd7fd1fc87460095c485b27dcc0e2c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,210 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using XGame.VirtualInput;
|
||||
|
||||
public sealed class VirtualActionButtonTests
|
||||
{
|
||||
private readonly List<GameObject> _roots = new List<GameObject>();
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
foreach (GameObject root in _roots)
|
||||
{
|
||||
if (root != null)
|
||||
{
|
||||
Object.DestroyImmediate(root);
|
||||
}
|
||||
}
|
||||
_roots.Clear();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PointerTap_EmitsDownThenTap()
|
||||
{
|
||||
VirtualActionButton button = CreateButton(ActionButtonId.Primary);
|
||||
List<ActionButtonEvent> events = Capture(button);
|
||||
|
||||
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, Time.unscaledTime);
|
||||
button.HandlePointerUpForTest(new Vector2(102f, 100f), 1, 0.1f);
|
||||
|
||||
Assert.That(events.Select(e => e.Phase), Is.EqualTo(new[] { ActionButtonPhase.Down, ActionButtonPhase.Tap }));
|
||||
Assert.That(events.Last().ButtonId, Is.EqualTo(ActionButtonId.Primary));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PointerDragPastThreshold_EmitsReleaseWithDirection()
|
||||
{
|
||||
VirtualActionButton button = CreateButton(ActionButtonId.Skill1);
|
||||
List<ActionButtonEvent> events = Capture(button);
|
||||
|
||||
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
||||
button.HandleDragForTest(new Vector2(160f, 100f), 1, 0.1f);
|
||||
button.HandlePointerUpForTest(new Vector2(160f, 100f), 1, 0.2f);
|
||||
|
||||
ActionButtonEvent release = events.Last();
|
||||
Assert.That(release.Phase, Is.EqualTo(ActionButtonPhase.Release));
|
||||
Assert.That(release.Direction.x, Is.EqualTo(1f).Within(0.001f));
|
||||
Assert.That(release.Direction.y, Is.EqualTo(0f).Within(0.001f));
|
||||
Assert.That(release.Distance01, Is.GreaterThan(0.7f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PointerUpInsideCancelArea_EmitsCancel()
|
||||
{
|
||||
VirtualActionButton button = CreateButton(ActionButtonId.Skill2);
|
||||
RectTransform cancelArea = CreateRect("CancelArea", new Vector2(200f, 100f), new Vector2(80f, 80f));
|
||||
button.SetCancelArea(cancelArea);
|
||||
List<ActionButtonEvent> events = Capture(button);
|
||||
|
||||
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
||||
button.HandleDragForTest(new Vector2(200f, 100f), 1, 0.1f);
|
||||
button.HandlePointerUpForTest(new Vector2(200f, 100f), 1, 0.2f);
|
||||
|
||||
Assert.That(events.Last().Phase, Is.EqualTo(ActionButtonPhase.Cancel));
|
||||
Assert.That(events.Last().IsCanceled, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cooldown_EmitsBlockedOnly()
|
||||
{
|
||||
VirtualActionButton button = CreateButton(ActionButtonId.Skill3);
|
||||
List<ActionButtonEvent> events = Capture(button);
|
||||
|
||||
button.SetCooldown(3f, 5f);
|
||||
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
||||
|
||||
Assert.That(events.Select(e => e.Phase), Is.EqualTo(new[] { ActionButtonPhase.Blocked }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChargesZero_EmitsBlockedAndShowsChargeText()
|
||||
{
|
||||
VirtualActionButton button = CreateButton(ActionButtonId.Skill4, out _, out TMP_Text chargeText);
|
||||
List<ActionButtonEvent> events = Capture(button);
|
||||
|
||||
button.SetCharges(0);
|
||||
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
||||
|
||||
Assert.That(events.Single().Phase, Is.EqualTo(ActionButtonPhase.Blocked));
|
||||
Assert.That(chargeText.text, Is.EqualTo("0"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BindNodes_FindsPrefabChildrenWithButtonPrefix()
|
||||
{
|
||||
VirtualActionButton button = CreateButton(ActionButtonId.Skill1, "Skill1", out _, out TMP_Text chargeText);
|
||||
|
||||
button.SetCharges(2);
|
||||
|
||||
Assert.That(chargeText.text, Is.EqualTo("2"));
|
||||
Assert.That(chargeText.gameObject.activeSelf, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Hold_EmitsHoldStartTickAndEnd()
|
||||
{
|
||||
VirtualActionButton button = CreateButton(ActionButtonId.Primary);
|
||||
List<ActionButtonEvent> events = Capture(button);
|
||||
|
||||
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
||||
button.HandleDragForTest(new Vector2(102f, 100f), 1, 0.4f);
|
||||
button.HandleDragForTest(new Vector2(102f, 100f), 1, 0.51f);
|
||||
button.HandlePointerUpForTest(new Vector2(102f, 100f), 1, 0.6f);
|
||||
|
||||
Assert.That(events.Select(e => e.Phase), Does.Contain(ActionButtonPhase.HoldStart));
|
||||
Assert.That(events.Select(e => e.Phase), Does.Contain(ActionButtonPhase.HoldTick));
|
||||
Assert.That(events.Select(e => e.Phase), Does.Contain(ActionButtonPhase.HoldEnd));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Update_EmitsHoldWhilePointerIsStationary()
|
||||
{
|
||||
VirtualActionButton button = CreateButton(ActionButtonId.Primary);
|
||||
List<ActionButtonEvent> events = Capture(button);
|
||||
float startedAt = Time.unscaledTime - 0.4f;
|
||||
|
||||
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, startedAt);
|
||||
InvokePrivate(button, "Update");
|
||||
|
||||
Assert.That(events.Select(e => e.Phase), Does.Contain(ActionButtonPhase.HoldStart));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnDisable_CancelsActivePointer()
|
||||
{
|
||||
VirtualActionButton button = CreateButton(ActionButtonId.Skill2);
|
||||
List<ActionButtonEvent> events = Capture(button);
|
||||
|
||||
button.HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
||||
InvokePrivate(button, "OnDisable");
|
||||
|
||||
Assert.That(events.Select(e => e.Phase), Is.EqualTo(new[] { ActionButtonPhase.Down, ActionButtonPhase.Cancel }));
|
||||
}
|
||||
|
||||
private VirtualActionButton CreateButton(ActionButtonId id)
|
||||
{
|
||||
return CreateButton(id, out _, out _);
|
||||
}
|
||||
|
||||
private VirtualActionButton CreateButton(ActionButtonId id, out Image cooldownMask, out TMP_Text chargeText)
|
||||
{
|
||||
return CreateButton(id, null, out cooldownMask, out chargeText);
|
||||
}
|
||||
|
||||
private VirtualActionButton CreateButton(ActionButtonId id, string childPrefix, out Image cooldownMask, out TMP_Text chargeText)
|
||||
{
|
||||
RectTransform rect = CreateRect("Btn_" + id, new Vector2(100f, 100f), new Vector2(100f, 100f));
|
||||
string prefix = string.IsNullOrEmpty(childPrefix) ? string.Empty : childPrefix + "_";
|
||||
new GameObject(prefix + "Icon", typeof(RectTransform), typeof(Image)).transform.SetParent(rect, false);
|
||||
cooldownMask = new GameObject(prefix + "CooldownMask", typeof(RectTransform), typeof(Image)).GetComponent<Image>();
|
||||
cooldownMask.transform.SetParent(rect, false);
|
||||
chargeText = new GameObject(prefix + "ChargeText", typeof(RectTransform), typeof(TextMeshProUGUI)).GetComponent<TMP_Text>();
|
||||
chargeText.transform.SetParent(rect, false);
|
||||
new GameObject(prefix + "CooldownText", typeof(RectTransform), typeof(TextMeshProUGUI)).transform.SetParent(rect, false);
|
||||
new GameObject(prefix + "AimIndicator", typeof(RectTransform)).transform.SetParent(rect, false);
|
||||
|
||||
VirtualActionButton button = rect.gameObject.AddComponent<VirtualActionButton>();
|
||||
button.ButtonId = id;
|
||||
SetPrivateField(button, "screenCenterOverride", new Vector2(100f, 100f));
|
||||
SetPrivateField(button, "screenRadiusOverride", 50f);
|
||||
InvokePrivate(button, "Awake");
|
||||
return button;
|
||||
}
|
||||
|
||||
private RectTransform CreateRect(string name, Vector2 position, Vector2 size)
|
||||
{
|
||||
GameObject go = new GameObject(name, typeof(RectTransform));
|
||||
_roots.Add(go);
|
||||
RectTransform rect = go.GetComponent<RectTransform>();
|
||||
rect.position = position;
|
||||
rect.sizeDelta = size;
|
||||
return rect;
|
||||
}
|
||||
|
||||
private static List<ActionButtonEvent> Capture(VirtualActionButton button)
|
||||
{
|
||||
var events = new List<ActionButtonEvent>();
|
||||
button.ButtonEvent += events.Add;
|
||||
return events;
|
||||
}
|
||||
|
||||
private static void SetPrivateField(object target, string name, object value)
|
||||
{
|
||||
FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(field, Is.Not.Null);
|
||||
field.SetValue(target, value);
|
||||
}
|
||||
|
||||
private static void InvokePrivate(object target, string name, params object[] args)
|
||||
{
|
||||
MethodInfo method = target.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(method, Is.Not.Null);
|
||||
method.Invoke(target, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2742fc0e43284790950df7a4a8b5cc6e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,117 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using XGame.VirtualInput;
|
||||
|
||||
public sealed class VirtualActionPadTests
|
||||
{
|
||||
private GameObject _root;
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
if (_root != null)
|
||||
{
|
||||
Object.DestroyImmediate(_root);
|
||||
_root = null;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Awake_FindsFiveButtonsByNameAndAssignsIds()
|
||||
{
|
||||
VirtualActionPad pad = CreatePadWithButtons();
|
||||
|
||||
Assert.That(pad.GetButton(ActionButtonId.Primary), Is.Not.Null);
|
||||
Assert.That(pad.GetButton(ActionButtonId.Skill1), Is.Not.Null);
|
||||
Assert.That(pad.GetButton(ActionButtonId.Skill2), Is.Not.Null);
|
||||
Assert.That(pad.GetButton(ActionButtonId.Skill3), Is.Not.Null);
|
||||
Assert.That(pad.GetButton(ActionButtonId.Skill4), Is.Not.Null);
|
||||
Assert.That(pad.GetButton(ActionButtonId.Skill4).ButtonId, Is.EqualTo(ActionButtonId.Skill4));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChildButtonEvent_IsForwardedByPad()
|
||||
{
|
||||
VirtualActionPad pad = CreatePadWithButtons();
|
||||
var events = new List<ActionButtonEvent>();
|
||||
pad.ButtonEvent += events.Add;
|
||||
|
||||
pad.GetButton(ActionButtonId.Primary).HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
||||
|
||||
Assert.That(events.Count, Is.EqualTo(1));
|
||||
Assert.That(events[0].ButtonId, Is.EqualTo(ActionButtonId.Primary));
|
||||
Assert.That(events[0].Phase, Is.EqualTo(ActionButtonPhase.Down));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PadStateApi_ForwardsToChildButton()
|
||||
{
|
||||
VirtualActionPad pad = CreatePadWithButtons();
|
||||
var events = new List<ActionButtonEvent>();
|
||||
pad.ButtonEvent += events.Add;
|
||||
|
||||
pad.SetCharges(ActionButtonId.Skill1, 0);
|
||||
pad.GetButton(ActionButtonId.Skill1).HandlePointerDownForTest(new Vector2(100f, 100f), 1, 0f);
|
||||
|
||||
Assert.That(events.Count, Is.EqualTo(1));
|
||||
Assert.That(events[0].Phase, Is.EqualTo(ActionButtonPhase.Blocked));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetCancelAreaVisible_TogglesCancelArea()
|
||||
{
|
||||
VirtualActionPad pad = CreatePadWithButtons();
|
||||
Transform cancel = _root.transform.Find("CancelArea");
|
||||
Assert.That(cancel, Is.Not.Null);
|
||||
|
||||
pad.SetCancelAreaVisible(true);
|
||||
Assert.That(cancel.gameObject.activeSelf, Is.True);
|
||||
|
||||
pad.SetCancelAreaVisible(false);
|
||||
Assert.That(cancel.gameObject.activeSelf, Is.False);
|
||||
}
|
||||
|
||||
private VirtualActionPad CreatePadWithButtons()
|
||||
{
|
||||
_root = new GameObject("UI_VirtualActionPad", typeof(RectTransform));
|
||||
CreateButtonChild("Btn_Primary", ActionButtonId.Primary);
|
||||
CreateButtonChild("Btn_Skill1", ActionButtonId.Skill1);
|
||||
CreateButtonChild("Btn_Skill2", ActionButtonId.Skill2);
|
||||
CreateButtonChild("Btn_Skill3", ActionButtonId.Skill3);
|
||||
CreateButtonChild("Btn_Skill4", ActionButtonId.Skill4);
|
||||
GameObject cancel = new GameObject("CancelArea", typeof(RectTransform));
|
||||
cancel.transform.SetParent(_root.transform, false);
|
||||
cancel.SetActive(false);
|
||||
|
||||
VirtualActionPad pad = _root.AddComponent<VirtualActionPad>();
|
||||
InvokePrivate(pad, "Awake");
|
||||
return pad;
|
||||
}
|
||||
|
||||
private void CreateButtonChild(string name, ActionButtonId id)
|
||||
{
|
||||
GameObject child = new GameObject(name, typeof(RectTransform));
|
||||
child.transform.SetParent(_root.transform, false);
|
||||
VirtualActionButton button = child.AddComponent<VirtualActionButton>();
|
||||
button.ButtonId = id;
|
||||
SetPrivateField(button, "screenCenterOverride", new Vector2(100f, 100f));
|
||||
SetPrivateField(button, "screenRadiusOverride", 50f);
|
||||
InvokePrivate(button, "Awake");
|
||||
}
|
||||
|
||||
private static void SetPrivateField(object target, string name, object value)
|
||||
{
|
||||
FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(field, Is.Not.Null);
|
||||
field.SetValue(target, value);
|
||||
}
|
||||
|
||||
private static void InvokePrivate(object target, string name, params object[] args)
|
||||
{
|
||||
MethodInfo method = target.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(method, Is.Not.Null);
|
||||
method.Invoke(target, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c66bc2b2fc0c435aa305004fd1d560e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
#if UNITY_EDITOR
|
||||
using XGame.EditorTools;
|
||||
#endif
|
||||
|
||||
public sealed class VirtualInputPrefabLayoutTests
|
||||
{
|
||||
[Test]
|
||||
public void VirtualJoystickJson_DefinesReusableJoystickPrefab()
|
||||
{
|
||||
string json = File.ReadAllText("../Doc/UIPrefabCreater/UI_VirtualJoystick.json");
|
||||
|
||||
Assert.That(json, Does.Contain("\"prefabName\": \"UI_VirtualJoystick\""));
|
||||
Assert.That(json, Does.Contain("\"prefabPath\": \"Assets/Game/Art/UI/Prefab/UI_VirtualJoystick.prefab\""));
|
||||
Assert.That(json, Does.Contain("\"name\": \"Knob\""));
|
||||
Assert.That(json, Does.Contain("\"typeName\": \"XGame.VirtualInput.VirtualJoystick\""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void VirtualActionPadJson_DefinesFiveButtonsAndCancelArea()
|
||||
{
|
||||
string json = File.ReadAllText("../Doc/UIPrefabCreater/UI_VirtualActionPad.json");
|
||||
|
||||
Assert.That(json, Does.Contain("\"prefabName\": \"UI_VirtualActionPad\""));
|
||||
Assert.That(json, Does.Contain("\"prefabPath\": \"Assets/Game/Art/UI/Prefab/UI_VirtualActionPad.prefab\""));
|
||||
Assert.That(json, Does.Contain("\"typeName\": \"XGame.VirtualInput.VirtualActionPad\""));
|
||||
Assert.That(json, Does.Contain("\"Btn_Primary\""));
|
||||
Assert.That(json, Does.Contain("\"Btn_Skill1\""));
|
||||
Assert.That(json, Does.Contain("\"Btn_Skill2\""));
|
||||
Assert.That(json, Does.Contain("\"Btn_Skill3\""));
|
||||
Assert.That(json, Does.Contain("\"Btn_Skill4\""));
|
||||
Assert.That(json, Does.Contain("\"CancelArea\""));
|
||||
Assert.That(json, Does.Contain("\"Primary_CooldownMask\""));
|
||||
Assert.That(json, Does.Contain("\"Skill1_ChargeText\""));
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[Test]
|
||||
public void VirtualInputJsonFiles_PassPrefabBuilderValidation()
|
||||
{
|
||||
Assert.That(JsonUIPrefabBuilder.Validate("../Doc/UIPrefabCreater/UI_VirtualJoystick.json").Ok, Is.True);
|
||||
Assert.That(JsonUIPrefabBuilder.Validate("../Doc/UIPrefabCreater/UI_VirtualActionPad.json").Ok, Is.True);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9cad9d7487d498986dca6b134d5a90b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using XGame.VirtualInput;
|
||||
|
||||
public sealed class VirtualJoystickTests
|
||||
{
|
||||
private GameObject _root;
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
if (_root != null)
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(_root);
|
||||
_root = null;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyPointerInsideDeadZone_ReportsInactiveAndZero()
|
||||
{
|
||||
VirtualJoystick joystick = CreateJoystick(out _);
|
||||
|
||||
ApplyPointer(joystick, new Vector2(106f, 100f));
|
||||
|
||||
Assert.That(joystick.IsActive, Is.False);
|
||||
Assert.That(joystick.Value, Is.EqualTo(Vector2.zero));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplyPointerBeyondRadius_ClampsValueAndKnobTravel()
|
||||
{
|
||||
VirtualJoystick joystick = CreateJoystick(out RectTransform knob);
|
||||
|
||||
ApplyPointer(joystick, new Vector2(300f, 100f));
|
||||
|
||||
Assert.That(joystick.IsActive, Is.True);
|
||||
Assert.That(joystick.Value.x, Is.EqualTo(1f).Within(0.001f));
|
||||
Assert.That(joystick.Value.y, Is.EqualTo(0f).Within(0.001f));
|
||||
Assert.That(knob.anchoredPosition.x, Is.EqualTo(24f).Within(0.001f));
|
||||
Assert.That(knob.anchoredPosition.y, Is.EqualTo(0f).Within(0.001f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReleasePointer_ResetsValueAndRaisesEnded()
|
||||
{
|
||||
VirtualJoystick joystick = CreateJoystick(out RectTransform knob);
|
||||
int ended = 0;
|
||||
joystick.Ended += () => ended++;
|
||||
|
||||
ApplyPointer(joystick, new Vector2(300f, 100f));
|
||||
ReleasePointer(joystick);
|
||||
|
||||
Assert.That(joystick.IsActive, Is.False);
|
||||
Assert.That(joystick.Value, Is.EqualTo(Vector2.zero));
|
||||
Assert.That(knob.anchoredPosition, Is.EqualTo(Vector2.zero));
|
||||
Assert.That(ended, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsScreenPositionInAcquireArea_UsesAcquireRadiusMultiplier()
|
||||
{
|
||||
VirtualJoystick joystick = CreateJoystick(out _);
|
||||
|
||||
Assert.That(joystick.IsScreenPositionInAcquireArea(new Vector2(179f, 100f)), Is.True);
|
||||
Assert.That(joystick.IsScreenPositionInAcquireArea(new Vector2(181f, 100f)), Is.False);
|
||||
}
|
||||
|
||||
private VirtualJoystick CreateJoystick(out RectTransform knob)
|
||||
{
|
||||
_root = new GameObject("UI_VirtualJoystick", typeof(RectTransform));
|
||||
RectTransform rootRect = _root.GetComponent<RectTransform>();
|
||||
rootRect.sizeDelta = new Vector2(100f, 100f);
|
||||
|
||||
GameObject knobObject = new GameObject("Knob", typeof(RectTransform));
|
||||
knobObject.transform.SetParent(_root.transform, false);
|
||||
knob = knobObject.GetComponent<RectTransform>();
|
||||
knob.sizeDelta = new Vector2(52f, 52f);
|
||||
|
||||
VirtualJoystick joystick = _root.AddComponent<VirtualJoystick>();
|
||||
SetPrivateField(joystick, "screenCenterOverride", new Vector2(100f, 100f));
|
||||
SetPrivateField(joystick, "screenRadiusOverride", 50f);
|
||||
InvokePrivate(joystick, "Awake");
|
||||
return joystick;
|
||||
}
|
||||
|
||||
private static void ApplyPointer(VirtualJoystick joystick, Vector2 screenPosition)
|
||||
{
|
||||
InvokePrivate(joystick, "ApplyPointer", screenPosition);
|
||||
}
|
||||
|
||||
private static void ReleasePointer(VirtualJoystick joystick)
|
||||
{
|
||||
InvokePrivate(joystick, "ReleasePointer");
|
||||
}
|
||||
|
||||
private static void SetPrivateField(object target, string name, object value)
|
||||
{
|
||||
FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(field, Is.Not.Null);
|
||||
field.SetValue(target, value);
|
||||
}
|
||||
|
||||
private static void InvokePrivate(object target, string name, params object[] args)
|
||||
{
|
||||
MethodInfo method = target.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(method, Is.Not.Null);
|
||||
method.Invoke(target, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 801fbb81f7954a04a5673c91a4d1a8a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user