diff --git a/Assets/JYY/Animator/Alien Big Blink.controller b/Assets/JYY/Animator/Alien Big Blink.controller index d9a11b13..f233680e 100644 --- a/Assets/JYY/Animator/Alien Big Blink.controller +++ b/Assets/JYY/Animator/Alien Big Blink.controller @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f224b789ebc993746abd7e94faed8370d442fa520a9935ca57efd0ec4402a8a9 -size 37778 +oid sha256:f0bce31c8017319b731daa8dfb9666036d51f1002b083d730d3897fd7ee1f2e7 +size 41312 diff --git a/Assets/JYY/Scenes/MonsterTest.unity b/Assets/JYY/Scenes/MonsterTest.unity index 9f548756..6c1ddbe6 100644 --- a/Assets/JYY/Scenes/MonsterTest.unity +++ b/Assets/JYY/Scenes/MonsterTest.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f04b7cb3c038c2e7ae9e2308a272957be162eeef89deb5fdcab430f447bde41 -size 25384 +oid sha256:3ddd026f66ac7cb1a8dbdc3118964fcd6ac9932fc136d052cff0406fe79ace39 +size 25822 diff --git a/Assets/Plugins/Editor.meta b/Assets/Plugins/Editor.meta new file mode 100644 index 00000000..59842daf --- /dev/null +++ b/Assets/Plugins/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0f665d75f6d77274a9371a87e014fd97 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Character/Debuff/KnockbackEffect.cs b/Assets/Scripts/Character/Debuff/KnockbackEffect.cs new file mode 100644 index 00000000..b87f6ae2 --- /dev/null +++ b/Assets/Scripts/Character/Debuff/KnockbackEffect.cs @@ -0,0 +1,43 @@ +using System.Collections; +using UnityEngine; + +public class KnockbackEffect : StatusEffect +{ + private Vector3 _sourcePosition; + private float _knockbackForce; + private float _elapsed = 0f; + + public KnockbackEffect(Vector3 sourcePosition, float knockbackForce,float duration) + { + effectName = DebuffType.Knockback.ToString(); + this.duration = duration; + _sourcePosition = sourcePosition; + _knockbackForce = knockbackForce; + } + + public override void ApplyEffect(CharacterBase target) + { + + Vector3 direction = (target.transform.position - _sourcePosition).normalized; + direction.y = 0f; // 수직 방향 제거 + target.StartCoroutine(KnockbackCoroutine(target, direction)); + } + private IEnumerator KnockbackCoroutine(CharacterBase pc, Vector3 direction) + { + CharacterController controller = pc.GetComponent(); + if (controller == null) yield break; + + _elapsed = 0f; + while (_elapsed < duration) + { + controller.Move(direction * (_knockbackForce * Time.deltaTime)); + _elapsed += Time.deltaTime; + yield return null; + } + } + + public override void RemoveEffect(CharacterBase target) + { + + } +} \ No newline at end of file diff --git a/Assets/Scripts/Character/Debuff/KnockbackEffect.cs.meta b/Assets/Scripts/Character/Debuff/KnockbackEffect.cs.meta new file mode 100644 index 00000000..633feab3 --- /dev/null +++ b/Assets/Scripts/Character/Debuff/KnockbackEffect.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 97861883fd2941e0a40071b12ca13de4 +timeCreated: 1746082842 \ No newline at end of file diff --git a/Assets/Scripts/Character/Debuff/SlowDebuff.cs b/Assets/Scripts/Character/Debuff/SlowDebuff.cs index 94ccbb0b..6f19334b 100644 --- a/Assets/Scripts/Character/Debuff/SlowDebuff.cs +++ b/Assets/Scripts/Character/Debuff/SlowDebuff.cs @@ -6,26 +6,21 @@ public class SlowDebuff : StatusEffect public SlowDebuff(float duration, float slowMultiplier) { - this.effectName = "Slow"; + this.effectName = DebuffType.Slow.ToString(); this.duration = duration; _slowMultiplier = slowMultiplier; } public override void ApplyEffect(CharacterBase target) { - if (target is PlayerController pc) - { - pc.moveSpeed *= _slowMultiplier; - Debug.Log($"{target.characterName}에게 이동 속도 감소 적용됨"); - } + target.moveSpeed *= _slowMultiplier; + Debug.Log($"{target.characterName}에게 이동 속도 감소 적용됨"); + } public override void RemoveEffect(CharacterBase target) { - if (target is PlayerController pc) - { - pc.moveSpeed /= _slowMultiplier; - Debug.Log($"{target.characterName}의 이동 속도 회복됨"); - } + target.moveSpeed /= _slowMultiplier; + Debug.Log($"{target.characterName}의 이동 속도 회복됨"); } } \ No newline at end of file diff --git a/Assets/Scripts/Character/Enemy/BossPattern/AoeControllerBase.cs b/Assets/Scripts/Character/Enemy/BossPattern/AoeControllerBase.cs index 61aef323..8717a99b 100644 --- a/Assets/Scripts/Character/Enemy/BossPattern/AoeControllerBase.cs +++ b/Assets/Scripts/Character/Enemy/BossPattern/AoeControllerBase.cs @@ -24,6 +24,7 @@ public abstract class AoeControllerBase : MonoBehaviour protected DamageEffectData _data; private Action _slashAction; private Action _destroyAction; + protected string EffectName; /// /// 범위 공격 이펙트를 설정하고, 딜레이 후 폭발을 실행합니다. @@ -38,6 +39,17 @@ public abstract class AoeControllerBase : MonoBehaviour StartCoroutine(ExplodeAfterDelay()); } + public void SetEffect(DamageEffectData data, Action slashAction, Action destroyAction, string effectName) + { + _data = data; + _slashAction = slashAction; + _destroyAction = destroyAction; + EffectName = effectName; + + ShowWarningEffect(); + StartCoroutine(ExplodeAfterDelay()); + } + protected virtual void ShowWarningEffect() { if (warningEffectInstance != null) diff --git a/Assets/Scripts/Character/Enemy/BossPattern/ChariotAoeController.cs b/Assets/Scripts/Character/Enemy/BossPattern/ChariotAoeController.cs index e658d92a..7df2fe99 100644 --- a/Assets/Scripts/Character/Enemy/BossPattern/ChariotAoeController.cs +++ b/Assets/Scripts/Character/Enemy/BossPattern/ChariotAoeController.cs @@ -2,8 +2,6 @@ using System.Collections; using UnityEngine; - - public class ChariotAoeController : AoeControllerBase { protected override void ShowDamageEffect() diff --git a/Assets/Scripts/Character/Enemy/BossPattern/MagicAoEField.cs b/Assets/Scripts/Character/Enemy/BossPattern/MagicAoEField.cs index 38808295..deb1e461 100644 --- a/Assets/Scripts/Character/Enemy/BossPattern/MagicAoEField.cs +++ b/Assets/Scripts/Character/Enemy/BossPattern/MagicAoEField.cs @@ -1,5 +1,11 @@ using UnityEngine; +public enum DebuffType +{ + Slow, + Knockback, +} + public class MagicAoEField : AoeControllerBase { protected override void HitCheck() @@ -13,14 +19,33 @@ public class MagicAoEField : AoeControllerBase Debug.Log($"{hit.name}에게 {_data.damage} 데미지 적용"); // TODO: 실제 데미지 처리 로직 호출 // 임시 데이미 처리 로직 - PlayerController playerController = hit.transform.GetComponent(); - if (playerController != null) - { - // playerController.AddStatusEffect(_slowDebuff); - var slow = new SlowDebuff(10f, 0.5f); // 10초간 50% 속도 - playerController.AddStatusEffect(slow); - } + ApplyEffect(hit); } } } + + private void ApplyEffect(Collider hit) + { + PlayerController playerController = hit.transform.GetComponent(); + switch (EffectName) + { + case "Slow": + + if (playerController != null) + { + var slow = new SlowDebuff(10f, 0.5f); // 10초간 50% 속도 + playerController.AddStatusEffect(slow); + } + break; + case "Knockback": + if (playerController != null) + { + var knPos = transform.position; + knPos.y += 0.5f; + var knockback = new KnockbackEffect(knPos,10f, 0.5f); // 장판 중심에서 10f만큼 + playerController.AddStatusEffect(knockback); + } + break; + } + } } \ No newline at end of file diff --git a/Assets/Scripts/Character/Enemy/CasterDemonController.cs b/Assets/Scripts/Character/Enemy/CasterDemonController.cs index a2b2cd48..e4c335c7 100644 --- a/Assets/Scripts/Character/Enemy/CasterDemonController.cs +++ b/Assets/Scripts/Character/Enemy/CasterDemonController.cs @@ -1,9 +1,6 @@ using System; using System.Collections; -using System.Collections.Generic; -using Unity.VisualScripting; using UnityEngine; -using UnityEngine.AI; using Random = UnityEngine.Random; public class CasterDemonController : EnemyController @@ -11,16 +8,15 @@ public class CasterDemonController : EnemyController // Animation public static readonly int Cast = Animator.StringToHash("Cast"); public static readonly int Flee = Animator.StringToHash("Flee"); - - private bool _doneBattleSequence = true; - private bool _isFirstNoPath = true; - - private Coroutine _currentSequence; + 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 teleportTransform; [SerializeField] private Transform bulletShotPosition; [SerializeField] private GameObject magicMissilePrefab; [SerializeField] private GameObject teleportEffectPrefab; + [SerializeField] private Vector3 teleportTargetPosition = Vector3.zero; [Header("각종 데미지 이펙트 세트")] [SerializeField] private GameObject chariotWarning; @@ -29,40 +25,128 @@ public class CasterDemonController : EnemyController [Space(10)] [SerializeField] private GameObject slowFieldWarning; [SerializeField] private GameObject slowFieldEffect; + [SerializeField] private GameObject knockbackEffect; + private float _knockbackTimer = 10f; + private const float KnockBackThresholdTime = 10f; - private float _teleportDistance = 4f; // 플레이어 뒤로 떨어질 거리 - - // 텔레포트 쿨타임 - private float _teleportTimer = 0; + // 텔레포트 쿨타임 처음엔 빨리 사용 가능함 + 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 ) + if (!(_teleportTimer >= TeleportThresholdTime)) return false; + _teleportTimer = 0; + return true; + } + } + private bool CanBattleSequence + { + get + { + if (_tinkingTimer >= _thinkingThresholdTime) { - _teleportTimer = 0; + _tinkingTimer = 0; return true; } return false; } } + private void LateUpdate() { _teleportTimer += Time.deltaTime; + _tinkingTimer += Time.deltaTime; + _knockbackTimer += Time.deltaTime; } public override void BattleSequence() { // 전투 행동이 이미 진행 중일 경우 실행 막기 - if (_doneBattleSequence) + if (_doneBattleSequence && CanBattleSequence) { // 전투 행동 시작 _doneBattleSequence = false; - // TODO : 배틀 중일 때 루프 - // Debug.Log("## 몬스터의 교전 행동 루프"); + // 사용할 공격 생각하기 Thinking(); } } @@ -74,26 +158,18 @@ public class CasterDemonController : EnemyController 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(ShotMagicMissile()); - SetSequence(SlowFieldSpell()); + SetSequence(SlowFieldSpell); break; } @@ -103,8 +179,13 @@ public class CasterDemonController : EnemyController { if (CanTeleport) { - action(); + action.Invoke(); Teleport(); + return; + } + if (CanKnockback) + { + SetSequence(KnockbackSpell); } } @@ -116,6 +197,7 @@ public class CasterDemonController : EnemyController // 플레이어 위치를 바라보고 transform.LookAt(aimPosition); + SetAnimation(MagicMissile); // 미사일 생성 및 초기화 var missile = Instantiate( @@ -126,12 +208,11 @@ public class CasterDemonController : EnemyController missile.GetComponent() .Initialize(new BulletData(aimPosition, 5f, 10f, 5f)); - yield return new WaitForSeconds(0.4f); + yield return Wait.For(0.4f); } // 짧은 텀 후 끝내기 - yield return new WaitForSeconds(1f); - _doneBattleSequence = true; + yield return Wait.For(1f); } private Vector3 TargetPosOracle(out Vector3 basePos, out Rigidbody rb) @@ -164,22 +245,16 @@ public class CasterDemonController : EnemyController // 텔레포트와 함께 시전하는 범위 공격 var aoe = Instantiate(chariotWarning, startPos, Quaternion.identity).GetComponent(); - var effectData = new DamageEffectData - { - damage = (int)attackPower, - radius = 10, - delay = 1.5f, - targetLayer = TargetLayerMask, - explosionEffectPrefab = chariotEffect - }; - aoe.SetEffect(effectData, null, null); - // 중앙으로 이동 - Agent.Warp(Vector3.zero); + aoe.SetEffect(TeleportEffectData, null, null); + + // 텔레포트 타겟 위치로 이동 + Agent.Warp(teleportTargetPosition); + SetAnimation(Telepo); if (teleportEffectPrefab != null) - Instantiate(teleportEffectPrefab, Vector3.zero, Quaternion.identity); + Instantiate(teleportEffectPrefab, teleportTargetPosition, Quaternion.identity); } private IEnumerator SlowFieldSpell() @@ -188,35 +263,42 @@ public class CasterDemonController : EnemyController // 1. 시전 애니메이션 transform.LookAt(aimPosition); SetAnimation(Cast); + // 2. 장판 생성과 세팅 - - var effectData = new DamageEffectData - { - damage = 0, - radius = 7.5f, - delay = 2.5f, - targetLayer = TargetLayerMask, - explosionEffectPrefab = slowFieldEffect - }; - var fixedPos = new Vector3(aimPosition.x, 0, aimPosition.z); var warning = Instantiate(chariotWarning, fixedPos, Quaternion.identity).GetComponent(); - warning.SetEffect(effectData, null, null); - // TODO : 효과 적용 + warning.SetEffect(SlowFieldEffectData, null, null); // 3. 짧은 텀 후 끝내기 - yield return new WaitForSeconds(1f); - _doneBattleSequence = true; + yield return Wait.For(1f); } - private void SetSequence(IEnumerator newSequence) + private IEnumerator KnockbackSpell() + { + // 시전 애니메이션 + SetAnimation(Spin); + + // 넉백 발생 + var knockback = Instantiate(chariotWarning, transform).GetComponent(); + knockback.SetEffect(KnockbackData, null, null, DebuffType.Knockback.ToString()); + + yield return Wait.For(1f); + } + + #region 유틸리티 + private void SetSequence(Func newSequence) { if (_currentSequence != null) - { StopCoroutine(_currentSequence); - } - _currentSequence = StartCoroutine(newSequence); + + _currentSequence = StartCoroutine(RunPattern(newSequence)); } -} + private IEnumerator RunPattern(Func pattern) + { + yield return StartCoroutine(pattern()); + _doneBattleSequence = true; + } + #endregion +} \ No newline at end of file diff --git a/Assets/Scripts/Character/Enemy/EnemyState/Caster/EnemyStateFlee.cs b/Assets/Scripts/Character/Enemy/EnemyState/Caster/EnemyStateFlee.cs index b88ff8c8..a8c88bd6 100644 --- a/Assets/Scripts/Character/Enemy/EnemyState/Caster/EnemyStateFlee.cs +++ b/Assets/Scripts/Character/Enemy/EnemyState/Caster/EnemyStateFlee.cs @@ -7,8 +7,11 @@ public class EnemyStateFlee :IEnemyState { private EnemyController _enemyController; private Transform _playerTransform; - private float _fleeDistance = 5f; // 도망치는 거리 - private float _safeRange = 7f; // 공격 범위 + private float _attackRange = 7f; // 공격 범위 + private float _fleeDistance = 15f; // 도망치는 거리 + + private float _attackRangeSqr; + private float _fleeDistanceSqr; // 경로 탐색 주기 조절용 private float _fleeSearchTimer = 0; @@ -33,6 +36,9 @@ public class EnemyStateFlee :IEnemyState _stuckTimer = 0f; _fleeSearchTimer = 0; + _attackRangeSqr = _attackRange * _attackRange; + _fleeDistanceSqr = _fleeDistance * _fleeDistance; + _enemyController.SetAnimation(CasterDemonController.Flee, true); } @@ -44,11 +50,17 @@ public class EnemyStateFlee :IEnemyState return; } - float currentDist = Vector3.Distance( - _enemyController.transform.position, - _playerTransform.position - ); - if (currentDist >= _safeRange) + 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; @@ -74,8 +86,8 @@ public class EnemyStateFlee :IEnemyState private void CheckPath() { - float moved = Vector3.Distance(_enemyController.transform.position, _lastPosition); - if (moved < StuckMoveThreshold) + float moved = (_enemyController.transform.position - _lastPosition).sqrMagnitude; + if (moved < StuckMoveThreshold * StuckMoveThreshold) { _stuckTimer += Time.deltaTime; if (_stuckTimer >= StuckThresholdTime) @@ -95,14 +107,12 @@ public class EnemyStateFlee :IEnemyState _fleeSearchTimer += Time.deltaTime; if (_fleeSearchTimer <= FleeThresholdTime) return; - // 1) 목표 도망 위치 계산 + // 플레이어 반대방향으로 도망 Vector3 fleeDirection = (_enemyController.transform.position - _playerTransform.position).normalized; Vector3 fleeTarget = _enemyController.transform.position + fleeDirection * _fleeDistance; - // 2) 경로 계산해 보기 + // 경로 설정 _enemyController.Agent.SetDestination(fleeTarget); - - // 3) 이동 _enemyController.Agent.isStopped = false; _fleeSearchTimer = 0; } diff --git a/Assets/Scripts/Utils.meta b/Assets/Scripts/Utils.meta new file mode 100644 index 00000000..73a2a189 --- /dev/null +++ b/Assets/Scripts/Utils.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6757ce2a26e44e9c931666533e7e8036 +timeCreated: 1746594438 \ No newline at end of file diff --git a/Assets/Scripts/Utils/Wait.cs b/Assets/Scripts/Utils/Wait.cs new file mode 100644 index 00000000..b7da55e6 --- /dev/null +++ b/Assets/Scripts/Utils/Wait.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using UnityEngine; + +// 코루틴의 WaitForSeconds를 캐싱하는 유틸 +public static class Wait +{ + private static readonly Dictionary _waits = new(); + + public static WaitForSeconds For(float seconds) + { + if (!_waits.TryGetValue(seconds, out var wait)) + { + wait = new WaitForSeconds(seconds); + _waits[seconds] = wait; + } + return wait; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Utils/Wait.cs.meta b/Assets/Scripts/Utils/Wait.cs.meta new file mode 100644 index 00000000..5b81f880 --- /dev/null +++ b/Assets/Scripts/Utils/Wait.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e6fe7172532f42069e3fc7088ebd0718 +timeCreated: 1746594464 \ No newline at end of file