using System; using System.Collections; using UnityEngine; using Random = UnityEngine.Random; public class CasterDemonController : EnemyController { // Animation public static readonly int Cast = Animator.StringToHash("Cast"); public static readonly int Flee = Animator.StringToHash("Flee"); public static readonly int MagicMissile = Animator.StringToHash("MagicMissile"); public static readonly int Telepo = Animator.StringToHash("Telepo"); public static readonly int Spin = Animator.StringToHash("Spin"); [SerializeField] private Transform bulletShotPosition; [SerializeField] private GameObject magicMissilePrefab; [SerializeField] private GameObject teleportEffectPrefab; [SerializeField] private Vector3 teleportTargetPosition = Vector3.zero; [Header("각종 데미지 이펙트 세트")] [SerializeField] private GameObject chariotWarning; [SerializeField] private GameObject chariotEffect; [Space(10)] [SerializeField] private GameObject slowFieldWarning; [SerializeField] private GameObject slowFieldEffect; [SerializeField] private GameObject knockbackEffect; // SFX [Space(10)] [Header("SFX")] [SerializeField] private AudioClip magicMissileShotSound; [SerializeField] private AudioClip teleportSound; [SerializeField] private AudioClip spinSound; [SerializeField] private AudioClip slowFieldSound; private float _knockbackTimer = 10f; private const float KnockBackThresholdTime = 10f; // 텔레포트 쿨타임 처음엔 빨리 사용 가능함 private float _teleportTimer = 10f; private const float TeleportThresholdTime = 20f; // 다음 행동 생각 처음엔 즉시 실행 private float _tinkingTimer = 3f; private float _thinkingThresholdTime = 3f; private bool _doneBattleSequence = true; private Coroutine _currentSequence; private DamageEffectData? _knockbackData; private DamageEffectData KnockbackData { get { if (_knockbackData == null) { _knockbackData = new DamageEffectData { damage = 0, radius = 7.5f, delay = 0.5f, targetLayer = TargetLayerMask, explosionEffectPrefab = knockbackEffect }; } return _knockbackData.Value; } } private DamageEffectData? _slowFieldEffectData; private DamageEffectData SlowFieldEffectData { get { if (_slowFieldEffectData == null) { _slowFieldEffectData = new DamageEffectData { damage = 0, radius = 7.5f, delay = 2.5f, targetLayer = TargetLayerMask, explosionEffectPrefab = slowFieldEffect }; } return _slowFieldEffectData.Value; } } private DamageEffectData? _teleportEffectData; private DamageEffectData TeleportEffectData { get { if (_teleportEffectData == null) { _teleportEffectData = new DamageEffectData { damage = (int)attackPower, radius = 10, delay = 1.5f, targetLayer = TargetLayerMask, explosionEffectPrefab = chariotEffect }; } return _teleportEffectData.Value; } } // 특수 스킬 사용 가능 여부 private bool CanKnockback { get { if (!(_knockbackTimer >= KnockBackThresholdTime)) return false; _knockbackTimer = 0f; return true; } } private bool CanTeleport { get { if (!(_teleportTimer >= TeleportThresholdTime)) return false; _teleportTimer = 0; return true; } } private bool CanBattleSequence { get { if (_tinkingTimer >= _thinkingThresholdTime) { _tinkingTimer = 0; return true; } return false; } } private void LateUpdate() { _teleportTimer += Time.deltaTime; _tinkingTimer += Time.deltaTime; _knockbackTimer += Time.deltaTime; } public override void BattleSequence() { // 전투 행동이 이미 진행 중일 경우 실행 막기 if (_doneBattleSequence && CanBattleSequence) { // 전투 행동 시작 _doneBattleSequence = false; // 사용할 공격 생각하기 Thinking(); } } private void Thinking() { int selectedPattern = Random.Range(0, 10); switch (selectedPattern) { case 0: case 1: case 2: case 3: case 4: SetSequence(ShotMagicMissile); break; case 5: case 6: case 7: case 8: case 9: SetSequence(SlowFieldSpell); break; } } public override void OnCannotFleeBehaviour(Action action) { if (CanTeleport) { action.Invoke(); Teleport(); return; } if (CanKnockback) { SetSequence(KnockbackSpell); } } private IEnumerator ShotMagicMissile() { for (int i = 0; i < 3; i++) { var aimPosition = TargetPosOracle(out var basePos, out var rb); // 플레이어 위치를 바라보고 transform.LookAt(aimPosition); SetAnimation(MagicMissile); SFXPlayer(magicMissileShotSound); // 미사일 생성 및 초기화 var missile = Instantiate( magicMissilePrefab, bulletShotPosition.position, transform.rotation ); missile.GetComponent() .Initialize(new BulletData(aimPosition, 5f, 10f, 5f)); yield return Wait.For(0.4f); } // 짧은 텀 후 끝내기 yield return Wait.For(1f); } private Vector3 TargetPosOracle(out Vector3 basePos, out Rigidbody rb) { // 1. 기본 위치 basePos = TraceTargetTransform.position; Vector3 aimPosition = basePos; // 2. 플레이어 Rigidbody로 속도 얻기 if (TraceTargetTransform.TryGetComponent(out rb)) { // 아주 짧은 시간만 예측 float predictionTime = 0.3f; aimPosition += rb.velocity * predictionTime; } // 높이는 변경할 필요 없음 float fixedY = bulletShotPosition.position.y; aimPosition.y = fixedY; return aimPosition; } private void Teleport() { Vector3 startPos = transform.position; var startTelepoEffect = Instantiate(teleportEffectPrefab, startPos, Quaternion.identity); // 텔레포트와 함께 시전하는 범위 공격 var aoe = Instantiate(chariotWarning, startPos, Quaternion.identity).GetComponent(); aoe.SetEffect(TeleportEffectData, null, null); SFXPlayer(teleportSound); // 텔레포트 타겟 위치로 이동 Agent.Warp(teleportTargetPosition); SetAnimation(Telepo); var endTelepoEffect = Instantiate(teleportEffectPrefab, teleportTargetPosition, Quaternion.identity); StartCoroutine(DelayedEffectDestroyer(startTelepoEffect, endTelepoEffect)); } private IEnumerator DelayedEffectDestroyer(GameObject effect, GameObject effect2) { yield return Wait.For(1f); Destroy(effect); yield return Wait.For(0.4f); Destroy(effect2); } private IEnumerator SlowFieldSpell() { var aimPosition = TargetPosOracle(out var basePos, out var rb); // 1. 시전 애니메이션 transform.LookAt(aimPosition); SetAnimation(Cast); // 2. 장판 생성과 세팅 var fixedPos = new Vector3(aimPosition.x, aimPosition.y, aimPosition.z); var warning = Instantiate(chariotWarning, fixedPos, Quaternion.identity).GetComponent(); warning.SetEffect(SlowFieldEffectData, () => { SFXPlayer(slowFieldSound);}, null); // 3. 짧은 텀 후 끝내기 yield return Wait.For(1f); } private IEnumerator KnockbackSpell() { var knockback = Instantiate(chariotWarning, transform).GetComponent(); knockback.SetEffect(KnockbackData, ()=>{ SetAnimation(Spin); SFXPlayer(spinSound);}, null, DebuffType.Knockback.ToString()); yield return Wait.For(1f); } private void SFXPlayer(AudioClip clip) { SoundManager.Instance.PlaySFX(clip); } #region 유틸리티 private void SetSequence(Func newSequence) { if (_currentSequence != null) StopCoroutine(_currentSequence); _currentSequence = StartCoroutine(RunPattern(newSequence)); } private IEnumerator RunPattern(Func pattern) { yield return StartCoroutine(pattern()); _doneBattleSequence = true; } #endregion }