43 KiB
06. Hero1 적용 설계 (Phase 1)
본 설계 = Phase 1 설계만. Phase 2 적용·Phase 3 검증은 별건. 본 문서 내 코드 변경 영역은 명세만 기록 (Edit/Write 금지 — Phase 2 영역).
§1. 아키텍처 개요
1-1. 설계 목표 (C11 개발 관점 3축)
- 자원 효율성: 모션 8종 × 평균 4프레임 = ~32 sprite 동시 로드. 개별 PNG import 방식이지만 모바일 60fps 충분 허용
- 코드 구조 직관성: 모션 = State, 발동 트리거 = Parameter, 인터럽트 보호 = Animator Behaviour 또는 Health.cs Trigger 가드. 다음 개발자 5분 내 파악 가능
- 범용성: 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건 그대로 활용)
근거:
- PD 직접 명세 자산 그대로 활용 (재발주 없음)
- 모바일 60fps 허용 — 8 sprite × 60fps = 480 draw call/s (Unity 2D 보편 허용)
캐릭터_리소스_규칙_v1.md§4.1 폴더 구조와 정합 (Sprites/Hero1/C1_*.png)- 향후 최적화 필요 시 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_0 → C1_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)
근거:
- SOT(
캐릭터_리소스_규칙_v1.md§3.1.2) "Unity 구현 영역" 명시 —StateMachineBehaviour.OnStateEnter영역 지목 - Health.cs는 Animator 미참조 (현재
health = GetComponent<Health>()정합) → B안은 신규 의존 발생 - Player-Hit
m_CanTransitionToSelf: 0단독으로도 80% 보호. Behaviour 추가 = 잔여 20% (transition 중 재진입) 차단 강화 - 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)
근거:
- C11 단일 책임 원칙 — PlayerController는 이동·점프·facing 책임만 유지
- BT7-Plan
PlayerAttackTicker패턴 정합 — "타이머 컴포넌트 별도" 구조 일관성 - Hero1 외 향후 적 캐릭터 동일 통계 필요 시 EnemyStateTimer로 재사용 가능
- 카드·특성 효과로 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(fileID7700000000000000001)
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에서 발화 가능 (현 설계 변경 없음)PassiveSkillRuntime이Health.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.mdv1 → v1.1)
변경 이력
| 일시 | 버전 | 내용 | 기안 |
|---|---|---|---|
| 2026-05-07 | v1.0 | Phase 1 설계 단독 완료 (BT5-Dev Hero1 캐릭터 교체 — 14 섹션 표준 + 기각 5+ 건 + Phase 2 작업 단위 분해 12건) | 개발팀장 (Opus) |