using System.Collections; using BumpCombat.Combat; using BumpCombat.Core; using BumpCombat.Player; using BumpCombat.Progression; using BumpCombat.Spawning; using NUnit.Framework; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools; namespace BumpCombat.PlayModeTests { public sealed class GuardVisualTests { [Test] public void GuardMasks_CoverAllSelectedColorAnimationFrames() { string[] sheets = { "Idle", "Walk", "Dash", "Hurt", "Death", "BumpAttack-v1", "BackBumpAttack-v2", "ArtifactUse-v1" }; foreach (string sheet in sheets) { Sprite[] masks = Resources.LoadAll("Combat/Guard-v1/Swordsman_" + sheet + "-outline"); Assert.That(masks.Length, Is.GreaterThan(0), sheet); foreach (string color in new[] { "Green", "Red", "Blue" }) { Sprite[] frames = Resources.LoadAll("Artifacts/ThreeColor-v1/Player/Swordsman_" + sheet + "-" + color + "-v1"); Assert.That(masks.Length, Is.EqualTo(frames.Length), sheet + color); foreach (Sprite frame in frames) { Sprite mask = System.Array.Find(masks, item => item.rect == frame.rect); Assert.That(mask, Is.Not.Null, frame.name); Assert.That(mask.pivot, Is.EqualTo(frame.pivot)); Assert.That(mask.pixelsPerUnit, Is.EqualTo(frame.pixelsPerUnit)); Assert.That(mask.texture.filterMode, Is.EqualTo(FilterMode.Point)); } } } } [UnityTest] public IEnumerator GuardOutline_FollowsFrameFlipPauseAndLifetime() { ArtifactRewardController.GrantCatalogForTests = true; RunManager.ForceProductionModeForTests = false; Time.timeScale = 1f; yield return SceneManager.LoadSceneAsync("CombatPrototype"); yield return null; yield return null; Object.FindAnyObjectByType().enabled = false; var health = Object.FindAnyObjectByType(); var visual = health.GetComponent(); var body = health.GetComponent(); Assert.That(health.TryActivateGuard(), Is.True); visual.RefreshEquipmentSprite(); visual.RefreshGuardOutline(); var outline = health.transform.Find("Player Guard Outline").GetComponent(); Assert.That(outline.enabled, Is.True); float guardedHealth = health.CurrentHealth; Assert.That(health.TryTakeDamage(25f, Vector2.right, 1.1f), Is.False); Assert.That(health.CurrentHealth, Is.EqualTo(guardedHealth)); Assert.That(health.IsHurtMovementLocked, Is.False); Assert.That(outline.sprite.rect, Is.EqualTo(body.sprite.rect)); Assert.That(outline.sprite.pivot, Is.EqualTo(body.sprite.pivot)); Assert.That(outline.sprite.pixelsPerUnit, Is.EqualTo(body.sprite.pixelsPerUnit)); body.flipX = true; body.sortingOrder = 123; visual.RefreshGuardOutline(); Assert.That(outline.flipX, Is.True); Assert.That(outline.sortingOrder, Is.EqualTo(124)); Time.timeScale = 0f; yield return new WaitForSecondsRealtime(0.6f); Assert.That(outline.enabled, Is.True); Assert.That(health.IsGuarding, Is.True); body.enabled = false; visual.RefreshGuardOutline(); Assert.That(outline.enabled, Is.False); body.enabled = true; visual.RefreshGuardOutline(); Assert.That(outline.enabled, Is.True); visual.enabled = false; Assert.That(outline.enabled, Is.False); visual.enabled = true; visual.RefreshGuardOutline(); Assert.That(outline.enabled, Is.True); Time.timeScale = 1f; yield return new WaitForSeconds(health.GuardDuration + 0.05f); visual.RefreshGuardOutline(); Assert.That(outline.enabled, Is.False); Assert.That(health.IsGuarding, Is.False); Assert.That(health.TryTakeDamage(1f, Vector2.zero, 0f), Is.True); Assert.That(health.CurrentHealth, Is.LessThan(guardedHealth)); } [UnityTest] public IEnumerator GuardBlockImpact_FollowsPlayerPausesAndCleansUp() { ArtifactRewardController.GrantCatalogForTests = true; RunManager.ForceProductionModeForTests = false; Time.timeScale = 1f; yield return SceneManager.LoadSceneAsync("CombatPrototype"); yield return null; yield return null; Object.FindAnyObjectByType().enabled = false; PlayerHealth health = Object.FindAnyObjectByType(); CombatFeedback feedback = health.GetComponent(); SpriteRenderer playerRenderer = health.GetComponent(); Collider2D playerCollider = health.GetComponent(); Assert.That(feedback, Is.Not.Null); Assert.That(playerRenderer, Is.Not.Null); Assert.That(playerCollider, Is.Not.Null); Sprite impactSprite = Resources.Load( CombatFeedback.GetGuardBlockImpactResourcePath()); Assert.That(impactSprite, Is.Not.Null); Assert.That(health.TryActivateGuard(), Is.True); Vector2 expectedPosition = (Vector2)playerCollider.bounds.center + Vector2.left * 0.35f; Assert.That( health.TryTakeDamage(5f, Vector2.right * 3f, 0.75f), Is.False); Assert.That(feedback.ActiveGuardBlockImpactVisualCount, Is.EqualTo(1)); Transform impact = health.transform.Find("Player Guard Block Impact"); Assert.That(impact, Is.Not.Null); Assert.That(impact.parent, Is.EqualTo(health.transform)); Assert.That( Vector2.Distance((Vector2)impact.position, expectedPosition), Is.LessThan(0.001f)); SpriteRenderer impactRenderer = impact.GetComponent(); Assert.That(impactRenderer.sprite, Is.EqualTo(impactSprite)); Assert.That(impactRenderer.color, Is.EqualTo(Color.white)); Assert.That( impactRenderer.sortingOrder, Is.EqualTo(playerRenderer.sortingOrder + 20)); Assert.That(health.TryTakeDamage(5f, Vector2.left, 0.75f), Is.False); Assert.That(feedback.ActiveGuardBlockImpactVisualCount, Is.EqualTo(1), "A second impact inside the 0.08-second interval must be suppressed."); yield return new WaitForSeconds(0.09f); Assert.That(health.TryTakeDamage(5f, Vector2.zero, 0.75f), Is.False); Assert.That(feedback.ActiveGuardBlockImpactVisualCount, Is.EqualTo(2)); Transform centeredImpact = null; foreach (Transform child in health.GetComponentsInChildren(true)) { if (child.name == "Player Guard Block Impact" && Vector2.Distance( (Vector2)child.position, (Vector2)playerCollider.bounds.center) < 0.001f) { centeredImpact = child; break; } } Assert.That(centeredImpact, Is.Not.Null, "A zero attack direction must place the impact at the body center."); Vector3 initialImpactPosition = impact.position; health.transform.position += Vector3.up * 0.25f; Assert.That(impact.position, Is.EqualTo(initialImpactPosition + Vector3.up * 0.25f)); Time.timeScale = 0f; yield return new WaitForSecondsRealtime(0.22f); Assert.That(feedback.ActiveGuardBlockImpactVisualCount, Is.EqualTo(2), "The impact lifetime must pause with game time."); feedback.enabled = false; Assert.That(feedback.ActiveGuardBlockImpactVisualCount, Is.Zero); feedback.enabled = true; Time.timeScale = 1f; yield return new WaitForSeconds(0.09f); Assert.That(health.TryTakeDamage(5f, Vector2.right, 0.75f), Is.False); Assert.That(feedback.ActiveGuardBlockImpactVisualCount, Is.EqualTo(1)); yield return new WaitForSeconds(0.2f); Assert.That(feedback.ActiveGuardBlockImpactVisualCount, Is.Zero); } [UnityTest] public IEnumerator GuardBlockImpact_ClearsWhenPlayerDies() { ArtifactRewardController.GrantCatalogForTests = true; RunManager.ForceProductionModeForTests = false; Time.timeScale = 1f; yield return SceneManager.LoadSceneAsync("CombatPrototype"); yield return null; yield return null; Object.FindAnyObjectByType().enabled = false; PlayerHealth health = Object.FindAnyObjectByType(); CombatFeedback feedback = health.GetComponent(); Assert.That(health.TryActivateGuard(), Is.True); Assert.That(health.TryTakeDamage(1f, Vector2.right, 0.75f), Is.False); Assert.That(feedback.ActiveGuardBlockImpactVisualCount, Is.EqualTo(1)); health.enabled = false; Assert.That( health.TryTakeDamage(health.CurrentHealth, Vector2.right, 0f), Is.True); Assert.That(health.CurrentHealth, Is.Zero); Assert.That(feedback.ActiveGuardBlockImpactVisualCount, Is.Zero); } [TearDown] public void TearDown() { ArtifactRewardController.GrantCatalogForTests = false; RunManager.ForceProductionModeForTests = false; Time.timeScale = 1f; } } }