[Unity] Behaviour Tree

조경민·2026년 2월 24일

FSM

  • 구조
public enum State
{
    Idle,
    Move,
    Jump
}

public class Player : MonoBehaviour
{
    State currentState;

    void Update()
    {
        switch (currentState)
        {
            case State.Idle:
                Idle();
                break;

            case State.Jump:
                Jump();
                break;
        }
    }
}

상태(State) 수가 적을 때(~3개)는 사용하기 적당함, 많으면 복잡
상태(State) 수가 늘수록 코드 복잡도 폭증

State Pattern

👉🏻 State Pattern

  • 구조
    • IState.cs (interface)
      • IdleState
      • MoveState
      • JumpState ...
    • StateMachine.cs
  • 상태 구성
    • Idle
    • Move
    • Jump
    • Attack
    • Air(Jump와 비슷한 상태)
      +상태(State)

-> 전이(Transition) 연결

상태(State)가 많아질수록 비슷한 상태(State)조건 중복이 많이 됨
상태(State)들간 결합도가 커질 수 있음


Behaviour Tree (BT)

루트 노드 -> 자식 노드를 순서대로 조건부 판단

  • State Pattern과의 차이
    • State Pattern은 선택된 현재 상태(State)만 실행 -> 그 상태(State)가 가진 조건부 판별만 함
    • BT는 조건을 매 프레임 단위로 루트 노드에서부터 판단

BT의 구조

Node들로 이뤄져있다.

public enum NodeState { Success, Failure, Running}

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

Evaluate() 실행 결과를 반환


틱(Tick)

루트 노드가 매 프레임(또는 지정 주기)에 발행하는 평가 호출으로, 트리 전체를 한 번 평가

  • 루트 → 리프 방향으로 하향 전파
  • 각 컴포지트 노드는 자식 노드의 반환값(Success / Failure / Running)에 따라 다음 자식에게 틱을 넘길지 상위로 결과를 반환할지 결정

노드(Node)

작업(Action) 또는 조건 판단(Condition) 을 수행하는 단위

  • 틱이 도착하면 자신의 로직을 실행하고, 결과를 부모에게 반환
    • Success : 작업 완료 또는 조건 충족
    • Failure : 작업 불가 또는 조건 불충족
    • Running : 작업 진행 중 → 다음 틱에서 재평가
  • Condition 노드는 Success / Failure만 반환하고, Action 노드가 장기 작업 시 Running을 반환할 수 있다.

Node 종류

Composite Node

  • 자식 노드들의 실행 흐름을 제어한다
    • 시퀀스(Sequence) - AND 구조
      • 순서대로 실행(앞이 Success여야 다음 실행 가능)
      • 하나라도 실패하면 즉시 Failure 반환
      • 모두 성공하면 Success 반환
      • 자식이 Running을 반환하면 Running을 반환하고 다음 틱에서 다시 호출
    • 셀렉터(Selector) - OR 구조
      • 자식 중 하나라도 SuccessRunning을 반환하면 즉시 중단하고 그 상태를 반환 (어느 순서에 나오든 Success나 Running이면 중단)
      • 모두 실패하면 Failure 반환

Leaf Node

  • 트리의 가장 하위 노드로 자식을 가지지 않는다
  • 조건(Condition)을 판단하거나 실제 행동(Action)을 수행
  • 조건(Condition) - 특정 상태나 환경을 판단
    • Success / Failure를 반환
  • 액션(Action) - 구체적인 행동을 수행
    • Success / Failure / Running 을 반환

BT 구조 (실습 예제)

Enemy가 Player 추적 및 공격 구현 (탐지범위에 있으면 추적, 공격범위에 있으면 공격)

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

public enum NodeState:byte { Success, Failure, Running }

public abstract class Node
{
    public abstract NodeState Evaluate();
}
using System.Collections.Generic;

public class Sequence : Node
{
    List<Node> _childrenList;

    public Sequence(List<Node> childrenList)
    {
        _childrenList = childrenList;
    }

    public override NodeState Evaluate()
    {
        foreach (Node node in _childrenList)
        {
            NodeState result = node.Evaluate();

            if (result == NodeState.Failure)
            {
                return NodeState.Failure;
            }
            if (result == NodeState.Running)
            {
                return NodeState.Running;
            }
        }

        return NodeState.Success;
    }
}
using System.Collections.Generic;

public class Selector : Node
{
    List<Node> _childrenList;

    public Selector(List<Node> childrenList)
    {
        _childrenList = childrenList;
    }

    public override NodeState Evaluate()
    {
        foreach (var node in _childrenList)
        {
            NodeState result = node.Evaluate();

            if(result == NodeState.Success)
            {
                return NodeState.Success;
            }
            if(result == NodeState.Running)
            {
                return NodeState.Running;
            }
        }

        return NodeState.Failure; // 기본 상태
    }
}
using UnityEngine;
using System.Collections.Generic;

public class EnemyBT : MonoBehaviour
{
    [SerializeField] Transform _target;
    [SerializeField] float _attackRange = 1.5f;
    [SerializeField] float _detectRange = 5f;
    [SerializeField] float _moveSpeed = 3f;

    Node _rootNode;

    void Start()
    {
        Node attack = new Attack(transform, _target, _attackRange);
        Node isNear = new IsTargetNear(transform, _target, _detectRange);
        Node move = new MoveToTarget(transform, _target, _moveSpeed);

        Sequence chase = new Sequence(new List<Node> { isNear, move });

        _rootNode = new Selector(new List<Node> { attack, chase });
    }

    void Update()
    {
        _rootNode.Evaluate();
    }
}

Selector (OR 구조)

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

BT 구조 만드는 과정

  1. 동작할 흐름 모식도로 그려보기
  2. 노드들 이해하기 (Composite, Lesf Node)
  3. 코드짜기

BT 한계점

  • 코드 복잡해짐
  • 구조 파악 어려움
  • 디자이너(기획자)가 수정 불편(못함)


정리

  • FSM
    • 정의: 상태를 분기
    • 한계점: 코드가 복잡해짐
  • State Pattern
    • 정의: 상태 책임 분리
    • 한계점: 전이 폭증 현상
  • Behaviour Tree
    • 정의: 상태 조건 판별 트리 구조
    • 한계점: 구조 파악 어려움, 협업 활용 못함
  • BT Graph
    • 정의: 코드 기반 BT 시각화
    • 한계점: Good at English
profile
안녕하세요

0개의 댓글