[20260224] Behavior Tree

SmartBear·2026년 2월 24일

Behavior Tree

FSM -> State Pattern -> BT based on Code -> BT based on Unity6 UI
보다 좋다 라기보다는, 케이스에 맞춰 잘 쓰는 것이 중요하다.

1. FSM 복습

기본 구조

switch (state) 
{
    case State.Idle:
        // behavior
        break;
    case State.Move:
        // behavior
        break;
    ...
}

(ex)

  • 상태(State) 3개일 때는?
  • 상태(State) 10개 이상일 때는?

-> 상태(State) 수가 늘수록 코드 복잡도가 폭증

2. State Pattern 복습

기본 구조

  • IState.cs (interface)
    • IdleState
    • MoveState
    • JumpState
    • ...
  • StateMachine.cs

상태(State) 구성

  • Idle, Move, Jump, Attack, Dash, Air관련, Crouch, Skill, Die, ...
  • "전이(transition) 연결"
    • 상태(State)가 많아질수록 간선의 수와 복잡도가 폭증.
      • 비슷한 상태(State)조건 중복이 많아짐. -> 유지보수 최악

Q. 전이 연결 코드를 StateMachine 으로 옮긴다면?

A: 결국 Machine 의 코드가 복잡해짐.

3. Behavior Tree

Root Node -> Child Node 를 순서대로 조건부 판단 (매 프레임마다)

3-1. 구조

Node 들로 이루어져 있다.

public enum NodeState { Success, Failure, Running }

public abstract class Node
{
    public abstract NodeState Evaluate();
}

Evaluate() 를 통해 State 반환

3-2. Nodes

집중 할 것

  • Selector (OR)
  • Sequence (AND)
  • Leaf (Condition/Action)

Node 종류

1. 복합 노드(Composite Node)

  • 자식 노드들의 실행 흐름을 제어한다
    • 시퀀스(Sequence); AND 과 비슷한 동작
      • 하나라도 실패하면 즉시 Failure 반환
    • 셀렉터(Selector); OR
      • 하나라도 성공하면 즉시 Success 반환
    • 병렬(Parallel); 자식 노드를 모두 실행한 뒤 결과를 반환한다. 반환 조건은 설정하기에 따라 다르다

2. Leaf Node

  • 트리의 가장 하위 노드로 자식을 가지지 않는다
  • 실제 행동을 수행(Action)하거나 조건(Condition)을 판단

3. 실습(BT 구조)

Enemy가 Player 추적 및 공격 구현

        [Root]
          |
      [Selector] - OR
      /        \
[Attack]       [Chase] - Sequence(And 구조)
                  | 
            [IsNearPlayer] - Leaf(Condition)
                  |
            [MoveToPlayer] - Leaf(Action)

Selector (OR 구조)

  • AttackAction 공격이 가능한가?
    • 가능하다면 공격 (Success / Failure)
    • Failure: Chase(추적) ? (Success / Failure)
      • Success: Target 추적
      • Failure: IdleState

BT 구조 짤떄 Tip

  • 통작할 흐름 모식도로 그려보기
  • 노드들 이해하기 (Composite, Leaf Node)
  • 코드 짜기

3-4. 장/단점

  • 단점

    • 코드가 복잡해짐
    • Leaf 가 많아질 수록 전체적인 구조 파악이 어려움
    • 디자이너(기획자) 수정 불편 (사실상 불가)
  • 장점

    • ChangeState 가 없음 => 전이가 느슨함

정리

정의한계점
FSM상태를 정의하고 분기코드가 복잡해짐
State Pattern상태 책임 분리전이 폭증 현상
Behaviour Tree상태 조건 판별 트리구조 파악 어려움. 협업 활용 못함
BT GraphBT 시각화(Low-Code)영어를 잘해야...

실습

  • Enemy
    • 일정거리 안에 있으면 Player 추적 / 공격 범위 안에 있으면 공격
    • 위 케이스가 아닌 경우 Patrol

개발

  • 코드로 만든 Behavior Tree
// 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),
});
  • Behavior Tree Graph 까지 적용

Graph 사용 후기

... 미묘하다.
확실히 눈에 보이는 것이 있어 좋긴 하다만,
먼저 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 모두

즉, 이번 실습처럼

  • attackRange 내 -> 공격 가능
  • chaseRange 내 -> 추적
  • chaseRange 외 -> 패트롤

위에서 아래의 우선순위가 있는 경우

  • Abort Target; lower priority
  • Abort Target; both
  • Abort Target; self

로 하면 State.Running이라도 정상적으로 강제적으로 끊고 가져오는 것이 확인되었다.

결과

profile
Python Dev with Infra -> Game Programmer

0개의 댓글