Initial commit: Tiny Tackle Heroes Unity project
This commit is contained in:
@@ -0,0 +1,355 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Player;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BumpCombat.PlayModeTests
|
||||
{
|
||||
public sealed class ArtifactSwitchEventPlayModeTests
|
||||
{
|
||||
private GameObject testPlayer;
|
||||
private ActiveArtifactController artifacts;
|
||||
private ActiveArtifactDefinition firstArtifact;
|
||||
private ActiveArtifactDefinition secondArtifact;
|
||||
private Keyboard virtualKeyboard;
|
||||
private bool virtualKeyboardInputActive;
|
||||
private InputSettings.BackgroundBehavior previousBackgroundBehavior;
|
||||
private InputSettings.EditorInputBehaviorInPlayMode previousEditorInputBehavior;
|
||||
private bool inputSettingsCaptured;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
Time.timeScale = 1f;
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
Time.timeScale = 1f;
|
||||
if (virtualKeyboardInputActive
|
||||
&& virtualKeyboard != null
|
||||
&& virtualKeyboard.added)
|
||||
{
|
||||
InputSystem.QueueStateEvent(virtualKeyboard, new KeyboardState());
|
||||
InputSystem.RemoveDevice(virtualKeyboard);
|
||||
}
|
||||
|
||||
if (inputSettingsCaptured)
|
||||
{
|
||||
InputSystem.settings.backgroundBehavior = previousBackgroundBehavior;
|
||||
InputSystem.settings.editorInputBehaviorInPlayMode =
|
||||
previousEditorInputBehavior;
|
||||
}
|
||||
|
||||
if (testPlayer != null)
|
||||
{
|
||||
Object.Destroy(testPlayer);
|
||||
}
|
||||
|
||||
if (firstArtifact != null)
|
||||
{
|
||||
Object.Destroy(firstArtifact);
|
||||
}
|
||||
|
||||
if (secondArtifact != null)
|
||||
{
|
||||
Object.Destroy(secondArtifact);
|
||||
}
|
||||
|
||||
virtualKeyboard = null;
|
||||
virtualKeyboardInputActive = false;
|
||||
inputSettingsCaptured = false;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ArtifactSwitchedEvent_OnlyFiresOnceAfterSuccessfulSwitch()
|
||||
{
|
||||
RunManager previousRunManager = RunManager.Instance;
|
||||
if (previousRunManager == null)
|
||||
{
|
||||
previousRunManager = null;
|
||||
}
|
||||
bool previousRunManagerEnabled = previousRunManager != null
|
||||
&& previousRunManager.enabled;
|
||||
if (previousRunManager != null)
|
||||
{
|
||||
previousRunManager.enabled = false;
|
||||
}
|
||||
|
||||
SetRunManagerInstanceForTest(null);
|
||||
Time.timeScale = 1f;
|
||||
ActiveArtifactController[] existingControllers =
|
||||
Object.FindObjectsByType<ActiveArtifactController>(
|
||||
FindObjectsSortMode.None);
|
||||
bool[] previousControllerStates = new bool[existingControllers.Length];
|
||||
for (int i = 0; i < existingControllers.Length; i++)
|
||||
{
|
||||
previousControllerStates[i] = existingControllers[i].enabled;
|
||||
existingControllers[i].enabled = false;
|
||||
}
|
||||
|
||||
Keyboard keyboard = null;
|
||||
try
|
||||
{
|
||||
testPlayer = new GameObject("Artifact Switch Event Test Player");
|
||||
testPlayer.AddComponent<PlayerStats>();
|
||||
artifacts = testPlayer.AddComponent<ActiveArtifactController>();
|
||||
firstArtifact = CreateArtifact(
|
||||
"switch-event-dash",
|
||||
ActiveArtifactEffect.Dash);
|
||||
secondArtifact = CreateArtifact(
|
||||
"switch-event-pulse",
|
||||
ActiveArtifactEffect.Pulse);
|
||||
|
||||
int switchCount = 0;
|
||||
int chargeStartCount = 0;
|
||||
ActiveArtifactDefinition eventDefinition = null;
|
||||
ActiveArtifactDefinition selectedDuringEvent = null;
|
||||
artifacts.OnArtifactSwitched += definition =>
|
||||
{
|
||||
switchCount++;
|
||||
eventDefinition = definition;
|
||||
selectedDuringEvent = artifacts.CurrentArtifact;
|
||||
};
|
||||
artifacts.OnArtifactChargeStarted += _ => chargeStartCount++;
|
||||
int useCount = 0;
|
||||
artifacts.OnArtifactUseSucceeded += (_, _) => useCount++;
|
||||
|
||||
Assert.That(artifacts.TryAddArtifact(firstArtifact), Is.True);
|
||||
Assert.That(artifacts.SelectNext(), Is.False);
|
||||
Assert.That(switchCount, Is.Zero,
|
||||
"Acquisition and a failed switch must not emit the switch event.");
|
||||
|
||||
artifacts.AddMovementCharge(1f);
|
||||
artifacts.AddMovementCharge(49f);
|
||||
Assert.That(artifacts.CurrentGauge, Is.GreaterThan(0f));
|
||||
Assert.That(artifacts.CurrentGauge, Is.EqualTo(100f));
|
||||
Assert.That(switchCount, Is.Zero,
|
||||
"Gauge changes must not emit the switch event.");
|
||||
|
||||
keyboard = BeginVirtualKeyboardInput();
|
||||
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A));
|
||||
yield return null;
|
||||
Assert.That(artifacts.IsCharging, Is.True);
|
||||
Assert.That(chargeStartCount, Is.EqualTo(1));
|
||||
Assert.That(artifacts.SelectNext(), Is.False);
|
||||
Assert.That(switchCount, Is.Zero,
|
||||
"A one-artifact inventory cannot switch and keeps charging.");
|
||||
Assert.That(artifacts.IsCharging, Is.True,
|
||||
"A failed single-slot switch must preserve the charge.");
|
||||
|
||||
Assert.That(artifacts.TryAddArtifact(secondArtifact), Is.True);
|
||||
Assert.That(switchCount, Is.Zero,
|
||||
"Adding another owned artifact must not be reported as switching.");
|
||||
yield return new WaitForSeconds(firstArtifact.ChargeDuration * 0.5f);
|
||||
Assert.That(artifacts.ChargeProgress, Is.GreaterThan(0f).And.LessThan(1f));
|
||||
|
||||
Assert.That(artifacts.SelectNext(), Is.True);
|
||||
Assert.That(artifacts.IsCharging, Is.False,
|
||||
"A successful switch must cancel a partial charge.");
|
||||
Assert.That(switchCount, Is.EqualTo(1));
|
||||
Assert.That(eventDefinition, Is.SameAs(secondArtifact));
|
||||
Assert.That(selectedDuringEvent, Is.SameAs(secondArtifact),
|
||||
"The event must observe the newly selected artifact.");
|
||||
Assert.That(artifacts.CurrentArtifact, Is.SameAs(secondArtifact));
|
||||
Assert.That(artifacts.CurrentGauge, Is.EqualTo(100f),
|
||||
"Switching during a partial charge must not spend gauge.");
|
||||
Assert.That(useCount, Is.Zero,
|
||||
"Switching during a partial charge must not activate an artifact.");
|
||||
|
||||
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
|
||||
yield return null;
|
||||
Assert.That(useCount, Is.Zero,
|
||||
"Releasing the held charge after switching must not activate either artifact.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (keyboard != null && keyboard.added)
|
||||
{
|
||||
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
|
||||
}
|
||||
|
||||
for (int i = 0; i < existingControllers.Length; i++)
|
||||
{
|
||||
if (existingControllers[i] != null)
|
||||
{
|
||||
existingControllers[i].enabled = previousControllerStates[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (previousRunManager != null)
|
||||
{
|
||||
previousRunManager.enabled = previousRunManagerEnabled;
|
||||
}
|
||||
SetRunManagerInstanceForTest(previousRunManager);
|
||||
}
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator FullyChargedSwitch_CancelsWithoutActivationOrGaugeCost()
|
||||
{
|
||||
testPlayer = new GameObject("Fully Charged Artifact Switch Test Player");
|
||||
testPlayer.AddComponent<PlayerStats>();
|
||||
artifacts = testPlayer.AddComponent<ActiveArtifactController>();
|
||||
firstArtifact = CreateArtifact(
|
||||
"fully-charged-switch-pulse",
|
||||
ActiveArtifactEffect.Pulse);
|
||||
secondArtifact = CreateArtifact(
|
||||
"fully-charged-switch-cyclone",
|
||||
ActiveArtifactEffect.Cyclone);
|
||||
Assert.That(artifacts.TryAddArtifact(firstArtifact), Is.True);
|
||||
Assert.That(artifacts.TryAddArtifact(secondArtifact), Is.True);
|
||||
artifacts.AddMovementCharge(50f);
|
||||
float gaugeBeforeSwitch = artifacts.CurrentGauge;
|
||||
int useCount = 0;
|
||||
artifacts.OnArtifactUseSucceeded += (_, _) => useCount++;
|
||||
|
||||
InvokePrivate(artifacts, "BeginCharge");
|
||||
typeof(ActiveArtifactController)
|
||||
.GetField("chargeStartedAt", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?.SetValue(
|
||||
artifacts,
|
||||
Time.time - firstArtifact.ChargeDuration - 0.01f);
|
||||
Assert.That(artifacts.IsFullyCharged, Is.True);
|
||||
|
||||
Assert.That(artifacts.SelectNext(), Is.True);
|
||||
Assert.That(artifacts.CurrentArtifact, Is.SameAs(secondArtifact));
|
||||
Assert.That(artifacts.IsCharging, Is.False);
|
||||
Assert.That(artifacts.IsExecutingArtifact, Is.False);
|
||||
Assert.That(artifacts.CurrentGauge, Is.EqualTo(gaugeBeforeSwitch));
|
||||
Assert.That(useCount, Is.Zero);
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator SWhileHoldingA_CancelsChargeAndReleaseWaitsForFreshPress()
|
||||
{
|
||||
testPlayer = new GameObject("Held A Artifact Switch Test Player");
|
||||
testPlayer.AddComponent<PlayerStats>();
|
||||
artifacts = testPlayer.AddComponent<ActiveArtifactController>();
|
||||
firstArtifact = CreateArtifact(
|
||||
"held-a-switch-pulse",
|
||||
ActiveArtifactEffect.Pulse);
|
||||
secondArtifact = CreateArtifact(
|
||||
"held-a-switch-cyclone",
|
||||
ActiveArtifactEffect.Cyclone);
|
||||
Assert.That(artifacts.TryAddArtifact(firstArtifact), Is.True);
|
||||
Assert.That(artifacts.TryAddArtifact(secondArtifact), Is.True);
|
||||
artifacts.AddMovementCharge(50f);
|
||||
float gaugeBeforeSwitch = artifacts.CurrentGauge;
|
||||
int chargeStartCount = 0;
|
||||
int useCount = 0;
|
||||
artifacts.OnArtifactChargeStarted += _ => chargeStartCount++;
|
||||
artifacts.OnArtifactUseSucceeded += (_, _) => useCount++;
|
||||
Keyboard keyboard = BeginVirtualKeyboardInput();
|
||||
|
||||
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A));
|
||||
yield return null;
|
||||
Assert.That(artifacts.IsCharging, Is.True);
|
||||
Assert.That(chargeStartCount, Is.EqualTo(1));
|
||||
yield return new WaitForSeconds(firstArtifact.ChargeDuration + 0.05f);
|
||||
Assert.That(artifacts.IsFullyCharged, Is.True);
|
||||
|
||||
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A, Key.S));
|
||||
yield return null;
|
||||
Assert.That(keyboard.aKey.isPressed, Is.True);
|
||||
Assert.That(keyboard.sKey.isPressed, Is.True);
|
||||
Assert.That(artifacts.CurrentArtifact, Is.SameAs(secondArtifact));
|
||||
Assert.That(artifacts.IsCharging, Is.False,
|
||||
"S must cancel a fully charged artifact while A remains held.");
|
||||
Assert.That(artifacts.CurrentGauge, Is.EqualTo(gaugeBeforeSwitch));
|
||||
Assert.That(useCount, Is.Zero);
|
||||
|
||||
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
|
||||
yield return null;
|
||||
Assert.That(artifacts.IsCharging, Is.False);
|
||||
Assert.That(artifacts.IsExecutingArtifact, Is.False);
|
||||
Assert.That(artifacts.CurrentGauge, Is.EqualTo(gaugeBeforeSwitch));
|
||||
Assert.That(useCount, Is.Zero,
|
||||
"Releasing the A held through the switch must not activate either artifact.");
|
||||
|
||||
InputSystem.QueueStateEvent(keyboard, new KeyboardState(Key.A));
|
||||
yield return null;
|
||||
Assert.That(artifacts.IsCharging, Is.True,
|
||||
"A fresh press after switching may begin a new charge.");
|
||||
Assert.That(chargeStartCount, Is.EqualTo(2));
|
||||
Assert.That(artifacts.CurrentArtifact, Is.SameAs(secondArtifact));
|
||||
|
||||
InvokePrivate(artifacts, "CancelCharge");
|
||||
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
|
||||
yield return null;
|
||||
Assert.That(useCount, Is.Zero);
|
||||
Assert.That(artifacts.CurrentGauge, Is.EqualTo(gaugeBeforeSwitch));
|
||||
}
|
||||
|
||||
private static ActiveArtifactDefinition CreateArtifact(
|
||||
string id,
|
||||
ActiveArtifactEffect effect)
|
||||
{
|
||||
ActiveArtifactDefinition definition =
|
||||
ScriptableObject.CreateInstance<ActiveArtifactDefinition>();
|
||||
definition.Configure(
|
||||
id,
|
||||
id,
|
||||
"",
|
||||
effect,
|
||||
DamageTag.Collision,
|
||||
Color.white,
|
||||
Color.white,
|
||||
25f,
|
||||
60f,
|
||||
0.8f,
|
||||
15f,
|
||||
30f,
|
||||
1.25f,
|
||||
2f,
|
||||
1.5f,
|
||||
3f);
|
||||
return definition;
|
||||
}
|
||||
|
||||
private Keyboard BeginVirtualKeyboardInput()
|
||||
{
|
||||
previousBackgroundBehavior = InputSystem.settings.backgroundBehavior;
|
||||
previousEditorInputBehavior =
|
||||
InputSystem.settings.editorInputBehaviorInPlayMode;
|
||||
inputSettingsCaptured = true;
|
||||
InputSystem.settings.backgroundBehavior =
|
||||
InputSettings.BackgroundBehavior.IgnoreFocus;
|
||||
InputSystem.settings.editorInputBehaviorInPlayMode =
|
||||
InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView;
|
||||
virtualKeyboard = InputSystem.AddDevice<Keyboard>();
|
||||
virtualKeyboard.MakeCurrent();
|
||||
virtualKeyboardInputActive = true;
|
||||
return virtualKeyboard;
|
||||
}
|
||||
|
||||
private static void InvokePrivate(
|
||||
ActiveArtifactController target,
|
||||
string methodName)
|
||||
{
|
||||
MethodInfo method = typeof(ActiveArtifactController).GetMethod(
|
||||
methodName,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(method, Is.Not.Null, $"Could not find {methodName}.");
|
||||
method.Invoke(target, null);
|
||||
}
|
||||
|
||||
private static void SetRunManagerInstanceForTest(RunManager instance)
|
||||
{
|
||||
FieldInfo instanceField = typeof(RunManager).GetField(
|
||||
"<Instance>k__BackingField",
|
||||
BindingFlags.Static | BindingFlags.NonPublic);
|
||||
Assert.That(instanceField, Is.Not.Null);
|
||||
instanceField.SetValue(null, instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user