334 lines
13 KiB
C#
334 lines
13 KiB
C#
using BumpCombat.Combat;
|
|
using BumpCombat.Enemies;
|
|
using NUnit.Framework;
|
|
using UnityEditor;
|
|
using UnityEditor.Animations;
|
|
using UnityEngine;
|
|
|
|
namespace BumpCombat.Tests.EditMode
|
|
{
|
|
public sealed class InteractionFeedbackTests
|
|
{
|
|
[TestCase("Slime/Slime_Idle", 46)]
|
|
[TestCase("Bat/Bat_Idle-shadow-v2", 40)]
|
|
[TestCase("ArmoredSkeleton/ArmoredSkeleton_Idle-shadow-v2", 39)]
|
|
[TestCase("NecroGolem/NecroGolem_Idle-shadow-v2", 29)]
|
|
public void CrowdControlHeadAnchor_IgnoresHiddenRendererAndTracksScaledHead(string asset, int top)
|
|
{
|
|
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/_Project/Art/Characters/" + asset + ".png");
|
|
Assert.That(texture, Is.Not.Null);
|
|
var sprite = Sprite.Create(texture, new Rect(0, 0, 100, 100), new Vector2(.5f, .5f), 32f);
|
|
var target = new GameObject("CC Head Test");
|
|
var renderer = target.AddComponent<SpriteRenderer>();
|
|
renderer.sprite = sprite;
|
|
renderer.enabled = false;
|
|
foreach (float scale in new[] { 1f, 1.25f, 2f })
|
|
{
|
|
target.transform.position = new Vector3(3f, -2f, 0f);
|
|
target.transform.localScale = Vector3.one * scale;
|
|
Vector3 actual = target.transform.TransformPoint(CombatFeedback.CalculateStunMarkerLocalPosition(target.transform, renderer));
|
|
Assert.That(actual.x, Is.EqualTo(3f).Within(.0001f));
|
|
Assert.That(actual.y, Is.EqualTo(-2f + (50f - top) / 32f * scale + (4f + 5f * scale) / 32f).Within(.0001f));
|
|
}
|
|
Object.DestroyImmediate(target);
|
|
Object.DestroyImmediate(sprite);
|
|
}
|
|
|
|
[Test]
|
|
public void CrowdControlSpiral_UsesDistinctPalettesAndLoopsEightFrames()
|
|
{
|
|
Sprite first = CrowdControlMarkerArt.Frame(0f, false);
|
|
Assert.That(first, Is.Not.Null);
|
|
Assert.That(first.rect.size, Is.EqualTo(new Vector2(32f, 24f)));
|
|
Assert.That(first.texture.filterMode, Is.EqualTo(FilterMode.Point));
|
|
Assert.That(CrowdControlMarkerArt.Frame(.07f, false).rect.x, Is.EqualTo(32f));
|
|
Assert.That(CrowdControlMarkerArt.Frame(.481f, false), Is.SameAs(first));
|
|
Assert.That(CrowdControlMarkerArt.Frame(0f, true).texture, Is.Not.SameAs(first.texture));
|
|
}
|
|
|
|
[Test]
|
|
public void StrongDashLaunchResult_IsDistinctFromStrongDashIntent()
|
|
{
|
|
CombatHitResult resisted = new(
|
|
null,
|
|
null,
|
|
true,
|
|
HitSide.Front,
|
|
1f,
|
|
Vector2.right,
|
|
1f,
|
|
Vector2.zero,
|
|
false,
|
|
true,
|
|
DamageTag.Collision,
|
|
"Dash",
|
|
true,
|
|
true,
|
|
false);
|
|
CombatHitResult launched = new(
|
|
null,
|
|
null,
|
|
true,
|
|
HitSide.Front,
|
|
1f,
|
|
Vector2.right,
|
|
1f,
|
|
Vector2.zero,
|
|
false,
|
|
true,
|
|
DamageTag.Collision,
|
|
"Dash",
|
|
true,
|
|
true,
|
|
true);
|
|
|
|
Assert.That(resisted.IsStrongDash, Is.True);
|
|
Assert.That(resisted.LaunchSucceeded, Is.False);
|
|
Assert.That(launched.IsStrongDash, Is.True);
|
|
Assert.That(launched.LaunchSucceeded, Is.True);
|
|
}
|
|
|
|
[TestCase(0.09f, false)]
|
|
[TestCase(0.1f, true)]
|
|
[TestCase(0.5f, true)]
|
|
public void PulseGatherDistance_UsesMinimumStreakThreshold(float distance, bool expectedStreak)
|
|
{
|
|
Assert.That(CombatFeedback.ShouldShowPulseStreak(distance), Is.EqualTo(expectedStreak));
|
|
}
|
|
|
|
[TestCase(0.49f, false)]
|
|
[TestCase(0.5f, true)]
|
|
public void KnockbackDistance_UsesMinimumDustThreshold(float distance, bool expectedDust)
|
|
{
|
|
Assert.That(CombatFeedback.ShouldShowKnockbackDust(distance), Is.EqualTo(expectedDust));
|
|
}
|
|
|
|
[TestCase(true, false, true)]
|
|
[TestCase(true, true, false)]
|
|
[TestCase(false, false, false)]
|
|
public void LaunchResult_SeparatesResistFromDeath(
|
|
bool canBeLaunched,
|
|
bool isDead,
|
|
bool expectedLaunch)
|
|
{
|
|
Assert.That(
|
|
BumpCombatResolver.CanLaunchAfterHit(canBeLaunched, isDead),
|
|
Is.EqualTo(expectedLaunch));
|
|
Assert.That(
|
|
BumpCombatResolver.ShouldShowLaunchResist(canBeLaunched),
|
|
Is.EqualTo(!canBeLaunched));
|
|
}
|
|
|
|
[TestCase(false, true, false, true)]
|
|
[TestCase(true, true, false, false)]
|
|
[TestCase(false, false, false, false)]
|
|
[TestCase(false, true, true, false)]
|
|
public void LineTransientRecycle_RejectsPendingOrDuplicateEntries(
|
|
bool destroyPending,
|
|
bool activeRegistrationRemoved,
|
|
bool alreadyPooled,
|
|
bool expected)
|
|
{
|
|
Assert.That(
|
|
CombatFeedback.CanRecycleLineTransient(
|
|
destroyPending,
|
|
activeRegistrationRemoved,
|
|
alreadyPooled),
|
|
Is.EqualTo(expected));
|
|
Assert.That(
|
|
CombatFeedback.MaxConcurrentHeavyEffects,
|
|
Is.EqualTo(12));
|
|
}
|
|
|
|
[Test]
|
|
public void WorldStreakRenderer_ResetsRingLoopState()
|
|
{
|
|
GameObject host = new("Line Renderer State Test");
|
|
LineRenderer line = host.AddComponent<LineRenderer>();
|
|
line.loop = true;
|
|
line.useWorldSpace = false;
|
|
|
|
CombatFeedback.ConfigureWorldStreakRenderer(
|
|
line,
|
|
null,
|
|
Color.white,
|
|
0.04f,
|
|
Vector2.zero,
|
|
Vector2.right);
|
|
|
|
Assert.That(line.loop, Is.False);
|
|
Assert.That(line.useWorldSpace, Is.True);
|
|
Assert.That(line.positionCount, Is.EqualTo(2));
|
|
Object.DestroyImmediate(host);
|
|
}
|
|
|
|
[TestCase(false, false, true, true)]
|
|
[TestCase(false, true, true, false)]
|
|
[TestCase(false, false, false, false)]
|
|
[TestCase(true, false, true, false)]
|
|
public void LineTransientAcquire_RejectsPendingOrInvalidEntries(
|
|
bool unityNull,
|
|
bool destroyPending,
|
|
bool hasLineRenderer,
|
|
bool expected)
|
|
{
|
|
Assert.That(
|
|
CombatFeedback.CanAcquireLineTransient(
|
|
unityNull,
|
|
destroyPending,
|
|
hasLineRenderer),
|
|
Is.EqualTo(expected));
|
|
}
|
|
|
|
[Test]
|
|
public void StunMarkerAnchor_FallbackKeepsClearanceAndScalesWithCharacter()
|
|
{
|
|
GameObject target = new("Stun Marker Anchor Test");
|
|
SpriteRenderer targetRenderer = target.AddComponent<SpriteRenderer>();
|
|
Sprite sprite = Sprite.Create(
|
|
Texture2D.whiteTexture,
|
|
new Rect(0f, 0f, 1f, 1f),
|
|
new Vector2(0.5f, 0.5f),
|
|
32f);
|
|
targetRenderer.sprite = sprite;
|
|
Vector3 runeLocalScale = new(0.11f, 0.035f, 1f);
|
|
|
|
foreach (float targetScale in new[] { 0.8f, 1f, 1.25f, 1.6f, 2f, 2.5f, 3.5f })
|
|
{
|
|
Vector3 expectedWorldScale = Vector3.Scale(new Vector3(.5f * targetScale, .5f * targetScale, 1f), runeLocalScale);
|
|
target.transform.localScale = Vector3.one * targetScale;
|
|
Vector3 localAnchor = CombatFeedback.CalculateStunMarkerLocalPosition(
|
|
target.transform,
|
|
targetRenderer);
|
|
Vector3 worldAnchor = target.transform.TransformPoint(localAnchor);
|
|
Assert.That(worldAnchor.x, Is.EqualTo(target.transform.position.x).Within(0.0001f));
|
|
Assert.That(
|
|
worldAnchor.y,
|
|
Is.EqualTo(targetRenderer.bounds.max.y + (4f + 5f * targetScale) / 32f).Within(0.0001f));
|
|
|
|
Vector3 baseLocalScale = CombatFeedback.CalculateStunMarkerBaseLocalScale(
|
|
target.transform.lossyScale);
|
|
Vector3 worldScale = Vector3.Scale(
|
|
target.transform.lossyScale,
|
|
Vector3.Scale(baseLocalScale, runeLocalScale));
|
|
Assert.That(worldScale.x, Is.EqualTo(expectedWorldScale.x).Within(0.0001f));
|
|
Assert.That(worldScale.y, Is.EqualTo(expectedWorldScale.y).Within(0.0001f));
|
|
}
|
|
|
|
Object.DestroyImmediate(sprite);
|
|
Object.DestroyImmediate(target);
|
|
}
|
|
|
|
[TestCase(485)]
|
|
[TestCase(1000)]
|
|
[TestCase(1515)]
|
|
public void StunMarkerSortingOrder_StaysAheadOfTarget(int targetSortingOrder)
|
|
{
|
|
Assert.That(
|
|
CombatFeedback.CalculateStunMarkerSortingOrder(targetSortingOrder),
|
|
Is.GreaterThan(targetSortingOrder));
|
|
}
|
|
|
|
[Test]
|
|
public void BumpImpactGrades_UseHitSideAndFixedFrameTiming()
|
|
{
|
|
Assert.That(
|
|
CombatFeedback.GetBumpImpactResourcePath(HitSide.Front),
|
|
Is.EqualTo("Combat/BumpImpact-Weak-v1"));
|
|
Assert.That(
|
|
CombatFeedback.GetBumpImpactResourcePath(HitSide.Side),
|
|
Is.EqualTo("Combat/BumpImpact-Medium-v1"));
|
|
Assert.That(
|
|
CombatFeedback.GetBumpImpactResourcePath(HitSide.Back),
|
|
Is.EqualTo("Combat/BumpImpact-Strong-v1"));
|
|
Assert.That(
|
|
CombatFeedback.GetBumpImpactFrameDuration(0),
|
|
Is.EqualTo(0.03f));
|
|
Assert.That(
|
|
CombatFeedback.GetBumpImpactFrameDuration(1),
|
|
Is.EqualTo(0.04f));
|
|
Assert.That(
|
|
CombatFeedback.GetBumpImpactFrameDuration(2),
|
|
Is.EqualTo(0.04f));
|
|
Assert.That(
|
|
CombatFeedback.GetBumpImpactFrameDuration(3),
|
|
Is.EqualTo(0.03f));
|
|
}
|
|
|
|
[Test]
|
|
public void BumpImpactResources_ContainFourCenteredWhitePixelFrames()
|
|
{
|
|
foreach (HitSide side in System.Enum.GetValues(typeof(HitSide)))
|
|
{
|
|
Sprite[] frames = Resources.LoadAll<Sprite>(
|
|
CombatFeedback.GetBumpImpactResourcePath(side));
|
|
System.Array.Sort(
|
|
frames,
|
|
(left, right) => System.String.CompareOrdinal(left.name, right.name));
|
|
|
|
Assert.That(frames, Has.Length.EqualTo(CombatFeedback.BumpImpactFrameCount));
|
|
for (int i = 0; i < frames.Length; i++)
|
|
{
|
|
Assert.That(frames[i].name, Does.EndWith($"_{i}"));
|
|
Assert.That(frames[i].rect.width, Is.EqualTo(64f));
|
|
Assert.That(frames[i].rect.height, Is.EqualTo(64f));
|
|
Assert.That(frames[i].pixelsPerUnit, Is.EqualTo(32f));
|
|
Assert.That(frames[i].pivot.x, Is.EqualTo(32f));
|
|
Assert.That(frames[i].pivot.y, Is.EqualTo(32f));
|
|
Assert.That(frames[i].texture, Is.Not.Null);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void SwordsmanController_WiresCombatStatesToExpectedClipLengths()
|
|
{
|
|
const string controllerPath =
|
|
"Assets/_Project/Art/Characters/Swordsman/Animations/Swordsman.controller";
|
|
AnimatorController controller =
|
|
AssetDatabase.LoadAssetAtPath<AnimatorController>(controllerPath);
|
|
Assert.That(controller, Is.Not.Null);
|
|
|
|
AnimatorState bumpAttack = FindState(controller, "BumpAttack");
|
|
AnimatorState artifactUse = FindState(controller, "ArtifactUse");
|
|
Assert.That(bumpAttack, Is.Not.Null);
|
|
Assert.That(artifactUse, Is.Not.Null);
|
|
Assert.That((bumpAttack.motion as AnimationClip).length, Is.EqualTo(0.24f).Within(0.0001f));
|
|
Assert.That((artifactUse.motion as AnimationClip).length, Is.EqualTo(0.4f).Within(0.0001f));
|
|
Assert.That(bumpAttack.transitions, Has.Length.EqualTo(2));
|
|
Assert.That(artifactUse.transitions, Has.Length.EqualTo(2));
|
|
foreach (AnimatorStateTransition transition in bumpAttack.transitions)
|
|
{
|
|
Assert.That(transition.hasExitTime, Is.True);
|
|
Assert.That(transition.exitTime, Is.EqualTo(1f));
|
|
}
|
|
foreach (AnimatorStateTransition transition in artifactUse.transitions)
|
|
{
|
|
Assert.That(transition.hasExitTime, Is.True);
|
|
Assert.That(transition.exitTime, Is.EqualTo(1f));
|
|
}
|
|
|
|
foreach (AnimatorControllerParameter parameter in controller.parameters)
|
|
{
|
|
Assert.That(parameter.name, Is.Not.EqualTo("BumpAttack"));
|
|
Assert.That(parameter.name, Is.Not.EqualTo("ArtifactUse"));
|
|
}
|
|
}
|
|
|
|
private static AnimatorState FindState(
|
|
AnimatorController controller,
|
|
string stateName)
|
|
{
|
|
foreach (ChildAnimatorState child in controller.layers[0].stateMachine.states)
|
|
{
|
|
if (child.state.name == stateName)
|
|
{
|
|
return child.state;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|