1855 lines
85 KiB
C#
1855 lines
85 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Reflection;
|
||
using BumpCombat.Combat;
|
||
using BumpCombat.Core;
|
||
using BumpCombat.Enemies;
|
||
using BumpCombat.Player;
|
||
using BumpCombat.Progression;
|
||
using BumpCombat.Spawning;
|
||
using BumpCombat.UI;
|
||
using NUnit.Framework;
|
||
using UnityEngine;
|
||
using UnityEngine.SceneManagement;
|
||
using UnityEngine.TestTools;
|
||
using UnityEngine.UI;
|
||
|
||
namespace BumpCombat.PlayModeTests
|
||
{
|
||
public sealed class VisualAlignmentTests
|
||
{
|
||
private static readonly Vector2[] Directions =
|
||
{
|
||
Vector2.right,
|
||
Vector2.left,
|
||
Vector2.up,
|
||
Vector2.down,
|
||
new Vector2(1f, 1f).normalized,
|
||
new Vector2(-1f, 1f).normalized,
|
||
new Vector2(1f, -1f).normalized,
|
||
new Vector2(-1f, -1f).normalized,
|
||
};
|
||
|
||
[SetUp]
|
||
public void SetUp()
|
||
{
|
||
RunManager.ForceProductionModeForTests = false;
|
||
ArtifactRewardController.GrantCatalogForTests = true;
|
||
Time.timeScale = 1f;
|
||
}
|
||
|
||
[TearDown]
|
||
public void TearDown()
|
||
{
|
||
RunManager.ForceProductionModeForTests = false;
|
||
ArtifactRewardController.GrantCatalogForTests = false;
|
||
Time.timeScale = 1f;
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator ScorchingRay_NormalVisualAndHitsAlignAcrossCardinalAndDiagonalDirections()
|
||
{
|
||
yield return AssertScorchingRayAlignment(false);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator ScorchingRay_ChargedVisualAndHitsAlignAcrossCardinalAndDiagonalDirections()
|
||
{
|
||
yield return AssertScorchingRayAlignment(true);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator PulseRing_RendererEllipseAndGroundTargetFilterStayAligned()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
director.enabled = false;
|
||
director.DebugSpawnImmediate(2);
|
||
yield return null;
|
||
|
||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||
Rigidbody2D playerBody = player.GetComponent<Rigidbody2D>();
|
||
ActiveArtifactController artifacts =
|
||
player.GetComponent<ActiveArtifactController>();
|
||
playerBody.position = new Vector2(1.25f, -0.75f);
|
||
EnemyController[] enemies = Object.FindObjectsByType<EnemyController>(
|
||
FindObjectsSortMode.InstanceID);
|
||
Assert.That(enemies.Length, Is.GreaterThanOrEqualTo(2));
|
||
for (int enemyIndex = 2; enemyIndex < enemies.Length; enemyIndex++)
|
||
{
|
||
enemies[enemyIndex].gameObject.SetActive(false);
|
||
}
|
||
|
||
ActiveArtifactDefinition definition = SelectArtifact(
|
||
artifacts,
|
||
ActiveArtifactEffect.Pulse);
|
||
float radius = definition.ChargedRange;
|
||
float verticalRadius = 0.8125f;
|
||
Vector2 diagonal = new Vector2(1f, 1f).normalized;
|
||
Vector2 targetStartPosition = playerBody.position
|
||
+ new Vector2(radius * diagonal.x, verticalRadius * diagonal.y);
|
||
float targetHealthBefore = enemies[0].CurrentHealth;
|
||
PlaceEnemy(
|
||
enemies[0],
|
||
targetStartPosition);
|
||
Vector2 outsideStartPosition = playerBody.position
|
||
+ Vector2.up * (verticalRadius + 0.1f);
|
||
float outsideHealthBefore = enemies[1].CurrentHealth;
|
||
PlaceEnemy(
|
||
enemies[1],
|
||
outsideStartPosition);
|
||
AddOverlappingCollider(enemies[1]);
|
||
Physics2D.SyncTransforms();
|
||
EnsureGauge(artifacts, definition.ChargedGaugeCost);
|
||
|
||
Assert.That(artifacts.TryUseCurrent(true), Is.True);
|
||
GameObject visual = GameObject.Find("Charged Artifact Pulse");
|
||
Assert.That(visual, Is.Not.Null);
|
||
Assert.That(
|
||
Vector2.Distance((Vector2)visual.transform.position, playerBody.position),
|
||
Is.LessThan(0.0001f));
|
||
Assert.That(
|
||
visual.GetComponentsInChildren<LineRenderer>(true),
|
||
Is.Empty);
|
||
Assert.That(
|
||
visual.GetComponentInChildren<SpriteRenderer>(),
|
||
Is.Not.Null);
|
||
Assert.That(enemies[0].IsStunned, Is.True);
|
||
Assert.That(enemies[1].IsStunned, Is.False);
|
||
Assert.That(enemies[0].transform.Find("Stun Runes"), Is.Not.Null);
|
||
Assert.That(enemies[1].transform.Find("Stun Runes"), Is.Null);
|
||
Vector2 repelledOffset =
|
||
enemies[0].GetComponent<Rigidbody2D>().position
|
||
- playerBody.position;
|
||
Vector2 initialOffset = targetStartPosition - playerBody.position;
|
||
float boundaryDistance = 1f / Mathf.Sqrt(
|
||
diagonal.x * diagonal.x / (radius * radius)
|
||
+ diagonal.y * diagonal.y
|
||
/ (verticalRadius * verticalRadius));
|
||
Assert.That(
|
||
Vector2.Distance(initialOffset, repelledOffset),
|
||
Is.GreaterThanOrEqualTo(definition.ChargedKnockback - 0.01f));
|
||
Assert.That(
|
||
repelledOffset.magnitude,
|
||
Is.GreaterThanOrEqualTo(boundaryDistance + 0.099f));
|
||
Assert.That(
|
||
repelledOffset.x * repelledOffset.x / (radius * radius)
|
||
+ repelledOffset.y * repelledOffset.y
|
||
/ (verticalRadius * verticalRadius),
|
||
Is.GreaterThan(1.0001f));
|
||
Assert.That(
|
||
Vector2.Dot(initialOffset.normalized, repelledOffset.normalized),
|
||
Is.GreaterThan(0.9999f));
|
||
Assert.That(enemies[0].CurrentHealth, Is.EqualTo(targetHealthBefore));
|
||
Assert.That(
|
||
enemies[1].GetComponent<Rigidbody2D>().position,
|
||
Is.EqualTo(outsideStartPosition));
|
||
Assert.That(enemies[1].CurrentHealth, Is.EqualTo(outsideHealthBefore));
|
||
|
||
enemies[0].enabled = true;
|
||
yield return new WaitForSeconds(0.7f);
|
||
Assert.That(enemies[0].IsStunned, Is.False);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator LevelUpSelectionPause_DoesNotLeaveArtifactRangeLineRenderer()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
director.enabled = false;
|
||
director.DebugSpawnImmediate(1);
|
||
yield return null;
|
||
|
||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||
ActiveArtifactController artifacts =
|
||
player.GetComponent<ActiveArtifactController>();
|
||
ActiveArtifactDefinition definition = SelectArtifact(
|
||
artifacts,
|
||
ActiveArtifactEffect.Pulse);
|
||
EnsureGauge(artifacts, definition.NormalGaugeCost);
|
||
Assert.That(artifacts.TryUseCurrent(false), Is.True);
|
||
GameObject visual = GameObject.Find("Artifact Pulse");
|
||
Assert.That(visual, Is.Not.Null);
|
||
|
||
ExperienceSystem experience =
|
||
player.GetComponent<ExperienceSystem>();
|
||
Assert.That(experience, Is.Not.Null);
|
||
experience.AddExperience(experience.RequiredExperience);
|
||
Assert.That(RunManager.Instance.IsSelectionOpen, Is.True);
|
||
Assert.That(Time.timeScale, Is.Zero);
|
||
Assert.That(
|
||
visual.GetComponentsInChildren<LineRenderer>(true),
|
||
Is.Empty);
|
||
|
||
RunManager.Instance.SetSelectionOpen(false);
|
||
Time.timeScale = 1f;
|
||
yield return new WaitForSecondsRealtime(0.25f);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator Cyclone_RendererFollowsPlayerBodyWhileItsHitCenterMoves()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
director.enabled = false;
|
||
director.DebugSpawnImmediate(2);
|
||
yield return null;
|
||
|
||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||
Rigidbody2D playerBody = player.GetComponent<Rigidbody2D>();
|
||
ActiveArtifactController artifacts =
|
||
player.GetComponent<ActiveArtifactController>();
|
||
SetPlayerDirection(player, Vector2.right);
|
||
playerBody.position = new Vector2(1.25f, -0.75f);
|
||
EnemyController[] enemies = Object.FindObjectsByType<EnemyController>(
|
||
FindObjectsSortMode.InstanceID);
|
||
Assert.That(enemies.Length, Is.GreaterThanOrEqualTo(2));
|
||
|
||
ActiveArtifactDefinition definition = SelectArtifact(
|
||
artifacts,
|
||
ActiveArtifactEffect.Cyclone);
|
||
PlaceEnemy(enemies[0], playerBody.position + Vector2.right * 0.3f);
|
||
PlaceEnemy(enemies[1], playerBody.position + Vector2.up * 0.3f);
|
||
AddOverlappingCollider(enemies[1]);
|
||
Physics2D.SyncTransforms();
|
||
EnsureGauge(artifacts, definition.NormalGaugeCost);
|
||
|
||
List<CombatHitResult> hits = new();
|
||
System.Action<CombatHitResult> handler = result =>
|
||
{
|
||
if (result.SourceId == definition.ArtifactId)
|
||
{
|
||
hits.Add(result);
|
||
}
|
||
};
|
||
CombatEvents.OnValidHit += handler;
|
||
try
|
||
{
|
||
Assert.That(artifacts.TryUseCurrent(false), Is.True);
|
||
GameObject visual = GameObject.Find("Cyclone");
|
||
Assert.That(visual, Is.Not.Null);
|
||
Vector3 expectedShadowOffset = player.transform.TransformVector(
|
||
new Vector3(0f, -7.5f / 32f, 0f));
|
||
Assert.That(
|
||
Vector2.Distance(
|
||
(Vector2)visual.transform.position,
|
||
playerBody.position + (Vector2)expectedShadowOffset),
|
||
Is.LessThan(0.0001f));
|
||
Assert.That(visual.transform.parent, Is.Null);
|
||
Assert.That(
|
||
visual.GetComponentsInChildren<LineRenderer>(true),
|
||
Is.Empty);
|
||
SpriteRenderer[] cycloneRenderers =
|
||
visual.GetComponentsInChildren<SpriteRenderer>();
|
||
Assert.That(cycloneRenderers, Has.Length.EqualTo(2));
|
||
foreach (SpriteRenderer cycloneRenderer in cycloneRenderers)
|
||
{
|
||
AssertUniformScale(cycloneRenderer.transform.lossyScale, 1f);
|
||
}
|
||
SpriteRenderer normalBack = visual.transform.Find(
|
||
"Cyclone Orbit Back").GetComponent<SpriteRenderer>();
|
||
SpriteRenderer normalFront = visual.transform.Find(
|
||
"Cyclone Orbit Front").GetComponent<SpriteRenderer>();
|
||
YSortRenderer ySort = player.GetComponent<YSortRenderer>();
|
||
Assert.That(normalBack.sortingOrder,
|
||
Is.EqualTo(ySort.CalculateSortingOrder() - 1));
|
||
Assert.That(normalFront.sortingOrder,
|
||
Is.EqualTo(ySort.CalculateSortingOrder() + 1));
|
||
Assert.That(hits.Count, Is.EqualTo(2));
|
||
Assert.That(
|
||
hits.TrueForAll(
|
||
hit => hit.Target == enemies[0].gameObject
|
||
|| hit.Target == enemies[1].gameObject),
|
||
Is.True);
|
||
Assert.That(
|
||
hits.Exists(hit => hit.Target == enemies[0].gameObject),
|
||
Is.True);
|
||
Assert.That(
|
||
hits.Exists(hit => hit.Target == enemies[1].gameObject),
|
||
Is.True);
|
||
|
||
Time.timeScale = 1f;
|
||
playerBody.position += new Vector2(0f, 0.4f);
|
||
Physics2D.SyncTransforms();
|
||
yield return new WaitForFixedUpdate();
|
||
Assert.That(visual, Is.Not.Null);
|
||
Assert.That(
|
||
Vector2.Distance(
|
||
(Vector2)visual.transform.position,
|
||
playerBody.position + (Vector2)expectedShadowOffset),
|
||
Is.LessThan(0.0001f));
|
||
Assert.That(normalBack.sortingOrder,
|
||
Is.EqualTo(ySort.CalculateSortingOrder() - 1));
|
||
Assert.That(normalFront.sortingOrder,
|
||
Is.EqualTo(ySort.CalculateSortingOrder() + 1));
|
||
|
||
float timeout = 1.5f;
|
||
while (artifacts.IsExecutingArtifact && timeout > 0f)
|
||
{
|
||
timeout -= Time.unscaledDeltaTime;
|
||
yield return null;
|
||
}
|
||
Assert.That(timeout, Is.GreaterThan(0f));
|
||
}
|
||
finally
|
||
{
|
||
CombatEvents.OnValidHit -= handler;
|
||
}
|
||
yield return null;
|
||
Assert.That(GameObject.Find("Cyclone"), Is.Null);
|
||
Assert.That(GameObject.Find("Cyclone Orbit Back"), Is.Null);
|
||
Assert.That(GameObject.Find("Cyclone Orbit Front"), Is.Null);
|
||
|
||
EnsureGauge(artifacts, definition.ChargedGaugeCost);
|
||
Assert.That(artifacts.TryUseCurrent(true), Is.True);
|
||
GameObject chargedVisual = GameObject.Find("Charged Cyclone");
|
||
Assert.That(chargedVisual, Is.Not.Null);
|
||
Assert.That(
|
||
chargedVisual.GetComponentsInChildren<LineRenderer>(true),
|
||
Is.Empty);
|
||
SpriteRenderer[] chargedRenderers =
|
||
chargedVisual.GetComponentsInChildren<SpriteRenderer>();
|
||
Assert.That(chargedRenderers, Has.Length.EqualTo(2));
|
||
foreach (SpriteRenderer chargedRenderer in chargedRenderers)
|
||
{
|
||
AssertUniformScale(chargedRenderer.transform.lossyScale, 1f);
|
||
}
|
||
yield return new WaitForSecondsRealtime(0.7f);
|
||
Assert.That(GameObject.Find("Charged Cyclone"), Is.Null);
|
||
Assert.That(GameObject.Find("Cyclone Orbit Back"), Is.Null);
|
||
Assert.That(GameObject.Find("Cyclone Orbit Front"), Is.Null);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator ThunderCrash_RendererAndFixedFrontHitCenterRejectOutsideBody()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
director.enabled = false;
|
||
director.DebugSpawnImmediate(2);
|
||
yield return null;
|
||
|
||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||
Rigidbody2D playerBody = player.GetComponent<Rigidbody2D>();
|
||
ActiveArtifactController artifacts =
|
||
player.GetComponent<ActiveArtifactController>();
|
||
SetPlayerDirection(player, Vector2.right);
|
||
playerBody.position = new Vector2(1.25f, -0.75f);
|
||
Vector2 playerGroundCenter = playerBody.position
|
||
+ (Vector2)player.transform.TransformVector(
|
||
new Vector2(0f, -0.1f));
|
||
EnemyController[] enemies = Object.FindObjectsByType<EnemyController>(
|
||
FindObjectsSortMode.InstanceID);
|
||
Assert.That(enemies.Length, Is.GreaterThanOrEqualTo(2));
|
||
|
||
ActiveArtifactDefinition definition = SelectArtifact(
|
||
artifacts,
|
||
ActiveArtifactEffect.ThunderCrash);
|
||
float chargedVisualScale = 1.05f * definition.ChargedRange;
|
||
Vector2 chargedHitCenter = playerGroundCenter
|
||
+ Vector2.right * 0.45f;
|
||
Vector2 effectCenter = chargedHitCenter
|
||
- new Vector2(-0.5f, 0.5f) / 32f * chargedVisualScale;
|
||
PlaceEnemy(enemies[0], chargedHitCenter + Vector2.right * 0.5f);
|
||
float chargedHitRadiusY = 7.25f / 32f * chargedVisualScale;
|
||
PlaceEnemy(
|
||
enemies[1],
|
||
chargedHitCenter
|
||
+ Vector2.up * (chargedHitRadiusY + 2f));
|
||
SetEnemyHealth(enemies[0], 1000f);
|
||
AddOverlappingCollider(enemies[1]);
|
||
Physics2D.SyncTransforms();
|
||
EnsureGauge(artifacts, definition.ChargedGaugeCost);
|
||
|
||
List<CombatHitResult> hits = new();
|
||
System.Action<CombatHitResult> handler = result =>
|
||
{
|
||
if (result.SourceId == definition.ArtifactId)
|
||
{
|
||
hits.Add(result);
|
||
}
|
||
};
|
||
CombatEvents.OnValidHit += handler;
|
||
Assert.That(artifacts.TryUseCurrent(true), Is.True);
|
||
GameObject visual = GameObject.Find("Charged Thunder Crash");
|
||
Assert.That(visual, Is.Not.Null);
|
||
Assert.That(
|
||
Vector2.Distance((Vector2)visual.transform.position, effectCenter),
|
||
Is.LessThan(0.0001f));
|
||
Assert.That(
|
||
visual.GetComponentsInChildren<LineRenderer>(true),
|
||
Is.Empty);
|
||
SpriteRenderer[] thunderRenderers =
|
||
visual.GetComponentsInChildren<SpriteRenderer>(true);
|
||
Assert.That(thunderRenderers, Has.Length.EqualTo(1));
|
||
SpriteRenderer reachRenderer = System.Array.Find(
|
||
thunderRenderers,
|
||
renderer => renderer.transform.name == "Thunder Crash Sprite");
|
||
Assert.That(reachRenderer, Is.Not.Null);
|
||
Assert.That(reachRenderer.sprite, Is.Not.Null);
|
||
Assert.That(reachRenderer.transform.localScale.x * 17f / 32f,
|
||
Is.EqualTo(17f / 32f * chargedVisualScale).Within(0.0001f));
|
||
Assert.That(reachRenderer.transform.localScale.y * 7.25f / 32f,
|
||
Is.EqualTo(chargedHitRadiusY).Within(0.0001f));
|
||
Vector2 reachCenter = (Vector2)reachRenderer.transform.position
|
||
+ new Vector2(-0.5f, 0.5f) / 32f * reachRenderer.transform.localScale.y;
|
||
Assert.That(Vector2.Distance(reachCenter, chargedHitCenter),
|
||
Is.LessThan(0.0001f));
|
||
SpriteRenderer warlockRenderer = System.Array.Find(
|
||
thunderRenderers,
|
||
renderer => renderer.transform.name == "Thunder Crash Sprite");
|
||
Assert.That(warlockRenderer, Is.Not.Null);
|
||
foreach (var layer in thunderRenderers)
|
||
Assert.That(layer.transform.localPosition, Is.EqualTo(Vector3.zero));
|
||
AssertUniformScale(warlockRenderer.transform.localScale, chargedVisualScale);
|
||
Assert.That(warlockRenderer.sprite.pivot,
|
||
Is.EqualTo(new Vector2(50.5f, 42.5f)));
|
||
Assert.That(warlockRenderer.sprite.rect,
|
||
Is.EqualTo(new Rect(0f, 0f, 100f, 100f)));
|
||
Vector2 fixedVisualPosition = visual.transform.position;
|
||
playerBody.position += Vector2.down * 0.2f;
|
||
Assert.That((Vector2)visual.transform.position,
|
||
Is.EqualTo(fixedVisualPosition));
|
||
|
||
Time.timeScale = 1f;
|
||
float timeout = 2f;
|
||
while (artifacts.IsExecutingArtifact && timeout > 0f)
|
||
{
|
||
timeout -= Time.unscaledDeltaTime;
|
||
yield return null;
|
||
}
|
||
CombatEvents.OnValidHit -= handler;
|
||
Assert.That(timeout, Is.GreaterThan(0f));
|
||
Assert.That(hits.Count, Is.EqualTo(5));
|
||
Assert.That(hits.TrueForAll(hit => hit.Target == enemies[0].gameObject), Is.True);
|
||
|
||
EnsureGauge(artifacts, definition.NormalGaugeCost);
|
||
Assert.That(artifacts.TryUseCurrent(false), Is.True);
|
||
GameObject normalVisual = GameObject.Find("Thunder Crash");
|
||
Assert.That(normalVisual, Is.Not.Null);
|
||
Vector2 normalGroundCenter = playerBody.position
|
||
+ (Vector2)player.transform.TransformVector(
|
||
new Vector2(0f, -0.1f))
|
||
+ Vector2.right * 0.45f;
|
||
Vector2 normalCenter = normalGroundCenter
|
||
- new Vector2(-0.5f, 0.5f) / 32f
|
||
* (1.05f * definition.NormalRange);
|
||
Assert.That(
|
||
Vector2.Distance((Vector2)normalVisual.transform.position, normalCenter),
|
||
Is.LessThan(0.0001f));
|
||
Assert.That(
|
||
normalVisual.GetComponentsInChildren<LineRenderer>(true),
|
||
Is.Empty);
|
||
SpriteRenderer normalGroundRenderer = System.Array.Find(
|
||
normalVisual.GetComponentsInChildren<SpriteRenderer>(true),
|
||
renderer => renderer.transform.name == "Thunder Crash Sprite");
|
||
Assert.That(normalGroundRenderer, Is.Not.Null);
|
||
AssertUniformScale(
|
||
normalGroundRenderer.transform.localScale,
|
||
1.05f * definition.NormalRange);
|
||
Assert.That(normalGroundRenderer.sprite.pivot,
|
||
Is.EqualTo(new Vector2(50.5f, 42.5f)));
|
||
yield return new WaitForSecondsRealtime(0.4f);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator RunHUD_UsesScorchingRayForHitDebugLabelAndPopup()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
director.enabled = false;
|
||
director.DebugSpawnImmediate(1);
|
||
yield return null;
|
||
|
||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||
Rigidbody2D playerBody = player.GetComponent<Rigidbody2D>();
|
||
ActiveArtifactController artifacts =
|
||
player.GetComponent<ActiveArtifactController>();
|
||
EnemyController enemy = Object.FindAnyObjectByType<EnemyController>();
|
||
enemy.enabled = false;
|
||
playerBody.position = new Vector2(1.25f, -0.75f);
|
||
enemy.GetComponent<Rigidbody2D>().position = playerBody.position
|
||
+ Vector2.right * 1.5f;
|
||
Physics2D.SyncTransforms();
|
||
|
||
ActiveArtifactDefinition definition = SelectArtifact(
|
||
artifacts,
|
||
ActiveArtifactEffect.Phoenix);
|
||
EnsureGauge(artifacts, definition.NormalGaugeCost);
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
Assert.That(hud, Is.Not.Null);
|
||
|
||
Assert.That(artifacts.TryUseCurrent(false), Is.True);
|
||
Text hitDebugText = (Text)typeof(RunHUD)
|
||
.GetField("hitDebugText", BindingFlags.Instance | BindingFlags.NonPublic)
|
||
.GetValue(hud);
|
||
Assert.That(hitDebugText.text, Is.EqualTo("ARTIFACT 잿불의 마검 35"));
|
||
|
||
Text[] texts = Object.FindObjectsByType<Text>(
|
||
FindObjectsInactive.Include,
|
||
FindObjectsSortMode.None);
|
||
Assert.That(
|
||
System.Array.Exists(
|
||
texts,
|
||
text => text != hitDebugText
|
||
&& text.gameObject.activeInHierarchy
|
||
&& text.text == "잿불의 마검 35"),
|
||
Is.True);
|
||
yield return new WaitForSecondsRealtime(0.2f);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator PresentationUi_UsesSharedSlicesIconsFontAndGameOverStyle()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
Assert.That(hud, Is.Not.Null);
|
||
SpriteRenderer arenaRenderer = GameObject.Find("Arena")
|
||
.GetComponent<SpriteRenderer>();
|
||
Assert.That(arenaRenderer.sharedMaterial, Is.Not.Null);
|
||
Assert.That(
|
||
arenaRenderer.sharedMaterial.shader.name,
|
||
Is.EqualTo("Universal Render Pipeline/2D/Sprite-Unlit-Default"));
|
||
Image healthFill = (Image)typeof(RunHUD)
|
||
.GetField("healthFill", BindingFlags.Instance | BindingFlags.NonPublic)
|
||
.GetValue(hud);
|
||
Text healthText = (Text)typeof(RunHUD)
|
||
.GetField("healthText", BindingFlags.Instance | BindingFlags.NonPublic)
|
||
.GetValue(hud);
|
||
Font galmuri = Resources.Load<Font>("Presentation/Fonts/Galmuri9");
|
||
Assert.That(galmuri, Is.Not.Null);
|
||
Assert.That(
|
||
Resources.Load<Font>("Presentation/Fonts/neodgm"),
|
||
Is.Not.Null);
|
||
Assert.That(
|
||
Resources.Load<Font>("Presentation/Fonts/NanumGothic-Regular"),
|
||
Is.Not.Null);
|
||
Assert.That(healthText.font, Is.Not.Null);
|
||
Assert.That(healthText.font, Is.SameAs(galmuri));
|
||
Assert.That(healthFill.sprite, Is.Not.Null);
|
||
Assert.That(healthFill.type, Is.EqualTo(Image.Type.Filled));
|
||
Assert.That(
|
||
healthFill.transform.parent.GetComponent<Image>().type,
|
||
Is.EqualTo(Image.Type.Sliced));
|
||
|
||
GameObject artifactHud = GameObject.Find("Active Artifact HUD");
|
||
Assert.That(artifactHud, Is.Not.Null);
|
||
Assert.That(
|
||
artifactHud.GetComponent<Image>().type,
|
||
Is.EqualTo(Image.Type.Sliced));
|
||
Image slotOne = GameObject.Find("Artifact Slot 1")
|
||
.GetComponent<Image>();
|
||
Image slotTwo = GameObject.Find("Artifact Slot 2")
|
||
.GetComponent<Image>();
|
||
Image slotThree = GameObject.Find("Artifact Slot 3")
|
||
.GetComponent<Image>();
|
||
Assert.That(slotOne.sprite, Is.Not.Null);
|
||
Assert.That(slotTwo.sprite, Is.Not.Null);
|
||
Assert.That(slotThree.sprite, Is.Not.Null);
|
||
Assert.That(slotOne.type, Is.EqualTo(Image.Type.Sliced));
|
||
Assert.That(slotTwo.type, Is.EqualTo(Image.Type.Sliced));
|
||
Assert.That(slotThree.type, Is.EqualTo(Image.Type.Sliced));
|
||
Image[] slotImages = slotOne.GetComponentsInChildren<Image>(true);
|
||
Assert.That(slotImages.Length, Is.GreaterThan(1));
|
||
Assert.That(slotImages[1].sprite, Is.Not.Null);
|
||
|
||
ExperienceSystem experience =
|
||
Object.FindAnyObjectByType<ExperienceSystem>();
|
||
experience.AddExperience(experience.RequiredExperience);
|
||
yield return null;
|
||
GameObject levelPanel = GameObject.Find("Level Up Selection");
|
||
Assert.That(levelPanel.activeSelf, Is.True);
|
||
Assert.That(levelPanel.GetComponent<Image>().type, Is.EqualTo(Image.Type.Sliced));
|
||
for (int i = 1; i <= 3; i++)
|
||
{
|
||
Image card = GameObject.Find($"Option Card {i}")
|
||
.GetComponent<Image>();
|
||
Text optionText = GameObject.Find($"Option {i}")
|
||
.GetComponent<Text>();
|
||
RectTransform cardRect = card.rectTransform;
|
||
RectTransform textRect = optionText.rectTransform;
|
||
Assert.That(card.sprite, Is.Not.Null);
|
||
Assert.That(card.type, Is.EqualTo(Image.Type.Sliced));
|
||
Assert.That(card.raycastTarget, Is.False);
|
||
Assert.That(card.preserveAspect, Is.False);
|
||
Assert.That(cardRect.anchorMin, Is.EqualTo(textRect.anchorMin));
|
||
Assert.That(cardRect.anchorMax, Is.EqualTo(textRect.anchorMax));
|
||
Assert.That(cardRect.pivot, Is.EqualTo(textRect.pivot));
|
||
Assert.That(cardRect.anchoredPosition,
|
||
Is.EqualTo(textRect.anchoredPosition));
|
||
Assert.That(cardRect.sizeDelta, Is.EqualTo(textRect.sizeDelta));
|
||
Assert.That(card.transform.GetSiblingIndex(),
|
||
Is.EqualTo(optionText.transform.GetSiblingIndex() - 1));
|
||
Assert.That(optionText.raycastTarget, Is.False);
|
||
}
|
||
Text levelTitle = levelPanel.transform.Find("Title").GetComponent<Text>();
|
||
Assert.That(levelTitle.resizeTextForBestFit, Is.True);
|
||
Assert.That(levelTitle.resizeTextMinSize, Is.EqualTo(24));
|
||
Assert.That(levelTitle.resizeTextMaxSize, Is.EqualTo(38));
|
||
Assert.That(levelTitle.horizontalOverflow, Is.EqualTo(HorizontalWrapMode.Overflow));
|
||
Assert.That(levelTitle.verticalOverflow, Is.EqualTo(VerticalWrapMode.Truncate));
|
||
|
||
levelPanel.SetActive(false);
|
||
RunManager.Instance.SetSelectionOpen(false);
|
||
RunManager.Instance.EndRun();
|
||
yield return null;
|
||
GameObject gameOver = GameObject.Find("Game Over");
|
||
Assert.That(gameOver.activeSelf, Is.True);
|
||
Assert.That(gameOver.GetComponent<Image>().type, Is.EqualTo(Image.Type.Sliced));
|
||
Assert.That(gameOver.GetComponent<Image>().sprite, Is.Not.Null);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator RunHUD_ReferenceCanvasLayoutBoundsAreSafeFor1280And1920()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
|
||
Canvas canvas = Object.FindAnyObjectByType<Canvas>();
|
||
CanvasScaler scaler = canvas.GetComponent<CanvasScaler>();
|
||
Assert.That(scaler.referenceResolution, Is.EqualTo(new Vector2(1920f, 1080f)));
|
||
|
||
RectTransform status = GameObject.Find("Run Status HUD")
|
||
.GetComponent<RectTransform>();
|
||
RectTransform artifact = GameObject.Find("Active Artifact HUD")
|
||
.GetComponent<RectTransform>();
|
||
RectTransform timer = GameObject.Find("Timer Panel")
|
||
.GetComponent<RectTransform>();
|
||
RectTransform debug = GameObject.Find("Debug Event Spawn Buttons")
|
||
.GetComponent<RectTransform>();
|
||
RectTransform minimap = GameObject.Find("Minimap")
|
||
.GetComponent<RectTransform>();
|
||
RectTransform minimapMap = GameObject.Find("Map")
|
||
.GetComponent<RectTransform>();
|
||
RectTransform minimapViewport = GameObject.Find("Minimap Camera Viewport")
|
||
.GetComponent<RectTransform>();
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
Text eventText = GetPrivateField<Text>(hud, "eventText");
|
||
Image artifactGaugeFill = GetPrivateField<Image>(hud, "artifactGaugeFill");
|
||
Text artifactName = GetPrivateField<Text>(hud, "artifactNameText");
|
||
Text healthText = GetPrivateField<Text>(hud, "healthText");
|
||
Text levelText = GetPrivateField<Text>(hud, "levelText");
|
||
Text artifactGaugeText = GetPrivateField<Text>(hud, "artifactGaugeText");
|
||
Text artifactState = GetPrivateField<Text>(hud, "artifactStateText");
|
||
Image guardTrack = GetPrivateField<Image>(hud, "guardGaugeTrack");
|
||
Image guardFill = GetPrivateField<Image>(hud, "guardGaugeFill");
|
||
Text guardLabel = GetPrivateField<Text>(hud, "guardLabelText");
|
||
RectTransform gauge = artifactGaugeFill.transform.parent.GetComponent<RectTransform>();
|
||
Image healthPanelImage = GetPrivateField<Image>(hud, "healthFill")
|
||
.transform.parent.GetComponent<Image>();
|
||
Image experiencePanelImage = GetPrivateField<Image>(hud, "experienceFill")
|
||
.transform.parent.GetComponent<Image>();
|
||
Image healthFill = GetPrivateField<Image>(hud, "healthFill");
|
||
Image experienceFill = GetPrivateField<Image>(hud, "experienceFill");
|
||
RectTransform healthPanel = healthFill.transform.parent
|
||
.GetComponent<RectTransform>();
|
||
RectTransform experiencePanel = experienceFill.transform.parent
|
||
.GetComponent<RectTransform>();
|
||
|
||
AssertRect(status, Vector2.zero, Vector2.zero, new Vector2(24f, 24f),
|
||
new Vector2(392f, 136f));
|
||
AssertRect(artifact, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
|
||
new Vector2(-16f, 0f), new Vector2(360f, 136f));
|
||
Assert.That(artifact.parent, Is.SameAs(status.transform));
|
||
AssertRect(timer, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f),
|
||
new Vector2(0f, -24f), new Vector2(180f, 66f));
|
||
AssertRect(debug, new Vector2(0f, 1f), new Vector2(0f, 1f),
|
||
new Vector2(24f, -24f),
|
||
new Vector2(240f, 144f));
|
||
AssertRect(minimap, Vector2.one, Vector2.one, new Vector2(-24f, -24f),
|
||
new Vector2(300f, 162f));
|
||
AssertRect(minimapMap, new Vector2(0.5f, 0.5f),
|
||
new Vector2(0.5f, 0.5f), Vector2.zero,
|
||
new Vector2(288f, 150f));
|
||
Assert.That(GameObject.Find("Minimap/Title"), Is.Null);
|
||
Assert.That(minimap.GetComponent<Image>().fillCenter, Is.False);
|
||
Image minimapMapImage = minimapMap.GetComponent<Image>();
|
||
Assert.That(minimapMapImage.sprite, Is.Null);
|
||
Assert.That(minimapMapImage.color.a, Is.EqualTo(0.28f).Within(0.001f));
|
||
Assert.That(minimapMap.GetComponent<RectMask2D>(), Is.Not.Null);
|
||
Rect mapWorldRect = GetWorldRect(minimapMap);
|
||
Rect viewportWorldRect = GetWorldRect(minimapViewport);
|
||
Assert.That(viewportWorldRect.xMin, Is.GreaterThanOrEqualTo(mapWorldRect.xMin - 0.01f));
|
||
Assert.That(viewportWorldRect.xMax, Is.LessThanOrEqualTo(mapWorldRect.xMax + 0.01f));
|
||
Assert.That(viewportWorldRect.yMin, Is.GreaterThanOrEqualTo(mapWorldRect.yMin - 0.01f));
|
||
Assert.That(viewportWorldRect.yMax, Is.LessThanOrEqualTo(mapWorldRect.yMax + 0.01f));
|
||
AssertRect(eventText.rectTransform, new Vector2(0.5f, 1f),
|
||
new Vector2(0.5f, 1f), new Vector2(0f, -100f),
|
||
new Vector2(420f, 28f));
|
||
Assert.That(eventText.gameObject.activeSelf, Is.True);
|
||
Assert.That(eventText.text, Does.Contain("다음"));
|
||
Assert.That(debug.gameObject.activeSelf, Is.True);
|
||
AssertRect(gauge, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
|
||
new Vector2(0f, -24f), new Vector2(352f, 18f));
|
||
AssertRect(healthText.rectTransform,
|
||
new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
|
||
new Vector2(78f, 48f), new Vector2(172f, 32f));
|
||
Assert.That(healthText.transform.parent, Is.SameAs(status.transform));
|
||
AssertRect(healthPanel,
|
||
new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
|
||
new Vector2(-16f, -7f), new Vector2(352f, 12f));
|
||
AssertRect(experiencePanel,
|
||
new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
|
||
new Vector2(-16f, -39f), new Vector2(352f, 8f));
|
||
AssertRect(levelText.rectTransform,
|
||
new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
|
||
Vector2.zero, new Vector2(352f, 24f));
|
||
Assert.That(levelText.transform.parent, Is.SameAs(experiencePanel));
|
||
AssertRect(artifactGaugeText.rectTransform,
|
||
new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
|
||
Vector2.zero, new Vector2(352f, 26f));
|
||
Assert.That(artifactName.transform.parent, Is.SameAs(gauge.transform));
|
||
Assert.That(artifactName.gameObject.activeSelf, Is.False);
|
||
Assert.That(artifactState.rectTransform.anchoredPosition,
|
||
Is.EqualTo(new Vector2(116f, 21f)));
|
||
Assert.That(artifactState.resizeTextForBestFit, Is.True);
|
||
Assert.That(artifactState.resizeTextMinSize, Is.EqualTo(11));
|
||
Assert.That(artifactState.resizeTextMaxSize, Is.EqualTo(18));
|
||
Assert.That(healthPanelImage.enabled, Is.False);
|
||
Assert.That(experiencePanelImage.enabled, Is.False);
|
||
Assert.That(healthFill.rectTransform.rect.width, Is.EqualTo(352f));
|
||
Assert.That(experienceFill.rectTransform.rect.width, Is.EqualTo(352f));
|
||
Assert.That(experienceFill.rectTransform.rect.height, Is.EqualTo(8f));
|
||
AssertRect(guardTrack.rectTransform,
|
||
new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
|
||
new Vector2(182f, -3f), new Vector2(18f, 94f));
|
||
AssertRect(guardLabel.rectTransform,
|
||
new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
|
||
new Vector2(182f, 56f), new Vector2(32f, 20f));
|
||
Assert.That(guardTrack.pixelsPerUnitMultiplier, Is.EqualTo(5f));
|
||
Assert.That(guardFill.fillMethod, Is.EqualTo(Image.FillMethod.Vertical));
|
||
Assert.That(guardFill.fillOrigin, Is.EqualTo(0));
|
||
Assert.That(guardFill.rectTransform.offsetMin,
|
||
Is.EqualTo(new Vector2(2f, 2f)));
|
||
Assert.That(guardFill.rectTransform.offsetMax,
|
||
Is.EqualTo(new Vector2(-2f, -2f)));
|
||
Assert.That(guardFill.fillAmount, Is.EqualTo(1f).Within(0.001f));
|
||
Assert.That((Color32)guardFill.color, Is.EqualTo(new Color32(255, 205, 92, 255)));
|
||
Assert.That(guardLabel.text, Is.EqualTo("D"));
|
||
Assert.That(guardTrack.transform.parent, Is.SameAs(status.transform));
|
||
Assert.That(guardFill.transform.parent, Is.SameAs(guardTrack.transform));
|
||
Assert.That(RectanglesOverlap(status, artifact), Is.True);
|
||
Assert.That(RectanglesOverlap(timer, eventText.rectTransform), Is.False);
|
||
|
||
int previousWidth = Screen.width;
|
||
int previousHeight = Screen.height;
|
||
try
|
||
{
|
||
int[,] resolutions = { { 1280, 720 }, { 1920, 1080 } };
|
||
for (int i = 0; i < resolutions.GetLength(0); i++)
|
||
{
|
||
Screen.SetResolution(
|
||
resolutions[i, 0],
|
||
resolutions[i, 1],
|
||
FullScreenMode.Windowed);
|
||
yield return null;
|
||
Canvas.ForceUpdateCanvases();
|
||
AssertRectInsideCanvas(status, canvas.GetComponent<RectTransform>());
|
||
AssertRectInsideCanvas(artifact, canvas.GetComponent<RectTransform>());
|
||
AssertRectInsideCanvas(timer, canvas.GetComponent<RectTransform>());
|
||
AssertRectInsideCanvas(debug, canvas.GetComponent<RectTransform>());
|
||
AssertRectInsideCanvas(minimap, canvas.GetComponent<RectTransform>());
|
||
AssertRectInsideCanvas(eventText.rectTransform,
|
||
canvas.GetComponent<RectTransform>());
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
if (previousWidth > 0 && previousHeight > 0)
|
||
{
|
||
Screen.SetResolution(
|
||
previousWidth,
|
||
previousHeight,
|
||
FullScreenMode.Windowed);
|
||
}
|
||
}
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator PixelPerfectCamera_PixelSnappingUsesEvenNativeViewportAtOddTarget()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
|
||
Camera camera = Camera.main;
|
||
Component pixelPerfect = camera.GetComponent(
|
||
"UnityEngine.Rendering.Universal.PixelPerfectCamera");
|
||
Assert.That(pixelPerfect, Is.Not.Null);
|
||
PropertyInfo gridSnapping = pixelPerfect.GetType()
|
||
.GetProperty("gridSnapping");
|
||
Assert.That(
|
||
gridSnapping.GetValue(pixelPerfect).ToString(),
|
||
Is.EqualTo("PixelSnapping"));
|
||
|
||
RenderTexture target = new(1399, 799, 24, RenderTextureFormat.ARGB32);
|
||
target.Create();
|
||
RenderTexture previousTarget = camera.targetTexture;
|
||
camera.targetTexture = target;
|
||
try
|
||
{
|
||
camera.Render();
|
||
yield return null;
|
||
|
||
Rect pixelRect = camera.pixelRect;
|
||
Assert.That(pixelRect.width, Is.EqualTo(1398f).Within(0.01f));
|
||
Assert.That(pixelRect.height, Is.EqualTo(798f).Within(0.01f));
|
||
Assert.That(pixelRect.x, Is.EqualTo(0f).Within(0.01f));
|
||
Assert.That(pixelRect.y, Is.EqualTo(0f).Within(0.01f));
|
||
Assert.That(pixelRect.width % 2f, Is.EqualTo(0f).Within(0.01f));
|
||
Assert.That(pixelRect.height % 2f, Is.EqualTo(0f).Within(0.01f));
|
||
Assert.That(camera.aspect, Is.EqualTo(1398f / 798f).Within(0.001f));
|
||
Assert.That(camera.orthographicSize, Is.EqualTo(6.234375f).Within(0.01f));
|
||
|
||
FieldInfo internalField = pixelPerfect.GetType().GetField(
|
||
"m_Internal",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
object internalCamera = internalField.GetValue(pixelPerfect);
|
||
FieldInfo widthField = internalCamera.GetType().GetField(
|
||
"offscreenRTWidth",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
FieldInfo heightField = internalCamera.GetType().GetField(
|
||
"offscreenRTHeight",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
FieldInfo useOffscreenField = internalCamera.GetType().GetField(
|
||
"useOffscreenRT",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
Assert.That(widthField.GetValue(internalCamera), Is.EqualTo(0));
|
||
Assert.That(heightField.GetValue(internalCamera), Is.EqualTo(0));
|
||
Assert.That(useOffscreenField.GetValue(internalCamera), Is.False);
|
||
|
||
float correctedOrthoSize = camera.orthographicSize;
|
||
camera.Render();
|
||
Assert.That(camera.pixelRect.width, Is.EqualTo(1398f).Within(0.01f));
|
||
Assert.That(camera.pixelRect.height, Is.EqualTo(798f).Within(0.01f));
|
||
Assert.That(camera.orthographicSize,
|
||
Is.EqualTo(correctedOrthoSize).Within(0.01f));
|
||
|
||
ArenaCameraFollow follow = camera.GetComponent<ArenaCameraFollow>();
|
||
bool previousFollowEnabled = follow.enabled;
|
||
follow.enabled = false;
|
||
camera.Render();
|
||
follow.enabled = previousFollowEnabled;
|
||
camera.Render();
|
||
Assert.That(camera.pixelRect.width, Is.EqualTo(1398f).Within(0.01f));
|
||
Assert.That(camera.pixelRect.height, Is.EqualTo(798f).Within(0.01f));
|
||
}
|
||
finally
|
||
{
|
||
camera.targetTexture = previousTarget;
|
||
target.Release();
|
||
Object.Destroy(target);
|
||
}
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator RunHUD_MinimapTracksPlayerAndEventEnemies()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||
ArenaBounds arena = ArenaBounds.Resolve();
|
||
Assert.That(hud, Is.Not.Null);
|
||
Assert.That(arena, Is.Not.Null);
|
||
|
||
director.enabled = false;
|
||
EnemyController[] existingEnemies = Object.FindObjectsByType<EnemyController>(
|
||
FindObjectsSortMode.None);
|
||
for (int i = 0; i < existingEnemies.Length; i++)
|
||
{
|
||
existingEnemies[i].gameObject.SetActive(false);
|
||
}
|
||
RunManager.Instance.SetSelectionOpen(false);
|
||
yield return null;
|
||
director.DebugSpawnImmediate(1);
|
||
Assert.That(director.TryDebugSpawnEventEnemy(RunTimedEvent.Elite), Is.True);
|
||
yield return null;
|
||
Time.timeScale = 0f;
|
||
yield return new WaitForSecondsRealtime(0.2f);
|
||
|
||
GameObject minimap = GameObject.Find("Minimap");
|
||
Image playerMarker = GameObject.Find("Minimap Player")
|
||
.GetComponent<Image>();
|
||
Transform markerRoot = GameObject.Find("Markers").transform;
|
||
Image eventMarker = null;
|
||
Image[] markerImages = markerRoot.GetComponentsInChildren<Image>(true);
|
||
for (int i = 0; i < markerImages.Length; i++)
|
||
{
|
||
if (((Color32)markerImages[i].color).Equals(
|
||
new Color32(255, 220, 107, 255)))
|
||
{
|
||
eventMarker = markerImages[i];
|
||
break;
|
||
}
|
||
}
|
||
RectTransform viewport = GameObject.Find("Minimap Camera Viewport")
|
||
.GetComponent<RectTransform>();
|
||
Assert.That(minimap.activeSelf, Is.True);
|
||
Assert.That(
|
||
((Color32)playerMarker.color).Equals(
|
||
new Color32(106, 236, 255, 255)),
|
||
Is.True);
|
||
Assert.That(eventMarker, Is.Not.Null);
|
||
Assert.That(
|
||
((Color32)eventMarker.color).Equals(
|
||
new Color32(255, 220, 107, 255)),
|
||
Is.True);
|
||
Assert.That(eventMarker.rectTransform.rect.width, Is.EqualTo(10f));
|
||
Assert.That(viewport.rect.width, Is.GreaterThan(0f));
|
||
Assert.That(viewport.rect.height, Is.GreaterThan(0f));
|
||
|
||
Vector2 oldMarkerPosition = playerMarker.rectTransform.anchoredPosition;
|
||
Vector2 movedPosition = arena.ClampPlayerPosition(new Vector2(6f, 2f));
|
||
player.GetComponent<Rigidbody2D>().position = movedPosition;
|
||
player.transform.position = movedPosition;
|
||
Physics2D.SyncTransforms();
|
||
yield return new WaitForSecondsRealtime(0.1f);
|
||
Assert.That(
|
||
playerMarker.rectTransform.anchoredPosition,
|
||
Is.Not.EqualTo(oldMarkerPosition));
|
||
Assert.That(
|
||
playerMarker.rectTransform.anchoredPosition.x,
|
||
Is.InRange(-144f, 144f));
|
||
Assert.That(
|
||
playerMarker.rectTransform.anchoredPosition.y,
|
||
Is.InRange(-75f, 75f));
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator RunHUD_ArtifactColorSwitchPreservesGaugeAndGuardReadiness()
|
||
{
|
||
RunManager.ForceProductionModeForTests = true;
|
||
ArtifactRewardController.GrantCatalogForTests = false;
|
||
yield return LoadCombatPrototype();
|
||
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
ActiveArtifactController artifacts =
|
||
Object.FindAnyObjectByType<ActiveArtifactController>();
|
||
Image artifactFill = GetPrivateField<Image>(hud, "artifactGaugeFill");
|
||
Image guardFill = GetPrivateField<Image>(hud, "guardGaugeFill");
|
||
Assert.That(artifacts.TryAddArtifact(artifacts.GetCatalogArtifactAt(0)), Is.True);
|
||
Assert.That(artifacts.TryAddArtifact(artifacts.GetCatalogArtifactAt(1)), Is.True);
|
||
Assert.That(artifacts.TryAddArtifact(artifacts.GetCatalogArtifactAt(4)), Is.True);
|
||
EnsureGauge(artifacts, 12f);
|
||
float gaugeBefore = artifacts.CurrentGauge;
|
||
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
yield return null;
|
||
Assert.That(artifactFill.color, Is.EqualTo(artifacts.CurrentArtifact.EffectColor));
|
||
Assert.That(artifacts.CurrentGauge, Is.EqualTo(gaugeBefore));
|
||
Assert.That(guardFill.fillAmount, Is.EqualTo(1f).Within(0.001f));
|
||
Assert.That(guardFill.fillMethod, Is.EqualTo(Image.FillMethod.Vertical));
|
||
Assert.That(guardFill.fillOrigin, Is.EqualTo(0));
|
||
if (i < 2)
|
||
{
|
||
Assert.That(artifacts.SelectNext(), Is.True);
|
||
}
|
||
}
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator RunHUD_GuardGaugeTracksActivationAndCooldownIndependently()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
PlayerHealth health = Object.FindAnyObjectByType<PlayerHealth>();
|
||
Image guardFill = GetPrivateField<Image>(hud, "guardGaugeFill");
|
||
Text guardLabel = GetPrivateField<Text>(hud, "guardLabelText");
|
||
Assert.That(health, Is.Not.Null);
|
||
Assert.That(guardFill.fillAmount, Is.EqualTo(1f).Within(0.001f));
|
||
Assert.That(guardLabel.text, Is.EqualTo("D"));
|
||
Assert.That((Color32)guardFill.color, Is.EqualTo(new Color32(255, 205, 92, 255)));
|
||
|
||
SetPrivateField(health, "guardCooldownDuration", 0.8f);
|
||
SetPrivateField(health, "guardDuration", 0.1f);
|
||
Assert.That(health.TryActivateGuard(), Is.True);
|
||
yield return null;
|
||
Assert.That(health.IsGuarding, Is.True);
|
||
Assert.That(
|
||
guardFill.fillAmount,
|
||
Is.EqualTo(health.GuardReadinessNormalized).Within(0.02f));
|
||
Assert.That((Color32)guardFill.color, Is.EqualTo(new Color32(255, 232, 128, 255)));
|
||
Assert.That((Color32)guardLabel.color, Is.EqualTo(new Color32(255, 232, 128, 255)));
|
||
|
||
yield return new WaitForSecondsRealtime(0.4f);
|
||
Assert.That(health.IsGuarding, Is.False);
|
||
float cooldownFill = guardFill.fillAmount;
|
||
Assert.That(
|
||
cooldownFill,
|
||
Is.EqualTo(health.GuardReadinessNormalized).Within(0.02f));
|
||
Assert.That(cooldownFill, Is.EqualTo(0.5f).Within(0.15f));
|
||
Assert.That((Color32)guardFill.color, Is.EqualTo(new Color32(137, 129, 123, 255)));
|
||
Assert.That((Color32)guardLabel.color, Is.EqualTo(new Color32(137, 129, 123, 255)));
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator RunHUD_CompactClusterFadesForVisibleActorOverlap()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
RectTransform cluster = GameObject.Find("Run Status HUD")
|
||
.GetComponent<RectTransform>();
|
||
CanvasGroup group = cluster.GetComponent<CanvasGroup>();
|
||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||
Rigidbody2D body = player.GetComponent<Rigidbody2D>();
|
||
MethodInfo refresh = typeof(RunHUD).GetMethod(
|
||
"UpdateCompactHudVisibility",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
Assert.That(refresh, Is.Not.Null);
|
||
Assert.That(group.alpha, Is.EqualTo(1f));
|
||
|
||
Vector3[] corners = new Vector3[4];
|
||
cluster.GetWorldCorners(corners);
|
||
Vector3 screenCenter = (corners[0] + corners[2]) * 0.5f;
|
||
Vector3 worldCenter = Camera.main.ScreenToWorldPoint(
|
||
new Vector3(screenCenter.x, screenCenter.y, -Camera.main.transform.position.z));
|
||
player.enabled = false;
|
||
body.position = worldCenter;
|
||
Physics2D.SyncTransforms();
|
||
refresh.Invoke(hud, null);
|
||
Assert.That(group.alpha, Is.EqualTo(0.4f));
|
||
|
||
body.position = Vector2.zero;
|
||
Physics2D.SyncTransforms();
|
||
refresh.Invoke(hud, null);
|
||
Assert.That(group.alpha, Is.EqualTo(1f));
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator RunHUD_AllTextUsesGalmuriPixelFont()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
Font galmuri = Resources.Load<Font>("Presentation/Fonts/Galmuri9");
|
||
Assert.That(galmuri, Is.Not.Null);
|
||
Text health = GetPrivateField<Text>(hud, "healthText");
|
||
Text level = GetPrivateField<Text>(hud, "levelText");
|
||
Text timer = GetPrivateField<Text>(hud, "timerText");
|
||
Text enemyCount = GetPrivateField<Text>(hud, "enemyCountText");
|
||
Text gauge = GetPrivateField<Text>(hud, "artifactGaugeText");
|
||
Text state = GetPrivateField<Text>(hud, "artifactStateText");
|
||
Text name = GetPrivateField<Text>(hud, "artifactNameText");
|
||
|
||
Assert.That(health.font, Is.SameAs(galmuri));
|
||
Assert.That(level.font, Is.SameAs(galmuri));
|
||
Assert.That(timer.font, Is.SameAs(galmuri));
|
||
Assert.That(name.font, Is.SameAs(galmuri));
|
||
Assert.That(health.fontSize, Is.EqualTo(24));
|
||
Assert.That(level.fontSize, Is.EqualTo(18));
|
||
Assert.That(timer.fontSize, Is.EqualTo(30));
|
||
Assert.That(gauge.fontSize, Is.EqualTo(18));
|
||
Assert.That(gauge.rectTransform.rect.size, Is.EqualTo(new Vector2(352f, 26f)));
|
||
Assert.That(name.fontSize, Is.EqualTo(30));
|
||
Assert.That(enemyCount.font, Is.SameAs(galmuri));
|
||
Assert.That(state.font, Is.SameAs(galmuri));
|
||
Text damagePopupTemplate = GetPrivateField<Text>(hud, "damagePopupTemplate");
|
||
Assert.That(damagePopupTemplate.font, Is.SameAs(galmuri));
|
||
Assert.That(damagePopupTemplate.fontSize, Is.EqualTo(30));
|
||
Outline outline = damagePopupTemplate.GetComponent<Outline>();
|
||
Assert.That(outline, Is.Not.Null);
|
||
Assert.That(outline.effectDistance, Is.EqualTo(Vector2.one));
|
||
Assert.That(name.gameObject.activeSelf, Is.False);
|
||
Assert.That(name.horizontalOverflow, Is.EqualTo(HorizontalWrapMode.Overflow));
|
||
Assert.That(name.verticalOverflow, Is.EqualTo(VerticalWrapMode.Overflow));
|
||
|
||
Canvas.ForceUpdateCanvases();
|
||
Text[] compactTexts = { health, level, gauge, state };
|
||
foreach (Text compactText in compactTexts)
|
||
{
|
||
Assert.That(compactText.isActiveAndEnabled, Is.True);
|
||
Assert.That(compactText.text, Is.Not.EqualTo("준비"), compactText.name);
|
||
if (compactText == state && string.IsNullOrEmpty(state.text))
|
||
{
|
||
continue;
|
||
}
|
||
Assert.That(compactText.cachedTextGenerator.characterCountVisible,
|
||
Is.GreaterThan(0), compactText.name);
|
||
Assert.That(compactText.preferredHeight,
|
||
Is.LessThanOrEqualTo(compactText.rectTransform.rect.height + 1f),
|
||
compactText.name);
|
||
}
|
||
|
||
ActiveArtifactController artifacts =
|
||
Object.FindAnyObjectByType<ActiveArtifactController>();
|
||
ActiveArtifactDefinition longNameDefinition = SelectArtifact(
|
||
artifacts,
|
||
ActiveArtifactEffect.ChainLightning);
|
||
yield return null;
|
||
Canvas.ForceUpdateCanvases();
|
||
Assert.That(name.text, Is.EqualTo(longNameDefinition.DisplayName));
|
||
Assert.That(name.gameObject.activeSelf, Is.False);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator RunHUD_UpdatesZeroOneTwoAndThreeArtifactSlotsWithoutOverflow()
|
||
{
|
||
RunManager.ForceProductionModeForTests = true;
|
||
ArtifactRewardController.GrantCatalogForTests = false;
|
||
yield return LoadCombatPrototype();
|
||
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
ActiveArtifactController artifacts =
|
||
Object.FindAnyObjectByType<ActiveArtifactController>();
|
||
Image[] slotIcons = GetPrivateField<Image[]>(hud, "artifactSlotIcons");
|
||
Text name = GetPrivateField<Text>(hud, "artifactNameText");
|
||
Assert.That(slotIcons, Has.Length.EqualTo(3));
|
||
Assert.That(slotIcons[0].enabled, Is.False);
|
||
Assert.That(slotIcons[1].enabled, Is.False);
|
||
Assert.That(slotIcons[2].enabled, Is.False);
|
||
Assert.That(name.text, Is.EqualTo("EMPTY"));
|
||
|
||
ActiveArtifactDefinition first = artifacts.GetCatalogArtifactAt(0);
|
||
ActiveArtifactDefinition second = artifacts.GetCatalogArtifactAt(2);
|
||
ActiveArtifactDefinition third = artifacts.GetCatalogArtifactAt(4);
|
||
Assert.That(artifacts.TryAddArtifact(first), Is.True);
|
||
yield return null;
|
||
Assert.That(slotIcons[0].enabled, Is.True);
|
||
Assert.That(slotIcons[1].enabled, Is.False);
|
||
Assert.That(slotIcons[2].enabled, Is.False);
|
||
Assert.That(name.text, Is.EqualTo(first.DisplayName));
|
||
|
||
Assert.That(artifacts.TryAddArtifact(second), Is.True);
|
||
yield return null;
|
||
Assert.That(slotIcons[0].enabled, Is.True);
|
||
Assert.That(slotIcons[1].enabled, Is.True);
|
||
Assert.That(slotIcons[2].enabled, Is.False);
|
||
|
||
Assert.That(artifacts.TryAddArtifact(third), Is.True);
|
||
yield return null;
|
||
Assert.That(slotIcons[0].enabled, Is.True);
|
||
Assert.That(slotIcons[1].enabled, Is.True);
|
||
Assert.That(slotIcons[2].enabled, Is.True);
|
||
Assert.That(artifacts.SelectNext(), Is.True);
|
||
yield return null;
|
||
Assert.That(name.text, Is.EqualTo(second.DisplayName));
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator PlayerDamagePopup_ReportsActualLethalHealthLoss()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
director.enabled = false;
|
||
PlayerHealth health = Object.FindAnyObjectByType<PlayerHealth>();
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
Font galmuri = Resources.Load<Font>("Presentation/Fonts/Galmuri9");
|
||
float healthBefore = health.CurrentHealth;
|
||
float reportedDamage = -1f;
|
||
health.OnDamageTaken += damage => reportedDamage = damage;
|
||
|
||
Assert.That(
|
||
health.TryTakeDamage(
|
||
healthBefore + 25f,
|
||
Vector2.right,
|
||
0f),
|
||
Is.True);
|
||
Assert.That(reportedDamage, Is.EqualTo(healthBefore).Within(0.0001f));
|
||
Assert.That(health.CurrentHealth, Is.Zero);
|
||
yield return null;
|
||
|
||
Text popup = FindActiveDamagePopup();
|
||
Assert.That(popup, Is.Not.Null);
|
||
Assert.That(popup.text, Is.EqualTo($"-{Mathf.FloorToInt(healthBefore)}"));
|
||
Assert.That(popup.font, Is.SameAs(galmuri));
|
||
Assert.That(popup.fontSize, Is.EqualTo(30));
|
||
Assert.That(popup.color.r, Is.GreaterThan(popup.color.g));
|
||
Assert.That(
|
||
popup.rectTransform.position.y,
|
||
Is.GreaterThan(
|
||
Camera.main.WorldToScreenPoint(health.transform.position).y));
|
||
yield return new WaitForSecondsRealtime(0.7f);
|
||
Assert.That(GetPrivateField<int>(hud, "activeDamagePopupCount"), Is.Zero);
|
||
Assert.That(FindActiveDamagePopup(), Is.Null);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator PlayerDamagePopup_SkipsInvulnerabilityAndHealing()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
director.enabled = false;
|
||
PlayerHealth health = Object.FindAnyObjectByType<PlayerHealth>();
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
int eventCount = 0;
|
||
health.OnDamageTaken += _ => eventCount++;
|
||
|
||
health.GrantInvulnerability(10f);
|
||
Assert.That(
|
||
health.TryTakeDamage(10f, Vector2.right, 0f),
|
||
Is.False);
|
||
health.Heal(20f);
|
||
yield return new WaitForSecondsRealtime(0.1f);
|
||
|
||
Assert.That(eventCount, Is.Zero);
|
||
Assert.That(GetPrivateField<int>(hud, "activeDamagePopupCount"), Is.Zero);
|
||
Assert.That(FindActiveDamagePopup(), Is.Null);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator DamagePopupPool_UsesSharedCapAndReturnsAllEntries()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
director.enabled = false;
|
||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
Text popupTemplate = GetPrivateField<Text>(hud, "damagePopupTemplate");
|
||
Transform popupParent = popupTemplate.transform.parent;
|
||
CombatHitResult result = new(
|
||
player.gameObject,
|
||
player.gameObject,
|
||
false,
|
||
HitSide.Front,
|
||
4f,
|
||
Vector2.right,
|
||
0f,
|
||
player.transform.position,
|
||
showsHitFeedback: false);
|
||
|
||
for (int i = 0; i < 16; i++)
|
||
{
|
||
CombatEvents.RaiseValidHit(result);
|
||
}
|
||
Assert.That(
|
||
GetPrivateField<int>(hud, "activeDamagePopupCount"),
|
||
Is.EqualTo(12));
|
||
|
||
PlayerHealth health = Object.FindAnyObjectByType<PlayerHealth>();
|
||
Assert.That(health.TryTakeDamage(1f, Vector2.right, 0f), Is.True);
|
||
Assert.That(
|
||
GetPrivateField<int>(hud, "activeDamagePopupCount"),
|
||
Is.EqualTo(12));
|
||
yield return new WaitForSecondsRealtime(0.7f);
|
||
Assert.That(GetPrivateField<int>(hud, "activeDamagePopupCount"), Is.Zero);
|
||
Assert.That(FindActiveDamagePopup(), Is.Null);
|
||
int popupChildCountBeforeToggle = popupParent.childCount;
|
||
|
||
hud.gameObject.SetActive(false);
|
||
yield return null;
|
||
Assert.That(GetPrivateField<int>(hud, "activeDamagePopupCount"), Is.Zero);
|
||
Assert.That(FindActiveDamagePopup(), Is.Null);
|
||
Assert.That(
|
||
popupParent.childCount,
|
||
Is.LessThanOrEqualTo(popupChildCountBeforeToggle));
|
||
hud.gameObject.SetActive(true);
|
||
yield return null;
|
||
yield return new WaitForSeconds(health.InvulnerabilityDuration + 0.05f);
|
||
Assert.That(health.TryTakeDamage(1f, Vector2.right, 0f), Is.True);
|
||
yield return null;
|
||
Assert.That(FindActiveDamagePopup(), Is.Not.Null);
|
||
hud.gameObject.SetActive(false);
|
||
yield return null;
|
||
Assert.That(
|
||
popupParent.childCount,
|
||
Is.LessThanOrEqualTo(popupChildCountBeforeToggle));
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator DamagePopup_UsesArtifactIdentityPaletteForNormalChargedAndLegacyHits()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
director.enabled = false;
|
||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
GameObject target = new("Artifact Popup Palette Target");
|
||
target.transform.position = player.transform.position + Vector3.right;
|
||
MethodInfo handleValidHit = typeof(RunHUD).GetMethod(
|
||
"HandleValidHit",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
ActiveArtifactEffect[] effects =
|
||
{
|
||
ActiveArtifactEffect.Dash,
|
||
ActiveArtifactEffect.Pulse,
|
||
ActiveArtifactEffect.Phoenix,
|
||
ActiveArtifactEffect.Cyclone,
|
||
ActiveArtifactEffect.ThunderCrash,
|
||
ActiveArtifactEffect.ChainLightning,
|
||
};
|
||
string[] sourceIds =
|
||
{
|
||
"dash", "pulse", "phoenix", "cyclone", "thunder_crash",
|
||
"chain_lightning",
|
||
};
|
||
Color32[] expectedColors =
|
||
{
|
||
new(103, 231, 178, 255),
|
||
new(255, 100, 100, 255),
|
||
new(255, 100, 100, 255),
|
||
new(103, 231, 178, 255),
|
||
new(104, 179, 255, 255),
|
||
new(104, 179, 255, 255),
|
||
};
|
||
string[] expectedNames =
|
||
{
|
||
"척후의 장화",
|
||
"군주의 방패",
|
||
"잿불의 마검",
|
||
"바람깃 브로치",
|
||
"뇌격의 수갑",
|
||
"창뢰의 보주",
|
||
};
|
||
|
||
for (int group = 0; group < 3; group++)
|
||
{
|
||
bool isArtifactHit = group < 2;
|
||
bool charged = group == 1;
|
||
for (int i = 0; i < effects.Length; i++)
|
||
{
|
||
// The charged Arc case intentionally carries a stale
|
||
// legacy source id; the actual effect in the hit result
|
||
// must take priority whenever it is available.
|
||
string sourceId = charged && i == 5
|
||
? "pulse"
|
||
: sourceIds[i];
|
||
CombatHitResult result = new(
|
||
player.gameObject,
|
||
target,
|
||
false,
|
||
HitSide.Back,
|
||
100f + group * 10f + i,
|
||
Vector2.right,
|
||
0f,
|
||
target.transform.position,
|
||
sourceId: sourceId,
|
||
usesPositionBonus: true,
|
||
showsHitFeedback: false,
|
||
launchSucceeded: charged,
|
||
isArtifactHit: isArtifactHit,
|
||
artifactEffect: effects[i],
|
||
isChargedArtifact: charged);
|
||
handleValidHit.Invoke(hud, new object[] { result });
|
||
}
|
||
|
||
for (int i = 0; i < effects.Length; i++)
|
||
{
|
||
int damage = 100 + group * 10 + i;
|
||
string sourceId = charged && i == 5
|
||
? "PULSE"
|
||
: sourceIds[i].ToUpperInvariant();
|
||
string displayName = charged && i == 5
|
||
? "군주의 방패"
|
||
: expectedNames[i];
|
||
string expectedText = charged
|
||
? $"LAUNCH {displayName} {damage}"
|
||
: $"{displayName} {damage}";
|
||
Text popup = FindActiveDamagePopupByText(expectedText);
|
||
Assert.That(popup, Is.Not.Null, expectedText);
|
||
AssertPopupColor(popup, expectedColors[i]);
|
||
}
|
||
yield return new WaitForSecondsRealtime(0.7f);
|
||
Assert.That(
|
||
GetPrivateField<int>(hud, "activeDamagePopupCount"),
|
||
Is.Zero);
|
||
}
|
||
|
||
HitSide[] ordinarySides =
|
||
{
|
||
HitSide.Front,
|
||
HitSide.Side,
|
||
HitSide.Back,
|
||
};
|
||
string[] ordinaryTexts =
|
||
{
|
||
"42",
|
||
"SIDE ×1.2 43",
|
||
"BACK ×1.5 44",
|
||
};
|
||
Color32[] ordinaryColors =
|
||
{
|
||
new(255, 255, 255, 255),
|
||
new(166, 255, 76, 255),
|
||
new(255, 191, 51, 255),
|
||
};
|
||
for (int i = 0; i < ordinarySides.Length; i++)
|
||
{
|
||
CombatHitResult ordinary = new(
|
||
player.gameObject,
|
||
target,
|
||
false,
|
||
ordinarySides[i],
|
||
42f + i,
|
||
Vector2.right,
|
||
0f,
|
||
target.transform.position,
|
||
showsHitFeedback: false);
|
||
handleValidHit.Invoke(hud, new object[] { ordinary });
|
||
Text ordinaryPopup = FindActiveDamagePopupByText(ordinaryTexts[i]);
|
||
Assert.That(ordinaryPopup, Is.Not.Null, ordinaryTexts[i]);
|
||
AssertPopupColor(ordinaryPopup, ordinaryColors[i]);
|
||
yield return new WaitForSecondsRealtime(0.7f);
|
||
}
|
||
|
||
CombatHitResult fractionalDamage = new(
|
||
player.gameObject,
|
||
target,
|
||
false,
|
||
HitSide.Front,
|
||
14.5f,
|
||
Vector2.right,
|
||
0f,
|
||
target.transform.position,
|
||
showsHitFeedback: false);
|
||
handleValidHit.Invoke(hud, new object[] { fractionalDamage });
|
||
Text hitDebugText = GetPrivateField<Text>(hud, "hitDebugText");
|
||
Assert.That(hitDebugText.text, Is.EqualTo("FRONT 14"));
|
||
Assert.That(FindActiveDamagePopupByText("14"), Is.Not.Null);
|
||
Assert.That(FindActiveDamagePopupByText("14.5"), Is.Null);
|
||
yield return new WaitForSecondsRealtime(0.7f);
|
||
|
||
CombatHitResult unknownSource = new(
|
||
player.gameObject,
|
||
target,
|
||
false,
|
||
HitSide.Front,
|
||
45f,
|
||
Vector2.right,
|
||
0f,
|
||
target.transform.position,
|
||
sourceId: "unknown_source",
|
||
showsHitFeedback: false);
|
||
handleValidHit.Invoke(hud, new object[] { unknownSource });
|
||
Text unknownPopup = FindActiveDamagePopupByText("UNKNOWN_SOURCE 45");
|
||
Assert.That(unknownPopup, Is.Not.Null);
|
||
AssertPopupColor(unknownPopup, new Color32(255, 255, 255, 255));
|
||
yield return new WaitForSecondsRealtime(0.7f);
|
||
Object.Destroy(target);
|
||
}
|
||
|
||
[UnityTest]
|
||
public IEnumerator DamagePopup_LongArtifactLabelsStayOnOneVisibleLine()
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
director.enabled = false;
|
||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||
ActiveArtifactController artifacts =
|
||
Object.FindAnyObjectByType<ActiveArtifactController>();
|
||
ActiveArtifactDefinition chainLightning = null;
|
||
for (int i = 0; i < artifacts.CatalogCount; i++)
|
||
{
|
||
ActiveArtifactDefinition definition =
|
||
artifacts.GetCatalogArtifactAt(i);
|
||
if (definition != null
|
||
&& string.Equals(
|
||
definition.ArtifactId,
|
||
"chain_lightning",
|
||
System.StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
chainLightning = definition;
|
||
break;
|
||
}
|
||
}
|
||
Assert.That(chainLightning, Is.Not.Null);
|
||
string previousChainLightningName = chainLightning.DisplayName;
|
||
SetPrivateField(chainLightning, "displayName", "CHAIN_LIGHTNING");
|
||
GameObject target = new("Long Artifact Popup Target");
|
||
target.transform.position = player.transform.position + Vector3.right;
|
||
MethodInfo handleValidHit = typeof(RunHUD).GetMethod(
|
||
"HandleValidHit",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
try
|
||
{
|
||
CombatHitResult normal = new(
|
||
player.gameObject,
|
||
target,
|
||
false,
|
||
HitSide.Front,
|
||
192f,
|
||
Vector2.right,
|
||
0f,
|
||
target.transform.position,
|
||
sourceId: "chain_lightning",
|
||
showsHitFeedback: false,
|
||
isArtifactHit: true,
|
||
artifactEffect: ActiveArtifactEffect.ChainLightning);
|
||
handleValidHit.Invoke(hud, new object[] { normal });
|
||
Text normalPopup = FindActiveDamagePopupByText("CHAIN_LIGHTNING 192");
|
||
AssertPopupIsSingleVisibleLine(normalPopup, "CHAIN_LIGHTNING 192");
|
||
yield return new WaitForSecondsRealtime(0.7f);
|
||
|
||
CombatHitResult charged = new(
|
||
player.gameObject,
|
||
target,
|
||
false,
|
||
HitSide.Front,
|
||
192f,
|
||
Vector2.right,
|
||
0f,
|
||
target.transform.position,
|
||
sourceId: "chain_lightning",
|
||
showsHitFeedback: false,
|
||
launchSucceeded: true,
|
||
isArtifactHit: true,
|
||
artifactEffect: ActiveArtifactEffect.ChainLightning,
|
||
isChargedArtifact: true);
|
||
handleValidHit.Invoke(hud, new object[] { charged });
|
||
Text chargedPopup = FindActiveDamagePopupByText(
|
||
"LAUNCH CHAIN_LIGHTNING 192");
|
||
AssertPopupIsSingleVisibleLine(
|
||
chargedPopup,
|
||
"LAUNCH CHAIN_LIGHTNING 192");
|
||
yield return new WaitForSecondsRealtime(0.7f);
|
||
}
|
||
finally
|
||
{
|
||
SetPrivateField(
|
||
chainLightning,
|
||
"displayName",
|
||
previousChainLightningName);
|
||
Object.Destroy(target);
|
||
}
|
||
}
|
||
|
||
private static T GetPrivateField<T>(object target, string fieldName)
|
||
{
|
||
return (T)target.GetType()
|
||
.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic)
|
||
.GetValue(target);
|
||
}
|
||
|
||
private static void SetPrivateField<T>(
|
||
object target,
|
||
string fieldName,
|
||
T value)
|
||
{
|
||
target.GetType()
|
||
.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic)
|
||
.SetValue(target, value);
|
||
}
|
||
|
||
private static Text FindActiveDamagePopup()
|
||
{
|
||
Text[] texts = Object.FindObjectsByType<Text>(
|
||
FindObjectsInactive.Include,
|
||
FindObjectsSortMode.None);
|
||
for (int i = 0; i < texts.Length; i++)
|
||
{
|
||
if (texts[i].gameObject.activeInHierarchy
|
||
&& !string.IsNullOrEmpty(texts[i].text)
|
||
&& texts[i].text[0] == '-')
|
||
{
|
||
return texts[i];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private static Text FindActiveDamagePopupByText(string expectedText)
|
||
{
|
||
Text[] texts = Object.FindObjectsByType<Text>(
|
||
FindObjectsInactive.Include,
|
||
FindObjectsSortMode.None);
|
||
for (int i = 0; i < texts.Length; i++)
|
||
{
|
||
if (texts[i].gameObject.activeInHierarchy
|
||
&& texts[i].GetComponent<Outline>() != null
|
||
&& texts[i].text == expectedText)
|
||
{
|
||
return texts[i];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private static void AssertPopupColor(Text popup, Color32 expected)
|
||
{
|
||
Assert.That(popup.color.r, Is.EqualTo(expected.r / 255f).Within(0.002f));
|
||
Assert.That(popup.color.g, Is.EqualTo(expected.g / 255f).Within(0.002f));
|
||
Assert.That(popup.color.b, Is.EqualTo(expected.b / 255f).Within(0.002f));
|
||
}
|
||
|
||
private static void AssertPopupIsSingleVisibleLine(
|
||
Text popup,
|
||
string expectedText)
|
||
{
|
||
Assert.That(popup, Is.Not.Null, expectedText);
|
||
Canvas.ForceUpdateCanvases();
|
||
Assert.That(popup.text, Is.EqualTo(expectedText));
|
||
Assert.That(popup.resizeTextForBestFit, Is.False);
|
||
Assert.That(
|
||
popup.horizontalOverflow,
|
||
Is.EqualTo(HorizontalWrapMode.Overflow));
|
||
Assert.That(
|
||
popup.verticalOverflow,
|
||
Is.EqualTo(VerticalWrapMode.Overflow));
|
||
TextGenerator generator = popup.cachedTextGenerator;
|
||
Assert.That(generator.lineCount, Is.EqualTo(1), expectedText);
|
||
Assert.That(
|
||
generator.characterCountVisible,
|
||
Is.EqualTo(expectedText.Length),
|
||
expectedText);
|
||
Assert.That(
|
||
popup.rectTransform.rect.width,
|
||
Is.GreaterThanOrEqualTo(popup.preferredWidth - 0.5f),
|
||
expectedText);
|
||
}
|
||
|
||
private static void AssertRect(
|
||
RectTransform rect,
|
||
Vector2 anchor,
|
||
Vector2 pivot,
|
||
Vector2 anchoredPosition,
|
||
Vector2 size)
|
||
{
|
||
Assert.That(rect.anchorMin, Is.EqualTo(anchor));
|
||
Assert.That(rect.anchorMax, Is.EqualTo(anchor));
|
||
Assert.That(rect.pivot, Is.EqualTo(pivot));
|
||
Assert.That(rect.anchoredPosition, Is.EqualTo(anchoredPosition));
|
||
Assert.That(rect.rect.size, Is.EqualTo(size));
|
||
}
|
||
|
||
private static bool RectanglesOverlap(RectTransform first, RectTransform second)
|
||
{
|
||
return GetWorldRect(first).Overlaps(GetWorldRect(second));
|
||
}
|
||
|
||
private static void AssertRectInsideCanvas(
|
||
RectTransform target,
|
||
RectTransform canvas)
|
||
{
|
||
Rect targetRect = GetWorldRect(target);
|
||
Rect canvasRect = GetWorldRect(canvas);
|
||
const float tolerance = 1f;
|
||
Assert.That(targetRect.xMin, Is.GreaterThanOrEqualTo(canvasRect.xMin - tolerance));
|
||
Assert.That(targetRect.xMax, Is.LessThanOrEqualTo(canvasRect.xMax + tolerance));
|
||
Assert.That(targetRect.yMin, Is.GreaterThanOrEqualTo(canvasRect.yMin - tolerance));
|
||
Assert.That(targetRect.yMax, Is.LessThanOrEqualTo(canvasRect.yMax + tolerance));
|
||
}
|
||
|
||
private static Rect GetWorldRect(RectTransform rect)
|
||
{
|
||
Vector3[] corners = new Vector3[4];
|
||
rect.GetWorldCorners(corners);
|
||
float minX = corners[0].x;
|
||
float maxX = corners[0].x;
|
||
float minY = corners[0].y;
|
||
float maxY = corners[0].y;
|
||
for (int i = 1; i < corners.Length; i++)
|
||
{
|
||
minX = Mathf.Min(minX, corners[i].x);
|
||
maxX = Mathf.Max(maxX, corners[i].x);
|
||
minY = Mathf.Min(minY, corners[i].y);
|
||
maxY = Mathf.Max(maxY, corners[i].y);
|
||
}
|
||
|
||
return Rect.MinMaxRect(minX, minY, maxX, maxY);
|
||
}
|
||
|
||
private static IEnumerator AssertScorchingRayAlignment(bool charged)
|
||
{
|
||
for (int i = 0; i < Directions.Length; i++)
|
||
{
|
||
yield return LoadCombatPrototype();
|
||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||
director.enabled = false;
|
||
director.DebugSpawnImmediate(2);
|
||
yield return null;
|
||
|
||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||
Rigidbody2D playerBody = player.GetComponent<Rigidbody2D>();
|
||
ActiveArtifactController artifacts =
|
||
player.GetComponent<ActiveArtifactController>();
|
||
Vector2 direction = Directions[i];
|
||
SetPlayerDirection(player, direction);
|
||
playerBody.position = new Vector2(1.25f, -0.75f);
|
||
EnemyController[] enemies = Object.FindObjectsByType<EnemyController>(
|
||
FindObjectsSortMode.InstanceID);
|
||
Assert.That(enemies.Length, Is.GreaterThanOrEqualTo(2));
|
||
for (int enemyIndex = 2; enemyIndex < enemies.Length; enemyIndex++)
|
||
{
|
||
enemies[enemyIndex].gameObject.SetActive(false);
|
||
}
|
||
|
||
ActiveArtifactDefinition definition = SelectArtifact(
|
||
artifacts,
|
||
ActiveArtifactEffect.Phoenix);
|
||
float range = charged
|
||
? definition.ChargedRange
|
||
: definition.NormalRange;
|
||
float width = charged
|
||
? definition.ChargedWidth
|
||
: definition.NormalWidth;
|
||
Vector2 perpendicular = new Vector2(-direction.y, direction.x);
|
||
PlaceEnemy(
|
||
enemies[0],
|
||
playerBody.position + direction * (range * 0.5f));
|
||
PlaceEnemy(
|
||
enemies[1],
|
||
playerBody.position
|
||
+ direction * (range * 0.5f)
|
||
+ perpendicular * (width * 0.5f + 0.06f));
|
||
AddOverlappingCollider(enemies[0]);
|
||
AddOverlappingCollider(enemies[1]);
|
||
Physics2D.SyncTransforms();
|
||
EnsureGauge(
|
||
artifacts,
|
||
charged
|
||
? definition.ChargedGaugeCost
|
||
: definition.NormalGaugeCost);
|
||
|
||
List<CombatHitResult> hits = new();
|
||
System.Action<CombatHitResult> handler = result =>
|
||
{
|
||
if (result.SourceId == definition.ArtifactId)
|
||
{
|
||
hits.Add(result);
|
||
}
|
||
};
|
||
CombatEvents.OnValidHit += handler;
|
||
Assert.That(artifacts.TryUseCurrent(charged), Is.True);
|
||
|
||
GameObject visual = GameObject.Find(
|
||
charged ? "Charged Scorching Ray" : "Scorching Ray");
|
||
Assert.That(visual, Is.Not.Null);
|
||
Assert.That(
|
||
Vector2.Distance((Vector2)visual.transform.position, playerBody.position),
|
||
Is.LessThan(0.0001f));
|
||
Assert.That(
|
||
Mathf.Abs(Mathf.DeltaAngle(
|
||
visual.transform.eulerAngles.z,
|
||
Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg)),
|
||
Is.LessThan(0.001f));
|
||
SpriteRenderer renderer = visual.GetComponent<SpriteRenderer>();
|
||
Assert.That(renderer, Is.Not.Null);
|
||
Assert.That(renderer.transform.localScale, Is.EqualTo(Vector3.one));
|
||
Assert.That(renderer.sprite.rect.width, Is.EqualTo(charged ? 256f : 128f));
|
||
Assert.That(renderer.sprite.rect.height, Is.EqualTo(charged ? 128f : 64f));
|
||
Assert.That(
|
||
renderer.sprite.pivot,
|
||
Is.EqualTo(charged ? new Vector2(16f, 64f) : new Vector2(8f, 32f)));
|
||
Vector2 bodyStart = renderer.transform.TransformPoint(Vector3.zero);
|
||
Vector2 bodyEnd = renderer.transform.TransformPoint(
|
||
new Vector3(range, 0f, 0f));
|
||
Assert.That(
|
||
Vector2.Distance(bodyStart, playerBody.position),
|
||
Is.LessThan(0.0001f));
|
||
Assert.That(
|
||
Vector2.Distance(
|
||
bodyEnd,
|
||
playerBody.position + direction * range),
|
||
Is.LessThan(0.0001f));
|
||
Vector2 bodyTop = renderer.transform.TransformPoint(
|
||
new Vector3(0f, width * 0.5f, 0f));
|
||
Vector2 bodyBottom = renderer.transform.TransformPoint(
|
||
new Vector3(0f, -width * 0.5f, 0f));
|
||
Assert.That(
|
||
Vector2.Distance(bodyTop, playerBody.position + new Vector2(
|
||
-direction.y * width * 0.5f,
|
||
direction.x * width * 0.5f)),
|
||
Is.LessThan(0.0001f));
|
||
Assert.That(
|
||
Vector2.Distance(bodyBottom, playerBody.position - new Vector2(
|
||
-direction.y * width * 0.5f,
|
||
direction.x * width * 0.5f)),
|
||
Is.LessThan(0.0001f));
|
||
Assert.That(hits.Count, Is.EqualTo(1));
|
||
Assert.That(hits[0].Target, Is.EqualTo(enemies[0].gameObject));
|
||
CombatEvents.OnValidHit -= handler;
|
||
|
||
Time.timeScale = 1f;
|
||
yield return new WaitForSecondsRealtime(0.2f);
|
||
}
|
||
}
|
||
|
||
private static IEnumerator LoadCombatPrototype()
|
||
{
|
||
AsyncOperation load = SceneManager.LoadSceneAsync("CombatPrototype");
|
||
while (!load.isDone)
|
||
{
|
||
yield return null;
|
||
}
|
||
|
||
yield return null;
|
||
yield return null;
|
||
}
|
||
|
||
private static ActiveArtifactDefinition SelectArtifact(
|
||
ActiveArtifactController artifacts,
|
||
ActiveArtifactEffect effect)
|
||
{
|
||
for (int i = 0; i < artifacts.OwnedArtifactCount; i++)
|
||
{
|
||
if (artifacts.CurrentArtifact != null
|
||
&& artifacts.CurrentArtifact.Effect == effect)
|
||
{
|
||
return artifacts.CurrentArtifact;
|
||
}
|
||
|
||
Assert.That(artifacts.SelectNext(), Is.True);
|
||
}
|
||
|
||
Assert.Fail($"Artifact {effect} was not found in the debug catalog.");
|
||
return null;
|
||
}
|
||
|
||
private static void EnsureGauge(
|
||
ActiveArtifactController artifacts,
|
||
float required)
|
||
{
|
||
while (artifacts.CurrentGauge < required)
|
||
{
|
||
artifacts.AddMovementCharge(1f);
|
||
}
|
||
}
|
||
|
||
private static void SetPlayerDirection(
|
||
PlayerController player,
|
||
Vector2 direction)
|
||
{
|
||
typeof(PlayerController)
|
||
.GetField(
|
||
"<FacingDirection>k__BackingField",
|
||
BindingFlags.Instance | BindingFlags.NonPublic)
|
||
.SetValue(player, direction);
|
||
typeof(PlayerController)
|
||
.GetField(
|
||
"<MoveDirection>k__BackingField",
|
||
BindingFlags.Instance | BindingFlags.NonPublic)
|
||
.SetValue(player, direction);
|
||
}
|
||
|
||
private static void PlaceEnemy(
|
||
EnemyController enemy,
|
||
Vector2 position)
|
||
{
|
||
enemy.enabled = false;
|
||
Rigidbody2D body = enemy.GetComponent<Rigidbody2D>();
|
||
body.linearVelocity = Vector2.zero;
|
||
body.position = position;
|
||
}
|
||
|
||
private static void SetEnemyHealth(
|
||
EnemyController enemy,
|
||
float health)
|
||
{
|
||
typeof(EnemyController)
|
||
.GetProperty("CurrentHealth")
|
||
.SetValue(enemy, health);
|
||
Assert.That(enemy.CurrentHealth, Is.EqualTo(health));
|
||
}
|
||
|
||
private static void AddOverlappingCollider(EnemyController enemy)
|
||
{
|
||
GameObject child = new("Alignment Overlap Collider");
|
||
child.transform.SetParent(enemy.transform, false);
|
||
BoxCollider2D collider = child.AddComponent<BoxCollider2D>();
|
||
collider.isTrigger = true;
|
||
collider.size = new Vector2(0.6f, 0.6f);
|
||
}
|
||
|
||
private static void AssertUniformScale(Vector3 actual, float expected)
|
||
{
|
||
Assert.That(actual.x, Is.EqualTo(expected).Within(0.0001f));
|
||
Assert.That(actual.y, Is.EqualTo(expected).Within(0.0001f));
|
||
Assert.That(actual.z, Is.EqualTo(expected).Within(0.0001f));
|
||
}
|
||
|
||
}
|
||
}
|