Files
tiny_tackle_heroes/Assets/_Project/Tests/EditMode/Defense25DMonsterCombatAdapterTests.cs
T
2026-09-28 11:07:17 +09:00

130 lines
5.9 KiB
C#

using System.Collections.Generic;
using System.Linq;
using BumpCombat.Constants;
using BumpCombat.Defense.Comparison;
using BumpCombat.Enemies;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
namespace BumpCombat.Tests
{
public sealed class Defense25DMonsterCombatAdapterTests
{
private const string PoolPath = "Assets/_Project/Constants/Defense25DComparison/MonsterPools.asset";
[Test]
public void EveryPoolEntryResolvesItsExactAuthoredAttackAction()
{
Defense25DMonsterPoolDefinition pool = AssetDatabase.LoadAssetAtPath<Defense25DMonsterPoolDefinition>(PoolPath);
Assert.That(pool, Is.Not.Null);
Assert.That(pool.TryValidate(out string error), Is.True, error);
Texture2D texture = new Texture2D(1, 1);
Sprite sourceFrame = Sprite.Create(texture, new Rect(0, 0, 1, 1), new Vector2(.5f, .5f));
try
{
foreach (Defense25DMonsterPoolEntry entry in pool.Tiers.SelectMany(tier => tier.Entries))
{
string action = entry.CombatProfile.AttackAction;
var actions = new Dictionary<string, Sprite[]>
{
[action] = new[] { sourceFrame },
};
var motion = new Defense25DMonsterSourceMotion(
entry.SourceCharacterId,
new[] { sourceFrame },
new[] { sourceFrame },
actions,
null,
.1f);
Assert.That(Defense25DMonsterCombatAdapter.TryResolveAttackFrames(
entry, motion, out Sprite[] resolved, out string resolveError),
Is.True, $"{entry.EnemyDefinitionId}: {resolveError}");
Assert.That(resolved, Is.EqualTo(new[] { sourceFrame }), entry.EnemyDefinitionId);
}
}
finally
{
Object.DestroyImmediate(sourceFrame);
Object.DestroyImmediate(texture);
}
}
[Test]
public void SourceMotionRejectsWrongSpeciesAndNeverFallsBackToSkeletonFrames()
{
Defense25DMonsterPoolDefinition pool = AssetDatabase.LoadAssetAtPath<Defense25DMonsterPoolDefinition>(PoolPath);
Defense25DMonsterPoolEntry entry = pool.Tiers.SelectMany(tier => tier.Entries)
.First(value => value.EnemyDefinitionId == "Demon_A");
Texture2D texture = new Texture2D(1, 1);
Sprite skeletonFrame = Sprite.Create(texture, new Rect(0, 0, 1, 1), new Vector2(.5f, .5f));
try
{
var wrongSpecies = new Defense25DMonsterSourceMotion(
"Skeleton", new[] { skeletonFrame }, new[] { skeletonFrame },
new Dictionary<string, Sprite[]> { ["Attack"] = new[] { skeletonFrame } }, null, .1f);
Assert.That(Defense25DMonsterCombatAdapter.TryResolveAttackFrames(
entry, wrongSpecies, out _, out string speciesError), Is.False);
Assert.That(speciesError, Does.Contain("SourceCharacterId"));
var missingAction = new Defense25DMonsterSourceMotion(
entry.SourceCharacterId, new[] { skeletonFrame }, new[] { skeletonFrame },
new Dictionary<string, Sprite[]> { ["Attack"] = new[] { skeletonFrame } }, null, .1f);
Assert.That(Defense25DMonsterCombatAdapter.TryResolveAttackFrames(
entry, missingAction, out _, out string actionError), Is.False);
Assert.That(actionError, Does.Contain(entry.CombatProfile.AttackAction));
}
finally
{
Object.DestroyImmediate(skeletonFrame);
Object.DestroyImmediate(texture);
}
}
[TestCase(EnemyAttackShape.Body, 0.2f, 0f, 0f, true)]
[TestCase(EnemyAttackShape.Body, 0.4f, 0f, 0f, false)]
[TestCase(EnemyAttackShape.Box, 0.8f, 0.2f, 1f, true)]
[TestCase(EnemyAttackShape.Box, -0.2f, 0f, 1f, false)]
[TestCase(EnemyAttackShape.Cone, 1f, 0.2f, 2f, true)]
[TestCase(EnemyAttackShape.Cone, 1f, 0.8f, 2f, false)]
[TestCase(EnemyAttackShape.TargetCircle, 1.9f, 0.2f, .5f, true)]
[TestCase(EnemyAttackShape.TargetCircle, .5f, 0.2f, .5f, false)]
public void AttackGeometryUsesItsAuthoredShape(
EnemyAttackShape shape, float targetX, float targetY, float lengthOrRadius, bool expected)
{
bool hit = Defense25DMonsterCombatAdapter.IntersectsAttack(
shape,
Vector2.zero,
Vector2.right,
new Vector2(2f, 0f),
new Vector2(targetX, targetY),
targetRadius: .1f,
bodyRadius: .25f,
attackLength: lengthOrRadius,
attackWidth: .6f,
attackRadius: lengthOrRadius);
Assert.That(hit, Is.EqualTo(expected));
}
[Test]
public void ProjectileSweptHitUsesTheWholeSegmentAndConfiguredRadius()
{
Assert.That(Defense25DMonsterCombatAdapter.ProjectileSegmentHits(
Vector2.zero, Vector2.right * 2f, new Vector2(1f, .09f), .1f), Is.True);
Assert.That(Defense25DMonsterCombatAdapter.ProjectileSegmentHits(
Vector2.zero, Vector2.right * 2f, new Vector2(1f, .12f), .1f), Is.False);
}
[Test]
public void AdapterExposesTheRunSpawnLifecycleContract()
{
Assert.That(typeof(Defense25DMonsterCombatAdapter).GetMethod("Bind"), Is.Not.Null);
Assert.That(typeof(Defense25DMonsterCombatAdapter).GetMethod("Step"), Is.Not.Null);
Assert.That(typeof(Defense25DMonsterCombatAdapter).GetMethod("Cancel"), Is.Not.Null);
}
}
}