58 KiB
맵 시스템 설계 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. 입력
[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)
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 정합)
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)
[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 자동 충돌 영역 표준 패턴
// 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 영역)
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)
[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 확장)
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 폴더)
// 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)
[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)
[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 표준 프로세스 적용
- 개발팀장 (Opus·본 문서) 설계 v1 산출 ✓
- 클라이언트팀장 (Opus) 본 문서 영역 Phase 2-A/B/C 분할 위임 결정
- 게임플레이 프로그래머 (Sonnet) 코드 구현
- 클라이언트팀장 (Opus) Phase 2 코드 검증
- 개발팀장 (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 진입 전 결정 의무)
[2026-05-18 PD 직접 결정 11건 — Phase 2.1 정정 채택 (직전 본 PM 자율 채택 6건 OVERRIDE)]
# PD 직접 채택 직전 본 PM 채택 15-1 마을(=로비) PD 수동 제작·랜덤 스테이지 1종 집중·추후 확장 (현 Phase = 스테이지 1종 영역 집중·마을 무시) OPT A 동일 (§5 OVERRIDE) 15-2 사선·이중 점프 도입 예정 단일 점프 유지→ OVERRIDE 도입15-3 미노출 (추후 필요시 추가) 미노출 동일 15-4 옵션 B — GameOptimizer 위임 (기존 Level/Foreground 개념 없어도 무관) OPT A 직접 분리→ OVERRIDE B15-5 공중 발판 거점 허용 지면만→ OVERRIDE 허용15-6 좌측 고정 좌측 고정 동일 15-7 사망 후 재출현 고려 (재-spawn 메커니즘 추가) 재-spawn X→ OVERRIDE 추가15-8 안 2 — Enemy.prefab + MonsterRandomizer 활용 Enemy_M00N 직접→ OVERRIDE 안 215-9 보스 방 자동 생성 (맵 가장 우측 끝) 별도 수동 Scene→ OVERRIDE 자동 생성15-10 Tile 단위 Tile 단위 동일 15-11 옵션 12종 모두 노출 12종 모두 노출 동일 Phase 2 영역 코드 구현 (직전 채택) — 17 신규 + SpawnPoint.cs 11→51줄. Phase 2.1 영역 PD 직접 결정 6건 OVERRIDE 구현 (본 응답 직후) — 12 파일 수정 (PlayerController 이중 점프·SpawnPoint 91줄 재-spawn+MonsterRandomizer·MapGenerationConfig·MapResourceCatalog baseEnemyPrefab+bossPrefab·SpawnPointConfig·PlatformPlanner SAFE 10.11m/11.56m·SpawnPointPlanner 공중 발판·ChunkPlanner BossRoom·MapApplier 단일 Level Tilemap·MapPreviewWindow 옵션 노출·MapAssetBootstrap·MapGeneratorTests 15/15). MCP 검증 12/12 → 15/15 green·컴파일 0건·warning 2 unrelated. 이중 점프 영역 점프 물리 재산출: H_max=11.24m·D_max=29.96m·SAFE_VERTICAL_HEIGHT=10.11m·SAFE_HORIZONTAL_DISTANCE=11.56m·maxPlatformHeight cap 10·maxPlatformGap default 10.
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) |
| 2026-05-18 | v1.1 — Phase 2 (a) 단일 채택·§15 11건 dev-team-lead 권고 일괄 채택·frontmatter "8건"→"11건" 정정 | PD 결정 "a 단일 설계 보고서로 진행해" + pm-auditor Major 2 정정 + 본 PM 자율 보완 | 총괄PM (pm-general) |
| 2026-05-18 | v1.2 — Phase 2 코드 구현 완료 (17 신규 + 1 수정 외부 git E:/EerieVillage) | client-team-lead Opus 직접 구현·MCP 검증 12/12 green·컴파일 0건·warning 0건 | 클라이언트팀장 (client-team-lead) |
| 2026-05-18 | v1.3 — Phase 2.1 PD 직접 결정 6건 OVERRIDE 구현 (15-2 이중 점프·15-4 B 위임·15-5 공중 발판·15-7 재-spawn·15-8 MonsterRandomizer·15-9 BossRoom 자동) | PD 직접 답변 11건 (2026-05-18·"15-2 도입할 예정·15-4 B안·15-5 허용·15-7 재출현·15-8 안 2·15-9 우측 끝 자동") + client-team-lead Opus OVERRIDE 구현·MCP 15/15 green | 클라이언트팀장 (client-team-lead) + PD 직접 결정 (PM 매개) |
| 2026-05-18 | v1.4 — Bootstrap baseEnemyPrefab 자동 link (잔여 (가) PM 직접 처리) | PD "결정해야할 안건 없으면 후속 작업 바로 진행해" + 본 PM 자율 처리 — MapAssetBootstrap.cs §1-A 영역 Enemy.prefab 자동 link·MCP run_tests Map 15/15 Passed (0.27s) | 총괄PM (pm-general) |
§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 맥락 분할 순차 진행