[refact] 도망 로직 개선, 코루틴 최적화

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

Work in JIRA ISSUE #DEG-100
This commit is contained in:
Fiore 2025-05-07 14:27:29 +09:00
parent cbc6817075
commit b9a014df88
5 changed files with 68 additions and 36 deletions

View File

@ -16,7 +16,6 @@ public class CasterDemonController : EnemyController
public static readonly int Spin = Animator.StringToHash("Spin"); public static readonly int Spin = Animator.StringToHash("Spin");
private bool _doneBattleSequence = true; private bool _doneBattleSequence = true;
private bool _isFirstNoPath = true;
private Coroutine _currentSequence; private Coroutine _currentSequence;
@ -24,7 +23,7 @@ public class CasterDemonController : EnemyController
[SerializeField] private Transform bulletShotPosition; [SerializeField] private Transform bulletShotPosition;
[SerializeField] private GameObject magicMissilePrefab; [SerializeField] private GameObject magicMissilePrefab;
[SerializeField] private GameObject teleportEffectPrefab; [SerializeField] private GameObject teleportEffectPrefab;
[SerializeField] private Vector3 teleportTargetPosition = Vector3.zero;
[Header("각종 데미지 이펙트 세트")] [Header("각종 데미지 이펙트 세트")]
[SerializeField] private GameObject chariotWarning; [SerializeField] private GameObject chariotWarning;
[SerializeField] private GameObject chariotEffect; [SerializeField] private GameObject chariotEffect;
@ -33,15 +32,13 @@ public class CasterDemonController : EnemyController
[SerializeField] private GameObject slowFieldWarning; [SerializeField] private GameObject slowFieldWarning;
[SerializeField] private GameObject slowFieldEffect; [SerializeField] private GameObject slowFieldEffect;
// private float _teleportDistance = 4f; // 플레이어 뒤로 떨어질 거리
// 텔레포트 쿨타임 처음엔 빨리 사용 가능함 // 텔레포트 쿨타임 처음엔 빨리 사용 가능함
private float _teleportTimer = 10f; private float _teleportTimer = 10f;
private const float TeleportThresholdTime = 20f; private const float TeleportThresholdTime = 20f;
// 다음 행동 생각 처음엔 즉시 실행 // 다음 행동 생각 처음엔 즉시 실행
private float _tinkingTimer = 3f; private float _tinkingTimer = 3f;
private float _tinkingThresholedTime = 3f; private float _thinkingThresholdTime = 3f;
// 프로퍼티 // 프로퍼티
private bool CanTeleport { private bool CanTeleport {
@ -59,7 +56,7 @@ public class CasterDemonController : EnemyController
{ {
get get
{ {
if (_tinkingTimer >= _tinkingThresholedTime) if (_tinkingTimer >= _thinkingThresholdTime)
{ {
_tinkingTimer = 0; _tinkingTimer = 0;
return true; return true;
@ -68,6 +65,7 @@ public class CasterDemonController : EnemyController
} }
} }
private void LateUpdate() private void LateUpdate()
{ {
_teleportTimer += Time.deltaTime; _teleportTimer += Time.deltaTime;
@ -82,8 +80,7 @@ public class CasterDemonController : EnemyController
// 전투 행동 시작 // 전투 행동 시작
_doneBattleSequence = false; _doneBattleSequence = false;
// TODO : 배틀 중일 때 루프 // 사용할 공격 생각하기
// Debug.Log("## 몬스터의 교전 행동 루프");
Thinking(); Thinking();
} }
} }
@ -99,14 +96,14 @@ public class CasterDemonController : EnemyController
case 2: case 2:
case 3: case 3:
case 4: case 4:
SetSequence(RunPattern(ShotMagicMissile));
break;
case 5: case 5:
case 6: case 6:
case 7: case 7:
SetSequence(ShotMagicMissile());
break;
case 8: case 8:
case 9: case 9:
SetSequence(SlowFieldSpell()); SetSequence(RunPattern(SlowFieldSpell));
break; break;
} }
@ -116,11 +113,11 @@ public class CasterDemonController : EnemyController
{ {
if (CanTeleport) if (CanTeleport)
{ {
action(); action.Invoke();
Teleport(); Teleport();
return; return;
} }
SetSequence(KnockbackSpell()); SetSequence(RunPattern(KnockbackSpell));
} }
private IEnumerator ShotMagicMissile() private IEnumerator ShotMagicMissile()
@ -142,12 +139,11 @@ public class CasterDemonController : EnemyController
missile.GetComponent<MagicMissile>() missile.GetComponent<MagicMissile>()
.Initialize(new BulletData(aimPosition, 5f, 10f, 5f)); .Initialize(new BulletData(aimPosition, 5f, 10f, 5f));
yield return new WaitForSeconds(0.4f); yield return Wait.For(0.4f);
} }
// 짧은 텀 후 끝내기 // 짧은 텀 후 끝내기
yield return new WaitForSeconds(1f); yield return Wait.For(1f);
_doneBattleSequence = true;
} }
private Vector3 TargetPosOracle(out Vector3 basePos, out Rigidbody rb) private Vector3 TargetPosOracle(out Vector3 basePos, out Rigidbody rb)
@ -191,8 +187,8 @@ public class CasterDemonController : EnemyController
aoe.SetEffect(effectData, null, null); aoe.SetEffect(effectData, null, null);
// 중앙으로 이동 // 텔레포트 타겟 위치로 이동
Agent.Warp(Vector3.zero); Agent.Warp(teleportTargetPosition);
SetAnimation(Telepo); SetAnimation(Telepo);
if (teleportEffectPrefab != null) if (teleportEffectPrefab != null)
@ -220,11 +216,9 @@ public class CasterDemonController : EnemyController
var warning = Instantiate(chariotWarning, fixedPos, Quaternion.identity).GetComponent<MagicAoEField>(); var warning = Instantiate(chariotWarning, fixedPos, Quaternion.identity).GetComponent<MagicAoEField>();
warning.SetEffect(effectData, null, null); warning.SetEffect(effectData, null, null);
// TODO : 효과 적용
// 3. 짧은 텀 후 끝내기 // 3. 짧은 텀 후 끝내기
yield return new WaitForSeconds(1f); yield return Wait.For(1f);
_doneBattleSequence = true;
} }
private IEnumerator KnockbackSpell() private IEnumerator KnockbackSpell()
@ -245,8 +239,7 @@ public class CasterDemonController : EnemyController
var knockback = Instantiate(chariotWarning, transform).GetComponent<MagicAoEField>(); var knockback = Instantiate(chariotWarning, transform).GetComponent<MagicAoEField>();
knockback.SetEffect(effectData, null, null, DebuffType.Knockback.ToString()); knockback.SetEffect(effectData, null, null, DebuffType.Knockback.ToString());
yield return new WaitForSeconds(1f); yield return Wait.For(1f);
_doneBattleSequence = true;
} }
private void SetSequence(IEnumerator newSequence) private void SetSequence(IEnumerator newSequence)
@ -258,4 +251,9 @@ public class CasterDemonController : EnemyController
_currentSequence = StartCoroutine(newSequence); _currentSequence = StartCoroutine(newSequence);
} }
} private IEnumerator RunPattern(Func<IEnumerator> pattern)
{
yield return StartCoroutine(pattern());
_doneBattleSequence = true;
}
}

View File

@ -7,8 +7,11 @@ public class EnemyStateFlee :IEnemyState
{ {
private EnemyController _enemyController; private EnemyController _enemyController;
private Transform _playerTransform; private Transform _playerTransform;
private float _fleeDistance = 10f; // 도망치는 거리
private float _attackRange = 7f; // 공격 범위 private float _attackRange = 7f; // 공격 범위
private float _fleeDistance = 15f; // 도망치는 거리
private float _attackRangeSqr;
private float _fleeDistanceSqr;
// 경로 탐색 주기 조절용 // 경로 탐색 주기 조절용
private float _fleeSearchTimer = 0; private float _fleeSearchTimer = 0;
@ -33,6 +36,9 @@ public class EnemyStateFlee :IEnemyState
_stuckTimer = 0f; _stuckTimer = 0f;
_fleeSearchTimer = 0; _fleeSearchTimer = 0;
_attackRangeSqr = _attackRange * _attackRange;
_fleeDistanceSqr = _fleeDistance * _fleeDistance;
_enemyController.SetAnimation(CasterDemonController.Flee, true); _enemyController.SetAnimation(CasterDemonController.Flee, true);
} }
@ -44,11 +50,17 @@ public class EnemyStateFlee :IEnemyState
return; return;
} }
float currentDist = Vector3.Distance( float currentDist = (_enemyController.transform.position - _playerTransform.position).sqrMagnitude;
_enemyController.transform.position,
_playerTransform.position if (currentDist >= _fleeDistanceSqr)
); {
if (currentDist >= _attackRange) _enemyController.Agent.isStopped = true;
_enemyController.Agent.ResetPath();
_enemyController.SetState(EnemyState.Idle);
return;
}
if (currentDist >= _attackRangeSqr)
{ {
// 목적지 리셋 후 전투 시작 // 목적지 리셋 후 전투 시작
_enemyController.Agent.isStopped = true; _enemyController.Agent.isStopped = true;
@ -74,8 +86,8 @@ public class EnemyStateFlee :IEnemyState
private void CheckPath() private void CheckPath()
{ {
float moved = Vector3.Distance(_enemyController.transform.position, _lastPosition); float moved = (_enemyController.transform.position - _lastPosition).sqrMagnitude;
if (moved < StuckMoveThreshold) if (moved < StuckMoveThreshold * StuckMoveThreshold)
{ {
_stuckTimer += Time.deltaTime; _stuckTimer += Time.deltaTime;
if (_stuckTimer >= StuckThresholdTime) if (_stuckTimer >= StuckThresholdTime)
@ -95,14 +107,12 @@ public class EnemyStateFlee :IEnemyState
_fleeSearchTimer += Time.deltaTime; _fleeSearchTimer += Time.deltaTime;
if (_fleeSearchTimer <= FleeThresholdTime) return; if (_fleeSearchTimer <= FleeThresholdTime) return;
// 1) 목표 도망 위치 계산 // 플레이어 반대방향으로 도망
Vector3 fleeDirection = (_enemyController.transform.position - _playerTransform.position).normalized; Vector3 fleeDirection = (_enemyController.transform.position - _playerTransform.position).normalized;
Vector3 fleeTarget = _enemyController.transform.position + fleeDirection * _fleeDistance; Vector3 fleeTarget = _enemyController.transform.position + fleeDirection * _fleeDistance;
// 2) 경로 계산해 보기 // 경로 설정
_enemyController.Agent.SetDestination(fleeTarget); _enemyController.Agent.SetDestination(fleeTarget);
// 3) 이동
_enemyController.Agent.isStopped = false; _enemyController.Agent.isStopped = false;
_fleeSearchTimer = 0; _fleeSearchTimer = 0;
} }

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6757ce2a26e44e9c931666533e7e8036
timeCreated: 1746594438

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
using UnityEngine;
// 코루틴의 WaitForSeconds를 캐싱하는 유틸
public static class Wait
{
private static readonly Dictionary<float, WaitForSeconds> _waits = new();
public static WaitForSeconds For(float seconds)
{
if (!_waits.TryGetValue(seconds, out var wait))
{
wait = new WaitForSeconds(seconds);
_waits[seconds] = wait;
}
return wait;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e6fe7172532f42069e3fc7088ebd0718
timeCreated: 1746594464