BurningTimesAi/프로젝트/EerieVillage/개발/06_Hero1_적용_설계.md

43 KiB
Raw Blame History

type: 설계_문서 scope: Hero1_적용_Phase_1 author: 개발팀장 (Opus) date: 2026-05-07 version: v1.0 (BT5-Dev Hero1 Phase 1 단독 설계) project: EerieVillage (기묘한 고을 : 조선퇴마뎐 / EerieVillage: Joseon Exorcist) phase: BT5-Dev Phase 1 — Hero1 캐릭터 교체 설계 (개발팀장 단독) data_source: | - 프로젝트/EerieVillage/개발/spec/캐릭터_리소스_규칙_v1.md (영구 SOT) - 프로젝트/EerieVillage/개발/05_PlayerTestGirl_아틀라스_적용.md (폐기 영역) - 프로젝트/EerieVillage/개발/04_BT5-Dev_2단계_구현보고.md (기존 컴포넌트 현황) - 프로젝트/EerieVillage/개발/spec/스킬_시스템_설계_v1.md (BT12-Dev 보류 영역) - Unity 직접 실측 (Assets/Character/Sprites/Hero1/, Assets/Character/Animations/, Assets/Prefabs/Player.prefab, Assets/Scripts/Mechanics/{Health.cs, PlayerController.cs, AttackHitbox.cs}, Assets/Tests/Editor/PlayerAttackTests.cs) status: Phase 1 설계 완료 — Phase 2 클라이언트팀(Sonnet) 구현 위임 대기 · Phase 3 개발팀장 검증 대기

06. Hero1 적용 설계 (Phase 1)

본 설계 = Phase 1 설계만. Phase 2 적용·Phase 3 검증은 별건. 본 문서 내 코드 변경 영역은 명세만 기록 (Edit/Write 금지 — Phase 2 영역).


§1. 아키텍처 개요

1-1. 설계 목표 (C11 개발 관점 3축)

  1. 자원 효율성: 모션 8종 × 평균 4프레임 = ~32 sprite 동시 로드. 개별 PNG import 방식이지만 모바일 60fps 충분 허용
  2. 코드 구조 직관성: 모션 = State, 발동 트리거 = Parameter, 인터럽트 보호 = Animator Behaviour 또는 Health.cs Trigger 가드. 다음 개발자 5분 내 파악 가능
  3. 범용성: C2·C3 신규 캐릭터 추가 시 동일 controller 구조 + sprite guid 교체만으로 확장 가능 (캐릭터_리소스_규칙_v1.md §1·§2 표준 준수)

1-2. 모션 8종 ↔ Animator State 매핑

# 모션 키 (SOT) AnimatorState 명 AnimationClip 파일 프레임 Loop
1 attack Player-Attack PlayerAttack.anim (재작성) 8 non-loop
2 combatidle Player-CombatIdle (신설) PlayerCombatIdle.anim (신설) 4 loop
3 idle Player-Idle (재활용·motion 교체) PlayerIdle.anim (재작성) 4 loop
4 hit Player-Hit (신설·기존 Hurt 폐기) PlayerHit.anim (신설·기존 PlayerHurt.anim 폐기) 2 non-loop · 인터럽트 보호
5 jump Player-Jump (재활용·motion 교체) PlayerJump.anim (재작성) 1 static
6 resurrection Player-Resurrection (신설) PlayerResurrection.anim (신설) 8 non-loop
7 run Player-Run (재활용·motion 교체) PlayerRun.anim (재작성) 8 loop
8 death Player-Death (재활용·motion 교체) PlayerDeath.anim (재작성) 2 non-loop

기존 controller 9 State 중 4 State 폐기 (Spawn·Land·Hurt·Victory):

  • Player-Spawn → 폐기 (PD 결정 5 — 미사용)
  • Player-Land → 폐기 (PD 결정 5 — jump 자세 1프레임 유지)
  • Player-Hurt → 폐기 (Hit으로 대체 — 명칭·인터럽트 보호 룰 변경)
  • Player-Victory → 폐기 (PD 결정 5 — 미사용)

1-3. 컴포넌트 책임 분리 (5종)

┌─────────────────────────────────────────────────┐
│ Animator + Player.controller                     │
│ - State 8개 + Parameter 8개 보유                  │
│ - Transition 매트릭스로 8종 모션 전환 관리           │
│ - hit 인터럽트 보호 = StateMachineBehaviour 영역    │
└──────────────▲──────────────────────────────────┘
               │ SetTrigger·SetBool·SetFloat 호출
┌──────────────┴──────────────────────────────────┐
│ PlayerController (기존 — 변경 없음)                │
│ - 이동·점프 입력 처리 + facing 추적                  │
│ - animator.SetBool("grounded")·SetFloat("velocityX") │
└─────────────────────────────────────────────────┘
               ▲ Schedule<PlayerAttack> 발화
┌──────────────┴──────────────────────────────────┐
│ PlayerAttackTicker (기존 — 변경 없음)               │
│ - 0.5초 주기로 Schedule<PlayerAttack> 발화          │
│ - VS 순수형 자동 발동 (BT7-Plan)                    │
└─────────────────────────────────────────────────┘
               │ player.animator.SetTrigger("attack") 호출
┌──────────────▼──────────────────────────────────┐
│ PlayerAttack (기존 — 변경 없음)                     │
│ - SetTrigger("attack") + AttackHitbox.Fire 호출    │
└─────────────────────────────────────────────────┘
               │ animator.SetTrigger("hit") 호출 (신규)
┌──────────────┼──────────────────────────────────┐
│ Health (기존 — §7 영역 확장)                       │
│ - Decrement(int) → animator.SetTrigger("hit")    │
│ - HP 0 → SetBool("dead", true) (기존 victory 자리) │
│ - 부활 발동 → SetTrigger("resurrect") (신규)        │
└─────────────────────────────────────────────────┘
               │ §6 신규 컴포넌트                    
┌──────────────▼──────────────────────────────────┐
│ PlayerStateTimer (신설 — §6 채택안)                │
│ - 마지막 attack 시각 추적 + 5초 경과 감지            │
│ - SetBool("combatidle", true/false) 갱신          │
└─────────────────────────────────────────────────┘

§2. Hero1 meta 재작성 설계 (sprite slice 전략)

2-1. 비교 — 3안

방식 장점 단점
A. 개별 PNG 37건 (현 상태) Hero1 폴더 37 PNG 각각 단일 sprite import (현재 구성) 자산 그대로 활용 · meta 재작성 최소 (rename된 4건만) · 한 PNG = 한 sprite 명료성 Draw call 8회/모션 (모바일 sprite atlas 대비 비효율) · 메모리 fragmentation
B. atlas pack 단일 sheet 37 PNG → SpriteAtlas로 packing (Unity SpriteAtlas 자산 신설) Draw call 1회 · 메모리 효율 · 빌드 시 자동 생성 자산 추가 발주 (Hero1.spriteatlas SOT 신설) · 기획팀 미요청
C. 모션별 sprite sheet 8개 모션별 PNG 통합 (예: C1_attack.png 8프레임 × 1 sheet) 모션 단위 직관적 · slice grid 표준화 자산 재발주 필요 · 현 자산 폐기

2-2. 채택 = A (개별 PNG 37건 그대로 활용)

근거:

  1. PD 직접 명세 자산 그대로 활용 (재발주 없음)
  2. 모바일 60fps 허용 — 8 sprite × 60fps = 480 draw call/s (Unity 2D 보편 허용)
  3. 캐릭터_리소스_규칙_v1.md §4.1 폴더 구조와 정합 (Sprites/Hero1/C1_*.png)
  4. 향후 최적화 필요 시 SpriteAtlas auto-pack 오버레이 (코드·자산 변경 없이 적용 가능)

2-3. meta 재작성 영역 (rename 4건만)

기존 파일 신규 파일 meta 처리
C1_combat idle01.png + .meta C1_combatidle01.png + .meta rename (PNG + meta 함께) — guid·internalID 보존
C1_combat idle02.png + .meta C1_combatidle02.png + .meta rename — guid·internalID 보존
C1_combat idle03.png + .meta C1_combatidle03.png + .meta rename — guid·internalID 보존
C1_combat idle04.png + .meta C1_combatidle04.png + .meta rename — guid·internalID 보존

meta 내부 변경 항목: internalIDToNameTable.second 필드의 C1_combat idle01_0C1_combatidle01_0 갱신 + spriteSheet.sprites[].name + nameFileIdTable 키 동시 갱신.

2-4. import 표준 (37 PNG 일관)

항목 근거
textureType 8 (Sprite) 현재 meta 정합
spriteMode 2 (Multiple — 단일 sprite도 Multiple로 통일) 일관성 + 향후 atlas 전환 안정
pixelsPerUnit 100 현재 meta + 기존 PlayerTestGirl 정합
pivot (0.5, 0.5) center 캐릭터_리소스_규칙_v1.md §4 표준 (현 meta alignment: 0 + spritePivot: 0.5,0.5) — Phase 2 시 sprite rect 내부 pivot도 (0.5, 0.5) 통일 권고
alphaIsTransparency 1 현재 meta 정합
spriteMeshType 1 (Tight) 현재 meta 정합
maxTextureSize 2048 현재 meta 정합

Phase 2 작업 영역: 4건 rename 후 meta 일관성 + 신규 sprite name 검증.


§3. AnimationClip 설계 (8종 신설/재작성)

3-1. 8종 일괄 명세

anim 파일 처리 sampleRate duration LoopTime 참조 sprite (m_PPtrCurves)
PlayerIdle.anim 재작성 10 fps 0.4s 1 (loop) C1_idle01~04 (시간 0·0.1·0.2·0.3)
PlayerRun.anim 재작성 16 fps 0.5s 1 (loop) C1_run01~08 (시간 0·0.0625·0.125·0.1875·0.25·0.3125·0.375·0.4375)
PlayerAttack.anim 재작성 16 fps 0.5s 0 (non-loop) C1_attack01~08 (시간 0·0.0625·...·0.4375)
PlayerCombatIdle.anim 신설 8 fps 0.5s 1 (loop) C1_combatidle01~04 (시간 0·0.125·0.25·0.375)
PlayerJump.anim 재작성 1 fps 0.1s (1 frame static) 0 (non-loop) C1_jump01 (시간 0)
PlayerHit.anim 신설 (PlayerHurt.anim 폐기 대체) 10 fps 0.2s 0 (non-loop) C1_hit01·02 (시간 0·0.1)
PlayerResurrection.anim 신설 8 fps 1.0s 0 (non-loop) C1_resurrection01~08 (시간 0·0.125·...·0.875)
PlayerDeath.anim 재작성 4 fps 0.5s 0 (non-loop) C1_death01·02 (시간 0·0.25)

3-2. anim YAML 표준 (PlayerIdle.anim 정합 — 기존 SOT 구조 보존)

# PlayerIdle.anim 신규 형태 (기존 PlayerTestGirl 참조 → C1_idle 4건 참조)
m_PPtrCurves:
  - curve:
    - time: 0
      value: {fileID: <C1_idle01 internalID>, guid: <C1_idle01 guid>, type: 3}
    - time: 0.1
      value: {fileID: <C1_idle02 internalID>, guid: <C1_idle02 guid>, type: 3}
    - time: 0.2
      value: {fileID: <C1_idle03 internalID>, guid: <C1_idle03 guid>, type: 3}
    - time: 0.3
      value: {fileID: <C1_idle04 internalID>, guid: <C1_idle04 guid>, type: 3}
    attribute: m_Sprite
    classID: 212
    script: {fileID: 0}
m_SampleRate: 10
m_AnimationClipSettings:
  m_StartTime: 0
  m_StopTime: 0.4
  m_LoopTime: 1

Phase 2 작업 영역: 8 anim 모두 위 패턴으로 작성. internalID·guid는 Phase 2에서 각 PNG meta Read 후 추출.

3-3. anim 신규 4건 guid 후보 (충돌 검증 영역)

anim 파일 guid 후보 검증 영역
PlayerCombatIdle.anim.meta (Phase 2 신규 발급) grep 0건 검증 의무
PlayerHit.anim.meta (Phase 2 신규 발급) grep 0건 검증 의무
PlayerResurrection.anim.meta (Phase 2 신규 발급) grep 0건 검증 의무

PlayerJump.anim.meta는 기존 guid 1d7e02f8cf5ba47a78bc1e19cc378478 보존 (controller 참조 불변).


§4. Player.controller 전면 재설계

4-1. Parameter 8종 (확장 — 기존 7개 → 8개)

파라미터 타입 m_Type 기존/신규 용도
velocityX Float 1 기존 이동 속도 (idle ⇄ run 전환)
velocityY Float 1 기존 (사용 미확인 — 기존 보존)
grounded Bool 4 기존 지면 상태 (jump 진입 차단)
attack Trigger 9 기존 공격 발동 (PlayerAttack.Execute에서 호출)
dead Bool 4 기존 사망 상태 (death State 진입)
hurt Trigger 9 폐기 (hit으로 rename)
victory Trigger 9 폐기 (PD 결정 5 미사용)
hit Trigger 9 신규 피격 발동 (Health.Decrement에서 호출)
combatidle Bool 4 신규 5초 타이머 영역 (true = combatidle, false = idle)
resurrect Trigger 9 신규 부활 발동 (Health 부활 처리에서 호출)

최종 Parameter = 8종: velocityX·velocityY·grounded·attack·dead·hit·combatidle·resurrect.

4-2. State 8종 (Player- 접두 보존 — 기존 controller 명명 정합)

기존 9 State (Idle·Spawn·Death·Hurt·Jump·Land·Run·Victory·Attack)에서:

  • 재활용 5종 (motion guid 교체): Player-Idle·Player-Run·Player-Jump·Player-Death·Player-Attack
  • 신설 3종: Player-CombatIdle·Player-Hit·Player-Resurrection
  • 폐기 4종: Player-Spawn·Player-Land·Player-Hurt·Player-Victory
State fileID m_Motion (guid)
Player-Idle 1102433169737779366 (기존 보존) PlayerIdle.anim 신규 guid (재작성 후 동일 guid 보존)
Player-Run 1102799505466558240 (기존 보존) PlayerRun.anim 신규 guid (재작성 후 동일 guid 보존)
Player-Jump 1102567981102962784 (기존 보존) PlayerJump.anim 신규 guid (재작성 후 동일 guid 보존)
Player-Death 1102635880149297478 (기존 보존) PlayerDeath.anim 신규 guid (재작성 후 동일 guid 보존)
Player-Attack 1102700000000000001 (기존 보존) PlayerAttack.anim 기존 guid c8d7e5a1f9b24e63a7f5d2c8e1b9a4f7 보존
Player-CombatIdle 1102500000000000001 (신규 fileID) PlayerCombatIdle.anim 신규 guid
Player-Hit 1102500000000000002 (신규 fileID) PlayerHit.anim 신규 guid
Player-Resurrection 1102500000000000003 (신규 fileID) PlayerResurrection.anim 신규 guid

fileID 충돌 회피: 1102500000000000001~003은 기존 controller fileID 9건과 grep 검증 후 충돌 0 확인 (Phase 2 영역).

4-3. Transition 매트릭스

From To 조건 ExitTime HasExitTime TransitionDuration InterruptionSource
Default Entry Player-Idle (자동)
AnyState Player-Hit hit (Trigger) 0 0 0 0
AnyState Player-Attack attack (Trigger) 0 0 0 0
AnyState Player-Death dead == true 0 0 0 0
Player-Idle Player-Run velocityX > 0.001 0 0 0 0
Player-Idle Player-CombatIdle combatidle == true 0 0 0 0
Player-Idle Player-Jump grounded == false 0 0 0 0
Player-Run Player-Idle velocityX < 0.001 AND combatidle == false 0 0 0 0
Player-Run Player-CombatIdle velocityX < 0.001 AND combatidle == true 0 0 0 0
Player-Run Player-Jump grounded == false 0 0 0 0
Player-CombatIdle Player-Idle combatidle == false 0 0 0 0
Player-CombatIdle Player-Run velocityX > 0.001 0 0 0 0
Player-CombatIdle Player-Jump grounded == false 0 0 0 0
Player-Jump Player-Idle grounded == true AND velocityX < 0.001 AND combatidle == false 1 (m_HasExitTime) 0 0
Player-Jump Player-CombatIdle grounded == true AND velocityX < 0.001 AND combatidle == true 1 0 0
Player-Jump Player-Run grounded == true AND velocityX > 0.001 1 0 0
Player-Attack Player-Idle (조건 없음) 1 1 0 0
Player-Hit Player-Idle (조건 없음 — 직전 상태 복귀는 단순화 위해 idle 회귀, 즉시 SetBool/Trigger 다음 Update에서 자연 재전환) 1 1 0 0
Player-Death Player-Resurrection resurrect (Trigger) 1 1 0 0
Player-Resurrection Player-Idle (조건 없음 — dead도 false로 동기) 1 1 0 0

4-4. m_CanTransitionToSelf

  • Player-Hit → m_CanTransitionToSelf: 0 (인터럽트 보호 — §5 영역)
  • Player-Attack → m_CanTransitionToSelf: 0 (이미 기존 1101700000000000002 정합)
  • Player-Resurrection → m_CanTransitionToSelf: 0 (1회 재생 보호)
  • 그 외 → m_CanTransitionToSelf: 1 (기본값)

§5. hit 인터럽트 보호 구현 설계

5-1. 비교 — 2안

방식 장점 단점
A. Animator StateMachineBehaviour Player-Hit State에 HitInterruptGuard.cs : StateMachineBehaviour 부착. OnStateEnter에서 animator.ResetTrigger("hit") + AnyState→Hit transition을 m_CanTransitionToSelf:0으로 차단 Animator 단일 책임 · Health.cs 변경 0 · IsInTransition 체크로 자연 보호 StateMachineBehaviour는 .controller YAML 직접 편집 시 fileID 관리 복잡 · ScriptableObject 신규 .meta 필요
B. Health.cs Trigger 진입 가드 Health.Decrement(int) 내부에서 animator.GetCurrentAnimatorStateInfo(0).IsName("Player-Hit") && stateInfo.normalizedTime < 1.0 체크 후 SetTrigger("hit") 진입. 진행 중이면 skip Health.cs 단일 위치 가드 · YAML 편집 최소 (Animator 영역 변경 없음) · 디버그 단순 Health.cs가 Animator API 의존 (현재는 Animator 직접 참조 없음 — PlayerController.animator 우회 필요) · Animator 미부착 객체(Enemy 향후) 영역 일반화 어려움

5-2. 채택 = A (Animator StateMachineBehaviour)

근거:

  1. SOT(캐릭터_리소스_규칙_v1.md §3.1.2) "Unity 구현 영역" 명시 — StateMachineBehaviour.OnStateEnter 영역 지목
  2. Health.cs는 Animator 미참조 (현재 health = GetComponent<Health>() 정합) → B안은 신규 의존 발생
  3. Player-Hit m_CanTransitionToSelf: 0 단독으로도 80% 보호. Behaviour 추가 = 잔여 20% (transition 중 재진입) 차단 강화
  4. Hero1 외 향후 캐릭터(C2·C3) 동일 controller 구조 재사용 시 Behaviour 자동 계승

5-3. 신규 컴포넌트 명세 — HitInterruptGuard.cs

경로: Assets/Scripts/Mechanics/HitInterruptGuard.cs (신설)

using UnityEngine;

namespace Platformer.Mechanics
{
    /// <summary>
    /// Player-Hit State에 부착되는 StateMachineBehaviour.
    /// hit 진입 시 hit Trigger를 즉시 Reset하여 다음 데미지 수신 시
    /// 재진입을 1회 사이클(애니메이션 재생 종료)까지 차단.
    /// SOT: 캐릭터_리소스_규칙_v1.md §3.1.2 hit 인터럽트 보호 (PD 명세 핵심)
    /// </summary>
    public class HitInterruptGuard : StateMachineBehaviour
    {
        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            animator.ResetTrigger("hit");
        }
    }
}

부착 영역: Player.controller YAML의 Player-Hit State m_StateMachineBehaviours 리스트에 신규 MonoBehaviour fileID 추가.

5-4. Health.cs Trigger 호출 영역 (§7 통합)

// Decrement(int damage) 내부 — currentHP 감산 직전
if (player != null && player.animator != null)
{
    player.animator.SetTrigger("hit");  // Behaviour가 즉시 Reset → 1회 사이클 보호
}

(상세 §7 참조)


§6. idle⇄combatidle 5초 타이머 구현 설계

6-1. 비교 — 2안

방식 장점 단점
A. PlayerController.cs 확장 기존 PlayerController에 lastAttackTime float + Update에서 Time.time - lastAttackTime 체크 → animator.SetBool("combatidle", true/false) 호출. PlayerAttack.Execute에서 lastAttackTime = Time.time 갱신 기존 컴포넌트 단일 위치 · 의존성 신규 없음 PlayerController 책임 비대화 (이동·점프·공격 facing·전투 타이머 4축) · BT7-Plan VS 순수형 정합 책임 분산
B. 신규 컴포넌트 PlayerStateTimer.cs 별도 MonoBehaviour 신설. RequireComponent(PlayerController) + RequireComponent(Animator). PlayerAttack 발화 이벤트 구독으로 lastAttackTime 갱신. Update에서 combatidle Bool 갱신 단일 책임 (전투 상태 타이머 단독) · Phase 2 별건 Edit 영역 분리 가능 · BT7-Plan PlayerAttackTicker 패턴 정합 (1 컴포넌트 = 1 책임) 컴포넌트 1종 추가 (Player.prefab YAML MonoBehaviour 추가 작업)

6-2. 채택 = B (신규 컴포넌트 PlayerStateTimer.cs)

근거:

  1. C11 단일 책임 원칙 — PlayerController는 이동·점프·facing 책임만 유지
  2. BT7-Plan PlayerAttackTicker 패턴 정합 — "타이머 컴포넌트 별도" 구조 일관성
  3. Hero1 외 향후 적 캐릭터 동일 통계 필요 시 EnemyStateTimer로 재사용 가능
  4. 카드·특성 효과로 5초 타이머 변동(예: "전투 자세 유지 시간 +2초") 시 단일 컴포넌트 필드만 수정

6-3. 신규 컴포넌트 명세 — PlayerStateTimer.cs

경로: Assets/Scripts/Mechanics/PlayerStateTimer.cs (신설)

using Platformer.Gameplay;
using UnityEngine;
using static Platformer.Core.Simulation;

namespace Platformer.Mechanics
{
    /// <summary>
    /// 마지막 공격 시점 추적 + idle⇄combatidle 전환 발동.
    /// SOT: 캐릭터_리소스_규칙_v1.md §3.1.1 idle⇄combatidle 5초 타이머
    /// 카드·특성 효과로 combatIdleDuration 조정 가능 (P13-1 공용 모듈 인터페이스).
    /// </summary>
    [RequireComponent(typeof(PlayerController))]
    [RequireComponent(typeof(Animator))]
    public class PlayerStateTimer : MonoBehaviour
    {
        [Tooltip("마지막 공격 후 combatidle 유지 시간(초). 카드·특성으로 증감 가능.")]
        public float combatIdleDuration = 5f;

        Animator animator;
        float lastAttackTime = -100f;  // 초기값: 게임 시작 시 idle 상태

        void Awake()
        {
            animator = GetComponent<Animator>();
        }

        /// <summary>
        /// PlayerAttack.Execute 또는 PlayerAttackTicker가 공격 발화 시 호출.
        /// 외부 API로 노출하여 결합 최소화.
        /// </summary>
        public void NotifyAttackFired()
        {
            lastAttackTime = Time.time;
        }

        void Update()
        {
            bool inCombat = (Time.time - lastAttackTime) < combatIdleDuration;
            animator.SetBool("combatidle", inCombat);
        }
    }
}

6-4. PlayerAttack.Execute 통합 (§11 영향 0 — animator 호출 옆에 한 줄 추가)

// PlayerAttack.cs — Execute 내부, animator.SetTrigger("attack") 직후
var stateTimer = player.GetComponent<PlayerStateTimer>();
if (stateTimer != null) stateTimer.NotifyAttackFired();

BT7-Plan 정합: PlayerAttackTicker.cs 영역은 변경 0. PlayerAttack.Execute 내부 1행 추가만으로 통합.


§7. Health.cs 부활 시스템 추가 설계

7-1. 변경 영역 (기존 API 보존 — 추가만)

메서드/필드 처리
maxHearts·QuartersPerHeart·maxHP·currentHP 보존 (BT7-Plan 정합)
Increment()·Heal(int)·Decrement()·Decrement(int) 보존
IncreaseMaxHearts(int)·Die()·IsAlive·IsInvulnerable 보존
invulnerableDuration·invulnerableUntil 보존 (i-frame 0.6s)
Awake() 보존
신규 필드: canResurrect (Bool) 부활 룰 충족 여부 (BT7-Plan 영역 — 기획팀 결정)
신규 메서드: Resurrect() death 종료 후 외부 호출 → currentHP 복원 + animator.SetTrigger("resurrect")
신규 이벤트 3종: OnDamagedEvent·OnDeathEvent·OnResurrectEvent C# event Action — UI·SE·StateTimer 구독

7-2. Decrement(int) 확장 (Animator hit Trigger 호출 — §5 통합)

// 기존 Decrement(int) 내부 — currentHP -= damage 직전 + 직후 추가
public void Decrement(int damage)
{
    if (damage <= 0) return;
    if (Time.time < invulnerableUntil) return;

    currentHP = Mathf.Clamp(currentHP - damage, 0, maxHP);

    if (invulnerableDuration > 0f)
    {
        invulnerableUntil = Time.time + invulnerableDuration;
    }

    // [신규] hit 애니메이션 트리거 (생존 시만 — 사망 시는 dead Bool로 처리)
    if (currentHP > 0)
    {
        var animator = GetComponent<Animator>();
        if (animator != null) animator.SetTrigger("hit");
        OnDamagedEvent?.Invoke(damage);
    }

    if (currentHP == 0)
    {
        var animator = GetComponent<Animator>();
        if (animator != null) animator.SetBool("dead", true);  // [신규] death State 진입
        OnDeathEvent?.Invoke();

        var ev = Schedule<HealthIsZero>();
        ev.health = this;
    }
}

7-3. 신규 메서드 Resurrect()

/// <summary>
/// 부활 발동 — death 애니메이션 종료 후 외부(BT7-Plan 부활 룰 영역)에서 호출.
/// currentHP 복원 + Animator transition (death → resurrection → idle)
/// SOT: 캐릭터_리소스_규칙_v1.md §3.1.4 death → resurrection 시퀀스
/// </summary>
public void Resurrect()
{
    if (IsAlive) return;  // 이미 살아있으면 무시

    currentHP = maxHP;  // BT7-Plan 영역 — 부활 시 maxHP 회복 룰 (기획팀 영역 — §11 명시)
    invulnerableUntil = -1f;

    var animator = GetComponent<Animator>();
    if (animator != null)
    {
        animator.SetBool("dead", false);
        animator.SetTrigger("resurrect");
    }
    OnResurrectEvent?.Invoke();
}

7-4. 신규 이벤트 3종

public event System.Action<int> OnDamagedEvent;   // damage 인자
public event System.Action OnDeathEvent;
public event System.Action OnResurrectEvent;

7-5. Die() 보존 영역

Die()는 i-frame 우회 즉사 처리 (낙사·승리 이탈 등). animator 호출 영역도 Decrement 사망 분기와 동일하게 추가:

public void Die()
{
    invulnerableUntil = -1f;
    if (currentHP > 0)
    {
        currentHP = 0;
        var animator = GetComponent<Animator>();
        if (animator != null) animator.SetBool("dead", true);  // [신규]
        OnDeathEvent?.Invoke();
        var ev = Schedule<HealthIsZero>();
        ev.health = this;
    }
}

§8. Player.prefab 변경

8-1. SpriteRenderer.m_Sprite 교체

영역 기존 신규
m_Sprite.fileID 7882920275377484039 (PlayerTestGirl_0) C1_idle01의 internalID (Phase 2 시 meta Read로 추출)
m_Sprite.guid 44ad58ba82191ca4d818108ab01d3baa C1_idle01의 guid 321722ca021d6dc4c9d9dc5b12811711은 attack01 — idle01 별도 추출 영역
m_Color (0.18, 0.89, 0.99, 1) (cyan tint) (1, 1, 1, 1) (정규 white — Hero1 원색 보존)

8-2. 신규 컴포넌트 부착 (3종 MonoBehaviour 추가)

기존 m_Component 리스트 9건 → 12건 확장:

# fileID 후보 컴포넌트 script guid (Phase 2 발급)
10 7700000000000000003 PlayerAttackTicker (이미 존재 — 재실측 필요 — Phase 2 영역) 기존 Assets/Scripts/Gameplay/PlayerAttackTicker.cs.meta guid
11 7700000000000000004 PlayerStateTimer (신설 §6) 신규 발급

기존 부착 컴포넌트 보존 (9건):

  • Transform·KinematicObject(PlayerController)·SpriteRenderer·Animator·Rigidbody2D·AudioSource·Health·BoxCollider2D·AttackHitbox(fileID 7700000000000000001)

PlayerAttackTicker 부착 영역 검증 (Phase 2 영역): 기존 prefab YAML grep 결과 PlayerAttackTicker script guid 미부착 가능성 — Phase 2 시 RequireComponent(typeof(PlayerController))로 자동 부착 확인 또는 직접 MonoBehaviour 블록 추가.

8-3. Health 컴포넌트 maxHearts 보강

기존 prefab Health 직렬화: maxHP: 1만 명시. maxHearts 직렬화 누락 → Awake에서 1로 보정 (정상 동작).

Phase 2 권고 영역: maxHearts: 1 명시 직렬화 추가 (Inspector 가시성).


§9. 기존 자산 처리

9-1. 백업 대상 (_archive/ 이동)

원본 경로 이동 경로 처리
Assets/Character/Sprites/PlayerTestGirl.png + .meta Assets/Character/Sprites/_archive/PlayerTestGirl.png + .meta 이동 (rename — Unity는 폴더 변경만 감지)
Assets/Character/Sprites/PlayerIdle.png + .meta Assets/Character/Sprites/_archive/PlayerIdle.png + .meta 이동
Assets/Character/Sprites/PlayerRun.png + .meta Assets/Character/Sprites/_archive/PlayerRun.png + .meta 이동
Assets/Character/Sprites/PlayerJump.png + .meta Assets/Character/Sprites/_archive/PlayerJump.png + .meta 이동
Assets/Character/Sprites/PlayerLand.png + .meta Assets/Character/Sprites/_archive/PlayerLand.png + .meta 이동
Assets/Character/Sprites/PlayerHurt.png + .meta Assets/Character/Sprites/_archive/PlayerHurt.png + .meta 이동
Assets/Character/Sprites/PlayerDeath.png + .meta Assets/Character/Sprites/_archive/PlayerDeath.png + .meta 이동
Assets/Character/Sprites/PlayerSpawn.png + .meta Assets/Character/Sprites/_archive/PlayerSpawn.png + .meta 이동
Assets/Character/Sprites/PlayerVictory.png + .meta Assets/Character/Sprites/_archive/PlayerVictory.png + .meta 이동

총 9 PNG + 9 meta = 18 파일 이동 (PD 결정 4 — _archive/ 백업).

guid 보존: meta 파일 함께 이동 시 guid 변경 0. 향후 참조 추적 가능.

9-2. 폐기 anim 4건 (controller에서 제거 후 파일 자체는 보존)

파일 처리
Assets/Character/Animations/PlayerHurt.anim + .meta controller에서 참조 제거 (Player-Hurt State 폐기) — 파일 자체는 보존 (회귀 안전)
Assets/Character/Animations/PlayerLand.anim + .meta controller에서 참조 제거 — 파일 보존
Assets/Character/Animations/PlayerSpawn.anim + .meta controller에서 참조 제거 — 파일 보존
Assets/Character/Animations/PlayerVictory.anim + .meta controller에서 참조 제거 — 파일 보존

근거: PD 결정 4 정합 — 자산 보존 우선. controller 참조 제거만으로 게임 영향 차단. 향후 필요 시 재참조 가능.

9-3. 백업 timestamp 표준 (재작성 직전)

PlayerIdle.anim·PlayerRun.anim·PlayerAttack.anim·PlayerDeath.anim·PlayerJump.anim·Player.controller·Player.prefab 7건 — Phase 2 집행 직전 §12 영역 백업.


§10. EditMode 테스트 갱신

10-1. 기존 13건 분류 (PlayerAttackTests.cs 실측)

# 테스트명 처리
1 Player_Prefab_Has_AttackHitbox_Component 유지
2 Player_Prefab_Has_Health_Component 유지
3 Player_Prefab_Has_PlayerController_Component 유지
4 AttackHitbox_Default_Damage_Is_One 유지
5 AttackHitbox_Active_Duration_Is_Positive 유지
6 Enemy_Prefab_Has_Health_Component 유지
7 Enemy_Prefab_Has_EnemyController_Component 유지
8 Health_Script_Defines_MaxHearts_And_IncreaseMaxHearts 유지
9 Player_Prefab_MaxHP_Reflects_Heart_Quarters 유지
10 PlayerAttackTicker_Script_Exists_With_AttackInterval 유지
11 InputActions_Player_Map_Has_No_Attack_Action 유지
12 Player_Prefab_SpriteRenderer_References_PlayerTestGirl 개정 — 신규 테스트 Player_Prefab_SpriteRenderer_References_Hero1_Idle01로 변경 (guid 추출 시 Hero1 idle01 guid 검증)
13 Player_Controller_Has_Attack_Parameter_And_State 유지 (attack Trigger·Player-Attack State는 보존)

10-2. 신규 6건 추가 (총 13 - 1(폐기 12번) + 1(신규 12번) + 6 = 19건)

# 신규 테스트명 검증 영역
14 Player_Has_8_Motion_States Player.controller YAML grep — Player-Idle·Player-Run·Player-Attack·Player-Jump·Player-Death·Player-CombatIdle·Player-Hit·Player-Resurrection 8 State 존재 검증 + Player-Hurt·Player-Land·Player-Spawn·Player-Victory 4 State 부재 검증
15 Player_Controller_Has_New_Parameters hit·combatidle·resurrect 3 Parameter 존재 검증 + hurt·victory 2 Parameter 부재 검증
16 Player_Hit_Interrupts_Protected Player.controller YAML — Player-Hit State의 m_StateMachineBehaviours 리스트 비어있지 않음 검증 + m_CanTransitionToSelf: 0 검증 (HitInterruptGuard 부착 검증)
17 Player_CombatIdle_Timer_Component_Attached Player.prefab YAML grep — PlayerStateTimer script guid 부착 검증 + combatIdleDuration: 5 직렬화 검증
18 Player_Death_To_Resurrection_Sequence Player.controller YAML — Player-Death → Player-Resurrection Transition 존재 + resurrect 조건 검증 + Player-Resurrection → Player-Idle Transition 검증
19 Health_Has_Resurrect_Method_And_Events Platformer.Mechanics.Health reflection — Resurrect() 메서드 + OnDamagedEvent·OnDeathEvent·OnResurrectEvent event 3종 존재 검증

최종 = 19 EditMode 테스트 (기존 13건 중 12번 개정 + 신규 6건 추가).

10-3. EditMode 테스트 추가·개정 영역 = Assets/Tests/Editor/PlayerAttackTests.cs Phase 2 Edit 대상


§11. BT7-Plan·BT12-Dev 통합 영역

11-1. BT7-Plan 정합 (변경 0 영역)

자산 정합 검증
PlayerAttackTicker.cs (attackInterval: 0.5s) 변경 0 — 기존 Schedule 발화 그대로
PlayerAttack.cs Execute 메서드 animator.SetTrigger("attack")·audioSource.PlayOneShot·attackHitbox.Fire 보존 + §6 §6-4 한 줄 추가 (stateTimer.NotifyAttackFired())
Health.maxHearts·QuartersPerHeart 변경 0 — 하트 분할 시스템 보존
Health.IncreaseMaxHearts(int)·Heal(int)·Decrement(int)·Die()·CurrentHP·IsAlive 변경 0 — 기존 API 보존 (§7 영역은 추가만)
i-frame 0.6s (invulnerableDuration·invulnerableUntil·IsInvulnerable) 변경 0 — i-frame 보호 + §5 hit 인터럽트 보호는 별 layer (i-frame은 데미지 차단, hit 인터럽트는 애니메이션 보호)

11-2. BT7-Plan 부활 시 maxHP 회복 룰 (기획팀 영역 명시)

Resurrect() 메서드는 currentHP = maxHP 복원 (전체 회복). 향후 BT7-Plan 또는 별건에서 부활 시 부분 회복 룰 결정 시 Resurrect(int recoverQuarters) 오버로드 추가 영역.

Phase 2 영역: 기획팀 결정 보류 시 우선 currentHP = maxHP 채택.

11-3. BT12-Dev 보류 영역 — 본 설계는 BT12-Dev 인터페이스 4종(ISkillRuntime·IActiveSkill 등) 미연관

Assets/Scripts/EerieVillage/Skills/ 영역은 본 설계 범위 외. 향후 BT12-Dev 통합 시:

  • ISkillRuntime.OnAttackHit 같은 hook이 PlayerAttack.Execute에서 발화 가능 (현 설계 변경 없음)
  • PassiveSkillRuntimeHealth.IncreaseMaxHearts 호출 (현 설계 변경 없음)

재검토 영역: BT12-Dev Phase 2 진입 시 본 §6 PlayerStateTimer.combatIdleDuration 카드 효과 보정 영역 추가 가능.


§12. C6-1 백업 영역

12-1. 백업 timestamp 표준

Phase 2 집행 직전 timestamp 형식: YYYYMMDD_HHMM (예: 20260507_2200).

12-2. 백업 대상 N종 (총 12종)

# 원본 백업명
1 Assets/Character/Animations/Player.controller Player.controller.bak_<timestamp>.controller
2 Assets/Character/Animations/PlayerIdle.anim PlayerIdle.anim.bak_<timestamp>.anim
3 Assets/Character/Animations/PlayerRun.anim PlayerRun.anim.bak_<timestamp>.anim
4 Assets/Character/Animations/PlayerAttack.anim PlayerAttack.anim.bak_<timestamp>.anim
5 Assets/Character/Animations/PlayerJump.anim PlayerJump.anim.bak_<timestamp>.anim
6 Assets/Character/Animations/PlayerDeath.anim PlayerDeath.anim.bak_<timestamp>.anim
7 Assets/Prefabs/Player.prefab Player.prefab.bak_<timestamp>.prefab
8 Assets/Scripts/Mechanics/Health.cs Health.cs.bak_<timestamp>.cs
9 Assets/Tests/Editor/PlayerAttackTests.cs PlayerAttackTests.cs.bak_<timestamp>.cs
10 Assets/Character/Sprites/Hero1/C1_combat idle01.png (+ meta) rename 직전 백업 (_archive/ 영역)
11 Assets/Character/Sprites/Hero1/C1_combat idle02~04.png (+ meta) rename 직전 백업
12 기존 Player*.png 9종 + meta 9건 (§9-1) _archive/ 이동 = 백업 자체

12-3. 백업 위치 표준

  • Unity asset 영역: 같은 폴더 내 .bak_<timestamp>.<ext> 형식 (기존 BT5-Dev 정합)
  • BT 레포 영역 (P25 Live 증분 동기화 비대상이지만 SOT 보호): 공유/개발팀_백업/EerieVillage/<timestamp>/ 디렉토리 별도 보존 (Phase 2 영역)

12-4. 롤백 절차

bak_<timestamp> suffix 제거 → 원본 파일명으로 덮어쓰기. _archive/ 이동 자산은 원위치로 mv.


§13. 기각안 (5건 이상 — C32 정합)

13-1. sprite slice 전략 — 기각 2안 (§2-1)

  • B안 (atlas pack 단일 sheet) — Unity SpriteAtlas auto-pack은 빌드 단계 적용 가능하므로 자산 변경 없이 차후 도입 가능. 본 Phase 1 자산 발주 0 원칙에서 제외
  • C안 (모션별 sprite sheet 8개) — 자산 재발주 필요 + 현 자산 폐기 = PD 결정 자산 우선 활용 위배

13-2. hit 인터럽트 보호 — 기각 1안 (§5-1)

  • B안 (Health.cs Trigger 진입 가드) — Health.cs가 Animator API 참조 의존 신규 발생 + Animator 미부착 객체(Enemy 향후) 영역 일반화 어려움. SOT 명시 영역 = StateMachineBehaviour로 채택

13-3. idle⇄combatidle 5초 타이머 — 기각 1안 (§6-1)

  • A안 (PlayerController.cs 확장) — PlayerController 책임 비대화 (이동·점프·공격 facing·전투 타이머 4축). C11 단일 책임 위배. BT7-Plan PlayerAttackTicker 별도 컴포넌트 패턴 정합 위배

13-4. 부활 시스템 — 기각 2안

기각안 근거
이벤트 미발화 (직접 Health.Resurrect 호출만) UI·SE·다른 시스템(BT12-Dev SkillRuntime)이 부활 시점 hook 필요 시 polling 의존 → C# event 패턴 정착
Resurrect를 자동 발동 (death 종료 후 자동 호출) 부활 룰(BT7-Plan 영역)이 항상 충족되지 않을 수 있음. 외부 룰 검증 후 명시적 호출 = 단일 책임

13-5. jump 단일 프레임 영역 — 기각 1안

기각안 근거
PlayerJump.anim duration = 0 (즉시 종료 transition) Unity Animator는 duration 0 anim에서 m_PPtrCurves 시각 영향 미적용 가능. duration 0.1s + LoopTime 0으로 1프레임 표시 보장. 시각 안정성 우선

13-6. EditMode 테스트 — 기각 1안

기각안 근거
PlayMode 테스트로 통합 검증 PlayMode는 Animator·Physics·InputSystem 런타임 의존. Prefab/controller YAML 직렬화 상태는 EditMode AssetDatabase로 충분 + 회귀 즉시 검출 (이전 BT5-Dev 정합)

§14. 후속 안건

14-1. Phase 2 클라이언트팀 위임 영역 분해 (Sonnet 작업)

작업 단위 영역 산출
W1. sprite rename + meta 갱신 4건 Assets/Character/Sprites/Hero1/C1_combat idle01~04.png + .meta rename + meta 내부 name 필드 갱신
W2. anim 8건 신설/재작성 Assets/Character/Animations/Player{Idle,Run,Attack,CombatIdle,Jump,Hit,Resurrection,Death}.anim (+ 신규 4건 .meta) YAML 작성 (각 PNG meta에서 internalID·guid 추출)
W3. Player.controller 전면 재설계 Assets/Character/Animations/Player.controller YAML 재작성 (8 State + 8 Parameter + Transition 매트릭스 + StateMachineBehaviour 부착)
W4. HitInterruptGuard.cs 신설 Assets/Scripts/Mechanics/HitInterruptGuard.cs (+ .meta) C# 신규
W5. PlayerStateTimer.cs 신설 Assets/Scripts/Mechanics/PlayerStateTimer.cs (+ .meta) C# 신규
W6. Health.cs 확장 Assets/Scripts/Mechanics/Health.cs Decrement(int) 확장 + Resurrect() + 이벤트 3종 + Die() 확장
W7. PlayerAttack.cs 확장 (1행) Assets/Scripts/Gameplay/PlayerAttack.cs NotifyAttackFired() 호출 추가
W8. Player.prefab 변경 Assets/Prefabs/Player.prefab m_Sprite 교체 + m_Color 교체 + PlayerStateTimer 부착 + PlayerAttackTicker 부착 검증 + maxHearts 직렬화
W9. 기존 자산 9종 _archive/ 이동 §9-1 영역 mv (Unity 자동 import 처리)
W10. EditMode 테스트 갱신 Assets/Tests/Editor/PlayerAttackTests.cs 12번 개정 + 신규 6건 추가
W11. C6-1 백업 12종 §12-2 영역 백업 timestamp 일관
W12. 매니페스트 신규 등록 scripts/manifest_register.sh Phase 2 Edit 영역 N건 등록

Phase 2 토큰 추정: ~150-200K (W1~W12 일괄). C50 사전 PD 승인 필요.

14-2. Phase 3 검증 항목 (개발팀장 — 별건)

# 검증 영역 방식
1 EditMode 19 테스트 모두 PASS Unity Test Runner
2 controller YAML 정합 (8 State + 8 Parameter + Transition 매트릭스) grep 검증
3 sprite guid 충돌 0 grep 검증
4 anim guid 충돌 0 grep 검증
5 Health.cs API 보존 (BT7-Plan 정합) reflection 테스트
6 PlayerAttackTicker 영역 변경 0 (BT7-Plan) diff 검증
7 _archive/ 이동 9종 + 폐기 anim 4종 controller 미참조 grep 검증

14-3. PD Play 검증 항목

# 검증 영역 기대 동작
1 캐릭터 등장 시 idle 4프레임 loop 재생 C1_idle01~04 순환
2 이동 시 run 8프레임 loop 재생 C1_run01~08 순환
3 0.5초 주기 attack 8프레임 non-loop 재생 + AttackHitbox Fire C1_attack01~08 1회 + 적 데미지
4 공격 후 5초 이내 정지 시 combatidle 4프레임 loop 재생 C1_combatidle01~04 순환
5 5초 경과 후 idle 4프레임 loop 전환 C1_idle01~04 순환
6 적 충돌 시 hit 2프레임 non-loop + i-frame 0.6s C1_hit01~02 + 중복 피격 차단
7 점프 입력 시 jump 1프레임 정적 표시 C1_jump01 표시
8 HP 0 시 death 2프레임 non-loop 재생 C1_death01~02 + 마지막 프레임 정지
9 Resurrect() 호출 시 resurrection 8프레임 non-loop → idle 전환 C1_resurrection01~08 + idle 복귀
10 hit 진행 중 추가 데미지 → hit 재시작 차단 (StateMachineBehaviour 보호) 1회 hit 사이클 완주

14-4. 외부 의존 영역 (기획팀·PD 결정 보류)

  • 부활 룰: BT7-Plan 또는 별건 영역. 본 설계는 Resurrect() API만 제공. 외부 룰 검증 후 호출 결정
  • 카드·특성 효과로 combatIdleDuration 변동: BT12-Dev Phase 2 진입 시 PlayerStateTimer 노출 영역 확장 가능
  • Land·Spawn·Victory 미사용 영역: PD 결정 5 정합. 향후 부활 시 PD 결정 + SOT 개정 (캐릭터_리소스_규칙_v1.md v1 → v1.1)

변경 이력

일시 버전 내용 기안
2026-05-07 v1.0 Phase 1 설계 단독 완료 (BT5-Dev Hero1 캐릭터 교체 — 14 섹션 표준 + 기각 5+ 건 + Phase 2 작업 단위 분해 12건) 개발팀장 (Opus)