Fiore b9a014df88 [refact] 도망 로직 개선, 코루틴 최적화
- 몬스터가 텔레포트로 도망칠 위치를 에디터에서 수정할 수 있도록 개선
- 플레이어와 거리 계산 최적화
- 코루틴에서 사용하는 WaitForSecond 객체 캐싱 유틸 추가

Work in JIRA ISSUE #DEG-100
2025-05-07 14:27:29 +09:00

150 lines
4.7 KiB
C#

using System;
using UnityEngine;
using UnityEngine.AI;
using Random = UnityEngine.Random;
public class EnemyStateFlee :IEnemyState
{
private EnemyController _enemyController;
private Transform _playerTransform;
private float _attackRange = 7f; // 공격 범위
private float _fleeDistance = 15f; // 도망치는 거리
private float _attackRangeSqr;
private float _fleeDistanceSqr;
// 경로 탐색 주기 조절용
private float _fleeSearchTimer = 0;
private const float FleeThresholdTime = 0.2f;
// 막다른길 검사용
private Vector3 _lastPosition;
private float _stuckTimer = 0f;
private const float StuckThresholdTime = 1f; // 1초 동안 거의 못 움직이면 막힌 걸로 간주
private const float StuckMoveThreshold = 0.1f; // 이내 이동은 “제자리”로 본다
private int _stuckCount = 0;
public void Enter(EnemyController enemyController)
{
_enemyController = enemyController;
Debug.Log("## Flee 상태 진입");
_playerTransform = _enemyController.TraceTargetTransform;
_lastPosition = _enemyController.transform.position;
_enemyController.Agent.ResetPath();
_enemyController.Agent.isStopped = false;
_stuckTimer = 0f;
_fleeSearchTimer = 0;
_attackRangeSqr = _attackRange * _attackRange;
_fleeDistanceSqr = _fleeDistance * _fleeDistance;
_enemyController.SetAnimation(CasterDemonController.Flee, true);
}
public void Update()
{
if (!_playerTransform)
{
_enemyController.SetState(EnemyState.Idle);
return;
}
float currentDist = (_enemyController.transform.position - _playerTransform.position).sqrMagnitude;
if (currentDist >= _fleeDistanceSqr)
{
_enemyController.Agent.isStopped = true;
_enemyController.Agent.ResetPath();
_enemyController.SetState(EnemyState.Idle);
return;
}
if (currentDist >= _attackRangeSqr)
{
// 목적지 리셋 후 전투 시작
_enemyController.Agent.isStopped = true;
_enemyController.Agent.ResetPath();
_enemyController.BattleSequence();
return;
}
FindPositionFlee();
if (!_enemyController.Agent.pathPending &&
_enemyController.Agent.pathStatus == NavMeshPathStatus.PathInvalid)
{
Debug.Log("## 길을 못찾음");
HandleDeadEnd();
}
// 막힘 감지 (실제 이동 체크)
CheckPath();
_lastPosition = _enemyController.transform.position;
}
private void CheckPath()
{
float moved = (_enemyController.transform.position - _lastPosition).sqrMagnitude;
if (moved < StuckMoveThreshold * StuckMoveThreshold)
{
_stuckTimer += Time.deltaTime;
if (_stuckTimer >= StuckThresholdTime)
{
HandleDeadEnd();
_stuckTimer = 0f;
}
}
else
{
_stuckTimer = 0f;
}
}
private void FindPositionFlee()
{
_fleeSearchTimer += Time.deltaTime;
if (_fleeSearchTimer <= FleeThresholdTime) return;
// 플레이어 반대방향으로 도망
Vector3 fleeDirection = (_enemyController.transform.position - _playerTransform.position).normalized;
Vector3 fleeTarget = _enemyController.transform.position + fleeDirection * _fleeDistance;
// 경로 설정
_enemyController.Agent.SetDestination(fleeTarget);
_enemyController.Agent.isStopped = false;
_fleeSearchTimer = 0;
}
private void HandleDeadEnd()
{
if (_stuckCount >= 4)
{
_enemyController.OnCannotFleeBehaviour(() => { _stuckCount = 0;});
return;
}
// 무작위 도망 지점 샘플링 시도
Vector3 randomDirection = Random.insideUnitSphere * (_fleeDistance * 2);
randomDirection += _playerTransform.position;
if (NavMesh.SamplePosition(randomDirection, out var hit, (_fleeDistance * 2), NavMesh.AllAreas))
{
// 샘플링에 성공했으면 일단 그 위치로 가 보도록 세팅
Debug.Log("## 일단 가봄");
_stuckCount++;
_enemyController.Agent.SetDestination(hit.position);
// _enemyController.OnCannotFleeBehaviour();
}
}
public void Exit()
{
_enemyController.SetAnimation(CasterDemonController.Flee, false);
_enemyController.Agent.isStopped = true;
_enemyController.Agent.ResetPath();
_playerTransform = null;
_enemyController = null;
}
}