diff --git a/Assets/Scripts/Character/Enemy/CasterDemonController.cs b/Assets/Scripts/Character/Enemy/CasterDemonController.cs index 190de9ad..ad492597 100644 --- a/Assets/Scripts/Character/Enemy/CasterDemonController.cs +++ b/Assets/Scripts/Character/Enemy/CasterDemonController.cs @@ -4,5 +4,16 @@ using UnityEngine; public class CasterDemonController : EnemyController { - + + public override void BattleSequence() + { + // TODO : 배틀 중일 때 루프 + Debug.Log("## 몬스터의 교전 행동 루프"); + } + + + public override void OnCannotFleeBehaviour() + { + Debug.Log("## 몬스터가 막다른 길에 몰려 뭔가 함"); + } } diff --git a/Assets/Scripts/Character/Enemy/EnemyController.cs b/Assets/Scripts/Character/Enemy/EnemyController.cs index 059a109d..894c7334 100644 --- a/Assets/Scripts/Character/Enemy/EnemyController.cs +++ b/Assets/Scripts/Character/Enemy/EnemyController.cs @@ -110,13 +110,24 @@ public abstract class EnemyController : CharacterBase } + #region 몬스터의 행동 패턴 위임 + // 전략 패턴과 템플릿 메서드 패턴을 활용 public virtual void BattleSequence() { // 이 메서드는 자식 요소에서 오버라이드하여 구현합니다. - Debug.LogWarning("BattleSequence가 구현되지 않음 : " + "BattleSequence()를 오버라이드하여 구현하십시오."); + Debug.LogWarning("BattleSequence가 구현되지 않음 : BattleSequence()를 오버라이드하여 구현하십시오."); } + // 도망치며 싸우는 몬스터가 도망칠 곳이 없을때 취할 행동 + public virtual void OnCannotFleeBehaviour() + { + Debug.LogWarning("OnCannotFleeBehaviour가 구현되지 않음 : OnCannotFleeBehaviour() 오버라이드하여 구현하십시오."); + } + + #endregion + + public override void Die() { base.Die(); diff --git a/Assets/Scripts/Character/Enemy/EnemyState/Caster/EnemyStateFlee.cs b/Assets/Scripts/Character/Enemy/EnemyState/Caster/EnemyStateFlee.cs index 56718a5b..9445ab69 100644 --- a/Assets/Scripts/Character/Enemy/EnemyState/Caster/EnemyStateFlee.cs +++ b/Assets/Scripts/Character/Enemy/EnemyState/Caster/EnemyStateFlee.cs @@ -1,45 +1,109 @@ using UnityEngine; +using UnityEngine.AI; public class EnemyStateFlee :IEnemyState { private EnemyController _enemyController; - private Transform _detectPlayerTransform; - - private const float MaxDetectPlayerInCircleWaitTime = 0.2f; - private float _detectPlayerInCircleWaitTime = 0f; - + private Transform _playerTransform; private float _fleeDistance = 5f; // 도망치는 거리 private float _attackRange = 7f; // 공격 범위 + // 막다른길 검사용 + private Vector3 _lastPosition; + private float _stuckTimer = 0f; + private const float StuckThresholdTime = 1f; // 1초 동안 거의 못 움직이면 막힌 걸로 간주 + private const float StuckMoveThreshold = 0.1f; // 이내 이동은 “제자리”로 본다 + public void Enter(EnemyController enemyController) { _enemyController = enemyController; Debug.Log("## Flee 상태 진입"); - _detectPlayerTransform = _enemyController.TraceTargetTransform; - if (!_detectPlayerTransform) - { - _enemyController.SetState(EnemyState.Idle); - return; - } + _playerTransform = _enemyController.TraceTargetTransform; + _lastPosition = _enemyController.transform.position; + _stuckTimer = 0f; + + } public void Update() { - // 도망치는 방향 계산 - Vector3 fleeDirection = (_enemyController.transform.position - _detectPlayerTransform.position).normalized; + if (!_playerTransform) + { + _enemyController.SetState(EnemyState.Idle); + return; + } + + FindPositionFlee(); + + // 막힘 감지 (실제 이동 체크) + CheckPath(); + + _lastPosition = _enemyController.transform.position; + } + + private void CheckPath() + { + float distance = Vector3.Distance(_enemyController.transform.position, _playerTransform.position); + if (distance < StuckMoveThreshold) + { + _stuckTimer += Time.deltaTime; + if (_stuckTimer >= StuckThresholdTime) + { + // 막다른 길임 : 대체 행동 실행 + HandleDeadEnd(); + _stuckTimer = 0f; + } + } + else + { + // 정상적인 길: 배틀 루프 실행 + _enemyController.BattleSequence(); + _stuckTimer = 0f; + } + } + + private void FindPositionFlee() + { + // 1) 목표 도망 위치 계산 + Vector3 fleeDirection = (_enemyController.transform.position - _playerTransform.position).normalized; Vector3 fleeTarget = _enemyController.transform.position + fleeDirection * _fleeDistance; - _enemyController.Agent.SetDestination(fleeTarget); + // 2) 경로 계산해 보기 + NavMeshPath path = new NavMeshPath(); + _enemyController.Agent.CalculatePath(fleeTarget, path); - float distance = Vector3.Distance(_enemyController.transform.position, _detectPlayerTransform.position); + if (path.status == NavMeshPathStatus.PathComplete) + { + // 제대로 도망갈 수 있으면 목적지 설정 + _enemyController.Agent.SetDestination(fleeTarget); + } + else + { + // 막다른 길임 : 대체 행동 실행 + HandleDeadEnd(); + } + } - _enemyController.BattleSequence(); + private void HandleDeadEnd() + { + // 무작위 도망 지점 샘플링 시도 + Vector3 randomDirection = Random.insideUnitSphere * (_fleeDistance * 2); + randomDirection += _playerTransform.position; + + if (NavMesh.SamplePosition(randomDirection, out var hit, (_fleeDistance * 2), NavMesh.AllAreas)) + { + _enemyController.Agent.SetDestination(hit.position); + return; + } + + // 대체 경로도 찾을 수 없는 경우 + _enemyController.OnCannotFleeBehaviour(); } public void Exit() { - _detectPlayerTransform = null; + _playerTransform = null; _enemyController = null; } } \ No newline at end of file diff --git a/Assets/Scripts/Character/Enemy/PldDogController.cs b/Assets/Scripts/Character/Enemy/PldDogController.cs index cc0887b0..c6cb613b 100644 --- a/Assets/Scripts/Character/Enemy/PldDogController.cs +++ b/Assets/Scripts/Character/Enemy/PldDogController.cs @@ -37,7 +37,7 @@ public class PldDogController : EnemyController [SerializeField] private GameObject horizontalWarning; [SerializeField] private GameObject horizontalSlash; - [SerializeField] private float _patternTimer = 0f; + private float _patternTimer = 0f; private int _lastPatternIndex = -1; private bool _isPatternRunning = false; private bool _isFirstAttack = true;