DEG-41 근접 범위패턴, 거리가 멀면 원거리 공격 패턴 추가

This commit is contained in:
fiore 2025-04-22 18:02:56 +09:00
parent c0fde7a8d2
commit b6e0458350
11 changed files with 142 additions and 108 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b9e37c8d0a91f3599d34a7715c632cbcf76fd3c0a96c5d5335ab9d6ff65d3b4c oid sha256:481922b21d2de476145ad4fefaccad66d344052ffc0a2808e577b717e59be4e9
size 18916 size 18696

BIN
Assets/JYY/Scenes/MonsterTest.unity (Stored with Git LFS)

Binary file not shown.

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d3b800a343fe42afa7494329ef235461
timeCreated: 1745300808

View File

@ -17,15 +17,17 @@ public struct DamageEffectData
public class ChariotAoeController : MonoBehaviour public class ChariotAoeController : MonoBehaviour
{ {
private DamageEffectData _data;
[SerializeField] private GameObject warningEffectInstance; [SerializeField] private GameObject warningEffectInstance;
private EnemyController _enemyController; private DamageEffectData _data;
private Action _destroyAction;
private Action _slashAction;
public void SetEffect(DamageEffectData data, EnemyController enemyController) public void SetEffect(DamageEffectData data, Action slashAction, Action destroyAction)
{ {
_data = data; _data = data;
_enemyController = enemyController; _slashAction = slashAction;
_destroyAction = destroyAction;
ShowWarningEffect(); ShowWarningEffect();
Invoke(nameof(Explode), _data.delay); Invoke(nameof(Explode), _data.delay);
@ -45,8 +47,11 @@ public class ChariotAoeController : MonoBehaviour
// 공격 전조 제거 // 공격 전조 제거
warningEffectInstance.SetActive(false); warningEffectInstance.SetActive(false);
// 공격 애니메이션 실행
_slashAction.Invoke();
effect.transform.localScale = new Vector3(_data.radius, _data.radius, _data.radius); effect.transform.localScale = new Vector3(_data.radius, _data.radius, _data.radius);
_enemyController.SetAttackTrigger(true);
// 폭발 반경 내의 모든 콜라이더 가져오기 // 폭발 반경 내의 모든 콜라이더 가져오기
Collider[] hitColliders = Physics.OverlapSphere(transform.position, _data.radius, _data.targetLayer); Collider[] hitColliders = Physics.OverlapSphere(transform.position, _data.radius, _data.targetLayer);
foreach (Collider hit in hitColliders) foreach (Collider hit in hitColliders)
@ -69,8 +74,7 @@ public class ChariotAoeController : MonoBehaviour
private void OnDestroy() private void OnDestroy()
{ {
_enemyController.SetAttackTrigger(false); _destroyAction.Invoke();
_enemyController = null;
} }
private void OnDrawGizmosSelected() private void OnDrawGizmosSelected()

View File

@ -12,26 +12,12 @@ public abstract class EnemyController : CharacterBase
[SerializeField] private float detectCircleRadius = 10f; // 플레이어 탐지 범위 [SerializeField] private float detectCircleRadius = 10f; // 플레이어 탐지 범위
[SerializeField] private LayerMask targetLayerMask; // 플레이어 레이어 마스크 [SerializeField] private LayerMask targetLayerMask; // 플레이어 레이어 마스크
public Transform TraceTargetTransform { get; private set; }
public NavMeshAgent Agent { get; private set; } public NavMeshAgent Agent { get; private set; }
public Animator EnemyAnimator { get; private set; } public Animator EnemyAnimator { get; private set; }
public EnemyState CurrentState {get; private set;} public EnemyState CurrentState {get; private set;}
public LayerMask TargetLayerMask => targetLayerMask; public LayerMask TargetLayerMask => targetLayerMask;
public bool AttackTrigger { get; protected set; } public float MoveSpeed => moveSpeed;
public bool IsInBattle { get => _isInBattle; protected set=> _isInBattle = value; }
private bool _isInBattle = false;
public bool IsBoss { get => _isBoss; protected set => _isBoss = value; }
private bool _isBoss = false;
public float WalkSpeed => walkSpeed;
public float RunSpeed => runSpeed;
public Transform TraceTargetTransform { get; private set; }
[Header("이동 능력")]
[SerializeField] private float walkSpeed = 5;
[SerializeField] private float runSpeed = 8;
// ----- // -----
// 상태 변수 // 상태 변수
@ -40,7 +26,6 @@ public abstract class EnemyController : CharacterBase
private EnemyStateAttack _enemyStateAttack; private EnemyStateAttack _enemyStateAttack;
private EnemyStateDead _enemyStateDead; private EnemyStateDead _enemyStateDead;
private Dictionary<EnemyState, IEnemyState> _enemyStates; private Dictionary<EnemyState, IEnemyState> _enemyStates;
protected virtual void Awake() protected virtual void Awake()
@ -88,11 +73,6 @@ public abstract class EnemyController : CharacterBase
_enemyStates[CurrentState].Enter(this); _enemyStates[CurrentState].Enter(this);
} }
public void SetInBattle(bool battle)
{
_isInBattle = battle;
}
public override void Die() public override void Die()
{ {
@ -101,11 +81,6 @@ public abstract class EnemyController : CharacterBase
} }
public void SetAttackTrigger(bool value)
{
AttackTrigger = value;
}
#region #region
// 일정 반경에 플레이어가 진입하면 플레이어 소리를 감지했다고 판단 // 일정 반경에 플레이어가 진입하면 플레이어 소리를 감지했다고 판단

View File

@ -4,52 +4,22 @@ using UnityEngine;
public class EnemyStateAttack : IEnemyState public class EnemyStateAttack : IEnemyState
{ {
private static readonly int VertiSlash = Animator.StringToHash("VertiSlash");
private static readonly int VertiAttack = Animator.StringToHash("VertiAttack");
private EnemyController _enemyController; private EnemyController _enemyController;
private Animator _animator; private Animator _animator;
private Coroutine _attackRoutine;
private const float AttackInterval = 2f;
private float _attackTimer = 0f;
public void Enter(EnemyController enemyController) public void Enter(EnemyController enemyController)
{ {
_enemyController = enemyController; _enemyController = enemyController;
_animator = _enemyController.EnemyAnimator; _animator = _enemyController.EnemyAnimator;
} }
public void Update() public void Update()
{ {
if (!_enemyController.IsBoss)
NonBossSequence();
if (_enemyController.AttackTrigger)
{
_animator.SetTrigger(VertiSlash);
_enemyController.SetState(EnemyState.Trace);
}
}
private void NonBossSequence()
{
_animator.SetBool(VertiAttack, true);
_attackTimer += Time.deltaTime;
if (_attackTimer >= AttackInterval)
{
_animator.SetTrigger(VertiSlash);
_attackTimer = 0f;
_enemyController.SetState(EnemyState.Trace);
}
} }
public void Exit() public void Exit()
{ {
_enemyController.SetAttackTrigger(false);
_animator.SetBool(VertiAttack, false);
_animator = null; _animator = null;
_enemyController = null; _enemyController = null;
} }

View File

@ -6,7 +6,6 @@
{ {
_enemyController = enemyController; _enemyController = enemyController;
_enemyController.EnemyAnimator.SetTrigger("Dead"); _enemyController.EnemyAnimator.SetTrigger("Dead");
_enemyController.SetInBattle(false);
} }
public void Update() public void Update()

View File

@ -2,13 +2,13 @@
public class EnemyStateIdle: IEnemyState public class EnemyStateIdle: IEnemyState
{ {
private static readonly int Idle = Animator.StringToHash("Idle");
private EnemyController _enemyController; private EnemyController _enemyController;
public void Enter(EnemyController enemyController) public void Enter(EnemyController enemyController)
{ {
_enemyController = enemyController; _enemyController = enemyController;
_enemyController.EnemyAnimator.SetBool("Idle", true); _enemyController.EnemyAnimator.SetBool(Idle, true);
_enemyController.SetInBattle(false);
} }
public void Update() public void Update()
@ -22,7 +22,7 @@ public class EnemyStateIdle: IEnemyState
public void Exit() public void Exit()
{ {
_enemyController.EnemyAnimator.SetBool("Idle", false); _enemyController.EnemyAnimator.SetBool(Idle, false);
_enemyController = null; _enemyController = null;
} }
} }

View File

@ -29,11 +29,12 @@ public class EnemyStateTrace : IEnemyState
} }
_enemyController.EnemyAnimator.SetBool(Trace, true); _enemyController.EnemyAnimator.SetBool(Trace, true);
_enemyController.SetInBattle(true);
} }
public void Update() public void Update()
{ {
if (_enemyController.Agent.enabled != true) return;
// 일정 주기로 찾은 플레이어의 위치를 갱신해서 갱신된 위치로 이동 // 일정 주기로 찾은 플레이어의 위치를 갱신해서 갱신된 위치로 이동
FindTargetPosition(); FindTargetPosition();
@ -44,7 +45,6 @@ public class EnemyStateTrace : IEnemyState
// TODO: 타겟에 도착함 -> 공격 준비 // TODO: 타겟에 도착함 -> 공격 준비
_enemyController.SetState(EnemyState.Attack); _enemyController.SetState(EnemyState.Attack);
} }
} }
private void FindTargetPosition() private void FindTargetPosition()
@ -70,24 +70,12 @@ public class EnemyStateTrace : IEnemyState
{ {
float distance = (_detectPlayerTransform.position - _enemyController.transform.position).magnitude; float distance = (_detectPlayerTransform.position - _enemyController.transform.position).magnitude;
if (distance > 5f) if (distance > 2f)
{
// 먼 거리: 뛰기
_enemyController.Agent.speed = _enemyController.RunSpeed;
_enemyController.Agent.acceleration = 20f;
_enemyController.Agent.angularSpeed = 270f;
// _enemyController.EnemyAnimator.SetFloat("MoveSpeed", 1f); // 애니메이션도 Run으로
// NavMeshAgent 회전에 맡기기
_enemyController.Agent.updateRotation = true;
}
else if (distance > 2f)
{ {
// 가까운 거리: 걷기 // 가까운 거리: 걷기
_enemyController.Agent.speed = _enemyController.WalkSpeed; _enemyController.Agent.speed = _enemyController.MoveSpeed;
_enemyController.Agent.acceleration = 8f; _enemyController.Agent.acceleration = 8f;
_enemyController.Agent.angularSpeed = 720f; _enemyController.Agent.angularSpeed = 720f;
// _enemyController.EnemyAnimator.SetFloat("MoveSpeed", 0.4f); // Walk 애니메이션
_enemyController.Agent.updateRotation = true; _enemyController.Agent.updateRotation = true;
} }
@ -108,11 +96,6 @@ public class EnemyStateTrace : IEnemyState
Time.deltaTime * 10f // 회전 속도 Time.deltaTime * 10f // 회전 속도
); );
} }
// _enemyController.Agent.angularSpeed = 1080f;
// _enemyController.Agent.acceleration = 999f;
// _enemyController.EnemyAnimator.SetFloat("MoveSpeed", 0f);
} }
// 실제 속도 기반으로 애니메이션 제어 // 실제 속도 기반으로 애니메이션 제어

View File

@ -2,9 +2,13 @@ using System;
using System.Collections; using System.Collections;
using Unity.VisualScripting; using Unity.VisualScripting;
using UnityEngine; using UnityEngine;
using Random = UnityEngine.Random;
public class PldDogController : EnemyController public class PldDogController : EnemyController
{ {
private static readonly int WindUp = Animator.StringToHash("WindUp");
private static readonly int VertiSlash = Animator.StringToHash("VertiSlash");
[Header("공격 패턴 관련")] [Header("공격 패턴 관련")]
[SerializeField] private float patternInterval = 3f; [SerializeField] private float patternInterval = 3f;
@ -12,7 +16,6 @@ public class PldDogController : EnemyController
private int _currentPatternIndex = 0; private int _currentPatternIndex = 0;
private bool _isPatternRunning = false; private bool _isPatternRunning = false;
[Header("각종 데미지 이펙트 세트")] [Header("각종 데미지 이펙트 세트")]
[SerializeField] private GameObject chariotSlashWarning; [SerializeField] private GameObject chariotSlashWarning;
[SerializeField] private GameObject chariotSlash; [SerializeField] private GameObject chariotSlash;
@ -25,27 +28,64 @@ public class PldDogController : EnemyController
[SerializeField] private GameObject horizontalWarning; [SerializeField] private GameObject horizontalWarning;
[SerializeField] private GameObject horizontalSlash; [SerializeField] private GameObject horizontalSlash;
// 몬스터의 행동 스크립트 private bool _isInTrace;
// IsBoos = 보스몬스터 여부 private bool _isInAttack;
// IsInBattle = 전투중인지 여부 private bool _isFirstAttack = true;
protected override void Awake()
{
base.Awake();
IsBoss = true;
}
private void Update() private void Update()
{ {
base.Update(); base.Update();
if (IsInBattle && !_isPatternRunning && CurrentState == EnemyState.Attack) CheckIsInBattle();
if (_isInAttack)
{ {
_patternTimer += Time.deltaTime; _patternTimer += Time.deltaTime;
if (_patternTimer >= patternInterval)
if (!_isPatternRunning && (_isFirstAttack || _patternTimer >= patternInterval))
{ {
ExecutePattern(0); Agent.enabled = false;
// TODO: 순서대로 패턴 실행
ExecutePattern(_currentPatternIndex);
// _currentPatternIndex = (_currentPatternIndex + 1) % 3; // 패턴 순환
_isFirstAttack = false;
} }
} }
if (_isInTrace)
{
_patternTimer += Time.deltaTime;
if (!_isPatternRunning && _patternTimer >= patternInterval)
{
_isPatternRunning = true;
float distanceToPlayer = Vector3.Distance(transform.position, TraceTargetTransform.position);
if (distanceToPlayer > 3f)
{
BombThrowPattern();
}
}
}
}
private void CheckIsInBattle()
{
switch (CurrentState)
{
case EnemyState.Attack:
_isInAttack = true;
_isInTrace = false;
break;
case EnemyState.Trace:
_isInTrace = true;
_isInAttack = false;
break;
}
} }
private void ExecutePattern(int patternIndex) private void ExecutePattern(int patternIndex)
@ -70,11 +110,41 @@ public class PldDogController : EnemyController
} }
} }
private void BombThrowPattern()
{
Debug.Log("BombThrowPattern: 보스가 폭탄을 던집니다.");
int bombCount = 1; // 한 번에 몇 개 던질지
float radius = 2f;
for (int i = 0; i < bombCount; i++)
{
Vector3 randomPos = TraceTargetTransform.position + (Random.insideUnitSphere * radius);
randomPos.y = 0.1f; // 지면에 맞추기
var boomObj = Instantiate(chariotSlashWarning, randomPos, Quaternion.identity);
boomObj.transform.localScale = new Vector3(5f, 5f, 5f);
var boom = boomObj.GetComponent<ChariotAoeController>();
DamageEffectData effectData = new DamageEffectData()
{
damage = (int)attackPower,
radius = 5f,
delay = 1.5f,
targetLayer = TargetLayerMask,
explosionEffectPrefab = chariotSlash
};
boom.SetEffect(effectData,()=>{ }, PatternClear);
}
}
private void ChariotSlashPattern() private void ChariotSlashPattern()
{ {
Debug.Log("ChariotSlashPattern: 보스가 차지 슬래시를 사용합니다."); Debug.Log("ChariotSlashPattern: 보스가 차지 슬래시를 사용합니다.");
EnemyAnimator.SetBool("VertiAttack", true); WindUpAnimationStart();
var warning = Instantiate(chariotSlashWarning, transform.position, Quaternion.identity)
var slash = Instantiate(chariotSlashWarning, transform.position, Quaternion.identity)
.GetComponent<ChariotAoeController>(); .GetComponent<ChariotAoeController>();
DamageEffectData effectData = new DamageEffectData() DamageEffectData effectData = new DamageEffectData()
@ -85,6 +155,36 @@ public class PldDogController : EnemyController
targetLayer = TargetLayerMask, targetLayer = TargetLayerMask,
explosionEffectPrefab = chariotSlash explosionEffectPrefab = chariotSlash
}; };
warning.SetEffect(effectData, this);
slash.SetEffect(effectData, SlashAnimationPlay,
() =>
{
PatternClear();
WindUpAnimationEnd();
SetState(EnemyState.Trace);
}
);
}
private void WindUpAnimationStart()
{
EnemyAnimator.SetBool(WindUp, true);
}
private void WindUpAnimationEnd()
{
EnemyAnimator.SetBool(WindUp, false);
}
private void SlashAnimationPlay()
{
EnemyAnimator.SetTrigger(VertiSlash);
}
private void PatternClear()
{
_isPatternRunning = false;
_patternTimer = 0f;
Agent.enabled = true;
} }
} }