diff --git a/Assets/Scripts/Mechanics/EnemyController.cs b/Assets/Scripts/Mechanics/EnemyController.cs index fe273af..bb6d6a0 100644 --- a/Assets/Scripts/Mechanics/EnemyController.cs +++ b/Assets/Scripts/Mechanics/EnemyController.cs @@ -35,6 +35,8 @@ namespace Platformer.Mechanics public float waitMaxTime = 3f; private float _startX; + private float _startY; // BT102: 시작 시 y 위치 — 떨어짐 검출 기준 + public float fallThreshold = 1.0f; // BT102: _startY - fallThreshold 영역 미만 시 시작 위치 텔레포트 private float _targetX; private int _patrolPhase; // 0: right out / 1: right back / 2: left out / 3: left back private float _lastX; // 벽 정지 검출용 @@ -91,6 +93,7 @@ namespace Platformer.Mechanics // PD 명시 2026-05-08 — 자동 patrol 시작 위치 저장 (측정·target은 Start 시점) _startX = transform.position.x; + _startY = transform.position.y; // BT102: 떨어짐 검출 기준 _lastX = _startX; _stuckTimer = 0f; _phaseCooldown = 0f; @@ -250,26 +253,22 @@ namespace Platformer.Mechanics // BT89 — 자동 patrol + 즉시 벽 검출 + 1~3초 대기 영역 UpdatePatrol(); - // BT98 — 방어 영역 (R1·PD 강한 어조 정합): 발 영역 자체 절벽 검사 → 시작 위치 즉시 텔레포트 복귀 - // 본 PM 17회 가설 누적 부정확. 알고리즘 영역 부정합 잔존 시도 안전 보장. - if (_isInitialized && _collider != null) + // BT102 — 떨어짐 검출 영역: y < _startY - fallThreshold 시 = 시작 위치 텔레포트 복귀 + // PD 명시 (2026-05-08): 투명벽 폐기·떨어짐 차단 다른 방법 + // 단순·근본 방법 — 떨어진 후 검출 영역 즉시 복귀 (영구 떨어짐 X) + if (_isInitialized && transform.position.y < _startY - fallThreshold) { - Vector2 footHere = new Vector2(_collider.bounds.center.x, _collider.bounds.min.y + 0.05f); - RaycastHit2D groundUnder = Physics2D.Raycast(footHere, Vector2.down, cliffCheckDepth, groundLayerMask); - if (groundUnder.collider == null) - { - Vector3 safe = new Vector3(_startX, transform.position.y, transform.position.z); - transform.position = safe; - if (_body != null) _body.position = safe; - if (control != null) control.velocity = Vector2.zero; - _patrolPhase = 0; - SetNextPatrolTarget(); - _phaseCooldown = PHASE_COOLDOWN; - _stuckTimer = 0f; - _lastX = _startX; - _waitTimer = Random.Range(waitMinTime, waitMaxTime); - if (control != null) control.move.x = 0f; - } + Vector3 safe = new Vector3(_startX, _startY, transform.position.z); + transform.position = safe; + if (_body != null) _body.position = safe; + if (control != null) control.velocity = Vector2.zero; + _patrolPhase = 0; + SetNextPatrolTarget(); + _phaseCooldown = PHASE_COOLDOWN; + _stuckTimer = 0f; + _lastX = _startX; + _waitTimer = Random.Range(waitMinTime, waitMaxTime); + if (control != null) control.move.x = 0f; } // 이하 — Player 충돌 검출 영역 (patrol 영역과 분리) diff --git a/Assets/Scripts/Mechanics/GameOptimizer.cs b/Assets/Scripts/Mechanics/GameOptimizer.cs index 6c7a57e..a660b48 100644 --- a/Assets/Scripts/Mechanics/GameOptimizer.cs +++ b/Assets/Scripts/Mechanics/GameOptimizer.cs @@ -29,8 +29,6 @@ namespace Platformer.Mechanics // Layer Matrix: Player(13) ↔ Enemy(14) 충돌 OFF. Physics2D.IgnoreLayerCollision(13, 14, true); - // BT99 — Player(13) ↔ EnemyWall(18) 충돌 OFF (투명벽은 Enemy만 차단). - Physics2D.IgnoreLayerCollision(13, 18, true); } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] @@ -152,80 +150,12 @@ namespace Platformer.Mechanics var lvlTc = levelGo.GetComponent(); if (lvlTc != null) lvlTc.ProcessTilemapChanges(); Debug.Log($"[GameOptimizer] AutoForeground moved={movedHigh + movedSmall} (high={movedHigh} small={movedSmall} threshold={airThresholdY:F2}) / Layer16 applied={applied} levelKept0={levelKept} excluded={excluded}"); - - // BT99 — 절벽 영역 자동 검출·투명벽 (EnemyWall Layer 18) GameObject 자동 생성 - SetupCliffWalls(levelTilemap, fgTilemap, pdForeground); } } - } - /// - /// BT99 — 절벽 영역 자동 검출 + 투명벽 GameObject 생성 (Enemy 차단·Player 통과). - /// 모든 Tilemap (Level·AutoForeground·PD Foreground) 영역에서 Tile 좌·우 인접 영역이 모든 Tilemap에 Tile X 시 = 절벽 가장자리. - /// - static void SetupCliffWalls(UnityEngine.Tilemaps.Tilemap levelTm, UnityEngine.Tilemaps.Tilemap autoFgTm, GameObject pdFg) - { - UnityEngine.Tilemaps.Tilemap pdFgTm = pdFg != null ? pdFg.GetComponent() : null; - - GameObject wallParent = GameObject.Find("CliffWalls"); - if (wallParent == null) wallParent = new GameObject("CliffWalls"); - - int wallCount = 0; - - // BT101 — 자체·인접 검사 = Level + AutoForeground 만 (PD Foreground = TilemapCollider X·Enemy 발판 X·검사 영역 X) - var checkTilemaps = new System.Collections.Generic.List(); - if (levelTm != null) checkTilemaps.Add(levelTm); - if (autoFgTm != null) checkTilemaps.Add(autoFgTm); - - var allTilemaps = checkTilemaps; - - foreach (var tm in checkTilemaps) - { - var bounds = tm.cellBounds; - for (int x = bounds.xMin; x <= bounds.xMax; x++) - { - for (int y = bounds.yMin; y <= bounds.yMax; y++) - { - var pos = new Vector3Int(x, y, 0); - if (!tm.HasTile(pos)) continue; - - bool leftCliff = !HasTileInAny(allTilemaps, pos + Vector3Int.left); - bool rightCliff = !HasTileInAny(allTilemaps, pos + Vector3Int.right); - if (!leftCliff && !rightCliff) continue; - - Vector3 worldPos = tm.CellToWorld(pos) + new Vector3(0.5f, 0.5f, 0f); // cell center - if (leftCliff) - { - CreateCliffWall(wallParent, worldPos + new Vector3(-0.5f, 0f, 0f)); - wallCount++; - } - if (rightCliff) - { - CreateCliffWall(wallParent, worldPos + new Vector3(0.5f, 0f, 0f)); - wallCount++; - } - } - } - } - Debug.Log($"[GameOptimizer] CliffWalls (EnemyWall Layer 18) 생성: {wallCount}개"); - } - - static bool HasTileInAny(System.Collections.Generic.List tms, Vector3Int pos) - { - foreach (var tm in tms) if (tm != null && tm.HasTile(pos)) return true; - return false; - } - - static void CreateCliffWall(GameObject parent, Vector3 worldPos) - { - GameObject wall = new GameObject("CliffWall"); - wall.transform.SetParent(parent.transform, false); - wall.transform.position = worldPos; - wall.layer = 18; // EnemyWall - var box = wall.AddComponent(); - // BT100 — size·offset 정정: 매우 좁은 영역(0.02) + Tile 표면 영역(offset.y=0)·절벽 위치 정확 - box.size = new Vector2(0.02f, 3f); - box.offset = new Vector2(0f, 0f); + // BT102 — 기존 CliffWalls GameObject 영역 정리 (PD 명시·투명벽 폐기) + GameObject existingCliffWalls = GameObject.Find("CliffWalls"); + if (existingCliffWalls != null) Object.Destroy(existingCliffWalls); } ///