Initial commit: Tiny Tackle Heroes Unity project

This commit is contained in:
2026-09-15 17:37:42 +09:00
commit 591fa9a826
1623 changed files with 182420 additions and 0 deletions
@@ -0,0 +1,210 @@
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<Sprite>("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<Sprite>("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<SpawnDirector>().enabled = false;
var health = Object.FindAnyObjectByType<PlayerHealth>();
var visual = health.GetComponent<ArtifactChargeVisual>();
var body = health.GetComponent<SpriteRenderer>();
Assert.That(health.TryActivateGuard(), Is.True);
visual.RefreshEquipmentSprite();
visual.RefreshGuardOutline();
var outline = health.transform.Find("Player Guard Outline").GetComponent<SpriteRenderer>();
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<SpawnDirector>().enabled = false;
PlayerHealth health = Object.FindAnyObjectByType<PlayerHealth>();
CombatFeedback feedback = health.GetComponent<CombatFeedback>();
SpriteRenderer playerRenderer = health.GetComponent<SpriteRenderer>();
Collider2D playerCollider = health.GetComponent<Collider2D>();
Assert.That(feedback, Is.Not.Null);
Assert.That(playerRenderer, Is.Not.Null);
Assert.That(playerCollider, Is.Not.Null);
Sprite impactSprite = Resources.Load<Sprite>(
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<SpriteRenderer>();
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<Transform>(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<SpawnDirector>().enabled = false;
PlayerHealth health = Object.FindAnyObjectByType<PlayerHealth>();
CombatFeedback feedback = health.GetComponent<CombatFeedback>();
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;
}
}
}