Degulleo3D/Assets/Scripts/Character/Enemy/EnemyController.cs
Fiore c2732adc76 [fix] 의도되지 않은 타이밍에도 텔레포트가 실행되는 문제 수정
- 텔레포트를 사용할 수 있는 빈도 조정
- 텔레포트 처리 로직 정리

Work in JIRA ISSUE DEG-100 DEG-109
#Close
2025-04-30 10:38:32 +09:00

198 lines
5.7 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public enum EnemyState { None, Idle, Trace, Dead, Flee}
public enum MonsterType { Melee, Caster, Ranged }
[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(Animator))]
public abstract class EnemyController : CharacterBase
{
[Header("AI")]
[SerializeField] private float detectCircleRadius = 10f; // 플레이어 탐지 범위
[SerializeField] private LayerMask targetLayerMask; // 플레이어 레이어 마스크
[SerializeField] private MonsterType monsterType;
public MonsterType MonsterType => monsterType;
public Transform TraceTargetTransform { get; private set; }
public NavMeshAgent Agent { get; private set; }
private Animator EnemyAnimator { get; 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");
// -----
// 상태 변수
// Commons
private EnemyStateIdle _enemyStateIdle;
private EnemyStateDead _enemyStateDead;
// Melee
private EnemyStateTrace _enemyStateTrace;
// Caster
private EnemyStateFlee _enemyStateFlee;
private Dictionary<EnemyState, IEnemyState> _enemyStates;
protected virtual void Awake()
{
EnemyAnimator = GetComponent<Animator>();
Agent = GetComponent<NavMeshAgent>();
}
protected override void Start()
{
base.Start();
// 상태 객체 생성
// Commons
_enemyStateIdle = new EnemyStateIdle();
_enemyStateDead = new EnemyStateDead();
switch (MonsterType)
{
case MonsterType.Melee:
_enemyStateTrace = new EnemyStateTrace();
_enemyStates = new Dictionary<EnemyState, IEnemyState>
{
{ EnemyState.Idle, _enemyStateIdle },
{ EnemyState.Trace, _enemyStateTrace },
{ EnemyState.Dead, _enemyStateDead },
};
break;
case MonsterType.Caster:
_enemyStateFlee = new EnemyStateFlee();
_enemyStates = new Dictionary<EnemyState, IEnemyState>
{
{ EnemyState.Idle, _enemyStateIdle },
{ EnemyState.Flee, _enemyStateFlee },
{ EnemyState.Dead, _enemyStateDead },
};
break;
case MonsterType.Ranged:
break;
}
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);
}
#region
// 전략 패턴과 템플릿 메서드 패턴을 활용
public virtual void BattleSequence()
{
// 이 메서드는 자식 요소에서 오버라이드하여 구현합니다.
Debug.LogWarning("BattleSequence가 구현되지 않음 : BattleSequence()를 오버라이드하여 구현하십시오.");
}
// 도망치며 싸우는 몬스터가 도망칠 곳이 없을때 취할 행동
public virtual void OnCannotFleeBehaviour()
{
Debug.LogWarning("OnCannotFleeBehaviour가 구현되지 않음 : OnCannotFleeBehaviour() 오버라이드하여 구현하십시오.");
}
public virtual void OnCannotFleeBehaviour(Action action)
{
Debug.LogWarning("OnCannotFleeBehaviour가 구현되지 않음 : OnCannotFleeBehaviour() 오버라이드하여 구현하십시오.");
}
#endregion
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;
}
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, detectCircleRadius);
}
#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
}