2.5d test
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Defense.Comparison;
|
||||
using BumpCombat.Player;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace BumpCombat.Tests
|
||||
{
|
||||
public sealed class Defense25DCombatPopupPlayModeTests
|
||||
{
|
||||
private const string SettingsPath =
|
||||
"Assets/_Project/Constants/Defense25DComparison/ComparisonSettings.asset";
|
||||
private Defense25DComparison world;
|
||||
private Defense25DComparisonSettings settings;
|
||||
private float previousTimeScale;
|
||||
|
||||
[UnitySetUp]
|
||||
public IEnumerator SetUp()
|
||||
{
|
||||
previousTimeScale = Time.timeScale;
|
||||
Time.timeScale = 1f;
|
||||
settings = Object.Instantiate(
|
||||
AssetDatabase.LoadAssetAtPath<Defense25DComparisonSettings>(SettingsPath));
|
||||
Assert.That(settings, Is.Not.Null);
|
||||
settings.Enemy = AssetDatabase.LoadAssetAtPath<BumpCombat.Enemies.EnemyDefinition>(
|
||||
"Assets/_Project/Constants/Enemies/Slime.asset");
|
||||
settings.EnemyArt = ReadCharacter();
|
||||
world = new GameObject("2.5D combat popup test world")
|
||||
.AddComponent<Defense25DComparison>();
|
||||
world.Initialize(settings);
|
||||
world.AutoSpawn = false;
|
||||
world.ReadInput = false;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[UnityTearDown]
|
||||
public IEnumerator TearDown()
|
||||
{
|
||||
if (world != null) Object.Destroy(world.gameObject);
|
||||
if (settings != null) Object.Destroy(settings);
|
||||
Time.timeScale = previousTimeScale;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ValidHitPopups_MatchNormalSideBackAndArtifactContracts()
|
||||
{
|
||||
GameObject target = world.Enemies[0].gameObject;
|
||||
RaiseHit(target, HitSide.Front, 31.9f);
|
||||
AssertPopup(0, "31", Color.white);
|
||||
|
||||
world.ResetRun();
|
||||
target = world.Enemies[0].gameObject;
|
||||
RaiseHit(target, HitSide.Side, 22.8f);
|
||||
AssertPopup(0, "SIDE ×1.2 22", new Color(0.65f, 1f, 0.3f));
|
||||
|
||||
world.ResetRun();
|
||||
target = world.Enemies[0].gameObject;
|
||||
RaiseHit(target, HitSide.Back, 9.8f);
|
||||
AssertPopup(0, "BACK ×1.5 9", new Color(1f, 0.75f, 0.2f));
|
||||
|
||||
world.ResetRun();
|
||||
target = world.Enemies[0].gameObject;
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
world.Player.gameObject, target, true, HitSide.Front, 13f,
|
||||
Vector2.right, 0f, Vector2.zero, launchSucceeded: true));
|
||||
AssertPopup(0, "LAUNCH 13", new Color(0.95f, 0.55f, 1f));
|
||||
|
||||
world.ResetRun();
|
||||
target = world.Enemies[0].gameObject;
|
||||
ActiveArtifactDefinition dash = world.Loadout.FindArtifact(ActiveArtifactEffect.Dash);
|
||||
Assert.That(dash, Is.Not.Null);
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
world.Player.gameObject, target, true, HitSide.Front, 15f, Vector2.right,
|
||||
0f, Vector2.zero, sourceId: dash.ArtifactId, isArtifactHit: true,
|
||||
artifactEffect: ActiveArtifactEffect.Dash));
|
||||
AssertPopup(0, $"{dash.DisplayName} 15",
|
||||
ActiveArtifactDefinition.GetArtifactPaletteColor(dash.ArtifactColor));
|
||||
|
||||
world.ResetRun();
|
||||
target = world.Enemies[0].gameObject;
|
||||
ActiveArtifactDefinition cyclone = world.Loadout.FindArtifact(ActiveArtifactEffect.Cyclone);
|
||||
Assert.That(cyclone, Is.Not.Null);
|
||||
CombatEvents.RaiseValidHit(CombatHitResult.ForArtifactContact(
|
||||
world.Player.gameObject, target, ActiveArtifactEffect.Cyclone, false,
|
||||
HitSide.Front, Vector2.right, Vector2.zero, cyclone.DamageTag, cyclone.ArtifactId));
|
||||
AssertPopup(0, $"{cyclone.DisplayName} 0",
|
||||
ActiveArtifactDefinition.GetArtifactPaletteColor(cyclone.ArtifactColor));
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator PopupEvents_AreWorldScopedResettableAndRemovedOnDisable()
|
||||
{
|
||||
GameObject worldTarget = world.Enemies[0].gameObject;
|
||||
GameObject unrelatedTarget = new("Unrelated popup target");
|
||||
GameObject unrelatedAttacker = new("Unrelated popup attacker");
|
||||
try
|
||||
{
|
||||
RaiseHit(unrelatedTarget, HitSide.Front, 10f);
|
||||
Assert.That(world.ActiveCombatDamagePopupCount, Is.Zero);
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
unrelatedAttacker, worldTarget, false, HitSide.Front, 10f,
|
||||
Vector2.right, 0f, Vector2.zero));
|
||||
Assert.That(world.ActiveCombatDamagePopupCount, Is.Zero,
|
||||
"Hits from another attacker against this world's enemy are ignored.");
|
||||
|
||||
RaiseHit(worldTarget, HitSide.Front, 10f);
|
||||
Assert.That(world.ActiveCombatDamagePopupCount, Is.EqualTo(1));
|
||||
world.ResetRun();
|
||||
Assert.That(world.ActiveCombatDamagePopupCount, Is.Zero);
|
||||
|
||||
world.enabled = false;
|
||||
RaiseHit(worldTarget, HitSide.Front, 10f);
|
||||
world.NotifyPlayerDamagePopup(10f, Vector3.zero);
|
||||
world.RecordTowerDamagePopup(10f, Color.cyan, Vector3.one);
|
||||
world.enabled = true;
|
||||
Assert.That(world.ActiveCombatDamagePopupCount, Is.Zero,
|
||||
"A disabled comparison must reject event and direct popup additions.");
|
||||
RaiseHit(world.Enemies[0].gameObject, HitSide.Front, 10f);
|
||||
Assert.That(world.ActiveCombatDamagePopupCount, Is.EqualTo(1),
|
||||
"Re-enabling the comparison must restore one event subscription.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.Destroy(unrelatedTarget);
|
||||
Object.Destroy(unrelatedAttacker);
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator PlayerAndTowerDamagePopups_ShareTwelveReusableUnscaledSlots()
|
||||
{
|
||||
for (int i = 0; i < 13; i++)
|
||||
world.NotifyPlayerDamagePopup(12.9f, Vector3.zero);
|
||||
Assert.That(world.ActiveCombatDamagePopupCount, Is.EqualTo(12));
|
||||
AssertPopup(0, "-12", new Color(244f / 255f, 117f / 255f, 112f / 255f));
|
||||
|
||||
world.RecordTowerDamagePopup(8.9f, Color.cyan, Vector3.one);
|
||||
Assert.That(world.ActiveTowerDamagePopupCount, Is.Zero,
|
||||
"An active-set overflow must be rejected for towers as well.");
|
||||
|
||||
Time.timeScale = 0f;
|
||||
yield return new WaitForSecondsRealtime(0.65f);
|
||||
Assert.That(world.ActiveCombatDamagePopupCount, Is.Zero,
|
||||
"Popup expiry uses unscaled time, including while paused.");
|
||||
|
||||
world.NotifyPlayerDamagePopup(14.5f, Vector3.zero);
|
||||
world.RecordTowerDamagePopup(8.9f, Color.cyan, Vector3.one);
|
||||
Assert.That(world.ActiveCombatDamagePopupCount, Is.EqualTo(2));
|
||||
Assert.That(world.ActiveTowerDamagePopupCount, Is.EqualTo(1));
|
||||
AssertPopup(0, "-14", new Color(244f / 255f, 117f / 255f, 112f / 255f));
|
||||
AssertPopup(1, "-8", Color.cyan);
|
||||
world.NotifyPlayerDamagePopup(0f, Vector3.zero);
|
||||
Assert.That(world.ActiveCombatDamagePopupCount, Is.EqualTo(2),
|
||||
"The public player damage API ignores zero actual HP loss.");
|
||||
}
|
||||
|
||||
private void RaiseHit(GameObject target, HitSide side, float damage)
|
||||
{
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
world.Player.gameObject, target, false, side, damage, Vector2.right,
|
||||
0f, Vector2.zero, isOffsetHit: false));
|
||||
}
|
||||
|
||||
private void AssertPopup(int index, string text, Color color)
|
||||
{
|
||||
FieldInfo popupArrayField = typeof(Defense25DComparison).GetField(
|
||||
"combatDamagePopups", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(popupArrayField, Is.Not.Null);
|
||||
Array popupArray = (Array)popupArrayField.GetValue(world);
|
||||
object popup = popupArray.GetValue(index);
|
||||
Type popupType = popup.GetType();
|
||||
Assert.That((string)popupType.GetField("Text").GetValue(popup), Is.EqualTo(text));
|
||||
Color actualColor = (Color)popupType.GetField("Color").GetValue(popup);
|
||||
Assert.That(actualColor.r, Is.EqualTo(color.r).Within(0.002f));
|
||||
Assert.That(actualColor.g, Is.EqualTo(color.g).Within(0.002f));
|
||||
Assert.That(actualColor.b, Is.EqualTo(color.b).Within(0.002f));
|
||||
}
|
||||
|
||||
private static ComparisonSpriteFrames ReadCharacter()
|
||||
{
|
||||
Sprite[] Read(string suffix) => AssetDatabase.LoadAllAssetsAtPath(
|
||||
$"Assets/_Project/Art/Characters/Slime/Slime_{suffix}.png")
|
||||
.OfType<Sprite>()
|
||||
.OrderBy(sprite => int.TryParse(
|
||||
sprite.name[(sprite.name.LastIndexOf('_') + 1)..], out int frame) ? frame : 0)
|
||||
.ToArray();
|
||||
return new ComparisonSpriteFrames
|
||||
{
|
||||
Idle = Read("Idle"), Walk = Read("Walk"), Attack = Read("Attack"),
|
||||
Hurt = Read("Hurt"), Death = Read("Death"),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user