1161 lines
54 KiB
Markdown
1161 lines
54 KiB
Markdown
|
|
---
|
|||
|
|
type: 설계_문서
|
|||
|
|
scope: 맵_시스템 (절차 생성 + 시각 시뮬레이터 + 리소스 카탈로그)
|
|||
|
|
author: 개발팀장 (dev-team-lead)
|
|||
|
|
date: 2026-05-18
|
|||
|
|
version: v1.0 (BT13-Dev-Map Phase 1 — 단일 SOT)
|
|||
|
|
project: EerieVillage (기묘한 고을 : 조선퇴마뎐)
|
|||
|
|
phase: BT13-Dev-Map Phase 1 (개발팀장 Opus 설계)
|
|||
|
|
data_source: |
|
|||
|
|
BT5-Dev 인수인계 2026-05-07·2026-05-08 (발판 시스템 BT47·BT75·몬스터 BT76~BT109)·
|
|||
|
|
E:/EerieVillage/Assets/Scripts/Mechanics/{KinematicObject,PlayerController,EnemyController,GameOptimizer,SpawnPoint,MonsterRandomizer}.cs·
|
|||
|
|
E:/EerieVillage/Assets/Scripts/Model/PlatformerModel.cs·
|
|||
|
|
E:/EerieVillage/Assets/Prefabs/{Player,Enemy,Enemy_M001~M006}.prefab·
|
|||
|
|
E:/EerieVillage/Assets/Tiles/{TileFloating*,TileGround*,cloud,fence,ShortBuilding,TallBuilding,MidgroundFiller}.asset·
|
|||
|
|
E:/EerieVillage/ProjectSettings/Physics2DSettings.asset·
|
|||
|
|
프로젝트/EerieVillage/기획/05_스테이지_구조_초안.md v0.1·
|
|||
|
|
프로젝트/EerieVillage/기획/level/01_스테이지_구조.md v0.1·
|
|||
|
|
프로젝트/EerieVillage/개발/spec/스킬_시스템_설계_v1.md v1.0
|
|||
|
|
status: Phase 1 설계 완료 — Phase 2 코드 구현 분할 권고 + PD 결정 대기 안건 11건 (§15 정합·2026-05-18 PM 정정·dev-team-lead 자기 검증 누락 보완)
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 맵 시스템 설계 v1 (BT13-Dev-Map)
|
|||
|
|
|
|||
|
|
## §1. 요구 분석 + PD 결정 4건 정합 확인
|
|||
|
|
|
|||
|
|
### 1-1. PD 핵심 요구 (2026-05-18 원문 7항)
|
|||
|
|
|
|||
|
|
> "이제 나는 우리 게임의 맵을 제작하고 싶어.
|
|||
|
|
> * 일반적인 플랫포머 게임의 맵 제작 방식을 고려해 맵을 만들고 싶어.
|
|||
|
|
> * 하지만 맵의 길이만 지정하면, 랜덤으로 자동으로 생성하는 형태의 맵 시스템을 만들고 싶어.
|
|||
|
|
> * 맵에는 자동으로 몬스터가 등장하게 될 생성 포인트(거점)이 배치되어야하고, 플레이어가 시작하는 스타트 포인트도 존재해야 해.
|
|||
|
|
> * 몬스터는 항상 생성 포인트 주변에서 생성되며, 현재 만들어진 몬스터 이동 패턴을 활용해서 동작하기를 바래.
|
|||
|
|
> * 시뮬레이션을 시각적인 정보로 미리 만들어질 맵의 구조를 보고 결정할 수 있는 형태면 좋겠어.
|
|||
|
|
> * 사용할 맵 리소스를 자유롭게 추가할 수 있도록 리소스를 등록하는 형태의 기능이 있으면 좋겠어.
|
|||
|
|
> * 게임에 등장하는 발판(충돌 영역 포함)의 배치 등이 플레이어가 점프로 이동 가능한 높이나 거리에 맞게 설계되면 좋겠어. (플랫포머 게임의 자연스러운 레벨디자인)
|
|||
|
|
> * 바닥의 길은 사용자가 일일이 수정하지 않아도 충돌 영역이 자연스럽게 적용되면 좋겠어."
|
|||
|
|
|
|||
|
|
### 1-2. PD 추가 결정 4건 (PM 사전 명세 안건 답변)
|
|||
|
|
|
|||
|
|
| # | 안건 | PD 결정 | 본 설계 반영 |
|
|||
|
|
|---|------|---------|------------|
|
|||
|
|
| 1 | 시각 시뮬레이터 | Unity Editor Window 1순위 · 어려우면 Scene 미리보기 fallback | §10 — EditorWindow 채택 (실측 결과 1순위 가능) |
|
|||
|
|
| 2 | 맵 리소스 단위 | 양쪽 다 — 배경=Tile, 일부 기믹=Prefab | §5 — Tile/Prefab 이원 분리 |
|
|||
|
|
| 3 | 랜덤성 | 매번 완전 랜덤 (Seed 입력 X) | §4 — `UnityEngine.Random` 매 생성 새 시드 |
|
|||
|
|
| 4 | 거점 밀도 | 맵 길이·비율을 옵션으로 미리 지정 → 옵션 값 동작 | §8 — `MapGenerationConfig.SpawnDensity 0.0~1.0` |
|
|||
|
|
|
|||
|
|
### 1-3. 헌법 제1원칙 정합
|
|||
|
|
|
|||
|
|
- **①** AI 에이전트 활용 — 개발팀장 Opus 설계, 클라이언트팀 Sonnet 구현 (C49)
|
|||
|
|
- **②** 경험 축적 — BT5-Dev 발판 시스템·BT12 스킬 시스템 자산 전부 계승
|
|||
|
|
- **③** 허위 보고 금지 — §2 사전 실측 영역에서 모든 추정 X·Read 결과만 기재
|
|||
|
|
- **④** 조직 단위 운영 — 본 단계 Phase 1 단독·Phase 2 분할 권고는 §13
|
|||
|
|
- **⑤** 세션 연속성 — 본 설계서 = 단일 SOT, Phase 2 어떤 세션에서 진입해도 정합
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §2. 사전 실측 결과 (C39-10 의무 — 위반 #1~#7 재발 차단)
|
|||
|
|
|
|||
|
|
### 2-1. KinematicObject (`E:/EerieVillage/Assets/Scripts/Mechanics/KinematicObject.cs`)
|
|||
|
|
|
|||
|
|
| 항목 | 실측값 | 본 설계 활용 |
|
|||
|
|
|------|--------|------------|
|
|||
|
|
| `minGroundNormalY` | 0.65f | 발판 경사 한계 — 본 설계 평탄 발판 고정 |
|
|||
|
|
| `gravityModifier` | public 1f (Player/Enemy prefab) | 점프 물리 식 계산 입력값 |
|
|||
|
|
| `Bounce(float)` | velocity.y 직접 set | 본 설계 미사용 |
|
|||
|
|
| `Teleport(Vector3)` | body.position + velocity *=0 | 본 설계 Player Spawn 시 활용 |
|
|||
|
|
| `body.Cast(contactFilter)` | hitBuffer[16] · contactFilter Layer mask 적용 | Drop-Through 발판 통과 메커니즘 SOT |
|
|||
|
|
| `velocity.x = targetVelocity.x` | FixedUpdate 매 frame 덮어씀 | 직접 set 1 frame만 효과 |
|
|||
|
|
| `ignoreCollisions` (2026-05-15) | 날으는 몬스터용·body.Cast skip | 본 설계 무관 (M002 박쥐 영역) |
|
|||
|
|
|
|||
|
|
### 2-2. PlayerController (`E:/EerieVillage/Assets/Scripts/Mechanics/PlayerController.cs`)
|
|||
|
|
|
|||
|
|
| 항목 | 실측값 |
|
|||
|
|
|------|--------|
|
|||
|
|
| `maxSpeed` | 3 (Player.prefab line 63) — BT12 Phase 2-D 영역 7→3 정정 |
|
|||
|
|
| `jumpTakeOffSpeed` | 7 (Player.prefab line 64) |
|
|||
|
|
| `JUMP_THROUGH_LAYER` | const 16 |
|
|||
|
|
| `JUMP_ASCENT_DURATION` | const 0.4f (점프 ascending mask OFF) |
|
|||
|
|
| `DROP_THROUGH_DURATION` | const 0.3f (Down+Jump Drop-Through mask OFF) |
|
|||
|
|
| `UpdateContactFilterForDropThrough` | footHit Raycast 3점(좌·중·우)·nearApex+horizontalIntent 영역 강제 Drop-Through |
|
|||
|
|
| `Awake()` 자동 부착 | PlayerInvulnerabilityFlash·ResurrectPromptUI·PlayerProgression·PlayerSkillInventory·SkillInventoryHUD |
|
|||
|
|
|
|||
|
|
### 2-3. PlatformerModel (`E:/EerieVillage/Assets/Scripts/Model/PlatformerModel.cs`)
|
|||
|
|
|
|||
|
|
| 항목 | 실측값 |
|
|||
|
|
|------|--------|
|
|||
|
|
| `jumpModifier` | 1.5f |
|
|||
|
|
| `jumpDeceleration` | 0.5f (release 시 velocity.y × 0.5) |
|
|||
|
|
|
|||
|
|
### 2-4. Physics2D (`E:/EerieVillage/ProjectSettings/Physics2DSettings.asset`)
|
|||
|
|
|
|||
|
|
| 항목 | 실측값 |
|
|||
|
|
|------|--------|
|
|||
|
|
| `m_Gravity.y` | -9.81f |
|
|||
|
|
| `m_VelocityIterations / PositionIterations` | 8 / 3 |
|
|||
|
|
|
|||
|
|
### 2-5. 점프 도달 한계 산출 (실측 수치 기반)
|
|||
|
|
|
|||
|
|
PlayerController.ComputeVelocity 영역:
|
|||
|
|
```
|
|||
|
|
velocity.y = jumpTakeOffSpeed × model.jumpModifier = 7 × 1.5 = 10.5 m/s
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
KinematicObject.FixedUpdate 영역 gravity 적용:
|
|||
|
|
```
|
|||
|
|
velocity.y -= 9.81 × gravityModifier × dt = 9.81 × 1 × dt
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 산출 | 공식 | 값 |
|
|||
|
|
|------|------|---|
|
|||
|
|
| 최대 점프 높이 (full hold) | H_max = v0² / (2g) = 10.5² / (2 × 9.81) | **5.62m** |
|
|||
|
|
| 점프 정점 도달 시간 | t_up = v0 / g = 10.5 / 9.81 | **1.07s** |
|
|||
|
|
| 점프 왕복 시간 (착지) | t_round = 2 × t_up | **2.14s** |
|
|||
|
|
| 점프 중 수평 최대 거리 | D_max = maxSpeed × t_round = 3 × 2.14 | **6.42m** |
|
|||
|
|
| Stop Jump (release) 시 ascending 절반 cap | v_after = 0.5 × v_now → H_partial ≈ H_max × 0.25 | **~1.4m (조기 release 영역)** |
|
|||
|
|
| 안전 점프 거리 (90% margin) | D_max × 0.9 | **~5.78m** |
|
|||
|
|
| 안전 점프 높이 (90% margin) | H_max × 0.9 | **~5.06m** |
|
|||
|
|
|
|||
|
|
**본 설계 발판 배치 룰 (§6)에 이 수치 직접 적용**.
|
|||
|
|
|
|||
|
|
### 2-6. EnemyController (`E:/EerieVillage/Assets/Scripts/Mechanics/EnemyController.cs`)
|
|||
|
|
|
|||
|
|
| 항목 | 실측값 | 본 설계 활용 |
|
|||
|
|
|------|--------|------------|
|
|||
|
|
| `patrolMinRange / patrolMaxRange` | 5 / 10 (Enemy.prefab) — 본 EC.cs default 50/75는 prefab override됨 | Spawn Point 영역 좌우 ±10m patrol 영역 확보 의무 |
|
|||
|
|
| `cliffCheckDistance / cliffCheckDepth` | 1.0 / 2.0 | 발판 끝단 cliff 검출 — Spawn 발판 폭 >2m 필수 |
|
|||
|
|
| `groundLayerMask` | (1<<0) \| (1<<16) — Layer 0 Level + Layer 16 AutoForeground | Spawn 발판 Layer 0 또는 16 |
|
|||
|
|
| `fallThreshold` | 1.0f → startY - 1.0 미만 시 시작 위치 텔레포트 | 떨어진 Enemy 자동 복귀 (BT102) |
|
|||
|
|
| `IsFlying` | controller name "M002" 매칭 시 true · cliffCheck skip | 본 설계 M002는 공중 spawn |
|
|||
|
|
| `_isInitialized` | Start 시점 MeasureSafeWalkDistance 완료 후 true | Enemy spawn은 Scene 로드 후 1 frame 대기 의무 |
|
|||
|
|
| `MeasureSafeWalkDistance` | Tilemap WorldToCell + HasTile cell 연속 + 위·아래 1 cell 인접 (계단 정합) | Spawn 발판 Tilemap 등록 필수 |
|
|||
|
|
| `_cachedPlayer` collide | distance < 1.5f or VisualBounds.Intersects → Decrement (i-frame 외) | 본 설계 무관 (Enemy 동작 SOT) |
|
|||
|
|
|
|||
|
|
### 2-7. GameOptimizer (`E:/EerieVillage/Assets/Scripts/Mechanics/GameOptimizer.cs`) — 본 설계 결정적 영향
|
|||
|
|
|
|||
|
|
| 동작 | 본 설계 영향 |
|
|||
|
|
|------|-------------|
|
|||
|
|
| `Init()` BeforeSceneLoad — targetFrameRate 60·Physics2D.IgnoreLayerCollision(13,14) | 본 설계 무관 (이미 적용) |
|
|||
|
|
| `SetupJumpThroughPlatforms()` AfterSceneLoad — 모든 Collider2D 분류 | **본 설계 절차 생성 Tilemap은 본 hook 진입 전 완료 의무** |
|
|||
|
|
| Level Tilemap(name="Level") = Layer 0 보존 | 본 설계 지면 Tilemap 영역 name="Level" 강제 |
|
|||
|
|
| 그 외 비-trigger Collider2D = Layer 16 강제 | 본 설계 공중 발판 Tilemap name≠"Level" 시 자동 Layer 16 |
|
|||
|
|
| `Foreground` GameObject = 가림막 시각만·TilemapCollider 제거 | 본 설계 절차 생성 시 "Foreground" name 회피 |
|
|||
|
|
| `AutoForeground` 자동 생성·Grid 자식 | 본 설계는 Level/AutoForeground 직접 생성 OR GameOptimizer 자동 분류 위임 (§7 결정) |
|
|||
|
|
| Level Tile worldY ≥ playerY+1.5 OR 작은 공중 발판(가로≤8+위아래 빈) → AutoForeground 이동·Sprite 강제 | **본 설계 = 두 옵션** (A) 절차 생성기에서 즉시 분리 (B) Level 단일 Tilemap 배치 후 GameOptimizer 위임 |
|
|||
|
|
| TileGround prefix 제외 | 본 설계 지면 Tile name "TileGround*" 강제 영역 |
|
|||
|
|
|
|||
|
|
### 2-8. Tile 자산 (`E:/EerieVillage/Assets/Tiles/`)
|
|||
|
|
|
|||
|
|
| Tile | m_ColliderType | 용도 |
|
|||
|
|
|------|---------------|------|
|
|||
|
|
| TileGround.asset | 1 (Sprite) | 지면 |
|
|||
|
|
| TileGroundTop.asset | 1 | 지면 표면 |
|
|||
|
|
| TileGroundDark.asset | 1 | 어두운 지면 변형 |
|
|||
|
|
| TileFloatingLeftEdge.asset | 1 | 공중 발판 좌측 끝 |
|
|||
|
|
| TileFloatingTileMiddle.asset | 1 | 공중 발판 중간 |
|
|||
|
|
| TileFloatingRightEdge.asset | 1 | 공중 발판 우측 끝 |
|
|||
|
|
| cloud.asset | 1 | 공중 발판 변형 (구름) |
|
|||
|
|
| fence.asset | (확인 보류 — 기획팀 장식 영역) | 배경 |
|
|||
|
|
| ShortBuilding/TallBuilding/MidgroundFiller.asset | (배경) | 배경 영역 |
|
|||
|
|
|
|||
|
|
**전 Tile m_ColliderType=1(Sprite) 사전 확정** → 본 설계 §7 자동 충돌 적용 핵심 근거.
|
|||
|
|
|
|||
|
|
### 2-9. Prefab (`E:/EerieVillage/Assets/Prefabs/`)
|
|||
|
|
|
|||
|
|
| Prefab | m_Layer | 본 설계 활용 |
|
|||
|
|
|--------|---------|------------|
|
|||
|
|
| Player.prefab | 13 | 1 인스턴스 (Start Point Spawn) |
|
|||
|
|
| Enemy.prefab | 14 | (구) MonsterRandomizer 영역 random 6종 |
|
|||
|
|
| Enemy_M001_Wolf~M006_Spider.prefab | 14 (추정) | 본 설계 SpawnPoint별 개별 prefab 선택 OR Enemy.prefab + MonsterRandomizer 위임 (§9) |
|
|||
|
|
|
|||
|
|
### 2-10. Scene·기획 정합 (`프로젝트/EerieVillage/기획/level/01_스테이지_구조.md` v0.1)
|
|||
|
|
|
|||
|
|
| 기획 영역 | 본 설계 정합 |
|
|||
|
|
|---------|------------|
|
|||
|
|
| 5종 스테이지 (마을 ↔ 1-5) | 본 설계 절차 생성기는 **스테이지별 옵션 카탈로그** 제공 (§11) |
|
|||
|
|
| 3단 구성 (도입·전개·보스 방) | 본 설계 §4 청크 분할 권고: Intro/Mid/BossPre 3 영역 + Boss 방은 별도 수동 Scene |
|
|||
|
|
| 횡스크롤 길이 5~10분 | 본 설계 맵 길이 옵션 = Tile 단위 길이 (§15 PD 결정 안건) |
|
|||
|
|
| **§5 자동 생성 vs 수동 배치 — "전체 수동 배치 + 구간 2 적 배치 소규모 셔플" 채택** | **PD 신규 지시(2026-05-18) = 본 기획 결정 OVERRIDE — "맵의 길이만 지정하면, 랜덤으로 자동 생성"** → 기획팀 후속 정합 안건 (§15) |
|
|||
|
|
| §10 §10-2 "낙하 함정" 등 지형 기믹 — 개발팀 C11 판단 이관 | 본 설계 §11 MapResourceCatalog → 기믹 Prefab 등록 영역 |
|
|||
|
|
|
|||
|
|
### 2-11. BT12 스킬 시스템 영향 평가 (`프로젝트/EerieVillage/개발/spec/스킬_시스템_설계_v1.md` v1.0)
|
|||
|
|
|
|||
|
|
| BT12 영역 | 본 설계 영향 |
|
|||
|
|
|----------|------------|
|
|||
|
|
| PlayerSkillInventory·ActiveSkillRuntime·6종 Effector(Lightning·Melee·Spirit·Poison·Laser·Projectile) | **영향 없음** — 본 설계는 맵 절차 생성만, Player 컴포넌트는 PlayerController.Awake 자동 부착 그대로 |
|
|||
|
|
| 스킬 효과 충돌 영역 (Effector → Health.Decrement) | 본 설계 영향 없음 — Enemy.prefab Health 그대로 |
|
|||
|
|
| Enemy ATK/HP | 본 설계 영향 없음 — Enemy.prefab 그대로 spawn |
|
|||
|
|
| MonsterRandomizer (6 controller random) | 본 설계 § 9 Spawn 영역에서 활용 가능 (옵션 1: Enemy.prefab spawn → Awake에서 Random / 옵션 2: 본 설계 SpawnPoint config에서 특정 M00N 강제) |
|
|||
|
|
|
|||
|
|
### 2-12. 기존 MapGenerator/Spawn 시스템 0건 사전 확정
|
|||
|
|
|
|||
|
|
`grep MapGenerator|RandomMap|ProceduralMap|MapBuilder E:/EerieVillage/Assets/Scripts` → 0건.
|
|||
|
|
`grep SpawnPoint` → 1건 (`Mechanics/SpawnPoint.cs` = 11줄 빈 marker 클래스).
|
|||
|
|
|
|||
|
|
→ 본 설계 = **완전 신규 시스템 도입**. 기존 코드와 충돌 없음. GameOptimizer·EnemyController·PlayerController 영역과 인터페이스 정합만 의무.
|
|||
|
|
|
|||
|
|
### 2-13. Editor 폴더 영역
|
|||
|
|
|
|||
|
|
`E:/EerieVillage/Assets/Editor/` 영역 GitAutoSync만 존재 → **EditorWindow 신규 도입 깨끗** (Phase 2-C 영역).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §3. 아키텍처 4계층
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 계층 4: 시각 시뮬레이터 (Editor only) │
|
|||
|
|
│ - MapPreviewWindow (EditorWindow) │
|
|||
|
|
│ · GUI 영역 옵션 입력 + Generate 버튼 + 미리보기 캔버스 │
|
|||
|
|
│ · Handles.DrawLine·DrawSolidArc → 발판·SpawnPoint·점프 영역 │
|
|||
|
|
│ - MapSceneApplier (Scene 적용 버튼) — Phase 2-C │
|
|||
|
|
└────────────────────────────────────────────────────────────────┘
|
|||
|
|
▲ 호출
|
|||
|
|
┌────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 계층 3: 절차 생성기 (Runtime + Editor 양립) │
|
|||
|
|
│ - MapGenerator (정적 메서드 또는 MonoBehaviour) │
|
|||
|
|
│ · Input: MapGenerationConfig (옵션 SO 또는 struct) │
|
|||
|
|
│ · Output: MapBlueprint (in-memory 데이터) │
|
|||
|
|
│ - PlatformLayoutPlanner (§6 발판 배치 룰) │
|
|||
|
|
│ - SpawnPointPlanner (§8 거점 배치) │
|
|||
|
|
│ - TerrainPlanner (§7 바닥 자동 충돌) │
|
|||
|
|
└────────────────────────────────────────────────────────────────┘
|
|||
|
|
▲ 참조
|
|||
|
|
┌────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 계층 2: 데이터 SOT (ScriptableObject) │
|
|||
|
|
│ - MapResourceCatalog (Tile·Prefab 카탈로그) │
|
|||
|
|
│ - MapGenerationConfig (옵션·길이·밀도) │
|
|||
|
|
│ - PlatformerJumpProfile (실측 점프 수치 SOT) │
|
|||
|
|
│ - SpawnRuleProfile (Enemy spawn 규칙) │
|
|||
|
|
└────────────────────────────────────────────────────────────────┘
|
|||
|
|
▲ 입력
|
|||
|
|
┌────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 계층 1: 실행·런타임 적용 │
|
|||
|
|
│ - MapApplier (Scene 적용 — Tilemap.SetTile + Instantiate) │
|
|||
|
|
│ - Player Start 배치 + Enemy SpawnPoint 인스턴스 생성 │
|
|||
|
|
│ - GameOptimizer.SetupJumpThroughPlatforms() 진입 전 완료 의무 │
|
|||
|
|
└────────────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3-1. 데이터 흐름
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[PD] [본 시스템]
|
|||
|
|
│ │
|
|||
|
|
├─ MapResourceCatalog SO 영역 등록 ───────►│ Tile·Prefab 풀
|
|||
|
|
│ (Editor·자유 추가) │
|
|||
|
|
│ │
|
|||
|
|
├─ MapGenerationConfig 옵션 지정 ─────────►│
|
|||
|
|
│ · LengthTiles=120 │
|
|||
|
|
│ · SpawnDensity=0.5 │
|
|||
|
|
│ · StageProfile=Stage1 │
|
|||
|
|
│ ▼
|
|||
|
|
│ [MapGenerator.Generate(config)]
|
|||
|
|
│ │
|
|||
|
|
│ ▼
|
|||
|
|
│ [MapBlueprint]
|
|||
|
|
│ · TerrainTiles[]
|
|||
|
|
│ · PlatformTiles[]
|
|||
|
|
│ · SpawnPoints[]
|
|||
|
|
│ · StartPoint
|
|||
|
|
│ · Goal
|
|||
|
|
│ │
|
|||
|
|
├─ MapPreviewWindow [Generate] ────────────┤ in-memory preview
|
|||
|
|
│ (시각 검토) ◄───── DrawHandles ──────────┤
|
|||
|
|
│ │
|
|||
|
|
├─ [Apply to Scene] ──────────────────────►│ MapApplier
|
|||
|
|
│ │ · Tilemap.SetTile
|
|||
|
|
│ │ · Instantiate(Enemy/Player)
|
|||
|
|
│ ▼
|
|||
|
|
│ [Scene 영역 완성]
|
|||
|
|
│ │
|
|||
|
|
│ ▼
|
|||
|
|
│ (Play) GameOptimizer.AfterSceneLoad
|
|||
|
|
│ │
|
|||
|
|
│ ▼
|
|||
|
|
│ EnemyController.Start → patrol
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §4. 절차 생성 알고리즘
|
|||
|
|
|
|||
|
|
### 4-1. 입력
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
[CreateAssetMenu(menuName = "EerieVillage/Map/Generation Config")]
|
|||
|
|
public class MapGenerationConfig : ScriptableObject
|
|||
|
|
{
|
|||
|
|
[Header("길이 영역")]
|
|||
|
|
public int lengthTiles = 120; // 가로 Tile 총 길이 (1 Tile = 1 Unity unit)
|
|||
|
|
|
|||
|
|
[Header("거점·밀도 영역")]
|
|||
|
|
[Range(0f, 1f)] public float spawnDensity = 0.4f;
|
|||
|
|
public int minSpawnSpacingTiles = 12;
|
|||
|
|
public int maxSpawnPerMap = 16;
|
|||
|
|
|
|||
|
|
[Header("발판 영역")]
|
|||
|
|
[Range(0f, 1f)] public float platformDensity = 0.5f;
|
|||
|
|
public int minPlatformGapTiles = 3;
|
|||
|
|
public int maxPlatformGapTiles = 6;
|
|||
|
|
public int minPlatformWidthTiles = 2;
|
|||
|
|
public int maxPlatformWidthTiles = 6;
|
|||
|
|
public int minPlatformHeight = 2; // 지면 위 Tile 단위
|
|||
|
|
public int maxPlatformHeight = 5; // 점프 도달 5.06m 영역 (§2-5)
|
|||
|
|
|
|||
|
|
[Header("Start/Goal 영역")]
|
|||
|
|
public int startMarginTiles = 4; // 좌측 끝 Start Point margin
|
|||
|
|
public int goalMarginTiles = 4; // 우측 끝 Goal margin
|
|||
|
|
|
|||
|
|
[Header("리소스 영역")]
|
|||
|
|
public MapResourceCatalog catalog;
|
|||
|
|
public PlatformerJumpProfile jumpProfile;
|
|||
|
|
public StageThemeProfile stageTheme; // 시각 테마 (Tile 가중치)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4-2. 출력 (in-memory)
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class MapBlueprint
|
|||
|
|
{
|
|||
|
|
public int lengthTiles;
|
|||
|
|
public int height; // 청크 영역 최대 높이
|
|||
|
|
public List<TerrainTilePlace> terrain; // 지면 Tile (Layer 0)
|
|||
|
|
public List<PlatformTilePlace> platforms; // 공중 발판 Tile (Layer 16)
|
|||
|
|
public List<SpawnPointPlace> spawnPoints; // Enemy spawn 거점
|
|||
|
|
public Vector2 startPoint; // Player 시작 좌표
|
|||
|
|
public Vector2 goalPoint; // VictoryZone 좌표
|
|||
|
|
public List<PropPlace> backgroundProps; // fence·building 등 배경 Prefab
|
|||
|
|
public List<GimmickPlace> gimmicks; // 함정·이동 발판 등 (§11)
|
|||
|
|
public int generationSeed; // 추적용 (PD 결정 3: 매번 랜덤이나 디버그 추적)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4-3. 절차 (의사 코드)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Generate(config):
|
|||
|
|
1. seed = Random.Range(int.MinValue, int.MaxValue)
|
|||
|
|
Random.InitState(seed) // PD 결정 3: 매번 완전 랜덤 (사용자 입력 X, 디버그용 기록만)
|
|||
|
|
|
|||
|
|
2. lengthTiles = config.lengthTiles
|
|||
|
|
|
|||
|
|
3. ── 청크 분할 ──
|
|||
|
|
chunks = SplitIntoChunks(lengthTiles, chunkSize=20)
|
|||
|
|
각 chunk 영역 Theme 변동 권고 (§4-4)
|
|||
|
|
|
|||
|
|
4. ── 지면 생성 (TerrainPlanner) ──
|
|||
|
|
terrain = []
|
|||
|
|
baseY = 0
|
|||
|
|
for x in 0..lengthTiles:
|
|||
|
|
// 자연스러운 고저차 — Perlin noise 변조 (±2 tile)
|
|||
|
|
y = baseY + RoundedPerlin(x * 0.1f, amplitude=2)
|
|||
|
|
terrain.Add( {x, y, TileGround} )
|
|||
|
|
// 표면 1 Tile = TileGroundTop
|
|||
|
|
terrain.Add( {x, y+1, TileGroundTop} ) — y+1 미사용 (충돌 영역 영역)
|
|||
|
|
|
|||
|
|
5. ── 공중 발판 생성 (PlatformLayoutPlanner) ──
|
|||
|
|
platforms = []
|
|||
|
|
x = config.startMarginTiles + 5
|
|||
|
|
while x < lengthTiles - config.goalMarginTiles:
|
|||
|
|
if Random.value < config.platformDensity:
|
|||
|
|
width = Random.Range(config.minPlatformWidthTiles, config.maxPlatformWidthTiles+1)
|
|||
|
|
height = Random.Range(config.minPlatformHeight, config.maxPlatformHeight+1)
|
|||
|
|
// 점프 도달 가능성 검증 (jumpProfile)
|
|||
|
|
canReach = ValidateJumpReach(prevPlatformOrGround, {x, terrainY+height, width})
|
|||
|
|
if canReach:
|
|||
|
|
platforms.AddRange(GeneratePlatformTiles(x, terrainY+height, width))
|
|||
|
|
x += width + Random.Range(config.minPlatformGapTiles, config.maxPlatformGapTiles+1)
|
|||
|
|
continue
|
|||
|
|
x += 1
|
|||
|
|
|
|||
|
|
6. ── 거점 생성 (SpawnPointPlanner) ──
|
|||
|
|
targetCount = floor(lengthTiles * config.spawnDensity / config.minSpawnSpacingTiles)
|
|||
|
|
targetCount = min(targetCount, config.maxSpawnPerMap)
|
|||
|
|
spawnPoints = []
|
|||
|
|
// 거점 = 지면 + 일부 공중 발판 (PD 결정 안건 §15)
|
|||
|
|
candidates = AllValidSpawnPositions(terrain, platforms, marginFromStart=8, marginFromGoal=8, minSpacing=config.minSpawnSpacingTiles)
|
|||
|
|
Shuffle(candidates)
|
|||
|
|
spawnPoints = candidates.Take(targetCount)
|
|||
|
|
각 SpawnPoint 영역 EnemyType = RandomFromCatalog(config.catalog.enemyPrefabs)
|
|||
|
|
|
|||
|
|
7. ── Start/Goal ──
|
|||
|
|
startPoint = (config.startMarginTiles, terrain[startMarginTiles].y + 1.5)
|
|||
|
|
goalPoint = (lengthTiles - config.goalMarginTiles, terrain[lengthTiles-goalMarginTiles].y + 1.5)
|
|||
|
|
|
|||
|
|
8. ── 배경·기믹 ──
|
|||
|
|
backgroundProps = ScatterBackgroundProps(terrain, config.catalog.backgroundPrefabs, density)
|
|||
|
|
gimmicks = ApplyGimmickRules(config.catalog.gimmickPrefabs, terrain, platforms, density)
|
|||
|
|
|
|||
|
|
9. return MapBlueprint { ... }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4-4. 청크 분할 권고 (단조 영역 회피)
|
|||
|
|
|
|||
|
|
- chunkSize = 20 Tile 권고
|
|||
|
|
- 각 chunk 영역 platformDensity 가중치 변동 (Intro=0.3, Mid=0.6, BossPre=0.4)
|
|||
|
|
- stageTheme.tileVariantWeights에 따라 chunk별 시각 Tile pool 변동
|
|||
|
|
- PD 결정 안건 §15-3 — chunk 영역 자체 옵션 (스테이지 진행 양상) 노출 영역
|
|||
|
|
|
|||
|
|
### 4-5. ValidateJumpReach (§6 정합)
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
bool ValidateJumpReach(PlatformAnchor from, PlatformAnchor to)
|
|||
|
|
{
|
|||
|
|
float dx = Mathf.Abs(to.x - from.x);
|
|||
|
|
float dy = to.y - from.y; // 위로 이동 = 양수
|
|||
|
|
|
|||
|
|
// 점프 도달 가능 영역: §2-5 실측
|
|||
|
|
float maxHorizontal = jumpProfile.safeHorizontalDistance; // 5.78m (90% margin)
|
|||
|
|
float maxVertical = jumpProfile.safeVerticalHeight; // 5.06m
|
|||
|
|
|
|||
|
|
// 수평 거리 cap
|
|||
|
|
if (dx > maxHorizontal) return false;
|
|||
|
|
|
|||
|
|
// 위로 점프: 높이 cap
|
|||
|
|
if (dy > maxVertical) return false;
|
|||
|
|
|
|||
|
|
// 발판 → 발판: 포물선 통과 가능성 (단순 보수 — 최대 높이까지 도달 가능 시 OK)
|
|||
|
|
// dy 음수(아래) 발판 = 항상 도달 가능
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §5. Tile (배경) vs Prefab (기믹) 분리 룰
|
|||
|
|
|
|||
|
|
PD 결정 2 — **양쪽 다** 활용.
|
|||
|
|
|
|||
|
|
### 5-1. Tile 영역 (Tilemap)
|
|||
|
|
|
|||
|
|
| 영역 | Tile 자산 | Layer | 사용 |
|
|||
|
|
|------|---------|-------|------|
|
|||
|
|
| 지면 (Level Tilemap) | TileGround·TileGroundTop·TileGroundDark | 0 | 무한 지면·고저차 |
|
|||
|
|
| 공중 발판 (AutoForeground Tilemap) | TileFloatingLeftEdge·TileFloatingTileMiddle·TileFloatingRightEdge·cloud | 16 | Drop-Through 발판 |
|
|||
|
|
| 배경 장식 (BackgroundTilemap) | fence·MidgroundFiller (선택) | (선택·collider X 또는 별도 Layer) | 시각 |
|
|||
|
|
|
|||
|
|
### 5-2. Prefab 영역 (Instantiate)
|
|||
|
|
|
|||
|
|
| 영역 | Prefab | 사용 |
|
|||
|
|
|------|--------|------|
|
|||
|
|
| Player | Player.prefab | Start Point 1 인스턴스 |
|
|||
|
|
| Enemy | Enemy.prefab 또는 Enemy_M00N_*.prefab | SpawnPoint 영역 인스턴스 |
|
|||
|
|
| 배경 건물 | ShortBuilding·TallBuilding | 배경 배치 (Prefab 또는 Tile both 옵션) |
|
|||
|
|
| 기믹 | (신규) MovingPlatform·Trap·CheckPoint·VictoryZone 등 | 옵션 등록 영역 (§11) |
|
|||
|
|
|
|||
|
|
### 5-3. 결정 룰
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
요소 = 동적 동작·복잡 컴포넌트 영역? → Prefab
|
|||
|
|
요소 = 정적 시각·단순 충돌·반복 배치? → Tile
|
|||
|
|
경계 영역(건물 등) → 옵션·MapResourceCatalog에서 PD 자유 선택
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §6. 발판 배치 룰 (자연스러운 레벨 디자인)
|
|||
|
|
|
|||
|
|
### 6-1. PlatformerJumpProfile (실측 SOT)
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
[CreateAssetMenu(menuName = "EerieVillage/Map/Jump Profile")]
|
|||
|
|
public class PlatformerJumpProfile : ScriptableObject
|
|||
|
|
{
|
|||
|
|
[Header("실측 (KinematicObject·PlayerController·PlatformerModel)")]
|
|||
|
|
public float jumpTakeOffSpeed = 7f;
|
|||
|
|
public float jumpModifier = 1.5f;
|
|||
|
|
public float gravity = 9.81f;
|
|||
|
|
public float maxSpeed = 3f;
|
|||
|
|
public float gravityModifier = 1f;
|
|||
|
|
|
|||
|
|
// 산출 (자동 계산 — OnValidate)
|
|||
|
|
public float v0 => jumpTakeOffSpeed * jumpModifier;
|
|||
|
|
public float effectiveGravity => gravity * gravityModifier;
|
|||
|
|
public float maxVerticalHeight => (v0 * v0) / (2f * effectiveGravity); // 5.62m
|
|||
|
|
public float jumpRoundTripTime => 2f * v0 / effectiveGravity; // 2.14s
|
|||
|
|
public float maxHorizontalDistance => maxSpeed * jumpRoundTripTime; // 6.42m
|
|||
|
|
|
|||
|
|
[Header("안전 margin (자연 레벨 디자인)")]
|
|||
|
|
[Range(0.5f, 1.0f)] public float safetyMargin = 0.9f;
|
|||
|
|
public float safeVerticalHeight => maxVerticalHeight * safetyMargin; // 5.06m
|
|||
|
|
public float safeHorizontalDistance => maxHorizontalDistance * safetyMargin; // 5.78m
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6-2. 배치 룰 (자연스러움)
|
|||
|
|
|
|||
|
|
| 룰 | 수치 | 근거 |
|
|||
|
|
|----|-----|------|
|
|||
|
|
| 발판 간 수평 간격 min | 3 Tile (3m) | 1 Tile 점프 가능·연속 점프 리듬 |
|
|||
|
|
| 발판 간 수평 간격 max | 6 Tile (6m) | safeHorizontalDistance 5.78m 영역 안 |
|
|||
|
|
| 발판 높이 min | 2 Tile (지면 위 2m) | Player 캡슐 높이 약 1m → 점프 의무 영역 |
|
|||
|
|
| 발판 높이 max | 5 Tile (지면 위 5m) | safeVerticalHeight 5.06m 영역 안 |
|
|||
|
|
| 발판 폭 min | 2 Tile (2m) | EnemyController cliffCheckDistance=1m 영역 안전 patrol |
|
|||
|
|
| 발판 폭 max | 6 Tile (6m) | 단조 영역 회피 |
|
|||
|
|
| 연속 점프 (계단식) 영역 dx | 3~4 Tile, dy +1~+2 Tile | 자연 상승 리듬 |
|
|||
|
|
| 사선·이중 점프 | **PD 결정 안건 §15-2** | 현 KinematicObject 영역 2단 점프 미구현 |
|
|||
|
|
|
|||
|
|
### 6-3. 검증 영역
|
|||
|
|
|
|||
|
|
`MapGenerator.PlatformLayoutPlanner`가 모든 발판 영역 `PlatformerJumpProfile.ValidateJumpReach()` 통과 검증. 미통과 시 해당 발판 폐기 후 위치·높이 재시도 (최대 5회 retry).
|
|||
|
|
|
|||
|
|
### 6-4. EnemyController patrol 영역 (§9 정합)
|
|||
|
|
|
|||
|
|
발판 폭 ≥ 2 Tile + EnemyController fallThreshold=1m·cliffCheckDistance=1m → spawn 발판 영역 자동 patrol 동작 보장.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §7. 바닥 자동 충돌 적용
|
|||
|
|
|
|||
|
|
### 7-1. PD 요구 "바닥의 길은 사용자가 일일이 수정하지 않아도 충돌 영역이 자연스럽게 적용"
|
|||
|
|
|
|||
|
|
### 7-2. 기존 자산 활용 (§2-8 실측)
|
|||
|
|
|
|||
|
|
- 모든 Tile 자산 m_ColliderType=1 (Sprite) 이미 강제
|
|||
|
|
- 본 설계 = Tilemap 영역 SetTile 시 자동 영역 collider 적용
|
|||
|
|
|
|||
|
|
### 7-3. Tilemap 자동 충돌 영역 표준 패턴
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// MapApplier.cs
|
|||
|
|
void ApplyTerrainToLevelTilemap(MapBlueprint blueprint)
|
|||
|
|
{
|
|||
|
|
Tilemap levelTilemap = FindOrCreateLevelTilemap();
|
|||
|
|
foreach (var place in blueprint.terrain)
|
|||
|
|
{
|
|||
|
|
levelTilemap.SetTile(new Vector3Int(place.x, place.y, 0), place.tile);
|
|||
|
|
levelTilemap.SetColliderType(new Vector3Int(place.x, place.y, 0),
|
|||
|
|
Tile.ColliderType.Sprite);
|
|||
|
|
}
|
|||
|
|
var tc = levelTilemap.GetComponent<TilemapCollider2D>();
|
|||
|
|
if (tc == null) tc = levelTilemap.gameObject.AddComponent<TilemapCollider2D>();
|
|||
|
|
tc.compositeOperation = Collider2D.CompositeOperation.Merge;
|
|||
|
|
|
|||
|
|
// CompositeCollider2D 통합 (지면 영역 단일 collider — 성능·정합)
|
|||
|
|
var cc = levelTilemap.GetComponent<CompositeCollider2D>();
|
|||
|
|
if (cc == null) cc = levelTilemap.gameObject.AddComponent<CompositeCollider2D>();
|
|||
|
|
var rb = levelTilemap.GetComponent<Rigidbody2D>();
|
|||
|
|
if (rb == null) rb = levelTilemap.gameObject.AddComponent<Rigidbody2D>();
|
|||
|
|
rb.bodyType = RigidbodyType2D.Static;
|
|||
|
|
|
|||
|
|
tc.ProcessTilemapChanges();
|
|||
|
|
levelTilemap.gameObject.name = "Level"; // §2-7 GameOptimizer 영역 보존
|
|||
|
|
levelTilemap.gameObject.layer = 0; // §2-7 GameOptimizer 영역 보존
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7-4. 공중 발판 Tilemap 영역 (Drop-Through 영역)
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
void ApplyPlatformsToAutoForegroundTilemap(MapBlueprint blueprint)
|
|||
|
|
{
|
|||
|
|
Tilemap fgTilemap = FindOrCreateAutoForegroundTilemap();
|
|||
|
|
foreach (var place in blueprint.platforms)
|
|||
|
|
{
|
|||
|
|
fgTilemap.SetTile(new Vector3Int(place.x, place.y, 0), place.tile);
|
|||
|
|
fgTilemap.SetColliderType(new Vector3Int(place.x, place.y, 0),
|
|||
|
|
Tile.ColliderType.Sprite);
|
|||
|
|
}
|
|||
|
|
// GameOptimizer 영역 자동 영역 Layer 16 + TilemapCollider 보장
|
|||
|
|
fgTilemap.gameObject.name = "AutoForeground";
|
|||
|
|
fgTilemap.gameObject.layer = 16;
|
|||
|
|
EnsureCollider(fgTilemap);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7-5. 두 옵션 (PD 결정 안건 §15-4)
|
|||
|
|
|
|||
|
|
**옵션 A (권고)**: 본 설계 MapApplier 영역 직접 Level/AutoForeground 분리 적용 → GameOptimizer 영역 자동 분류 의존 X (예측성·결정성↑)
|
|||
|
|
|
|||
|
|
**옵션 B**: 모두 Level Tilemap에 배치 → GameOptimizer.SetupJumpThroughPlatforms() 영역 자동 분류 위임 (기존 시스템 활용·중복 회피)
|
|||
|
|
|
|||
|
|
→ **§15-4 PD 결정**.
|
|||
|
|
|
|||
|
|
본 PM 권고 옵션 A — Drop-Through 패턴 28회 시도(BT22~BT47)의 교훈 영역 명시적 분리가 안정.
|
|||
|
|
|
|||
|
|
### 7-6. Composite·자연스러운 충돌
|
|||
|
|
|
|||
|
|
CompositeCollider2D + Merge → 인접 Tile 영역 단일 collider 영역 통합. 가장자리 jitter·작은 틈 영역 차단.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §8. 거점(생성 포인트) + Start Point 배치
|
|||
|
|
|
|||
|
|
### 8-1. 옵션 정합 (PD 결정 4)
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
[Range(0f, 1f)] public float spawnDensity = 0.4f;
|
|||
|
|
// 0.0 = 거점 0개 / 0.5 = 중간 밀도 / 1.0 = 최대 밀도 (maxSpawnPerMap cap)
|
|||
|
|
public int minSpawnSpacingTiles = 12;
|
|||
|
|
public int maxSpawnPerMap = 16;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8-2. 산출 공식
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
desiredCount = floor(lengthTiles * spawnDensity / minSpawnSpacingTiles)
|
|||
|
|
finalCount = min(desiredCount, maxSpawnPerMap)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
예: lengthTiles=120·spawnDensity=0.5·minSpawnSpacingTiles=12 → desiredCount=5·finalCount=5
|
|||
|
|
|
|||
|
|
### 8-3. 거점 후보 영역
|
|||
|
|
|
|||
|
|
| 영역 | 적격 |
|
|||
|
|
|------|------|
|
|||
|
|
| 지면 평탄 영역 (Tile 폭 ≥3 연속) | ✓ |
|
|||
|
|
| 공중 발판 (폭 ≥3 Tile) | ✓ (PD 결정 안건 §15-5: 거점을 공중 발판에도 둘지) |
|
|||
|
|
| Start Point ±8 Tile | ✗ (난이도 초기) |
|
|||
|
|
| Goal ±8 Tile | ✗ |
|
|||
|
|
| 다른 거점 < minSpawnSpacingTiles | ✗ |
|
|||
|
|
|
|||
|
|
### 8-4. Start Point
|
|||
|
|
|
|||
|
|
- 좌측 끝 + `startMarginTiles` (default 4) 위치
|
|||
|
|
- 지면 Tile 표면 + 1.5m 위 (Player 캡슐 영역 안전 spawn)
|
|||
|
|
- 본 설계 = 항상 좌측 고정 (PD 결정 안건 §15-6 — 양 끝 random 옵션)
|
|||
|
|
|
|||
|
|
### 8-5. Goal
|
|||
|
|
|
|||
|
|
- 우측 끝 - `goalMarginTiles` 위치
|
|||
|
|
- VictoryZone Prefab Instantiate (현 영역 VictoryZone Prefab 영역 검증 필요 — `Mechanics/VictoryZone.cs` 존재 확인됨, §2-7 GameOptimizer 영역 excluded)
|
|||
|
|
|
|||
|
|
### 8-6. SpawnPoint 영역 (MonoBehaviour 확장)
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class SpawnPoint : MonoBehaviour // 기존 11줄 영역 확장
|
|||
|
|
{
|
|||
|
|
[Header("Spawn 영역")]
|
|||
|
|
public GameObject enemyPrefab; // Enemy_M00N or Enemy.prefab + MonsterRandomizer
|
|||
|
|
public float spawnRadiusX = 0.5f; // 거점 ±X Tile 영역 random spawn
|
|||
|
|
public int initialSpawnCount = 1; // 최초 spawn 수
|
|||
|
|
public int maxConcurrent = 1; // 동시 spawn 상한 (현 영역 1 — re-spawn 영역 PD 결정 §15-7)
|
|||
|
|
|
|||
|
|
[Header("미리보기 영역 — Editor only")]
|
|||
|
|
public Color gizmoColor = new Color(1f, 0.3f, 0.3f, 0.5f);
|
|||
|
|
|
|||
|
|
void Start()
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < initialSpawnCount; i++)
|
|||
|
|
{
|
|||
|
|
Vector3 pos = transform.position + new Vector3(
|
|||
|
|
Random.Range(-spawnRadiusX, spawnRadiusX), 0f, 0f);
|
|||
|
|
var enemy = Instantiate(enemyPrefab, pos, Quaternion.identity);
|
|||
|
|
// EnemyController.Awake → _startX·_startY 설정 자동 진행 (§2-6 정합)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#if UNITY_EDITOR
|
|||
|
|
void OnDrawGizmos()
|
|||
|
|
{
|
|||
|
|
Gizmos.color = gizmoColor;
|
|||
|
|
Gizmos.DrawWireSphere(transform.position, 0.5f);
|
|||
|
|
Gizmos.DrawLine(transform.position + Vector3.left * spawnRadiusX,
|
|||
|
|
transform.position + Vector3.right * spawnRadiusX);
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §9. 몬스터 spawn 룰
|
|||
|
|
|
|||
|
|
### 9-1. PD 요구 "현재 만들어진 몬스터 이동 패턴을 활용"
|
|||
|
|
|
|||
|
|
§2-6 EnemyController 자동 patrol 시스템 = **완전 계승**. SpawnPoint 영역 Instantiate만 처리.
|
|||
|
|
|
|||
|
|
### 9-2. Enemy spawn 절차
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. MapApplier가 SpawnPoint Prefab 영역 Scene에 배치 (위치=거점 좌표)
|
|||
|
|
2. SpawnPoint.Start() → enemyPrefab Instantiate (Awake → MonsterRandomizer 영역 controller random)
|
|||
|
|
3. EnemyController.Awake() → _startX = transform.position.x, _startY = transform.position.y
|
|||
|
|
4. EnemyController.Start() → MeasureSafeWalkDistance(±1) — Tilemap cell 기반 좌우 patrol 거리 측정
|
|||
|
|
5. _isInitialized = true → UpdatePatrol() 영역 자동 patrol 시작
|
|||
|
|
6. 떨어짐 검출 (y < _startY - 1.0) → 시작 위치 텔레포트
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9-3. EnemyType 카탈로그 (MapResourceCatalog 영역)
|
|||
|
|
|
|||
|
|
| Catalog 슬롯 | 영역 |
|
|||
|
|
|------------|------|
|
|||
|
|
| `enemyPrefabs[]` | Enemy_M001_Wolf·M002_Bat·M003_ZombieM·M004_ZombieF·M005_Ghost·M006_Spider |
|
|||
|
|
| `enemyWeights[]` | stageTheme별 가중치 (Stage1=Wolf 70%·Bat 30% 등) |
|
|||
|
|
| `flyingEnemyPrefabs[]` | M002 Bat (cliffCheck skip·공중 spawn) |
|
|||
|
|
|
|||
|
|
### 9-4. spawn 위치 영역 — 거점 주변
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
spawnPos = spawnPoint.position + Vector3(Random(-spawnRadiusX, spawnRadiusX), 0, 0)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
EnemyController _startX는 Awake 시점 transform.position.x → 본 spawn 위치 = patrol 기준점.
|
|||
|
|
|
|||
|
|
### 9-5. 동시 spawn 상한
|
|||
|
|
|
|||
|
|
현 영역 SpawnPoint 1개당 1 Enemy. **재-spawn (사망 후 시간 경과 재출현) 영역 = PD 결정 안건 §15-7**.
|
|||
|
|
|
|||
|
|
### 9-6. 공중 SpawnPoint (M002 Bat)
|
|||
|
|
|
|||
|
|
`IsFlying=true` 영역 EnemyController는 `flyingYOffset = 2f` 자동 적용 → SpawnPoint 영역 지면 위 SpawnPoint에 M002 prefab 배치해도 Awake 영역 +2m 자동 상승. 별도 영역 처리 불요.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §10. 시각 시뮬레이터 — Unity Editor Window 1순위 결정
|
|||
|
|
|
|||
|
|
### 10-1. 가능성 판단 (PD 결정 1 — 1순위 EditorWindow)
|
|||
|
|
|
|||
|
|
**판단 결과: 가능. 1순위 EditorWindow 채택.**
|
|||
|
|
|
|||
|
|
근거:
|
|||
|
|
- `E:/EerieVillage/Assets/Editor/` 영역 GitAutoSync만 존재·신규 EditorWindow 도입 깨끗 (§2-13)
|
|||
|
|
- Unity EditorWindow + GUILayout + Handles.Draw* 영역 표준 패턴
|
|||
|
|
- Tile data·SpawnPoint 위치·점프 도달 영역 모두 in-memory MapBlueprint 영역 직접 그리기 가능 (Scene 적용 전)
|
|||
|
|
|
|||
|
|
### 10-2. MapPreviewWindow 구조 (Editor 폴더)
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// E:/EerieVillage/Assets/Editor/Map/MapPreviewWindow.cs (신규)
|
|||
|
|
public class MapPreviewWindow : EditorWindow
|
|||
|
|
{
|
|||
|
|
[MenuItem("EerieVillage/Map/Preview")]
|
|||
|
|
public static void Open() => GetWindow<MapPreviewWindow>("Map Preview");
|
|||
|
|
|
|||
|
|
MapGenerationConfig config;
|
|||
|
|
MapBlueprint currentBlueprint;
|
|||
|
|
Vector2 previewScroll;
|
|||
|
|
float zoom = 1f;
|
|||
|
|
|
|||
|
|
void OnGUI()
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.LabelField("Map Preview", EditorStyles.boldLabel);
|
|||
|
|
config = (MapGenerationConfig)EditorGUILayout.ObjectField("Config", config,
|
|||
|
|
typeof(MapGenerationConfig), false);
|
|||
|
|
EditorGUILayout.BeginHorizontal();
|
|||
|
|
if (GUILayout.Button("Generate", GUILayout.Width(100)))
|
|||
|
|
{
|
|||
|
|
currentBlueprint = MapGenerator.Generate(config);
|
|||
|
|
}
|
|||
|
|
if (GUILayout.Button("Apply to Scene", GUILayout.Width(120)))
|
|||
|
|
{
|
|||
|
|
MapApplier.Apply(currentBlueprint, config);
|
|||
|
|
}
|
|||
|
|
if (GUILayout.Button("Clear Scene", GUILayout.Width(100)))
|
|||
|
|
{
|
|||
|
|
MapApplier.Clear();
|
|||
|
|
}
|
|||
|
|
EditorGUILayout.EndHorizontal();
|
|||
|
|
|
|||
|
|
zoom = EditorGUILayout.Slider("Zoom", zoom, 0.2f, 3f);
|
|||
|
|
|
|||
|
|
if (currentBlueprint != null)
|
|||
|
|
{
|
|||
|
|
DrawPreview(currentBlueprint);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void DrawPreview(MapBlueprint bp)
|
|||
|
|
{
|
|||
|
|
// GUI.DrawTexture로 grid 배경
|
|||
|
|
// bp.terrain·bp.platforms·bp.spawnPoints·bp.startPoint·bp.goalPoint를
|
|||
|
|
// Rect로 변환·GUI.Box 영역 그림
|
|||
|
|
// 점프 도달 영역 = Handles.DrawSolidArc 또는 GUI line
|
|||
|
|
var rect = GUILayoutUtility.GetRect(position.width, position.height - 100);
|
|||
|
|
previewScroll = GUI.BeginScrollView(rect, previewScroll,
|
|||
|
|
new Rect(0, 0, bp.lengthTiles * 16 * zoom,
|
|||
|
|
bp.height * 16 * zoom));
|
|||
|
|
// Terrain (회색)
|
|||
|
|
foreach (var t in bp.terrain)
|
|||
|
|
EditorGUI.DrawRect(TileToRect(t.x, t.y, zoom), new Color(0.4f, 0.3f, 0.2f));
|
|||
|
|
// Platforms (녹색)
|
|||
|
|
foreach (var p in bp.platforms)
|
|||
|
|
EditorGUI.DrawRect(TileToRect(p.x, p.y, zoom), new Color(0.3f, 0.7f, 0.3f));
|
|||
|
|
// SpawnPoints (빨강)
|
|||
|
|
foreach (var s in bp.spawnPoints)
|
|||
|
|
EditorGUI.DrawRect(WorldToRect(s.position, zoom, size: 0.6f),
|
|||
|
|
new Color(0.9f, 0.2f, 0.2f));
|
|||
|
|
// Start (파랑)
|
|||
|
|
EditorGUI.DrawRect(WorldToRect(bp.startPoint, zoom, size: 1f),
|
|||
|
|
new Color(0.2f, 0.5f, 0.9f));
|
|||
|
|
// Goal (노랑)
|
|||
|
|
EditorGUI.DrawRect(WorldToRect(bp.goalPoint, zoom, size: 1f),
|
|||
|
|
new Color(0.9f, 0.9f, 0.2f));
|
|||
|
|
// 점프 도달 영역 (반투명 영역) — option toggle
|
|||
|
|
if (showJumpReach)
|
|||
|
|
DrawJumpReachOverlays(bp);
|
|||
|
|
GUI.EndScrollView();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 10-3. 시각 요소
|
|||
|
|
|
|||
|
|
| 요소 | 색상 | 표시 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 지면 Tile | 갈색 0.4·0.3·0.2 | 1×1 Rect |
|
|||
|
|
| 공중 발판 Tile | 녹색 0.3·0.7·0.3 | 1×1 Rect |
|
|||
|
|
| SpawnPoint | 빨강 0.9·0.2·0.2 | 0.6×0.6 Rect + 거점 ±spawnRadiusX 선 |
|
|||
|
|
| Start Point | 파랑 0.2·0.5·0.9 | 1×1 Rect + "S" 텍스트 |
|
|||
|
|
| Goal Point | 노랑 0.9·0.9·0.2 | 1×1 Rect + "G" 텍스트 |
|
|||
|
|
| 점프 도달 영역 | 반투명 청 (toggle) | 발판 가장자리 영역 ±5.78m·+5.06m 호 |
|
|||
|
|
|
|||
|
|
### 10-4. Fallback — Scene 미리보기 (옵션 어렵 시)
|
|||
|
|
|
|||
|
|
본 PM 판단 = EditorWindow 영역 가능 — Fallback 비활성.
|
|||
|
|
만약 GUILayout 영역 성능 한계 발생 시 → Scene Hierarchy 영역 Empty GameObject ("MapPreview_Temp") 영역 placeholder Sprite Instantiate → SceneView 영역 직접 표시.
|
|||
|
|
|
|||
|
|
### 10-5. Editor 호출 영역 워크플로우
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. PD: MapGenerationConfig SO Editor 영역 옵션 조정
|
|||
|
|
2. PD: EerieVillage/Map/Preview 메뉴 → MapPreviewWindow 활성
|
|||
|
|
3. PD: [Generate] → in-memory MapBlueprint 확인
|
|||
|
|
4. PD: 시각 영역 만족 시 [Apply to Scene] → Tilemap·Prefab 적용
|
|||
|
|
5. PD: 불만족 시 [Generate] 반복 (매번 완전 랜덤·PD 결정 3)
|
|||
|
|
6. PD: Play → 실제 게임 확인
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §11. 맵 리소스 등록 시스템
|
|||
|
|
|
|||
|
|
### 11-1. MapResourceCatalog (ScriptableObject)
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
[CreateAssetMenu(menuName = "EerieVillage/Map/Resource Catalog")]
|
|||
|
|
public class MapResourceCatalog : ScriptableObject
|
|||
|
|
{
|
|||
|
|
[Header("지면 Tile 영역")]
|
|||
|
|
public TileEntry[] groundTiles; // TileGround·TileGroundTop·TileGroundDark
|
|||
|
|
|
|||
|
|
[Header("공중 발판 Tile 영역")]
|
|||
|
|
public TileEntry[] platformLeftEdge;
|
|||
|
|
public TileEntry[] platformMiddle;
|
|||
|
|
public TileEntry[] platformRightEdge;
|
|||
|
|
public TileEntry[] platformCloud; // 변형 (cloud.asset)
|
|||
|
|
|
|||
|
|
[Header("배경 Tile 영역 (선택)")]
|
|||
|
|
public TileEntry[] backgroundTiles; // fence·MidgroundFiller
|
|||
|
|
|
|||
|
|
[Header("Enemy Prefab 영역")]
|
|||
|
|
public PrefabEntry[] enemyPrefabs; // Enemy_M001~M006
|
|||
|
|
|
|||
|
|
[Header("Player Prefab 영역")]
|
|||
|
|
public GameObject playerPrefab;
|
|||
|
|
|
|||
|
|
[Header("Goal Prefab 영역")]
|
|||
|
|
public GameObject goalPrefab; // VictoryZone
|
|||
|
|
|
|||
|
|
[Header("배경 Prefab 영역")]
|
|||
|
|
public PrefabEntry[] backgroundPrefabs; // ShortBuilding·TallBuilding 등
|
|||
|
|
|
|||
|
|
[Header("기믹 Prefab 영역 (신규·향후 확장)")]
|
|||
|
|
public PrefabEntry[] gimmickPrefabs; // 이동 발판·낙하 함정·체크포인트 등
|
|||
|
|
|
|||
|
|
[Header("SpawnPoint Prefab 영역")]
|
|||
|
|
public GameObject spawnPointPrefab; // SpawnPoint.cs 부착 영역
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[System.Serializable]
|
|||
|
|
public class TileEntry
|
|||
|
|
{
|
|||
|
|
public TileBase tile;
|
|||
|
|
[Range(0f, 10f)] public float weight = 1f;
|
|||
|
|
public string[] tags; // "stage1", "dark" 등
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[System.Serializable]
|
|||
|
|
public class PrefabEntry
|
|||
|
|
{
|
|||
|
|
public GameObject prefab;
|
|||
|
|
[Range(0f, 10f)] public float weight = 1f;
|
|||
|
|
public string[] tags;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 11-2. PD 자유 등록 영역
|
|||
|
|
|
|||
|
|
PD가 신규 Tile·Prefab 영역 영역 추가 시 → MapResourceCatalog asset 영역 Inspector 영역 슬롯 영역 드래그·드롭. 코드 수정 X·tag·weight 영역 분류만.
|
|||
|
|
|
|||
|
|
### 11-3. StageThemeProfile (스테이지별 Theme)
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
[CreateAssetMenu(menuName = "EerieVillage/Map/Stage Theme")]
|
|||
|
|
public class StageThemeProfile : ScriptableObject
|
|||
|
|
{
|
|||
|
|
public string stageName = "Stage1_DuskVillageOutskirts";
|
|||
|
|
public MapResourceCatalog catalog; // base catalog
|
|||
|
|
public string[] filterTags; // "stage1" tag 영역 Tile만 활용
|
|||
|
|
public Color skyTint = Color.white;
|
|||
|
|
// 추후 — 음악·환경 영역
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 11-4. 기획 SOT 5종 스테이지 정합
|
|||
|
|
|
|||
|
|
`프로젝트/EerieVillage/기획/level/01_스테이지_구조.md` §2 영역 5종 stageTheme 영역 매핑:
|
|||
|
|
|
|||
|
|
| Stage | StageThemeProfile.stageName | filterTags |
|
|||
|
|
|-------|---------------------------|------------|
|
|||
|
|
| 1 황혼 고을 어귀 | Stage1_DuskVillageOutskirts | stage1·dusk |
|
|||
|
|
| 2 버려진 산사 | Stage2_AbandonedMountainTemple | stage2·moonlit |
|
|||
|
|
| 3 뼈 바위 계곡 | Stage3_BoneRockValley | stage3·foggy |
|
|||
|
|
| 4 폐가·지하 창고 | Stage4_AbandonedHouseBasement | stage4·midnight |
|
|||
|
|
| 5 공동묘지 심부 | Stage5_GraveyardDepths | stage5·dawn |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §12. 기존 시스템 통합 영역
|
|||
|
|
|
|||
|
|
### 12-1. BT5-Dev 발판 시스템 영역
|
|||
|
|
|
|||
|
|
| 시스템 | 본 설계 활용 |
|
|||
|
|
|--------|------------|
|
|||
|
|
| KinematicObject contactFilter | Drop-Through Layer 16 발판 — 본 설계 AutoForeground Tilemap Layer 16 강제 정합 |
|
|||
|
|
| PlayerController UpdateContactFilterForDropThrough | 본 설계 무관 (Player 영역 그대로) |
|
|||
|
|
| GameOptimizer.SetupJumpThroughPlatforms | **상호작용 의무** — §7-5 옵션 A/B 결정 |
|
|||
|
|
| Tile m_ColliderType=1 (Sprite) | §2-8 모든 Tile 기 강제 → 본 설계 SetColliderType(Sprite) 영역 정합 |
|
|||
|
|
| CompositeCollider2D Merge | 본 설계 Level Tilemap 영역 부착 (§7-3) |
|
|||
|
|
|
|||
|
|
### 12-2. EnemyController patrol 영역
|
|||
|
|
|
|||
|
|
| 영역 | 본 설계 정합 |
|
|||
|
|
|------|------------|
|
|||
|
|
| _startX·_startY Awake 측정 | SpawnPoint.Instantiate → Enemy.Awake 자동 동작 |
|
|||
|
|
| MeasureSafeWalkDistance Tilemap cell 기반 | 본 설계 = AutoForeground Tilemap 영역 등록 시 자동 patrol |
|
|||
|
|
| cliffCheckDistance=1m | 본 설계 발판 폭 min 2 Tile 영역 안전 |
|
|||
|
|
| fallThreshold=1m → 시작 위치 텔레포트 | 본 설계 spawn 발판 영역 EnemyController 자동 복귀 |
|
|||
|
|
| IsFlying M002 Bat | 본 설계 catalog 영역 flyingEnemyPrefabs 영역 별도 분류 |
|
|||
|
|
|
|||
|
|
### 12-3. KinematicObject 점프 물리 영역
|
|||
|
|
|
|||
|
|
본 설계 = `PlatformerJumpProfile` SO 영역 실측 수치 직접 참조. KinematicObject 영역 변경 X.
|
|||
|
|
|
|||
|
|
### 12-4. M001~M006 6 Enemy prefab 영역
|
|||
|
|
|
|||
|
|
본 설계 = MapResourceCatalog 영역 6 prefab 등록·stageTheme별 가중치. MonsterRandomizer 영역 random 6종 활용 영역 = 옵션 (Enemy.prefab + MonsterRandomizer) vs (Enemy_M00N 직접 prefab) — **PD 결정 안건 §15-8**.
|
|||
|
|
|
|||
|
|
### 12-5. BT12 스킬 시스템 영역
|
|||
|
|
|
|||
|
|
**영향 없음** (§2-11). Player·Enemy 영역 그대로 spawn → PlayerSkillInventory·ActiveSkillRuntime·6 Effector 모두 정상 동작.
|
|||
|
|
|
|||
|
|
### 12-6. GitAutoSync (Editor)
|
|||
|
|
|
|||
|
|
EerieVillage `E:/EerieVillage/Assets/Editor/GitAutoSync/` 영역 영향 X (별도 영역).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §13. Phase 2 작업 단위 분해 (C49 단계 정합)
|
|||
|
|
|
|||
|
|
### 13-1. Phase 2-A — 데이터 SOT 영역 (클라이언트팀 Sonnet)
|
|||
|
|
|
|||
|
|
| 산출물 | 위치 | 분량 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| PlatformerJumpProfile.cs (ScriptableObject) | `Assets/Scripts/Map/Data/` | 50줄 |
|
|||
|
|
| MapResourceCatalog.cs + TileEntry/PrefabEntry | `Assets/Scripts/Map/Data/` | 80줄 |
|
|||
|
|
| StageThemeProfile.cs | `Assets/Scripts/Map/Data/` | 30줄 |
|
|||
|
|
| MapGenerationConfig.cs | `Assets/Scripts/Map/Data/` | 60줄 |
|
|||
|
|
| MapBlueprint.cs + Place 영역 4종 struct | `Assets/Scripts/Map/Data/` | 100줄 |
|
|||
|
|
| Default .asset 인스턴스 4종 (실측 수치 반영) | `Assets/Data/Map/` | (PD 미리 1개·확장 PD) |
|
|||
|
|
|
|||
|
|
### 13-2. Phase 2-B — 절차 생성기 (클라이언트팀 Sonnet)
|
|||
|
|
|
|||
|
|
| 산출물 | 분량 |
|
|||
|
|
|--------|------|
|
|||
|
|
| MapGenerator.cs (static·Generate 메서드) | 150줄 |
|
|||
|
|
| TerrainPlanner.cs (Perlin 변조·고저차) | 80줄 |
|
|||
|
|
| PlatformLayoutPlanner.cs (배치 룰·ValidateJumpReach) | 150줄 |
|
|||
|
|
| SpawnPointPlanner.cs (밀도·간격·후보 영역) | 100줄 |
|
|||
|
|
| BackgroundPropPlanner.cs (배경 산포) | 50줄 |
|
|||
|
|
| EditMode 테스트 — 100회 Generate·ValidateJumpReach 모든 발판 통과 검증 | 200줄 |
|
|||
|
|
|
|||
|
|
### 13-3. Phase 2-C — Editor Window + Applier (클라이언트팀 Sonnet)
|
|||
|
|
|
|||
|
|
| 산출물 | 분량 |
|
|||
|
|
|--------|------|
|
|||
|
|
| MapPreviewWindow.cs (EditorWindow + DrawPreview) | 250줄 |
|
|||
|
|
| MapApplier.cs (Tilemap.SetTile + Instantiate + Clear) | 150줄 |
|
|||
|
|
| SpawnPoint.cs 확장 (Spawn 영역 옵션·OnDrawGizmos) | 80줄 |
|
|||
|
|
| Editor 메뉴·shortcut | (포함) |
|
|||
|
|
|
|||
|
|
### 13-4. Phase 2-D — QA·정합 (개발팀장 Opus 검증)
|
|||
|
|
|
|||
|
|
| 영역 | 영역 |
|
|||
|
|
|------|------|
|
|||
|
|
| Stage1~5 Default Config 각 영역 Generate + Apply + Play 검증 | 5종 |
|
|||
|
|
| 점프 도달 가능성 검증 — 100% pass | 정량 |
|
|||
|
|
| Enemy patrol 정합 검증 (BT109 Tilemap cell 영역 정합) | 정량 |
|
|||
|
|
| GameOptimizer SetupJumpThroughPlatforms 충돌 검증 | 정성 |
|
|||
|
|
| 시뮬레이터 Apply→Play 일관성 검증 | 정성 |
|
|||
|
|
|
|||
|
|
### 13-5. C49 표준 프로세스 적용
|
|||
|
|
|
|||
|
|
1. **개발팀장 (Opus·본 문서)** 설계 v1 산출 ✓
|
|||
|
|
2. **클라이언트팀장 (Opus)** 본 문서 영역 Phase 2-A/B/C 분할 위임 결정
|
|||
|
|
3. **게임플레이 프로그래머 (Sonnet)** 코드 구현
|
|||
|
|
4. **클라이언트팀장 (Opus)** Phase 2 코드 검증
|
|||
|
|
5. **개발팀장 (Opus)** Phase 2-D 영역 정합 검증 → 완료 아카이브
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §14. 기각안
|
|||
|
|
|
|||
|
|
### 14-1. (기각 1) Seed 입력 기반 재현 가능 랜덤 채택
|
|||
|
|
|
|||
|
|
**기각 이유**: PD 결정 3 — "매번 완전 랜덤 (Seed 입력 X · 재현성 비요구)". Seed 입력 UI·재현 로직 영역 코드 부담 + PD 명시 비요구.
|
|||
|
|
|
|||
|
|
**보완**: MapBlueprint.generationSeed 영역 자동 기록 (디버그·로그 영역 추적용). PD가 후속 재현 의무 발생 시 generationSeed 영역 InitState 재 invoke 가능.
|
|||
|
|
|
|||
|
|
### 14-2. (기각 2) 전체 수동 배치 + 부분 셔플 (기존 기획 §5 채택)
|
|||
|
|
|
|||
|
|
**기각 이유**: PD 신규 지시 (2026-05-18) = "맵의 길이만 지정하면, 랜덤으로 자동 생성" → 기획 §5 "전체 수동 배치 + 구간 2 적 배치 소규모 셔플" **OVERRIDE**.
|
|||
|
|
|
|||
|
|
**기획 후속 정합 안건**: §15-1 PD 결정 대기. 기획팀 영역 SOT 정정 의무 (level-designer 후속).
|
|||
|
|
|
|||
|
|
### 14-3. (기각 3) Cellular Automata 또는 BSP 영역 던전 생성 알고리즘
|
|||
|
|
|
|||
|
|
**기각 이유**: 횡스크롤 플랫포머 영역 1D 진행 영역 단순 chunk 분할 (4-3) 영역 충분. CA·BSP 영역 2D top-down 영역 적합.
|
|||
|
|
|
|||
|
|
### 14-4. (기각 4) Runtime Generate (게임 진입 시 매번 절차 생성)
|
|||
|
|
|
|||
|
|
**기각 이유**: 본 설계 Phase 1 = Editor 영역 Generate→Apply→Save Scene. Runtime Generate 옵션은 향후 확장 (Phase 3-C 영역). 현 Phase = PD 시각 검토 영역 = Editor 영역 적용 후 Play.
|
|||
|
|
|
|||
|
|
**보완**: MapGenerator·MapApplier 영역 Editor·Runtime 양립 가능 영역 설계 → Phase 3-C 영역 즉시 활성 가능.
|
|||
|
|
|
|||
|
|
### 14-5. (기각 5) Procedural 영역 별도 신규 Scene 생성
|
|||
|
|
|
|||
|
|
**기각 이유**: 기존 `Ingame.unity` 영역 단일 Scene 영역 활용 — Player·UI·Camera·Health·Skill 영역 모두 부착됨. 본 설계 = Ingame.unity 영역 Tilemap·SpawnPoint만 절차 영역 교체.
|
|||
|
|
|
|||
|
|
### 14-6. (기각 6) Boss 방 영역 절차 생성 포함
|
|||
|
|
|
|||
|
|
**기각 이유**: 기획 §3-2 영역 보스 방 = "고정 카메라·후방 차단·전용 지형". 자연 생성 영역 적합 X. 본 설계 = 도입·전개 영역만, 보스 방 = 별도 수동 Scene 또는 본 설계 영역 마지막 chunk 영역 hand-place placeholder.
|
|||
|
|
|
|||
|
|
**보완**: MapGenerationConfig 영역 `bool includeBossRoom` 영역 옵션 + Boss 방 영역 별도 prefab 영역 Instantiate (현 Phase 1 영역 PD 결정 보류·§15-9 영역).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §15. PD 결정 대기 안건 (Phase 2 진입 전 결정 의무)
|
|||
|
|
|
|||
|
|
### 15-1. 기획 SOT 정합 — `프로젝트/EerieVillage/기획/level/01_스테이지_구조.md` §5 OVERRIDE 처리
|
|||
|
|
|
|||
|
|
기획 영역 §5 "전체 수동 배치 + 구간 2 적 배치 소규모 셔플" vs PD 신규 지시 "맵의 길이만 지정하면 랜덤으로 자동 생성" 충돌.
|
|||
|
|
|
|||
|
|
**옵션 A**: 기획 SOT 정정 — level-designer 후속 갱신
|
|||
|
|
**옵션 B**: 두 영역 공존 — 본 설계 절차 생성기 + 기획 수동 배치 영역 두 가지 운용
|
|||
|
|
|
|||
|
|
### 15-2. 사선·이중 점프 도입 여부
|
|||
|
|
|
|||
|
|
현 KinematicObject 영역 2단 점프 X. 본 설계 §6-2 영역 단일 점프 영역만 ValidateJumpReach.
|
|||
|
|
|
|||
|
|
**옵션 A**: 단일 점프만 유지 — 본 설계 그대로
|
|||
|
|
**옵션 B**: 2단 점프 추가 — 본 설계 maxVerticalHeight 약 2배 영역 재산출 의무 (PlayerController.UpdateJumpState 영역 추가 구현)
|
|||
|
|
|
|||
|
|
### 15-3. chunk 영역 옵션 노출 영역
|
|||
|
|
|
|||
|
|
본 설계 §4-4 영역 chunk 영역 platformDensity 가중치 변동 (Intro 0.3·Mid 0.6·BossPre 0.4) 기본값.
|
|||
|
|
|
|||
|
|
**옵션 A**: 기본값 고정 — PD 옵션 노출 X
|
|||
|
|
**옵션 B**: MapGenerationConfig 영역 `ChunkProfile[]` 영역 PD 노출 — 세부 영역 영역
|
|||
|
|
|
|||
|
|
### 15-4. Level/AutoForeground 분리 전략
|
|||
|
|
|
|||
|
|
§7-5 영역 옵션 A (본 설계 영역 직접 분리·권고) vs 옵션 B (GameOptimizer 위임).
|
|||
|
|
|
|||
|
|
### 15-5. 공중 발판 영역 SpawnPoint 배치 여부
|
|||
|
|
|
|||
|
|
§8-3 영역 공중 발판 (폭 ≥3 Tile) 영역 거점 후보 영역.
|
|||
|
|
|
|||
|
|
**옵션 A**: 지면만 거점 — 안전·patrol 거리 보장
|
|||
|
|
**옵션 B**: 공중 발판 영역 거점 — 입체적 전투·플랫포밍 강조 (단·spawn Enemy 떨어짐 위험)
|
|||
|
|
|
|||
|
|
### 15-6. Start Point 좌측 고정 vs random
|
|||
|
|
|
|||
|
|
§8-4 영역 본 설계 영역 항상 좌측 고정. 향후 영역 양 끝 random·역방향 진행 영역.
|
|||
|
|
|
|||
|
|
### 15-7. Enemy 재-spawn (사망 후 시간 경과 재출현)
|
|||
|
|
|
|||
|
|
§9-5 영역 SpawnPoint 1개당 1 Enemy 영역 1회 spawn 영역. 재-spawn 영역 옵션·timer·count 영역.
|
|||
|
|
|
|||
|
|
### 15-8. Enemy spawn 방식 — `Enemy.prefab + MonsterRandomizer` vs `Enemy_M00N` 직접
|
|||
|
|
|
|||
|
|
§12-4 영역 두 영역 영역. 본 PM 권고 = 후자 (catalog 영역 명시적 prefab 영역 가중치).
|
|||
|
|
|
|||
|
|
### 15-9. Boss 방 포함 여부 (현 Phase 1)
|
|||
|
|
|
|||
|
|
§14-6 영역 보스 방 영역 별도 영역 영역 → 본 Phase 1 영역 영역 X. 추후 영역.
|
|||
|
|
|
|||
|
|
### 15-10. 맵 길이 단위 — Tile vs 미터 vs 청크
|
|||
|
|
|
|||
|
|
본 설계 영역 Tile 단위 영역 (`lengthTiles`). 1 Tile = 1 Unity unit 가정.
|
|||
|
|
|
|||
|
|
**옵션 A**: Tile 단위 유지 (개발 영역 정합)
|
|||
|
|
**옵션 B**: 미터·청크 단위 영역 PD 친화 영역 변환 UI 추가
|
|||
|
|
|
|||
|
|
### 15-11. 옵션 노출 영역 — MapGenerationConfig 영역 PD 자유도
|
|||
|
|
|
|||
|
|
본 설계 §4-1 영역 옵션 12종. PD 영역 너무 많은 옵션 영역 = 결정 부담 영역.
|
|||
|
|
|
|||
|
|
**옵션 A**: 12종 모두 노출 (PD 자유도 최대)
|
|||
|
|
**옵션 B**: 5~6종만 노출 (lengthTiles·spawnDensity·platformDensity·stageTheme·includeBossRoom 등) — Advanced 영역 toggle
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §16. C50 Phase 2 토큰 옵션 사전 보고
|
|||
|
|
|
|||
|
|
### 16-1. Phase 2 분량 추정
|
|||
|
|
|
|||
|
|
| 단계 | 코드 라인 추정 | 토큰 추정 |
|
|||
|
|
|------|--------------|---------|
|
|||
|
|
| Phase 2-A 데이터 SOT (5종 SO + 인스턴스) | 320 라인 | ~12K |
|
|||
|
|
| Phase 2-B 절차 생성기 (5종 Planner + 테스트) | 530 라인 | ~25K |
|
|||
|
|
| Phase 2-C Editor Window + Applier (3종) | 480 라인 | ~22K |
|
|||
|
|
| Phase 2-D 정합 검증 + 5 stage Config | 100 라인 + 검증 | ~10K |
|
|||
|
|
| **합계** | **1430 라인** | **~69K** |
|
|||
|
|
|
|||
|
|
### 16-2. 옵션 사전 보고 (C50)
|
|||
|
|
|
|||
|
|
**옵션 (a) 단일 Phase 2 일괄 처리**:
|
|||
|
|
- 단일 세션 영역 ~70K 토큰
|
|||
|
|
- 위험: 컨텍스트 영역 한계 영역 일부 정합 영역 영역
|
|||
|
|
- 권고도: 중
|
|||
|
|
|
|||
|
|
**옵션 (b) Phase 2-A/B/C/D 분할 순차 진행** (P32 정합·본 PM 권고):
|
|||
|
|
- 2-A 단독 ~12K → 검증 → 2-B 단독 ~25K → 검증 → 2-C 단독 ~22K → 검증 → 2-D
|
|||
|
|
- 각 단계 영역 클라이언트팀장 Opus 위임·게임플레이 프로그래머 Sonnet 작업·클라이언트팀장 Opus 검증
|
|||
|
|
- 권고도: **상** — Drop-Through 발판 영역 BT22~BT47 28회 시도 영역 교훈
|
|||
|
|
|
|||
|
|
### 16-3. 본 PM 권고
|
|||
|
|
|
|||
|
|
**옵션 (b) Phase 2-A/B/C/D 분할 순차 진행** 영역 PD 승인 의무.
|
|||
|
|
|
|||
|
|
각 Phase 영역 클라이언트팀장 (`client-team-lead`) Opus 영역 위임. 단순 반복 영역 = 게임플레이 프로그래머 Sonnet 영역 직접 위임 가능 (Phase 2-A 데이터 SO 영역 등).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §17. 변경 이력
|
|||
|
|
|
|||
|
|
| 일시 | 변경 | 사유 | 기안 |
|
|||
|
|
|------|------|-----|------|
|
|||
|
|
| 2026-05-18 | v1.0 초안 — Phase 1 설계 완료 | PD 직접 지시 BT13-Dev-Map (2026-05-18) | 개발팀장 (dev-team-lead) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## §18. 종결
|
|||
|
|
|
|||
|
|
본 Phase 1 설계 완료. Phase 2 진입 전 §15 PD 결정 대기 안건 11건 영역 결정 의무. 결정 후 클라이언트팀장 영역 Phase 2-A 위임 진입.
|
|||
|
|
|
|||
|
|
**관련 SOT 영역**:
|
|||
|
|
- 본 문서 = 맵 시스템 설계 SOT (단일)
|
|||
|
|
- 스킬 시스템 영역 영향 X (BT12 정합)
|
|||
|
|
- BT5-Dev 발판 시스템 영역 완전 계승 (GameOptimizer·KinematicObject·EnemyController)
|
|||
|
|
- 기획 §5 자동 생성 vs 수동 배치 영역 PD OVERRIDE 후속 정합 의무 (§15-1)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**작성**: 개발팀장 (`dev-team-lead`)
|
|||
|
|
**근거**: PD 직접 지시 BT13-Dev-Map (2026-05-18) + PD 추가 결정 4건 (시각 시뮬레이터·리소스 단위·랜덤성·거점 밀도)
|
|||
|
|
**관련 규칙**: C39-10 사전 실측 의무·C49 표준 프로세스·C50 토큰 사전 승인·P18 설계 문서화·P32 맥락 분할 순차 진행
|