diff --git a/Assets/Scripts/Mechanics/EnemyController.cs b/Assets/Scripts/Mechanics/EnemyController.cs index 9d695f4..fe0810a 100644 --- a/Assets/Scripts/Mechanics/EnemyController.cs +++ b/Assets/Scripts/Mechanics/EnemyController.cs @@ -166,18 +166,63 @@ namespace Platformer.Mechanics _lastX = transform.position.x; } - // BT105 — 정확 측정·안전 margin 2.0m (PD 보고 떨어짐 잔존: 보수적 영역으로 절벽 도달 X 보장) + // BT107 — Tilemap 영역 cell 기반 측정 (PD 근본 진단: Raycast 영역 자체 부정확) + // Enemy 시작 위치 cell → 좌·우 연속 Tile 영역 끝 영역까지 거리 = 안전 patrol 거리 float MeasureSafeWalkDistance(float dir) { - if (_collider == null) return patrolMaxRange; - float groundY = _collider.bounds.min.y + 0.05f; - for (float d = 0.1f; d <= patrolMaxRange; d += 0.1f) + if (_collider == null) return 0f; + + var groundTilemaps = new System.Collections.Generic.List(); + var levelGo = GameObject.Find("Level"); + if (levelGo != null) { - Vector2 origin = new Vector2(_startX + dir * d, groundY); - RaycastHit2D hit = Physics2D.Raycast(origin, Vector2.down, cliffCheckDepth, groundLayerMask); - if (hit.collider == null) return Mathf.Max(0f, d - 2.0f); // BT105: 0.5→2.0m + var t = levelGo.GetComponent(); + if (t != null) groundTilemaps.Add(t); } - return patrolMaxRange; + var autoFgGo = GameObject.Find("AutoForeground"); + if (autoFgGo != null) + { + var t = autoFgGo.GetComponent(); + if (t != null) groundTilemaps.Add(t); + } + if (groundTilemaps.Count == 0) return 0f; + + // 시작 cell = Enemy 발 아래 cell + Vector3 footPos = new Vector3(_startX, _collider.bounds.min.y - 0.1f, 0f); + UnityEngine.Tilemaps.Tilemap startTm = null; + Vector3Int startCell = Vector3Int.zero; + foreach (var tm in groundTilemaps) + { + Vector3Int cell = tm.WorldToCell(footPos); + if (tm.HasTile(cell)) + { + startTm = tm; + startCell = cell; + break; + } + } + if (startTm == null) return 0f; + + // 좌·우 연속 Tile 영역 끝 영역 검색 + int dirCell = (int)Mathf.Sign(dir); + Vector3Int cellIter = startCell; + for (int steps = 0; steps < 500; steps++) + { + Vector3Int nextCell = cellIter + new Vector3Int(dirCell, 0, 0); + if (!HasTileInAnyTilemap(groundTilemaps, nextCell)) break; + cellIter = nextCell; + } + + // 마지막 Tile cell 영역 center 영역까지 거리 + Vector3 lastTileWorld = startTm.CellToWorld(cellIter) + new Vector3(0.5f, 0f, 0f); + float distance = Mathf.Abs(lastTileWorld.x - _startX); + return Mathf.Max(0f, distance - 0.5f); // 안전 margin 0.5m (Tile cell 영역 안) + } + + static bool HasTileInAnyTilemap(System.Collections.Generic.List tms, Vector3Int cell) + { + foreach (var tm in tms) if (tm != null && tm.HasTile(cell)) return true; + return false; } void SetNextPatrolTarget() @@ -210,15 +255,7 @@ namespace Platformer.Mechanics return; } - // BT106 — y 강제 고정 (PD 명시 2026-05-08): Enemy 영역 매 frame _startY 영역 영구 위치 - // 떨어짐 영역 0.1m 이상 발생 시 = 즉시 _startY 복귀 → 떨어짐 영역 영구 차단 - if (Mathf.Abs(transform.position.y - _startY) > 0.1f) - { - Vector3 fixedY = new Vector3(transform.position.x, _startY, transform.position.z); - transform.position = fixedY; - if (_body != null) _body.position = fixedY; - if (control != null) control.velocity = new Vector2(control.velocity.x, 0f); - } + // BT107 — BT106 y 강제 고정 영역 폐기 (PD 보고: 공중 부유 영역 원인) // 대기 영역 — control.move.x = 0 + Timer 감소 if (_waitTimer > 0f)