적이 가만히 있다가 플레이어가 가까이 오면 추적하고,
가끔씩 돌진도 만들게 싶어서 Finite State Machine을 이용해 해당 방법을 구현해 보았습니다.
Udemy에 있는 FSM 구현방법과, 도서관에서 빌린 "유니티로 배우는 게임 디자인패턴"에 있는
상태 패턴 코드를 읽어본 다음 저만의 방식으로 그냥 한번 구현해 보았습니다.
구현 방법
제가 구현한 방법은 크게 EnemyAI, EnemyStateContext, EnemyState(abstract) 세가지 클래스로
나누어서 구현하였습니다.
EnemyAI
매 Update마다 EnemyStateContext에 저장된 current State를 실행시킵니다.
-> _enemyStateContext.RunState() 부분
public class EnemyAI : MonoBehaviour
{
[SerializeField]
public Transform target;
private EnemyStateContext _enemyStateContext;
[Header("Events"), Space(10)]
[SerializeField]
private GameEventSO enemyMoveEvent;
private void Awake()
{
target = GameObject.FindGameObjectWithTag("Player").transform;
_enemyStateContext = GetComponentInChildren<EnemyStateContext>();
}
// Update is called once per frame
void Update()
{
_enemyStateContext.RunState();
}
public void Transition(State state)
{
_enemyStateContext.Transition(state);
}
public void Move(AgentMovementParameter movementParameter)
{
enemyMoveEvent.Raise(this, movementParameter);
}
}
EnemyStateContext
State를 관리하는 클래스 입니다.
현재 위치하고 있는 currentState 변수와,
state를 실행하는 함수, 다음 state로 이동하는 transition함수로 구성되어 있습니다.
[RequireComponent(typeof(EnemyIdleState))]
[RequireComponent(typeof(EnemyPursueState))]
public class EnemyStateContext : MonoBehaviour
{
public EnemyState CurrentState { get; set; }
private EnemyState _IdleState, _AttckState, _PursueState, _AvoidState, _RushState, _WanderState;
private void AttachStatetoObject()
{
_IdleState = gameObject.GetComponent<EnemyIdleState>();
_PursueState = gameObject.GetComponent<EnemyPursueState>();
_RushState = gameObject.GetComponent<EnemyRushState>();
}
// Start is called before the first frame update
void Start()
{
AttachStatetoObject();
CurrentState = _IdleState;
}
public void Transition(State state)
{
switch (state)
{
case State.IdleState:
CurrentState = _IdleState;
break;
case State.AttckState:
CurrentState = _AttckState;
break;
case State.PursueState:
CurrentState = _PursueState;
break;
case State.AvoidState:
CurrentState = _AvoidState;
break;
case State.RushState:
CurrentState = _RushState;
break;
case State.WanderState:
CurrentState = _WanderState;
break;
}
}
public void RunState()
{
CurrentState.Handle();
}
}
Enemy State
public abstract class EnemyState : MonoBehaviour
{
protected EnemyAI _enemyAI;
protected virtual void Awake()
{
_enemyAI = GetComponentInParent<EnemyAI>();
}
public void Handle()
{
Action();
Decision();
}
protected abstract void Action();
protected abstract void Decision();
}
추상 클래스로 해당 클래스를 상속받아 concrete state(Idle, Attack, Move 등등)을 만듭니다.
크게 Action과 Decision으로 이루어져 있습니다.
Action은 해당 State가 수행해야 하는 일로, Move는 움직이기, Idle은 가만히 있기를 예로 들 수 있습니다.
Decision은 해당 State가 다음 state로 가야할 조건을 판단하고, 다음 state로 이동하게 됩니다.
// StateIdle에서의 Decision 구현
protected override void Decision()
{
float distance = Vector3.Distance(_enemyAI.target.position, transform.position);
if(distance < Settings.Astar.PursueDistance)
{
_enemyAI.Transition(State.PursueState);
}
}
구현 이미지
몬스터가 플레이어를 추적하다가, 가까워지면 돌진하도록 FSM을 만들구 구현해보았습니다.
용량문제로 15fps라 조금 버벅거려 보이긴 하지만 실제로는 잘 작동해서 만족스러웠습니다.
'작심삼일 > 로그라이크 게임 개발 일지' 카테고리의 다른 글
Finite State Machine 그래프 에디터 만들기(2) (0) | 2023.08.03 |
---|---|
Finite State Machine 그래프 에디터 만들기(1) (0) | 2023.08.03 |
A* 알고리즘으로 플레이어 추적 구현(7) (0) | 2023.07.31 |
플레이어 입력 처리 및 이동 구현(6) (0) | 2023.07.30 |
플레이어 구현 체크리스트(5) (0) | 2023.07.30 |