2026-04-23 14:50:20 +00:00
|
|
|
using System.Linq;
|
2026-04-23 14:47:51 +00:00
|
|
|
using NUnit.Framework;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-05-07 04:22:51 +00:00
|
|
|
/// EerieVillage BT7-Plan 통합 EditMode 테스트 — Player 근거리 공격 체계·하트 분할 HP·자동 발동 타이머·Hero1 sprite 참조.
|
2026-04-24 07:22:13 +00:00
|
|
|
/// Prefab/Script 자산의 컴포넌트·필드·YAML 구성이 기획 방향(BT7-Plan 2026-04-24)을 충족하는지 검증.
|
|
|
|
|
/// Play 모드 실행 불요 — prefab/script YAML 직렬화 상태를 직접 검증하여 회귀 방지.
|
2026-04-23 14:50:20 +00:00
|
|
|
///
|
2026-04-24 07:22:13 +00:00
|
|
|
/// 2026-04-23 BT5-Dev: reflection 기반 전환 (Scripts/ 하위 asmdef 부재 대응).
|
|
|
|
|
/// 2026-04-24 BT7-Plan: VS 순수형 자동 발동 전환에 맞춰 수동 입력(Attack 액션·attackCooldown) 검증 제거 +
|
|
|
|
|
/// PlayerAttackTicker·Health.maxHearts/maxHP·IncreaseMaxHearts·Heal 필드 검증 신규.
|
2026-05-07 04:22:51 +00:00
|
|
|
/// 2026-05-07 BT5-Dev Hero1: 테스트 12번 개정(Hero1 idle01 guid) + 신규 6건 추가 (8 State·8 Parameter·
|
|
|
|
|
/// hit 인터럽트·PlayerStateTimer·death→resurrection·Health Resurrect/이벤트).
|
2026-04-23 14:47:51 +00:00
|
|
|
/// </summary>
|
|
|
|
|
public class PlayerAttackTests
|
|
|
|
|
{
|
|
|
|
|
const string PlayerPrefabPath = "Assets/Prefabs/Player.prefab";
|
|
|
|
|
const string EnemyPrefabPath = "Assets/Prefabs/Enemy.prefab";
|
2026-05-07 04:22:51 +00:00
|
|
|
const string PlayerControllerPath = "Assets/Character/Animations/Player.controller";
|
2026-04-23 14:47:51 +00:00
|
|
|
|
2026-04-23 14:50:20 +00:00
|
|
|
const string AttackHitboxType = "Platformer.Mechanics.AttackHitbox";
|
|
|
|
|
const string HealthType = "Platformer.Mechanics.Health";
|
|
|
|
|
const string PlayerControllerType = "Platformer.Mechanics.PlayerController";
|
|
|
|
|
const string EnemyControllerType = "Platformer.Mechanics.EnemyController";
|
2026-04-24 07:22:13 +00:00
|
|
|
const string PlayerAttackTickerType = "Platformer.Gameplay.PlayerAttackTicker";
|
2026-05-07 04:22:51 +00:00
|
|
|
const string PlayerStateTimerType = "Platformer.Mechanics.PlayerStateTimer";
|
2026-04-24 07:22:13 +00:00
|
|
|
|
|
|
|
|
static System.Type FindTypeByFullName(string fullName)
|
|
|
|
|
{
|
|
|
|
|
foreach (var asm in System.AppDomain.CurrentDomain.GetAssemblies())
|
|
|
|
|
{
|
|
|
|
|
var t = asm.GetType(fullName, throwOnError: false);
|
|
|
|
|
if (t != null) return t;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2026-04-23 14:50:20 +00:00
|
|
|
|
|
|
|
|
static Component FindComponentByFullName(GameObject go, string fullName)
|
|
|
|
|
{
|
|
|
|
|
if (go == null) return null;
|
|
|
|
|
return go.GetComponents<Component>()
|
|
|
|
|
.FirstOrDefault(c => c != null && c.GetType().FullName == fullName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static object GetFieldOrProperty(object obj, string memberName)
|
|
|
|
|
{
|
|
|
|
|
if (obj == null) return null;
|
|
|
|
|
var t = obj.GetType();
|
|
|
|
|
var field = t.GetField(memberName,
|
|
|
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
|
if (field != null) return field.GetValue(obj);
|
|
|
|
|
var prop = t.GetProperty(memberName,
|
|
|
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
|
return prop?.GetValue(obj);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 04:22:51 +00:00
|
|
|
// ===== 1. Player/Enemy Prefab 컴포넌트 구성 =====
|
2026-04-24 07:22:13 +00:00
|
|
|
|
2026-04-23 14:47:51 +00:00
|
|
|
[Test]
|
|
|
|
|
public void Player_Prefab_Has_AttackHitbox_Component()
|
|
|
|
|
{
|
|
|
|
|
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
|
|
|
|
|
Assert.IsNotNull(prefab, $"Player.prefab not found at {PlayerPrefabPath}");
|
|
|
|
|
|
2026-04-23 14:50:20 +00:00
|
|
|
var hitbox = FindComponentByFullName(prefab, AttackHitboxType);
|
2026-04-23 14:47:51 +00:00
|
|
|
Assert.IsNotNull(hitbox,
|
|
|
|
|
"Player.prefab에 AttackHitbox 컴포넌트가 누락. " +
|
|
|
|
|
"BT5-Dev 2단계 재위임 집행분 (2026-04-23) 이 prefab YAML 에 반영되어야 함.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Player_Prefab_Has_Health_Component()
|
|
|
|
|
{
|
|
|
|
|
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
|
|
|
|
|
Assert.IsNotNull(prefab, $"Player.prefab not found at {PlayerPrefabPath}");
|
|
|
|
|
|
2026-04-23 14:50:20 +00:00
|
|
|
var health = FindComponentByFullName(prefab, HealthType);
|
2026-04-23 14:47:51 +00:00
|
|
|
Assert.IsNotNull(health, "Player.prefab 에 Health 컴포넌트 누락 (템플릿 기본).");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Player_Prefab_Has_PlayerController_Component()
|
|
|
|
|
{
|
|
|
|
|
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
|
|
|
|
|
Assert.IsNotNull(prefab);
|
|
|
|
|
|
2026-04-23 14:50:20 +00:00
|
|
|
var controller = FindComponentByFullName(prefab, PlayerControllerType);
|
2026-04-23 14:47:51 +00:00
|
|
|
Assert.IsNotNull(controller, "Player.prefab 에 PlayerController 컴포넌트 누락.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void AttackHitbox_Default_Damage_Is_One()
|
|
|
|
|
{
|
|
|
|
|
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
|
2026-04-23 14:50:20 +00:00
|
|
|
var hitbox = FindComponentByFullName(prefab, AttackHitboxType);
|
2026-04-23 14:47:51 +00:00
|
|
|
Assert.IsNotNull(hitbox);
|
|
|
|
|
|
2026-04-23 14:50:20 +00:00
|
|
|
var damage = GetFieldOrProperty(hitbox, "damage");
|
|
|
|
|
Assert.IsNotNull(damage, "AttackHitbox.damage 필드/프로퍼티 접근 불가");
|
|
|
|
|
Assert.AreEqual(1, System.Convert.ToInt32(damage),
|
2026-04-24 07:22:13 +00:00
|
|
|
"기본 대미지 1 (Phase 3-B balance/01 v0.2 튠 전 파일럿 값).");
|
2026-04-23 14:47:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void AttackHitbox_Active_Duration_Is_Positive()
|
|
|
|
|
{
|
|
|
|
|
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
|
2026-04-23 14:50:20 +00:00
|
|
|
var hitbox = FindComponentByFullName(prefab, AttackHitboxType);
|
2026-04-23 14:47:51 +00:00
|
|
|
Assert.IsNotNull(hitbox);
|
|
|
|
|
|
2026-04-23 14:50:20 +00:00
|
|
|
var duration = GetFieldOrProperty(hitbox, "activeDuration");
|
|
|
|
|
Assert.IsNotNull(duration, "AttackHitbox.activeDuration 필드/프로퍼티 접근 불가");
|
|
|
|
|
Assert.Greater(System.Convert.ToSingle(duration), 0f,
|
2026-04-23 14:47:51 +00:00
|
|
|
"activeDuration 가 0 이하면 OverlapBox 판정이 즉시 종료되어 공격 무효.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Enemy_Prefab_Has_Health_Component()
|
|
|
|
|
{
|
|
|
|
|
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EnemyPrefabPath);
|
|
|
|
|
Assert.IsNotNull(prefab, $"Enemy.prefab not found at {EnemyPrefabPath}");
|
|
|
|
|
|
2026-04-23 14:50:20 +00:00
|
|
|
var health = FindComponentByFullName(prefab, HealthType);
|
2026-04-23 14:47:51 +00:00
|
|
|
Assert.IsNotNull(health,
|
|
|
|
|
"Enemy.prefab 에 Health 컴포넌트 누락. " +
|
|
|
|
|
"Health 없으면 AttackHitbox.Update 의 Decrement 호출이 불가 → EnemyDeath 체인 미발동.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
2026-04-24 07:22:13 +00:00
|
|
|
public void Enemy_Prefab_Has_EnemyController_Component()
|
2026-04-23 14:47:51 +00:00
|
|
|
{
|
|
|
|
|
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EnemyPrefabPath);
|
2026-04-24 07:22:13 +00:00
|
|
|
var controller = FindComponentByFullName(prefab, EnemyControllerType);
|
|
|
|
|
Assert.IsNotNull(controller,
|
|
|
|
|
"EnemyDeath 체인에 EnemyController 필수 (AttackHitbox.Update 에서 Schedule<EnemyDeath>().enemy = enemy).");
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 04:22:51 +00:00
|
|
|
// ===== 8. BT7-Plan 하트 분할 시스템 검증 (2026-04-24) =====
|
2026-04-24 07:22:13 +00:00
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Health_Script_Defines_MaxHearts_And_IncreaseMaxHearts()
|
|
|
|
|
{
|
|
|
|
|
var healthType = FindTypeByFullName(HealthType);
|
|
|
|
|
Assert.IsNotNull(healthType, "Platformer.Mechanics.Health 타입 로드 실패.");
|
|
|
|
|
|
|
|
|
|
var maxHeartsField = healthType.GetField("maxHearts",
|
|
|
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
|
Assert.IsNotNull(maxHeartsField, "Health.maxHearts 필드 부재 (BT7-Plan 하트 분할 전제).");
|
|
|
|
|
|
|
|
|
|
var maxHPField = healthType.GetField("maxHP",
|
|
|
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
|
Assert.IsNotNull(maxHPField, "Health.maxHP 필드 부재 (기존 API 호환 유지 필요).");
|
|
|
|
|
|
|
|
|
|
var increaseMethod = healthType.GetMethod("IncreaseMaxHearts",
|
|
|
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
|
Assert.IsNotNull(increaseMethod, "Health.IncreaseMaxHearts(int) 메서드 부재 — 패시브 카드 훅 필수.");
|
|
|
|
|
|
|
|
|
|
var healMethod = healthType.GetMethod("Heal",
|
|
|
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
|
Assert.IsNotNull(healMethod, "Health.Heal(int) 메서드 부재 — 쿼터 단위 회복 필수.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Player_Prefab_MaxHP_Reflects_Heart_Quarters()
|
|
|
|
|
{
|
|
|
|
|
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
|
2026-04-23 14:50:20 +00:00
|
|
|
var health = FindComponentByFullName(prefab, HealthType);
|
2026-04-23 14:47:51 +00:00
|
|
|
Assert.IsNotNull(health);
|
|
|
|
|
|
2026-04-24 07:22:13 +00:00
|
|
|
var maxHearts = System.Convert.ToInt32(GetFieldOrProperty(health, "maxHearts"));
|
|
|
|
|
var maxHP = System.Convert.ToInt32(GetFieldOrProperty(health, "maxHP"));
|
|
|
|
|
Assert.GreaterOrEqual(maxHearts, 1, "Player 초기 maxHearts는 1 이상 (BT7-Plan 기본 하트 1개).");
|
|
|
|
|
Assert.IsTrue(maxHP == maxHearts * 4 || maxHP == 1,
|
2026-05-07 04:22:51 +00:00
|
|
|
$"Player.prefab Health.maxHP({maxHP})는 maxHearts*4({maxHearts * 4}) 또는 구식 기본값 1이어야 함.");
|
2026-04-23 14:47:51 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-07 04:22:51 +00:00
|
|
|
// ===== 10. BT7-Plan 자동 발동 타이머 검증 (2026-04-24) =====
|
2026-04-24 07:22:13 +00:00
|
|
|
|
2026-04-23 14:47:51 +00:00
|
|
|
[Test]
|
2026-04-24 07:22:13 +00:00
|
|
|
public void PlayerAttackTicker_Script_Exists_With_AttackInterval()
|
2026-04-23 14:47:51 +00:00
|
|
|
{
|
2026-04-24 07:22:13 +00:00
|
|
|
var tickerType = FindTypeByFullName(PlayerAttackTickerType);
|
|
|
|
|
Assert.IsNotNull(tickerType,
|
|
|
|
|
"Platformer.Gameplay.PlayerAttackTicker 타입 로드 실패. " +
|
|
|
|
|
"BT7-Plan VS 순수형 자동 발동 전환 전제.");
|
|
|
|
|
|
|
|
|
|
var intervalField = tickerType.GetField("attackInterval",
|
|
|
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
|
Assert.IsNotNull(intervalField, "PlayerAttackTicker.attackInterval 필드 부재.");
|
|
|
|
|
Assert.AreEqual(typeof(float), intervalField.FieldType,
|
|
|
|
|
"attackInterval은 float이어야 함 (Time.deltaTime 누적용).");
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 04:22:51 +00:00
|
|
|
// ===== 11. BT7-Plan 공격 버튼 제거 검증 =====
|
2026-04-24 07:22:13 +00:00
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void InputActions_Player_Map_Has_No_Attack_Action()
|
|
|
|
|
{
|
|
|
|
|
var path = System.IO.Path.GetFullPath("Assets/Settings/InputSystem_Actions.inputactions");
|
|
|
|
|
Assert.IsTrue(System.IO.File.Exists(path), $"InputActions 파일 부재 — {path}");
|
|
|
|
|
var json = System.IO.File.ReadAllText(path);
|
|
|
|
|
|
|
|
|
|
var playerMapStart = json.IndexOf("\"name\": \"Player\"", System.StringComparison.Ordinal);
|
|
|
|
|
Assert.Greater(playerMapStart, 0, "Player 맵 식별 실패.");
|
|
|
|
|
var uiMapStart = json.IndexOf("\"name\": \"UI\"", playerMapStart, System.StringComparison.Ordinal);
|
|
|
|
|
Assert.Greater(uiMapStart, playerMapStart, "UI 맵 식별 실패.");
|
|
|
|
|
|
|
|
|
|
var playerMapSection = json.Substring(playerMapStart, uiMapStart - playerMapStart);
|
|
|
|
|
Assert.IsFalse(
|
|
|
|
|
playerMapSection.Contains("\"name\": \"Attack\""),
|
|
|
|
|
"Player 맵에 Attack 액션 잔존. BT7-Plan VS 순수형 전환으로 제거되어야 함.");
|
2026-04-23 14:47:51 +00:00
|
|
|
}
|
2026-04-23 15:21:38 +00:00
|
|
|
|
2026-05-07 04:22:51 +00:00
|
|
|
// ===== 12. BT5-Dev Hero1: SpriteRenderer Hero1 idle01 참조 검증 (2026-05-07 개정) =====
|
2026-04-23 15:21:38 +00:00
|
|
|
|
2026-05-07 04:22:51 +00:00
|
|
|
const string Hero1Idle01Guid = "78c7da0e2fc366543ae4ad5e3ceb1b94";
|
2026-04-23 15:21:38 +00:00
|
|
|
|
|
|
|
|
[Test]
|
2026-05-07 04:22:51 +00:00
|
|
|
public void Player_Prefab_SpriteRenderer_References_Hero1_Idle01()
|
2026-04-23 15:21:38 +00:00
|
|
|
{
|
|
|
|
|
var path = System.IO.Path.GetFullPath(PlayerPrefabPath);
|
|
|
|
|
Assert.IsTrue(System.IO.File.Exists(path), $"Player.prefab 부재 — {path}");
|
|
|
|
|
var yaml = System.IO.File.ReadAllText(path);
|
|
|
|
|
|
|
|
|
|
var match = System.Text.RegularExpressions.Regex.Match(
|
|
|
|
|
yaml,
|
|
|
|
|
@"m_Sprite:\s*\{fileID:\s*[-0-9]+,\s*guid:\s*([a-f0-9]{32}),\s*type:\s*3\}");
|
|
|
|
|
Assert.IsTrue(match.Success,
|
|
|
|
|
"Player.prefab YAML 에서 SpriteRenderer.m_Sprite guid 추출 실패.");
|
2026-05-07 04:22:51 +00:00
|
|
|
Assert.AreEqual(Hero1Idle01Guid, match.Groups[1].Value,
|
|
|
|
|
$"Player.prefab SpriteRenderer.m_Sprite guid 가 Hero1 idle01({Hero1Idle01Guid}) 이어야 함. 실제: {match.Groups[1].Value}");
|
2026-04-23 15:21:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Player_Controller_Has_Attack_Parameter_And_State()
|
|
|
|
|
{
|
2026-05-07 04:22:51 +00:00
|
|
|
var path = System.IO.Path.GetFullPath(PlayerControllerPath);
|
2026-04-23 15:21:38 +00:00
|
|
|
Assert.IsTrue(System.IO.File.Exists(path), $"Player.controller 부재 — {path}");
|
|
|
|
|
var yaml = System.IO.File.ReadAllText(path);
|
|
|
|
|
|
|
|
|
|
Assert.IsTrue(
|
|
|
|
|
System.Text.RegularExpressions.Regex.IsMatch(
|
|
|
|
|
yaml,
|
|
|
|
|
@"m_Name:\s*attack\s*\r?\n\s*m_Type:\s*9"),
|
2026-05-07 04:22:51 +00:00
|
|
|
"Player.controller 에 attack Trigger 파라미터(m_Type:9) 누락.");
|
2026-04-23 15:21:38 +00:00
|
|
|
|
|
|
|
|
Assert.IsTrue(
|
|
|
|
|
yaml.Contains("m_Name: Player-Attack"),
|
2026-05-07 04:22:51 +00:00
|
|
|
"Player.controller 에 Player-Attack State 누락.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 14. BT5-Dev Hero1 신규: 8 State 존재·4 State 부재 검증 (2026-05-07) =====
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Player_Has_8_Motion_States()
|
|
|
|
|
{
|
|
|
|
|
var path = System.IO.Path.GetFullPath(PlayerControllerPath);
|
|
|
|
|
Assert.IsTrue(System.IO.File.Exists(path), $"Player.controller 부재 — {path}");
|
|
|
|
|
var yaml = System.IO.File.ReadAllText(path);
|
|
|
|
|
|
|
|
|
|
// 8 State 존재 검증
|
|
|
|
|
string[] requiredStates = {
|
|
|
|
|
"Player-Idle", "Player-Run", "Player-Attack", "Player-Jump",
|
|
|
|
|
"Player-Death", "Player-CombatIdle", "Player-Hit", "Player-Resurrection"
|
|
|
|
|
};
|
|
|
|
|
foreach (var state in requiredStates)
|
|
|
|
|
{
|
|
|
|
|
Assert.IsTrue(yaml.Contains($"m_Name: {state}"),
|
|
|
|
|
$"Player.controller에 {state} State 누락 (Hero1 8종 모션 정합).");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4 State 부재 검증 (폐기)
|
|
|
|
|
string[] deprecatedStates = { "Player-Hurt", "Player-Land", "Player-Spawn", "Player-Victory" };
|
|
|
|
|
foreach (var state in deprecatedStates)
|
|
|
|
|
{
|
|
|
|
|
Assert.IsFalse(yaml.Contains($"m_Name: {state}"),
|
|
|
|
|
$"Player.controller에 폐기된 {state} State 잔존 (PD 결정 5 정합).");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 15. 신규 Parameter 3종 존재·폐기 Parameter 2종 부재 검증 (2026-05-07) =====
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Player_Controller_Has_New_Parameters()
|
|
|
|
|
{
|
|
|
|
|
var path = System.IO.Path.GetFullPath(PlayerControllerPath);
|
|
|
|
|
Assert.IsTrue(System.IO.File.Exists(path));
|
|
|
|
|
var yaml = System.IO.File.ReadAllText(path);
|
|
|
|
|
|
|
|
|
|
// 신규 3종 존재
|
|
|
|
|
Assert.IsTrue(yaml.Contains("m_Name: hit"), "hit Trigger 파라미터 누락.");
|
|
|
|
|
Assert.IsTrue(yaml.Contains("m_Name: combatidle"), "combatidle Bool 파라미터 누락.");
|
|
|
|
|
Assert.IsTrue(yaml.Contains("m_Name: resurrect"), "resurrect Trigger 파라미터 누락.");
|
|
|
|
|
|
|
|
|
|
// 폐기 2종 부재
|
|
|
|
|
Assert.IsFalse(yaml.Contains("m_Name: hurt"), "폐기된 hurt Trigger 잔존.");
|
|
|
|
|
Assert.IsFalse(yaml.Contains("m_Name: victory"), "폐기된 victory Trigger 잔존.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 16. hit 인터럽트 보호 — Player-Hit StateMachineBehaviour 부착 검증 (2026-05-07) =====
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Player_Hit_Interrupts_Protected()
|
|
|
|
|
{
|
|
|
|
|
var path = System.IO.Path.GetFullPath(PlayerControllerPath);
|
|
|
|
|
Assert.IsTrue(System.IO.File.Exists(path));
|
|
|
|
|
var yaml = System.IO.File.ReadAllText(path);
|
|
|
|
|
|
|
|
|
|
// Player-Hit State에 m_StateMachineBehaviours 비어있지 않음 검증
|
|
|
|
|
// HitInterruptGuard guid: d4e5f6789012345678abcdef01020304
|
|
|
|
|
Assert.IsTrue(yaml.Contains("d4e5f6789012345678abcdef01020304"),
|
|
|
|
|
"Player-Hit State에 HitInterruptGuard(guid d4e5f6789012345678abcdef01020304) 부착 누락.");
|
|
|
|
|
|
|
|
|
|
// Player-Hit AnyState→Hit CanTransitionToSelf: 0 검증
|
|
|
|
|
// AnyState→Hit transition fileID 1101800000000000001 의 m_CanTransitionToSelf: 0 확인
|
|
|
|
|
Assert.IsTrue(
|
|
|
|
|
System.Text.RegularExpressions.Regex.IsMatch(
|
|
|
|
|
yaml,
|
|
|
|
|
@"1101800000000000001[\s\S]{0,500}m_CanTransitionToSelf:\s*0"),
|
|
|
|
|
"AnyState→Player-Hit Transition의 m_CanTransitionToSelf가 0이어야 함 (인터럽트 보호).");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 17. PlayerStateTimer 부착 + combatIdleDuration: 5 검증 (2026-05-07) =====
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Player_CombatIdle_Timer_Component_Attached()
|
|
|
|
|
{
|
|
|
|
|
var path = System.IO.Path.GetFullPath(PlayerPrefabPath);
|
|
|
|
|
Assert.IsTrue(System.IO.File.Exists(path));
|
|
|
|
|
var yaml = System.IO.File.ReadAllText(path);
|
|
|
|
|
|
|
|
|
|
// PlayerStateTimer script guid: e5f6789012345678abcdef0102030405
|
|
|
|
|
Assert.IsTrue(yaml.Contains("e5f6789012345678abcdef0102030405"),
|
|
|
|
|
"Player.prefab에 PlayerStateTimer(guid e5f6789012345678abcdef0102030405) 부착 누락.");
|
|
|
|
|
|
|
|
|
|
// combatIdleDuration: 5 직렬화 검증
|
|
|
|
|
Assert.IsTrue(
|
|
|
|
|
System.Text.RegularExpressions.Regex.IsMatch(yaml, @"combatIdleDuration:\s*5"),
|
|
|
|
|
"PlayerStateTimer.combatIdleDuration: 5 직렬화 누락.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 18. death→resurrection Transition 존재 검증 (2026-05-07) =====
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Player_Death_To_Resurrection_Sequence()
|
|
|
|
|
{
|
|
|
|
|
var path = System.IO.Path.GetFullPath(PlayerControllerPath);
|
|
|
|
|
Assert.IsTrue(System.IO.File.Exists(path));
|
|
|
|
|
var yaml = System.IO.File.ReadAllText(path);
|
|
|
|
|
|
|
|
|
|
// Player-Death State에 resurrect 조건 Transition 존재
|
|
|
|
|
Assert.IsTrue(yaml.Contains("m_Name: resurrect"),
|
|
|
|
|
"resurrect Trigger 파라미터 누락.");
|
|
|
|
|
|
|
|
|
|
// Player-Death → Player-Resurrection Transition (fileID 1101000000000000004)
|
|
|
|
|
Assert.IsTrue(yaml.Contains("1101000000000000004"),
|
|
|
|
|
"Player-Death → Player-Resurrection Transition(fileID 1101000000000000004) 누락.");
|
|
|
|
|
|
|
|
|
|
// Player-Resurrection → Player-Idle Transition (fileID 1101000000000000009)
|
|
|
|
|
Assert.IsTrue(yaml.Contains("1101000000000000009"),
|
|
|
|
|
"Player-Resurrection → Player-Idle Transition(fileID 1101000000000000009) 누락.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 19. Health.Resurrect() 메서드 + event 3종 존재 검증 (2026-05-07) =====
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Health_Has_Resurrect_Method_And_Events()
|
|
|
|
|
{
|
|
|
|
|
var healthType = FindTypeByFullName(HealthType);
|
|
|
|
|
Assert.IsNotNull(healthType, "Platformer.Mechanics.Health 타입 로드 실패.");
|
|
|
|
|
|
|
|
|
|
// Resurrect() 메서드
|
|
|
|
|
var resurrectMethod = healthType.GetMethod("Resurrect",
|
|
|
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
|
Assert.IsNotNull(resurrectMethod, "Health.Resurrect() 메서드 부재 (BT5-Dev Hero1 부활 시스템).");
|
|
|
|
|
|
|
|
|
|
// OnDamagedEvent
|
|
|
|
|
var damagedEvent = healthType.GetEvent("OnDamagedEvent",
|
|
|
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
|
Assert.IsNotNull(damagedEvent, "Health.OnDamagedEvent event 부재.");
|
|
|
|
|
|
|
|
|
|
// OnDeathEvent
|
|
|
|
|
var deathEvent = healthType.GetEvent("OnDeathEvent",
|
|
|
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
|
Assert.IsNotNull(deathEvent, "Health.OnDeathEvent event 부재.");
|
|
|
|
|
|
|
|
|
|
// OnResurrectEvent
|
|
|
|
|
var resurrectEvent = healthType.GetEvent("OnResurrectEvent",
|
|
|
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
|
Assert.IsNotNull(resurrectEvent, "Health.OnResurrectEvent event 부재.");
|
2026-04-23 15:21:38 +00:00
|
|
|
}
|
2026-04-23 14:47:51 +00:00
|
|
|
}
|