using UnityEngine;
using DG.Tweening;
using System;
using System.Collections;
public enum EnemyState
{
Idle,
Chase,
Attack
}
public class EnemyController2 : MonoBehaviour
{
public float moveSpeed = 3f;
public float DetectionRange = 10f;
public EnemyState _currentState = EnemyState.Idle;
public Tile lastCheckedTile = null;
IdleBaseBehaviour idleBehaviour;
ChaseBaseBehaviour chaseBehaviour;
AttackBaseBehaviour attackBehaviour;
IMovementStrategy moveStrategy;
EnemyAnimatorController animationController;
PlayerStats _playerStats;
EnemyStats _enemyStats;
Tween _currentTween;
void Awake()
{
GameManager.Instance.RegisterEnemy(GetComponent<EnemyStats>());
_enemyStats = gameObject.GetComponent<EnemyStats>();
idleBehaviour = GetComponent<IdleBaseBehaviour>();
chaseBehaviour = GetComponent<ChaseBaseBehaviour>();
attackBehaviour = GetComponent<AttackBaseBehaviour>();
moveStrategy = GetComponent<IMovementStrategy>();
animationController = GetComponent<EnemyAnimatorController>();
}
void Start()
{
_playerStats = GameManager.Instance.PlayerTransform.GetComponent<PlayerStats>();
}
public IEnumerator TakeTurn()
{
if (_currentTween != null && _currentTween.IsActive()) _currentTween.Kill();
switch (_currentState)
{
case EnemyState.Idle:
HandleIdle();
break;
case EnemyState.Chase:
HandleChase();
break;
case EnemyState.Attack:
HandleAttack();
break;
}
yield break;
}
public void ChangeState(EnemyState newState)
{
_currentState = newState;
TakeTurn(); // 상태 바뀌자마자 바로 행동 즉 실질 행동시작 전에 상태체크를우선해야함
}
private void HandleIdle()
{
idleBehaviour?.Excute();
}
private void HandleChase()
{
chaseBehaviour?.Excute();
}
private void HandleAttack()
{
attackBehaviour?.Excute();
}
public void MoveTo(Vector2Int targetPosition, Action onComplete = null)
{
Vector3 position = new Vector3(targetPosition.x, targetPosition.y, 0);
if(moveStrategy != null)
moveStrategy.Move(this.transform , position, animationController, onComplete);
else
BasicMove(position, onComplete);
}
void BasicMove(Vector3 targetPosition, Action onComplete = null)
{
animationController?.TriggerMove();
_currentTween?.Kill(); // 기존 움직임 취소
_currentTween = transform.DOMove(targetPosition, 1f / moveSpeed)
.SetEase(Ease.Linear)
.OnComplete(() =>
{
animationController?.TriggerIdle();
onComplete?.Invoke();
});
}
public void Attack( Action onComplete = null)
{
animationController?.TriggerAttack();
onComplete?.Invoke();
}
}
enemy controller에 상태머신과 전략패턴을 적용하여 유동성 있는 코드로 작성하였다.
public IEnumerator TakeTurn()
{
if (_currentTween != null && _currentTween.IsActive()) _currentTween.Kill();
switch (_currentState)
{
case EnemyState.Idle:
HandleIdle();
break;
case EnemyState.Chase:
HandleChase();
break;
case EnemyState.Attack:
HandleAttack();
break;
}
yield break;
}
public void ChangeState(EnemyState newState)
{
_currentState = newState;
TakeTurn(); // 상태 바뀌자마자 바로 행동 즉 실질 행동시작 전에 상태체크를우선해야함
}
상태머신을 이용하여 상태가 변하면 자동으로 변한 상태의 메서드를 실행한다.
현재 idle, chase, attack에는 전략패턴을 적용하여 컴퍼넌츠를 바꾸면 행동패턴을 바꾸도록 구성하였다.
public class ChaseBaseBehaviour : BaseBehaviour
{
public override void Excute()
{
if (StateCheck())
Action();
}
public override bool StateCheck()
{
foreach (Tile tile in attackRangeTile)//공격 사거리 안에 있으며 시야에 있는지 체크
{
if (tile.characterStats is PlayerStats player)
{
if (TileUtility.IsTileVisible(level, CurTile, tile))
{
controller.ChangeState(EnemyState.Attack);
return false;
}
break;
}
}
foreach (Tile tile in vision)//플레이어가 시야에 있다면 lastchecked에 넣기
{
if (tile.characterStats is PlayerStats player)
{
controller.lastCheckedTile = tile;
break;
}
}
if (controller.lastCheckedTile == null)//플레이어가 시야에 없다면 마지막으로 보인곳으로 감 마지막으로 보인곳에 도달하면 다시 idle
{
controller.ChangeState(EnemyState.Idle);
return false;
}
return true;
}
public override void Action()
{
}
protected void MoveTo(Tile target)
{
CurTile.characterStats = null;
controller.MoveTo(target.gridPosition);
CurTile = target;
CurTile.characterStats = enemyStats;
if (target == controller.lastCheckedTile)
controller.lastCheckedTile = null;
}
}
해당 클래스에서 StateCheck 후 Action을 실행한다.
이를통해 각 모듈이 자신의 책임만 가지게 되어 SRP(단일 책임 원칙)을 만족했고코드 의존성이 줄어들어 테스트와 리팩토링이 쉬워졌다
또한 이동 메서드 역시 전략패턴을 통해 유동성 있는 구조를 구성하였다.
public interface IMovementStrategy
{
void Move(Transform enemy, Vector3 targetPos, EnemyAnimatorController animationController = null, Action onComplete = null);
}
public class BasicMovement : IMovementStrategy
{
public void Move(Transform enemy, Vector3 target, EnemyAnimatorController animationController = null, Action onComplete = null)
{
animationController?.TriggerMove();
enemy.DOMove(target, 0.5f)
.SetEase(Ease.Linear)
.OnComplete(() =>
{
animationController?.TriggerIdle();
onComplete?.Invoke();
});
}
이 역시 move 메서드만 변경해주면 캐릭터의 움직임 제어를 유동적으로 구성할수있게 된다.