146 lines
4.0 KiB
C#
146 lines
4.0 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
|
|
public enum EnemyState { None, Idle, Trace, Attack, Dead }
|
|
|
|
[RequireComponent(typeof(NavMeshAgent))]
|
|
[RequireComponent(typeof(Animator))]
|
|
public abstract class EnemyController : CharacterBase
|
|
{
|
|
[Header("AI")]
|
|
[SerializeField] private float detectCircleRadius = 10f; // 플레이어 탐지 범위
|
|
[SerializeField] private LayerMask targetLayerMask; // 플레이어 레이어 마스크
|
|
|
|
public Transform TraceTargetTransform { get; private set; }
|
|
public NavMeshAgent Agent { get; private set; }
|
|
public Animator EnemyAnimator { get; private set; }
|
|
public EnemyState CurrentState {get; private set;}
|
|
public LayerMask TargetLayerMask => targetLayerMask;
|
|
public float MoveSpeed => moveSpeed;
|
|
public bool IsMeleeCombat { get; protected set; }
|
|
|
|
// -----
|
|
// 애니메이션 관련
|
|
private int _currentAnimationTrigger = -1;
|
|
|
|
// 애니메이션 파라미터 해시값
|
|
public static readonly int Idle = Animator.StringToHash("Idle");
|
|
public static readonly int Dead = Animator.StringToHash("Dead");
|
|
public static readonly int Trace = Animator.StringToHash("Trace");
|
|
|
|
// -----
|
|
// 상태 변수
|
|
private EnemyStateIdle _enemyStateIdle;
|
|
private EnemyStateTrace _enemyStateTrace;
|
|
private EnemyStateAttack _enemyStateAttack;
|
|
private EnemyStateDead _enemyStateDead;
|
|
|
|
private Dictionary<EnemyState, IEnemyState> _enemyStates;
|
|
|
|
protected virtual void Awake()
|
|
{
|
|
EnemyAnimator = GetComponent<Animator>();
|
|
Agent = GetComponent<NavMeshAgent>();
|
|
}
|
|
|
|
protected override void Start()
|
|
{
|
|
base.Start();
|
|
|
|
// 상태 객체 생성
|
|
_enemyStateIdle = new EnemyStateIdle();
|
|
_enemyStateTrace = new EnemyStateTrace();
|
|
_enemyStateAttack = new EnemyStateAttack();
|
|
_enemyStateDead = new EnemyStateDead();
|
|
|
|
_enemyStates = new Dictionary<EnemyState, IEnemyState>
|
|
{
|
|
{ EnemyState.Idle, _enemyStateIdle },
|
|
{ EnemyState.Trace, _enemyStateTrace },
|
|
{ EnemyState.Attack, _enemyStateAttack },
|
|
{ EnemyState.Dead, _enemyStateDead },
|
|
};
|
|
|
|
SetState(EnemyState.Idle);
|
|
}
|
|
|
|
protected virtual void Update()
|
|
{
|
|
if (CurrentState != EnemyState.None)
|
|
{
|
|
_enemyStates[CurrentState].Update();
|
|
}
|
|
}
|
|
|
|
public void SetState(EnemyState newState)
|
|
{
|
|
if (CurrentState != EnemyState.None)
|
|
{
|
|
_enemyStates[CurrentState].Exit();
|
|
}
|
|
CurrentState = newState;
|
|
_enemyStates[CurrentState].Enter(this);
|
|
}
|
|
|
|
|
|
public override void Die()
|
|
{
|
|
base.Die();
|
|
// TODO : 사망 후 동작
|
|
SetState(EnemyState.Dead);
|
|
}
|
|
|
|
#region 적 탐지
|
|
|
|
// 일정 반경에 플레이어가 진입하면 플레이어 소리를 감지했다고 판단
|
|
public Transform DetectPlayerInCircle()
|
|
{
|
|
var hitColliders = Physics.OverlapSphere(transform.position,
|
|
detectCircleRadius, targetLayerMask);
|
|
if (hitColliders.Length > 0)
|
|
{
|
|
TraceTargetTransform = hitColliders[0].transform;
|
|
return TraceTargetTransform;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 애니메이션 제어
|
|
|
|
// Trigger
|
|
public void SetAnimation(int hashName)
|
|
{
|
|
if (_currentAnimationTrigger != -1)
|
|
{
|
|
EnemyAnimator.ResetTrigger(_currentAnimationTrigger);
|
|
}
|
|
|
|
EnemyAnimator.SetTrigger(hashName);
|
|
_currentAnimationTrigger = hashName;
|
|
}
|
|
|
|
// Bool
|
|
public void SetAnimation(int hashName, bool value)
|
|
{
|
|
EnemyAnimator.SetBool(hashName, value);
|
|
}
|
|
|
|
// Float
|
|
public void SetAnimation(int hashName, float value)
|
|
{
|
|
EnemyAnimator.SetFloat(hashName, value);
|
|
}
|
|
|
|
// Integer
|
|
public void SetAnimation(int hashName, int value)
|
|
{
|
|
EnemyAnimator.SetInteger(hashName, value);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|