FSM -> State Pattern -> BT based on Code -> BT based on Unity6 UI
보다 좋다 라기보다는, 케이스에 맞춰 잘 쓰는 것이 중요하다.
switch (state)
{
case State.Idle:
// behavior
break;
case State.Move:
// behavior
break;
...
}
상태(State) 3개일 때는? 상태(State) 10개 이상일 때는? -> 상태(State) 수가 늘수록 코드 복잡도가 폭증
전이(transition) 연결"상태(State)가 많아질수록 간선의 수와 복잡도가 폭증.상태(State) 간 조건 중복이 많아짐. -> 유지보수 최악A: 결국 Machine 의 코드가 복잡해짐.
Root Node -> Child Node 를 순서대로 조건부 판단 (매 프레임마다)
Node 들로 이루어져 있다.
public enum NodeState { Success, Failure, Running }
public abstract class Node
{
public abstract NodeState Evaluate();
}
Evaluate() 를 통해 State 반환
집중 할 것
- Selector (OR)
- Sequence (AND)
- Leaf (Condition/Action)
시퀀스(Sequence); AND 과 비슷한 동작셀렉터(Selector); OREnemy가 Player 추적 및 공격 구현
[Root]
|
[Selector] - OR
/ \
[Attack] [Chase] - Sequence(And 구조)
|
[IsNearPlayer] - Leaf(Condition)
|
[MoveToPlayer] - Leaf(Action)
Selector (OR 구조)
AttackAction 공격이 가능한가? 단점
장점
| 정의 | 한계점 | |
|---|---|---|
| FSM | 상태를 정의하고 분기 | 코드가 복잡해짐 |
| State Pattern | 상태 책임 분리 | 전이 폭증 현상 |
| Behaviour Tree | 상태 조건 판별 트리 | 구조 파악 어려움. 협업 활용 못함 |
| BT Graph | BT 시각화(Low-Code) | 영어를 잘해야... |
- Enemy
- 일정거리 안에 있으면 Player 추적 / 공격 범위 안에 있으면 공격
- 위 케이스가 아닌 경우 Patrol
// Tree 생성 부분
_rootNode = new Selector(new List<Node>
{
new Attack(attackRange, player, transform),
new Sequence(new List<Node>
{
new IsNearTarget(chaseRange, player, transform),
new MoveToTarget(speed, player, transform),
}),
new Patrol(transform, PatrolLeftPosition, PatrolRightPosition, speed),
});

... 미묘하다.
확실히 눈에 보이는 것이 있어 좋긴 하다만,
먼저 Running State 에 대한 것이 문제다.
우선적으로 판별해야 하는 Behaivour 가 있기 때문에 Running State에서 강제로 탈출해야하는데, 이러한 것이 Code 에 들어가면 결국 해당 Behaviour 의 책임이 너무 커지게 된다.
전체 Tree 를 Scan 해야 하는 방식보다는 최적화 측면에서 좋겠지만,
이 부분에 대해서는 여러 궁리를 하면서 사용하거나,
Running State 를 사용하지 않는 것으로 일단 대체하거나 Decorator 를 활용하는 방안이 좋을 것 같다.
(근데 Python 처럼 가볍게 Decorator 를 입히는 것이 어렵다면 이것도 좀 어려운 영역이 될 것 같다)
Running State는 자체로 문제기 보다는 Condition 사용법을 숙지하지 못해서 나온 문제였다.
https://docs.unity3d.com/Packages/com.unity.behavior@1.0/manual/observer-abort.html
사용한 Condition 안에 Abort Target 은 곧 Condition Node 내 조건에 대한 Watcher를 설정하는 것이다.
None: 한번만 조건을 타고 그 이후로는 암것도 안함
Self: 내가 실행 중 내 조건이 맞지 않으면 나 스스로를 중단.
Lower Priority: 내가 실행 중이 아닐때 내 조건이 맞으면 나보다 우선순위가 낮은(오른쪽노드)를 강제로 종료시키고 내가 실행.
Both: self & lower priority 모두
즉, 이번 실습처럼
위에서 아래의 우선순위가 있는 경우
로 하면 State.Running이라도 정상적으로 강제적으로 끊고 가져오는 것이 확인되었다.
