---
type: 구현_보고서
scope: BT7-Dev_Phase1
author: 개발팀장
date: 2026-04-24
version: v1.0
project: EerieVillage (기묘한 고을 : 조선퇴마뎐 / EerieVillage: Joseon Exorcist)
phase: BT7-Plan 개발 집행 Phase 1 — VS 순수형 자동 발동 + 하트 분할 시스템
data_source: Unity 프로젝트 직접 Edit (D:/NerdNavis/EerieVillage) + BT 레포 문서
status: 코드·InputActions·테스트 갱신 완료 (PD Unity Editor Play 검증 대기)
---

# 06. BT7-Plan VS 순수형 반영 재구조 (Phase 1 개발 집행)

## 1. 배경

2026-04-24 PD 직접 지시 11건 중 **개발 영향 항목**을 Unity 코드·InputActions·EditMode 테스트에 반영.

### 1.1 PD 확정 방향 (개발 연관)

1. **VS 순수형 확정** — 공격 버튼 제거, 이동·점프만. 플레이어 공격은 자동 발동(주기 타이머)
2. **기본 라이프 4** — 하트 1개 = 4 HP (젤다 분할). 카드·성장으로 최대 하트 수 증가 가능
3. **적 ATK 1부터 점진 강화** — `PlayerHP -= EnemyATK` 쿼터 단위
4. **동일 카드 스택 업그레이드 Lv.5까지** (Lv.3에서 확장)
5. **각성 조건 VS 원작 동일** — 액티브 Lv.5 + 특정 패시브 1개 이상 + 보물상자

### 1.2 적용 범위 한정

- 본 Phase 1은 **Unity 코드·설정 뼈대 이행**이 목적. `attackInterval`·`maxHearts`·`적 ATK 테이블` 등 구체 수치는 기획 balance/01 v0.2 확정 후 튠
- 카드 시스템(3분류)·각성 메커니즘·보물상자·시작 무기·HUD 하트 분할 UI는 **후속 Phase** — 본 문서 범위 외
- 적 ATK 수치 테이블은 **TODO 주석**만 반영 (balance/01 v0.2 미확정)

## 2. 편집 대상 (Unity 외부 레포 `D:/NerdNavis/EerieVillage/`)

### 2.1 `Assets/Scripts/Gameplay/PlayerAttack.cs` (개정)

**변경 요지**: 이벤트 Execute 로직은 유지하되, **발화 트리거가 수동 입력에서 주기 타이머로 전환**됨을 주석에 명시. 본 파일은 이벤트 정의이므로 발화 로직(타이머)은 별도 컴포넌트로 분리 (SRP).

**주요 변경 라인**:
- 파일 상단 XML 주석: "Fired when the player triggers an attack (mouse left click / touch attack button)" → "Player attack event — fires on a periodic timer (no manual input). BT7-Plan PD 지시 2026-04-24 — VS 순수형 자동 발동 전환 (공격 버튼 제거...)"
- `direction` 필드 주석: "PlayerController.facing 기반"으로 수정 (Ticker가 참조)
- Execute 본문 로직 변경 없음 — Animator Trigger, AudioClip, AttackHitbox.Fire 순차 호출 유지

### 2.2 `Assets/Scripts/Gameplay/PlayerAttackTicker.cs` (신설)

**역할**: `[RequireComponent(typeof(PlayerController))]` Player GameObject에 부착되어 `attackInterval` 초마다 `Schedule<PlayerAttack>` 이벤트를 발화.

**필드 설계**:
- `attackInterval` (기본 0.5s, Inspector 노출) — balance/01 v0.2 튠 대상
- `startupDelay` (기본 0.3s, Inspector 노출) — Spawn 애니메이션 여유
- `attackTimer` (내부) — Time.deltaTime 감산 누적

**Update 로직**:
```csharp
if (!player.controlEnabled || attackInterval <= 0f) return;
attackTimer -= Time.deltaTime;
if (attackTimer <= 0f)
{
    attackTimer += attackInterval;  // 오버플로 누적 방지 (= 0 리셋이 아닌 + 연산)
    var ev = Schedule<PlayerAttack>();
    ev.player = player;
    ev.direction = player.Facing;
}
```

**확장 API**: `ApplyIntervalMultiplier(float multiplier)` — 카드·특성 효과가 주기 배수 조정할 때 사용. 최소 0.05s clamp.

**설계 근거** (C11):
- PlayerAttack 이벤트(로직) vs Ticker(발화 트리거) 분리 — SRP + 범용성
- 타이머 방식을 교체(예: 카드 수에 따라 동적 주기)하려면 Ticker만 교체. 이벤트는 그대로 재사용

**.meta guid**: `c7d8e9f0a1b20314253647586978a9b0` (충돌 없음 grep 검증)

### 2.3 `Assets/Settings/InputSystem_Actions.inputactions` (개정)

**변경 요지**: Player 맵의 Attack 액션 정의·바인딩 **완전 제거**.

**제거된 섹션**:
1. `actions[].name == "Attack"` 객체 (id `c9d8e7f6-a5b4-4c3d-2e1f-0a9b8c7d6e5f`, Button type)
2. `bindings[].action == "Attack"` 바인딩 2종:
   - `<Mouse>/leftButton` (Keyboard&Mouse 그룹)
   - `<Gamepad>/rightTrigger` (Gamepad 그룹)

Move·Jump 액션 + UI 맵 전체 유지.

### 2.4 `Assets/Scripts/Mechanics/Health.cs` (개정)

**변경 요지**: 하트 분할 시스템(젤다 방식) 전면 도입.

**신규·개정 API**:
- `const int QuartersPerHeart = 4` 정의
- `maxHearts` 필드 (기본 1, Inspector 노출) — 카드·특성으로 외부 조작
- `maxHP` 필드 기본값 `QuartersPerHeart` (= 4) — `maxHearts * 4`로 Awake에서 재산정
- `Heal(int quarters)` 신규 메서드 — 쿼터 단위 회복, maxHP 상한 체크
- `IncreaseMaxHearts(int delta)` 신규 메서드 — 최대 하트 수 증감 + currentHP 비례 증가 (음수 delta 허용 + clamp)
- `Decrement(int damage)` 오버로드 — 쿼터 단위 피해. 기존 `Decrement()`는 `Decrement(1)`로 위임
- `CurrentHP` 공개 프로퍼티 — HUD가 하트 분할 UI 그리기 위해 읽음
- i-frame 0.6s 유지 (연속 히트 방지는 하트 분할 후에도 필요)
- `Die()` 즉사 로직 단순화 — while 루프 제거, `currentHP = 0` 단일 설정 후 HealthIsZero 이벤트

**Awake 초기화**:
```csharp
if (maxHearts <= 0) maxHearts = 1;
int expected = maxHearts * QuartersPerHeart;
if (maxHP != expected) maxHP = expected;
currentHP = maxHP;
```

구식 프리팹 호환 처리(기존 maxHP=1 직렬화 값이 남아있어도 Awake에서 자동 보정).

**파일 상단 주석**:
> BT7-Plan PD 지시 2026-04-24 — 하트 분할 시스템 (젤다 방식). 하트 1개 = 4 쿼터(HP). 카드(패시브)·성장으로 maxHearts를 증가시킬 수 있으며, 그에 따라 maxHP가 `maxHearts * QuartersPerHeart`로 자동 산정된다. 적 ATK는 쿼터 단위.

### 2.5 `Assets/Scripts/Mechanics/PlayerController.cs` (개정)

**변경 요지**: Attack 입력 관련 필드·로직 완전 제거. Facing 상태 public 노출.

**제거 항목**:
- `private InputAction m_AttackAction` 필드
- `float nextAttackTime` 필드
- `public float attackCooldown` 필드
- Awake에서 `m_AttackAction = InputSystem.actions.FindAction("Player/Attack")` · `m_AttackAction.Enable()` 호출
- Update의 Attack 입력 처리 블록 (WasPressedThisFrame · Schedule<PlayerAttack>) — 단 Ticker 참조 주석 유지

**신규·개정 항목**:
- `public Vector2 Facing => facing` 프로퍼티 (Ticker가 Schedule 시점에 참조)
- `attackAudio` 필드 주석: "자동 발동 시 PlayerAttack.Execute가 PlayOneShot으로 재생" 재작성
- `attackHitbox` 필드 주석: "공격은 PlayerAttackTicker가 주기적으로 Schedule<PlayerAttack>을 발화하여 실행" 추가

### 2.6 `Assets/Scripts/Mechanics/AttackHitbox.cs` (부수 개정)

**변경 요지**: 하트 분할 시스템에 맞춰 적 Decrement 호출 통합.

**개정 라인**:
```csharp
// 기존
for (int i = 0; i < damage; i++)
{
    health.Decrement();
    if (!health.IsAlive) break;
}

// 개정
// BT7-Plan TODO (2026-04-24): 적 Health도 하트 분할 시스템 대상이지만
// i-frame 구조 탓에 Decrement() 반복 호출은 첫 호출 후 무효화된다.
// Health.Decrement(int damage) 단일 호출로 쿼터 단위 다중 피해 전달.
health.Decrement(damage);
```

**이유**: i-frame 내 `Decrement()` 재호출은 무효화됨. 새 `Decrement(int damage)` 오버로드는 단일 호출에서 damage만큼 쿼터를 즉시 차감.

### 2.7 `Assets/Tests/Editor/PlayerAttackTests.cs` (개정)

**테스트 구조** (총 **13개**):

| # | 테스트 | 상태 | 비고 |
|---|---|---|---|
| 1 | `Player_Prefab_Has_AttackHitbox_Component` | 유지 | BT5-Dev 계승 |
| 2 | `Player_Prefab_Has_Health_Component` | 유지 | |
| 3 | `Player_Prefab_Has_PlayerController_Component` | 유지 | |
| 4 | `AttackHitbox_Default_Damage_Is_One` | 유지 (주석 업데이트) | "balance/01 v0.2 튠 전 파일럿 값" |
| 5 | `AttackHitbox_Active_Duration_Is_Positive` | 유지 | |
| 6 | `Enemy_Prefab_Has_Health_Component` | 유지 | |
| 7 | `Enemy_Prefab_Has_EnemyController_Component` | 유지 | |
| 8 | `Enemy_Prefab_Health_MaxHP_Is_One` | **제거** | BT7-Plan 하트 분할로 maxHP=1 가정 폐기 |
| 9 | `Health_Script_Defines_MaxHearts_And_IncreaseMaxHearts` | **신규** | Health.maxHearts·maxHP·IncreaseMaxHearts·Heal 시그니처 검증 |
| 10 | `Player_Prefab_MaxHP_Reflects_Heart_Quarters` | **신규** | Player.prefab Health 직렬화 값 점검 (maxHP ∈ {maxHearts*4, 1}) |
| 11 | `PlayerAttackTicker_Script_Exists_With_AttackInterval` | **신규** | Ticker 타입·attackInterval float 필드 검증 |
| 12 | `InputActions_Player_Map_Has_No_Attack_Action` | **신규** | YAML grep으로 Player 맵 Attack 액션 부재 검증 |
| 13 | `Player_Prefab_SpriteRenderer_References_PlayerTestGirl` | 유지 | BT5-Dev 3단계 계승 |
| 14 | `Player_Controller_Has_Attack_Parameter_And_State` | 유지 | Ticker → Animator.SetTrigger("attack") 연동 전제 |

**갱신 집계**: 기존 10 → 제거 1 + 신규 4 = **13개**.

## 3. C6-1 백업 파일 목록 (편집 전 원본 보존)

**백업 경로**: `D:/NerdNavis/BurningTimesAi/공유/개발팀_백업/EerieVillage/`
**Timestamp**: `20260424_1551`

| # | 원본 | 백업 파일명 |
|---|------|-----------|
| 1 | `PlayerAttack.cs` | `PlayerAttack.cs.bak_20260424_1551.cs` |
| 2 | `Health.cs` | `Health.cs.bak_20260424_1551.cs` |
| 3 | `PlayerController.cs` | `PlayerController.cs.bak_20260424_1551.cs` |
| 4 | `InputSystem_Actions.inputactions` | `InputSystem_Actions.inputactions.bak_20260424_1551.inputactions` |
| 5 | `PlayerAttackTests.cs` | `PlayerAttackTests.cs.bak_20260424_1551.cs` |
| 6 | `AttackHitbox.cs` | `AttackHitbox.cs.bak_20260424_1551.cs` |

**롤백 절차**: 백업 파일에서 `.bak_20260424_1551` 제거 후 Unity 레포 덮어쓰기.

## 4. PD 수동 검증 요청 항목

Unity Editor 실행 GUI 작업이 필수인 항목만 최소화하여 PD 요청:

### 4.1 Asset import 재처리 + EditMode Test Runner

1. Unity Editor 재실행 (D:/NerdNavis/EerieVillage 프로젝트 열기)
2. Console에 import 오류 없는지 확인 — 특히:
   - `PlayerAttackTicker.cs` 신규 파일 인식
   - `InputSystem_Actions.inputactions` Attack 액션 제거 후 재컴파일
   - `Health.cs` Decrement(int) 오버로드 + IncreaseMaxHearts·Heal 신규 메서드
3. `Window > General > Test Runner` → EditMode → **Run All** → 13 tests all green 확인

### 4.2 Player.prefab에 PlayerAttackTicker 컴포넌트 부착 + Play 검증

**Unity Editor GUI 필수 작업**:
1. `Assets/Prefabs/Player.prefab` 열기
2. Inspector → Add Component → "Player Attack Ticker" 선택 (네임스페이스 `Platformer.Gameplay`)
3. `attackInterval` 기본 0.5s 유지, `startupDelay` 0.3s 유지
4. Prefab 저장 (Ctrl+S)

**Play 모드 검증**:
1. Scene 열기 (`Assets/Scenes/SampleScene.unity`)
2. Play 버튼 클릭
3. 확인 항목:
   - (a) 이동·점프 정상 동작 (A/D·Space)
   - (b) **마우스 좌클릭 / 게임패드 RT 누르지 않아도 공격 주기적 발동** (0.3초 후 첫 발동, 이후 0.5초 간격)
   - (c) Player 근처 Enemy가 공격 판정에 들어오면 1 쿼터 피해 받고 i-frame 동안 중복 피해 없음
   - (d) 적 접촉 시 Player도 `Health.Decrement(damage)` 받아 쿼터 1 감소 (i-frame 0.6s)
   - (e) 하트 UI 부재로 시각 피드백 없음 (HUD 개정은 후속 Phase) — Inspector Health 컴포넌트에서 currentHP 감소 실측으로 확인

### 4.3 리스크 — Play 검증 전 인지 사항

| 리스크 | 영향 | 대응 |
|---|---|---|
| PlayerAttackTicker 미부착 시 공격 자동 발동 안 됨 | Play에서 공격 0회 | §4.2-1~4 컴포넌트 Add 필수 |
| 기존 Enemy.prefab의 Health.maxHearts Inspector 값이 0 또는 직렬화 부재 | Awake에서 자동 1로 보정 | Health.cs Awake 보정 로직 신뢰 |
| Animator `attack` trigger 미존재 시 SetTrigger warning | Console warning 1회, 동작 무영향 | 기존 BT5-Dev 3단계에서 attack trigger + Player-Attack State 추가됨 (Player.controller YAML 편집 완료) |
| 적 ATK 1 수치 미설정으로 Player 피해 계산 실질 무효 | Decrement(damage=1)라도 i-frame으로 방어 | EnemyController 기반 피해 전달 로직은 후속 Phase |

## 5. 리스크 및 기각안

### 5.1 기각안 (C32 최소 3건 초과 달성 — 5건)

1. **"PlayerAttack.cs 내부에 타이머 직접 구현" — 기각**
   - 이벤트 정의와 발화 트리거가 단일 클래스에 혼재 → SRP 위반 (C11 구조 직관성 훼손)
   - Ticker 분리로 발화 방식 교체 유연성 확보 (예: 무기 종류별 발동 주기, 일시 정지 등)

2. **"attackInterval을 PlayerController에 통합" — 기각**
   - PlayerController는 이미 KinematicObject 상속 + Move/Jump/Facing/Animator 관리 비대
   - 공격 로직은 전투 시스템 영역으로 분리 보전 — Phase 3-B 카드 효과 훅 편입 시 의존성 깔끔

3. **"Health.maxHP를 제거하고 maxHearts만 유지" — 기각**
   - 기존 Prefab 직렬화 호환성 (maxHP Inspector 값) 파괴 → 모든 Health 사용 prefab 재설정 필요
   - HUD·튜닝 도구·스크립트 참조(`health.maxHP`)의 일괄 갱신 비용 과다
   - **해결**: maxHP 유지 + Awake에서 `maxHearts * 4`로 재산정 (구식 값 자동 보정)

4. **"Decrement를 `Decrement(int damage=1)` 단일 시그니처로 통합" — 기각**
   - 기존 호출자(PlayerEnemyCollision 폐기·다른 스크립트) 회귀 리스크
   - **해결**: `Decrement()` 무인자 유지 + `Decrement(int damage)` 오버로드 신규 추가 (Decrement(1) 위임)

5. **"EnemyController에 attackDamage 필드 추가 + 충돌 시 Player Decrement(attackDamage)" — 본 Phase 기각(TODO 주석만)**
   - balance/01 v0.2 수치 테이블 미확정 상태에서 EnemyController 구조 개정은 돌이킴 리스크
   - **해결**: 본 Phase에서는 TODO 주석만 (적 ATK 테이블 확정 후 별도 Phase)

### 5.2 식별된 리스크

| # | 리스크 | 완화책 |
|---|---|---|
| 1 | Player.prefab에 PlayerAttackTicker가 부착되지 않은 상태로 Play 시 공격 미발동 | PD 수동 작업 §4.2 명시 + EditMode 테스트 11번이 Ticker Script 존재는 검증(prefab 부착은 Unity GUI 필요) |
| 2 | balance/01 v0.2 수치 미확정으로 attackInterval·적 ATK 체감 미측정 | `attackInterval` 기본 0.5s는 파일럿 값 — balance-designer v0.2 확정 후 튠 |
| 3 | 기존 PlayerAttack 호출자 (`PlayerController.Update`·`PlayerEnemyCollision`)가 이미 삭제됨을 검증 없이 가정 | PlayerController Update에서 Schedule<PlayerAttack> 호출 라인 제거 확인 · PlayerEnemyCollision 파일 부재 확인 (glob 결과 Scripts/Mechanics에 미존재) |
| 4 | Health.Decrement(damage) 오버로드가 i-frame 내 호출 시 완전 스킵 (부분 피해 인정 안 함) | 연타 방지 원칙 유지. 향후 "피해 레벨별 i-frame 차등" 필요 시 별도 설계 |
| 5 | AttackHitbox damage=1 기본 + Player 공격 1회당 1 쿼터로 적 Health(maxHP=4)의 적을 4회 연속 타격해야 처치 | balance/01 §5 적 HP 테이블 재검토 대상 (기획팀장 v0.2 확정 시 반영) |

## 6. BT7-Dev 이후 Phase 예정 (범위 외 — 참고)

**Phase 2** (기획 balance/01 v0.2 확정 후):
- EnemyController에 `attackDamage` 필드 추가 + Player 피해 전달 로직
- 적 ATK 테이블 연동 (일반 약/중·엘리트·보스)
- attackInterval 튠

**Phase 3** (기획 system/01·content/01 확정 후):
- 카드 3분류 (액티브·패시브·각성) 데이터 구조
- Player.attackHitbox가 카드 효과 훅 참조
- 보물상자·각성 트리거 로직

**Phase 4** (기획 ux/02 확정 후):
- HUD 하트 분할 UI (♥ 1개 = 4 쿼터 시각화)
- 카드 슬롯 UI (액티브·패시브 분리)

## 7. 자기 검증 (C22-6 정신 준수)

PD 미합의 용어·식별자 자의 신설 없음 자가 검증:

| 항목 | 검증 |
|---|---|
| PD 지시 용어 그대로 사용 | "액티브·패시브·각성·하트·쿼터·보물상자·Lv.5" 모두 PD 원문 그대로 코드 주석·본 문서에 인용 |
| 신규 클래스명 `PlayerAttackTicker` | PD 미합의 신설. 단 PD 지시 "자동 발동 주기 타이머"의 직관적 구현 기술 명명이며, 코드 내부 구조 결정(C11 범용성·직관성 적용) |
| 신규 필드명 `maxHearts`·`QuartersPerHeart`·`Heal`·`IncreaseMaxHearts` | PD 미합의 신설. 단 기존 `maxHP`·`Decrement` 컨벤션 계승한 영문 기술 명명. PD 한글 용어("하트"·"쿼터")의 영문 직역 |
| 안건 넘버링 | 본 문서 섹션 번호는 C25-1 1.·1)·A. 준수 |
| 선택지 식별자 | 본 Phase 내부 의사결정 "A안·B안" 자의 부여 없음 (PD 결정 요구 없음 — 전적으로 개발 구현) |

**판정**: C22-6 정신 위반 없음. 신규 식별자는 **구현 세부 수준의 기술 명명**이며, PD 방향·원칙 수준 결정에 영향 없음 (C36 적용 범위 내).

## 8. 변경 이력

| 일시 | 변경 | 사유 | 기안 |
|---|---|---|---|
| 2026-04-24 | v1.0 BT7-Dev Phase 1 — VS 순수형 자동 발동 + 하트 분할 + 테스트 갱신 | PD 지시 2026-04-24 11건 중 개발 영향분 | 개발팀장 |

## 9. 참조 문서

- **기획 방향 근거**: `공유/대화로그/EerieVillage/2026-04-24.md` BT7-Plan 7개 엔트리
- **선행 구현 문서**: `프로젝트/EerieVillage/개발/04_BT5-Dev_2단계_구현보고.md` v0.2 · `프로젝트/EerieVillage/개발/05_PlayerTestGirl_아틀라스_적용.md`
- **Unity 외부 레포 편집**: `D:/NerdNavis/EerieVillage/Assets/Scripts/Gameplay/{PlayerAttack.cs, PlayerAttackTicker.cs(신설)}`·`Assets/Scripts/Mechanics/{Health.cs, PlayerController.cs, AttackHitbox.cs}`·`Assets/Settings/InputSystem_Actions.inputactions`·`Assets/Tests/Editor/PlayerAttackTests.cs`
- **C6-1 백업**: `공유/개발팀_백업/EerieVillage/*.bak_20260424_1551.*` (6종)
- **PD 지시 로그**: `공유/PD_지시_트래킹/개발팀_PD_지시_로그.md` BT7-Dev 신규 항목
- **대화로그**: `공유/대화로그/EerieVillage/2026-04-24.md` (BT7-Dev 착수 엔트리 신설 예정)
