diff --git a/Assets/Scripts/Mechanics/EnemyController.cs b/Assets/Scripts/Mechanics/EnemyController.cs index 320dce0..8083730 100644 --- a/Assets/Scripts/Mechanics/EnemyController.cs +++ b/Assets/Scripts/Mechanics/EnemyController.cs @@ -29,14 +29,18 @@ namespace Platformer.Mechanics public float cliffCheckDistance = 0.3f; // 발 앞 절벽 검출 거리 (BT86: 0.6→0.3 — 더 빠른 검출·낭떠러지 근처 Enemy 보호) public float cliffCheckDepth = 1.5f; // 발 아래 Raycast 거리 public LayerMask groundLayerMask = (1 << 0) | (1 << 16); // Layer 0 (지면) + Layer 16 (발판) - public float stuckThresholdTime = 0.1f; // 벽 정지 검출 시간 (BT87: 0.3→0.1 — 움찔거림 차단·즉시 방향 전환) + public float stuckThresholdTime = 0.1f; // 벽 정지 검출 시간 (보조·수평 Raycast 미감지 시 fallback) public float stuckMoveThreshold = 0.02f; // 정지 판정 거리 임계값 (미세 밀림 검출) + public float wallCheckDistance = 0.1f; // BT89: 수평 벽 Raycast distance (bounds 외부 0.05+0.1=0.15m·인접 Tile 검출 X) + public float waitMinTime = 1f; // BT89: patrol arrive 후 대기 random 영역 + public float waitMaxTime = 3f; private float _startX; private float _targetX; private int _patrolPhase; // 0: right out / 1: right back / 2: left out / 3: left back - private float _lastX; // BT81: 벽 정지 검출용 - private float _stuckTimer; // BT81: 벽 정지 누적 시간 + private float _lastX; // 벽 정지 검출용 + private float _stuckTimer; // 벽 정지 누적 시간 + private float _waitTimer; // BT89: arrive 후 대기 누적 시간 (1~3초 random) internal PatrolPath.Mover mover; // legacy 호환·미사용 internal AnimationController control; @@ -99,46 +103,58 @@ namespace Platformer.Mechanics } } - void Update() + // BT89 — patrol·벽·절벽 검출·대기 영역 통합 처리 + void UpdatePatrol() { - // PD 명시 2026-05-08 — PatrolPath 영역 폐기·자동 patrol (생성 위치 기준 좌/우 random 100~150 왕복) + 절벽 검출 + // 대기 영역 — control.move.x = 0 + Timer 감소 + if (_waitTimer > 0f) + { + _waitTimer -= Time.deltaTime; + if (control != null) control.move.x = 0f; + _lastX = transform.position.x; + _stuckTimer = 0f; + return; + } + float dx = _targetX - transform.position.x; + // patrol arrive — 1~3초 대기 후 다음 phase if (Mathf.Abs(dx) < patrolArriveThreshold) { _patrolPhase = (_patrolPhase + 1) % 4; SetNextPatrolTarget(); - dx = _targetX - transform.position.x; + _waitTimer = Random.Range(waitMinTime, waitMaxTime); + if (control != null) control.move.x = 0f; + _lastX = transform.position.x; + _stuckTimer = 0f; + return; } float moveDir = Mathf.Sign(dx); - // 벽 정지·미세 밀림 검출 — BT88: phase+2 (반대 방향 patrol·시작 위치 복귀 폐기) - if (Mathf.Abs(transform.position.x - _lastX) < stuckMoveThreshold) + // 즉시 벽 검출 — 수평 Raycast (bounds 외부 + 0.1m·인접 Tile X) → phase+2 즉시 전환 + if (_collider != null) { - _stuckTimer += Time.deltaTime; - if (_stuckTimer > stuckThresholdTime) + float halfWidth = _collider.bounds.extents.x; + Vector2 wallOrigin = new Vector2( + _collider.bounds.center.x + moveDir * (halfWidth + 0.05f), + _collider.bounds.center.y + ); + RaycastHit2D wallHit = Physics2D.Raycast(wallOrigin, new Vector2(moveDir, 0), wallCheckDistance, groundLayerMask); + if (wallHit.collider != null) { _patrolPhase = (_patrolPhase + 2) % 4; SetNextPatrolTarget(); - dx = _targetX - transform.position.x; - moveDir = Mathf.Sign(dx); + _waitTimer = Random.Range(waitMinTime, waitMaxTime); + if (control != null) control.move.x = 0f; _stuckTimer = 0f; + _lastX = transform.position.x; + return; } } - else - { - _stuckTimer = 0f; - } - _lastX = transform.position.x; - // BT85 — 수평 벽 Raycast 영역 폐기 (PD 보고 2026-05-08: 모든 Enemy 정지) - // BT83/BT84 영역 = bounds 외부+0.5m 영역도 옆 Tile (평지 인접) 검출 → 거짓 양성 → patrol X - // 정정: 수평 Raycast 폐기 + stuckTimer 영역 0.5초 (BT81 영역 회복) — 벽 정지 시 다음 phase - - // 절벽 검출 — BT88: phase+2 (반대 방향 patrol·시작 위치 복귀 폐기) - // 시작 위치 복귀 영역 = 시작 위치도 절벽 근처 시 도중 떨어짐 → phase+2로 즉시 반대 방향 - if (Mathf.Abs(dx) > patrolArriveThreshold && _collider != null) + // 절벽 검출 — 발 앞 Raycast 지면 X 시 phase+2 즉시 전환 (대기) + if (_collider != null) { Vector2 footAhead = new Vector2( _collider.bounds.center.x + moveDir * cliffCheckDistance, @@ -149,13 +165,44 @@ namespace Platformer.Mechanics { _patrolPhase = (_patrolPhase + 2) % 4; SetNextPatrolTarget(); - dx = _targetX - transform.position.x; - moveDir = Mathf.Sign(dx); + _waitTimer = Random.Range(waitMinTime, waitMaxTime); + if (control != null) control.move.x = 0f; _stuckTimer = 0f; + _lastX = transform.position.x; + return; } } - control.move.x = Mathf.Clamp(dx, -1, 1); + // stuckTimer 보조 — 수평 Raycast 미감지 fallback (벽 정지 + 미세 밀림 0.1초 후 phase+2) + if (Mathf.Abs(transform.position.x - _lastX) < stuckMoveThreshold) + { + _stuckTimer += Time.deltaTime; + if (_stuckTimer > stuckThresholdTime) + { + _patrolPhase = (_patrolPhase + 2) % 4; + SetNextPatrolTarget(); + _waitTimer = Random.Range(waitMinTime, waitMaxTime); + if (control != null) control.move.x = 0f; + _stuckTimer = 0f; + _lastX = transform.position.x; + return; + } + } + else + { + _stuckTimer = 0f; + } + _lastX = transform.position.x; + + if (control != null) control.move.x = Mathf.Clamp(dx, -1, 1); + } + + void Update() + { + // BT89 — 자동 patrol + 즉시 벽 검출 + 1~3초 대기 영역 + UpdatePatrol(); + + // 이하 — Player 충돌 검출 영역 (patrol 영역과 분리) // PD 지시 2026-05-07 — Player ↔ Enemy 통과 가능이지만 Bounds.Intersects로 매 프레임 감지 if (_cachedPlayer == null)