https://water-beetle.tistory.com/23
전에 Finite State Machine을 구현한 후 생긴 문제점이 하나 있었습니다.
적 패턴 구현(8)
적이 가만히 있다가 플레이어가 가까이 오면 추적하고, 가끔씩 돌진도 만들게 싶어서 Finite State Machine을 이용해 해당 방법을 구현해 보았습니다. Udemy에 있는 FSM 구현방법과, 도서관에서 빌린 "유
water-beetle.tistory.com
위의 링크에서 구현한 적 패턴 말고, 이제 다시 새로운 적 패턴을 만들려고 하는데,
저는 상태에서 다른 상태로 전이될 때의 조건을, 상태와 같이 결합했기 때문에
새로운 조건을 가진 상태를 만들려면, 똑같은 상태를 두개 만들어야 했습니다.
ex) Idle State(1) : 가만히 있고, 적이 5범위 안에 들어오면 Purse State로 이동
Idle State(2) : 가만히 있고, 적이 10범위 안에 들어오면 Purse State로 이동
-> 전이 조건이 달라, 똑같은 state를 여러번 만들어야함.
그래서 가장 먼저 든 생각은 State안에 있는 Action과 Decision을 분리하자였습니다.
그런데 이렇게 분리해봤자 직접 하드 코딩으로 FSM graph를 작성해야되는 건 똑같았기 때문에,
하루종일 고민하다가 그냥 한번 직접 FSM graph를 만드는 에디터를 만들어보자라는 생각이 문득 들었습니다.
유니티 에디터를 제작하는 방법은 전에 Udemy의 Unity강의를 들으면서 한번 따라 만들어 보았기 때문에,
그 때 얻었던 지식을 바탕으로 Node, Edge를 생성해서 FSM Graph를 만들어주는 에디터를 구현해보기로
마음먹었습니다.
FSM Graph를 위한 자료구조
먼저 FSM 그래프를 만들기 위해서는 일단 해당 정보를 저장하는 자료구조를 먼저 만들어야했습니다.
그래프는 노드, 엣지로 구성되어 있기 때문에 아래 3개의 클래스를 제일 먼저 만들었습니다.
1. FSM 그래프의 정보를 저장하는 클래스
2. FSM 그래프의 노드 정보를 저장하는 클래스 (State)
3. FSM 그래프의 엣지 정보를 저장하는 클래스 (Decision, 다음 상태로 넘어갈 조건)
1. FSM Graph SO (Scriptable Object) - 그래프 정보를 저장하는 클래스
나머지 함수들도 많이 있지만, 에디터를 제작하기 위한 함수들이라 포함하지 않았습니다.
[CreateAssetMenu(fileName = "FSM_", menuName = "ScriptableObjects/FSM")]
public class FSMGraphSO : ScriptableObject
{
[HideInInspector] public List<StateNodeSO> stateNodeList = new List<StateNodeSO>();
[HideInInspector] public Dictionary<string, StateNodeSO> stateNodeDictionary = new Dictionary<string, StateNodeSO>();
[HideInInspector] public StateNodeSO startStateNode;
[HideInInspector] public EnemyAI enemyAI;
먼저 그래프에서 저장할 데이터로는 다음과 같습니다.
1. stateNodeList : 그래프 안의 stateNode들을 저장하는 리스트
2. stateNodeDictionary : stateNode의 고유 번호로 바로 해당되는 stateNode를 접근하기 위해 추가한 딕셔너리
3. startStateNode : 그래프에서 제일 처음 시작되는 stateNode
(그래프가 실행되면 자동으로 IdleState를 만들고 startStateNode에 대입되도록 구현하였습니다.)
4. enemyAI : 해당 그래프의 Node와 Edge들이 enemyAI 클래스를 이용해 적을 움직이기 하기 위해 추가.
2. StateNodeSO (Scriptable Object) - 그래프의 노드 정보를 저장하는 클래스
에디터를 제작하기 위한 함수들을 제외한 부분
public class StateNodeSO : ScriptableObject
{
[HideInInspector] public string id;
[HideInInspector] public List<string> parentStateNodeIDList = new List<string>();
[HideInInspector] public List<string> childStateNodeIDList = new List<string>();
[HideInInspector] public FSMGraphSO fsmGraph;
[HideInInspector] public State stateType = State.IdleState;
[HideInInspector] public List<StateDecisionSO> stateDecisionList = new List<StateDecisionSO>();
public StateNodeSO PlayStateDecision()
{
foreach(var decision in stateDecisionList)
{
if (decision.check())
{
return decision.nextState;
}
}
return null;
}
Node에 저장할 데이터로는 다음과 같습니다.
1. id : 해당 노드의 고유 번호, GUID를 이용해 생성하였고 FSM Graph SO의 dictionary에서 사용
2. parentStateNodeIDList : 에디터에서 노드를 지울 때 연결된 간선들을 지워야 한다.
다른노드에서 자신으로 오는 간선을 지우기 위해 추가한 리스트입니다.
3. childStateNodeIDList : 마찬가지로 해당 노드와 자식노드 간의 간서을 지울 때 사용하기 위한 리스트입니다.
4. fsmGraph : 해당 노드가 어떤 fsm Graph에 속해 있는지 정보
5. stateType : 해당 노드가 어떤 State인지 ( Enum으로 State 구현)
6. stateDecisionList : 노드와 연결된 간선정보 ( 해당 데이터를 이용해 참, 거짓을 판별하고 다음 노드로 이동)
3. StateDecisionSO (Scriptable Object) - 그래프의 간선 정보를 저장하는 클래스
해당 클래스는 현재 노드와 다음 노드에 대한 정보를 가지고 있어서
check()함수로 다음 상태로 넘어가기 위한 조건 true, false를 판별하고
return값이 true이면 다음 노드로 전이하도록 만듭니다.
public class StateDecisionSO : ScriptableObject
{
public StateNodeSO currentState;
public StateNodeSO nextState;
public bool distanceCheck = true;
public bool isDistanceLarger = false;
public int distance = 3;
public void SetState(StateNodeSO from, StateNodeSO to)
{
this.currentState = from;
this.nextState = to;
this.name = "Decision";
}
public bool check()
{
if (distanceCheck)
{
Vector2 agentPos = currentState.fsmGraph.enemyAI.transform.position;
Vector2 targetPos = currentState.fsmGraph.enemyAI.target.transform.position;
if(Vector2.Distance(agentPos, targetPos) < distance)
{
if (isDistanceLarger)
return false;
return true;
}
if (isDistanceLarger)
return true;
return false;
}
return true;
}
}
필요한 조건에 따라 해당 클래스만 수정하면, 쉽게 조건을 바꿀 수 있습니다.
저는 일단 플레이어와의 거리에 따라 몬스터 패턴을 변화시키고 싶어서 distance 부분만 구현하였습니다.
1. currentState, nextState : 해당 간선과 연결된 노드들
2. distance check : 해당값이 true이면 다음 노드 전이조건을 플레이어와의 거리로 설정합니다.
3. isDistanceLarger : true이면 Distance값이 플레이어와의 거리보다 크면 true를 반환합니다 (false이면 반대)
4. distance : 비교할 거리 길이
5. check() : nextState로 넘어갈 수 있는지 판단합니다. 반환값이 True이면 nextState로 넘어가야 합니다.
이렇게 FSM Graph 에디터를 만들기 위한 자료구조를 작성해보았습니다.
나머지 부분은 다시 이어서 작성하겠습니다.
'작심삼일 > 로그라이크 게임 개발 일지' 카테고리의 다른 글
인벤토리 설계 (0) | 2023.10.05 |
---|---|
Finite State Machine 그래프 에디터 만들기(2) (0) | 2023.08.03 |
적 패턴 구현(8) (0) | 2023.08.02 |
A* 알고리즘으로 플레이어 추적 구현(7) (0) | 2023.07.31 |
플레이어 입력 처리 및 이동 구현(6) (0) | 2023.07.30 |