diff --git a/Assets/Scripts/Mechanics/EnemyController.cs b/Assets/Scripts/Mechanics/EnemyController.cs index 829f2a1..dfb53a4 100644 --- a/Assets/Scripts/Mechanics/EnemyController.cs +++ b/Assets/Scripts/Mechanics/EnemyController.cs @@ -39,7 +39,11 @@ namespace Platformer.Mechanics private int _patrolPhase; // 0: right out / 1: right back / 2: left out / 3: left back private float _lastX; // 벽 정지 검출용 private float _stuckTimer; // 벽 정지 누적 시간 - private float _waitTimer; // BT89: arrive 후 대기 누적 시간 (1~3초 random) + private float _waitTimer; // arrive 후 대기 누적 시간 (1~3초 random) + private float _phaseCooldown; // BT94: phase 전환 직후 절벽·벽 검출 비활성 시간 + private const float PHASE_COOLDOWN = 0.5f; + private float _maxRightRange; // BT94: 시작 시 측정 — 우측 안전 patrol 거리 + private float _maxLeftRange; // BT94: 시작 시 측정 — 좌측 안전 patrol 거리 internal PatrolPath.Mover mover; // legacy 호환·미사용 internal AnimationController control; @@ -86,19 +90,46 @@ namespace Platformer.Mechanics _startX = transform.position.x; _lastX = _startX; _stuckTimer = 0f; + _phaseCooldown = 0f; + + // BT94 — 시작 시 좌·우 안전 patrol 거리 측정 (좁은 영역 Enemy 떨어짐 차단) + _maxRightRange = MeasureSafeWalkDistance(1f); + _maxLeftRange = MeasureSafeWalkDistance(-1f); + _patrolPhase = 0; SetNextPatrolTarget(); } + // BT94 — 시작 위치에서 좌·우 walk 영역 절벽 검출까지 거리 측정 (안전 margin 1.5m 차감) + float MeasureSafeWalkDistance(float dir) + { + float groundY = transform.position.y - 0.34f; // 발 영역 위 0.05m (Capsule offset 영역 기반 추정) + for (float d = 0.5f; d <= patrolMaxRange; d += 0.5f) + { + 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 - 1.5f); + } + return patrolMaxRange; + } + void SetNextPatrolTarget() { float range = Random.Range(patrolMinRange, patrolMaxRange); switch (_patrolPhase) { - case 0: _targetX = _startX + range; break; // 우측 random 이동 - case 1: _targetX = _startX; break; // 시작 위치 복귀 - case 2: _targetX = _startX - range; break; // 좌측 random 이동 - case 3: _targetX = _startX; break; // 시작 위치 복귀 + case 0: // 우측 random — 안전 거리 cap + _targetX = _startX + Mathf.Min(range, _maxRightRange); + break; + case 1: + _targetX = _startX; + break; + case 2: // 좌측 random — 안전 거리 cap + _targetX = _startX - Mathf.Min(range, _maxLeftRange); + break; + case 3: + _targetX = _startX; + break; } } @@ -115,6 +146,9 @@ namespace Platformer.Mechanics return; } + // BT94 — phase cooldown 감소 (절벽·벽 검출 비활성 시간) + if (_phaseCooldown > 0f) _phaseCooldown -= Time.deltaTime; + float dx = _targetX - transform.position.x; // patrol arrive — 1~3초 대기 후 다음 phase @@ -134,48 +168,51 @@ namespace Platformer.Mechanics // BT90 — 수평 Raycast 영역 폐기 (BT89 거짓 양성 — 같은 Tile cell 영역 검출) // 벽 영역 = stuckTimer 영역 (50ms 정지 시 즉시 phase+2)으로 처리 - // BT93 — 절벽 검출: velocity.x = 0 강제 (관성 차단·발 절벽 진입 X) + phase+2 + 반대 방향 이동 - // transform.position 직접 set 폐기 (BT92 영역·KinematicObject body.position 충돌) - if (_collider != null) + // BT94 — 절벽·벽 검출은 phase cooldown 영역 끝난 후 활성 (좌우 반복 차단) + if (_phaseCooldown <= 0f) { - Vector2 footAhead = new Vector2( - _collider.bounds.center.x + moveDir * cliffCheckDistance, - _collider.bounds.min.y + 0.05f - ); - RaycastHit2D groundHit = Physics2D.Raycast(footAhead, Vector2.down, cliffCheckDepth, groundLayerMask); - if (groundHit.collider == null) + // 절벽 검출 — velocity.x = 0 + phase+2 + cooldown 활성 + if (_collider != null) { - if (control != null) + Vector2 footAhead = new Vector2( + _collider.bounds.center.x + moveDir * cliffCheckDistance, + _collider.bounds.min.y + 0.05f + ); + RaycastHit2D groundHit = Physics2D.Raycast(footAhead, Vector2.down, cliffCheckDepth, groundLayerMask); + if (groundHit.collider == null) { - control.velocity = new Vector2(0f, control.velocity.y); + if (control != null) control.velocity = new Vector2(0f, control.velocity.y); + _patrolPhase = (_patrolPhase + 2) % 4; + SetNextPatrolTarget(); + _stuckTimer = 0f; + _phaseCooldown = PHASE_COOLDOWN; + _lastX = transform.position.x; + dx = _targetX - transform.position.x; + if (control != null) control.move.x = Mathf.Clamp(dx, -1, 1); + return; } - _patrolPhase = (_patrolPhase + 2) % 4; - SetNextPatrolTarget(); - _stuckTimer = 0f; - _lastX = transform.position.x; - dx = _targetX - transform.position.x; - if (control != null) control.move.x = Mathf.Clamp(dx, -1, 1); - return; } - } - // BT93 — 벽 정지 (stuckTimer 150ms): velocity.x = 0 + phase+2 + 반대 방향 - if (Mathf.Abs(transform.position.x - _lastX) < stuckMoveThreshold) - { - _stuckTimer += Time.deltaTime; - if (_stuckTimer > stuckThresholdTime) + // 벽 정지 (stuckTimer 150ms): velocity.x = 0 + phase+2 + cooldown 활성 + if (Mathf.Abs(transform.position.x - _lastX) < stuckMoveThreshold) { - if (control != null) + _stuckTimer += Time.deltaTime; + if (_stuckTimer > stuckThresholdTime) { - control.velocity = new Vector2(0f, control.velocity.y); + if (control != null) control.velocity = new Vector2(0f, control.velocity.y); + _patrolPhase = (_patrolPhase + 2) % 4; + SetNextPatrolTarget(); + _stuckTimer = 0f; + _phaseCooldown = PHASE_COOLDOWN; + _lastX = transform.position.x; + dx = _targetX - transform.position.x; + if (control != null) control.move.x = Mathf.Clamp(dx, -1, 1); + return; } - _patrolPhase = (_patrolPhase + 2) % 4; - SetNextPatrolTarget(); + } + else + { _stuckTimer = 0f; - _lastX = transform.position.x; - dx = _targetX - transform.position.x; - if (control != null) control.move.x = Mathf.Clamp(dx, -1, 1); - return; } } else