Behaviour Tree (B-Tree)

딜탕·2026년 9월 26일

C# 프로그래밍

목록 보기
11/11
post-thumbnail

첫 팀프로젝트가 시작되었다. 우리팀은 스카이워드 소드를 레퍼런스 삼아서 개발하기로 했는데 여기서 몬스터 AI를 구현하기 위한 디자인패턴으로 FSM / B-Tree를 강사님께서 추천해주셨다.

FSM은 콘솔 게임으로 한번 구현해봤고 이번 프로젝트도 몬스터의 상태가 많아봤자 3~4개라서 FSM으로 구현하는게 충분하다고 생각하지만 학습 목적 에셋 스토어 없이 순수 C# Behaviour Tree로 구현해보려고 한다.

그래서 BT가 뭐냐면,

Behaviour Tree(BT, 행동 트리)는 AI가 이번 순간에 무엇을 할지 고르는 방식이다.
트리는 여러 개의 노드로 이루어진 계층 구조고, 매 프레임(또는 매 틱) 루트부터 아래로 내려가며 평가한다.

핵심은 이 한 줄이다.

지금 가능한 일 중에서, 미리 정해 둔 우선순위대로 하나만 실행한다.

FSM이 “나는 지금 추격 상태다”라고 말한다면,
BT는 “이번 틱에 추격 가지가 성공했다”라고 말한다.

게임 AI에서 BT가 쓰이는 이유는 행동이 조건 + 실행의 묶음으로 잘게 나뉘기 때문이다.

  • 플레이어가 보이는가?
  • 공격 거리인가?
  • 체력이 0인가?
  • 그러면 추격 / 공격 / 사망 / 순찰

이 질문들을 코드의 if-else 탑으로 쌓지 않고, 트리의 가지로 늘어놓는다.

유니티에서 돌릴 때는 보통 이런 루프다.

void Update()
{
    blackboard.SyncFromWorld();
    root.Tick(blackboard);
}

FSM과 BT의 차이점

FSMBehaviour Tree
중심지금 어떤 상태인가이번 틱에 어떤 Node가 성공하는가
흐름특정 상태에서 로직 실행 + 상태 전이 체크Root 노트부터 매번 평가
확장상태 종류가 늘면 복잡해진다노드를 붙이면 우선순위를 바꿀 수 있다
디버그현재 상태가 한눈에 보인다어떤 노드가 Running 인지 찍어 봐야 한다

그림과 코드로 좀 더 살펴보도록 하자.
몬스터 AI에 정찰 / 추격 / 공격 / 사망 네가지 상태가 있을 경우

enum MonsterState { Patrol, Trace, Attack, Die }

void Update()
{
    switch (state)
    {
        case MonsterState.Patrol:
            Patrol();
            if (CanSeePlayer()) state = MonsterState.Trace;
            break;
        case MonsterState.Trace:
            Trace();
            if (!CanSeePlayer()) state = MonsterState.Patrol;
            else if (InAttackRange()) state = MonsterState.Attack;
            break;
        case MonsterState.Attack:
            Attack();
            if (!InAttackRange()) state = MonsterState.Trace;
            break;
        case MonsterState.Die:
            Die();
            break;
    }

    if (hp <= 0) state = MonsterState.Die;
}

그러면 이제 BT구조를 살펴보자

Blackboard bb;
    Node root;

    void Awake()
    {
        bb = new Blackboard { Self = transform, Target = target, Hp = hp };

        root = new Selector(
            new Sequence(
                new Condition(isDead),
                new ActionNode(Die)
            ),
            new Sequence(
                new Condition(canAttack),
                new ActionNode(Attack)
            ),
            new Sequence(
                new Condition(CanTrace),
                new ActionNode(Trace)
            ),
            new ActionNode(Patrol)
        );
    }

    void Update()
    {
        bb.Hp = hp;
        bb.Target = target;
        root.Tick(bb);
    }

BT는 Pre-Order로 왼쪽 자식부터 순회하게 된다. 그래서 Patrol의 경우 맨 마지막 else에 해당하는 상태이기 때문에 Condition노드가 추가되지 않은걸 볼 수 있다.

BT의 구성요소

BT는 구성요소들이 FSM과는 달리 비교적 많기 때문에 하나하나씩 나열하며 개념을 정리해보자.

노드

트리의 한 칸을 말한다. Tick을 받아서 행동하며 NodeStatus의 결과 3개중 하나를 반환한다. 노드는 역할에 따라 다시 3가지로 나눌 수 있다.

  • Composite: 자식을 여러개 가진다
  • Decorator: 자식을 한개 가진다
  • Leaf: 자식이 없다

노드 상태

public enum NodeStatus
{
	Success,
    Failure,
    Running
}

노드들이 반환하게 되는 상태값이다. 여러 Tick이 걸리는 상태는 Running을 반환하게 되어 상태를 유지한다

Root

최상단 노드로써 Update 이벤트함수에서 호출한다. 주로 Selector 노드가 된다.

Composite

자식을 어떤 규칙으로 실행할지 결정하는 노드로 Selector와 Sequence 가 해당된다.

Selector

if / else-if / else 구문의 느낌으로 왼쪽부터 자식을 순회하며 반환되는 NodeStatus를 보고 Success 또는 Running 상태가 반환되면 멈춘다.

Sequence

순서대로 전부를 수행하게 된다. Success면 다음 자식을 수행하게 되며 Failure 또는 Running이면 반환하게 된다.

Decorator

자식 하나를 감싸서 결과를 바꾸거나 실행 여부를 바꾸거나 반복하게 된다.

Decorator하는 일예
InverterSuccess ↔ Failure“타겟이 없다”를 “타겟이 있다”의 반대로
Succeeder실패해도 Success부가 행동을 실패로 전체 가지를 끊고 싶지 않을 때
Failer성공해도 Failure거의 디버그/특수 케이스
Repeater자식을 N번 또는 무한 반복순찰 웨이포인트를 계속 돌 때
Repeat Until Fail실패할 때까지 반복
Cooldown한 번 성공 후 일정 시간 실패공격 후 1초 쉬기
TimeoutRunning이 너무 길면 실패추격이 10초 넘으면 포기

Leaf

가장 끝의 노드로 실제 게임의 실행과 맞닿는 곳이다

  • condition : Acition을 실행하기 전에 판단을 하는 곳으로 Success/Failure를 반환한
  • Action : 조건이 맞는 경우 실행된다. 여러 Tick이 걸린다면 Running 상태를 반환한다

BlackBoard

Root 노드가 실행될 때 매개변수로 함께 받는 것으로 각종 노드의 Condition을 판별할 때 사용하는 정보다.

public class Blackboard
{
	public Transform Self;
    public Transform Target;
    
    public bool IsDead;
    public bool CanTrace;
    public bool InAttackRange;

    public float Hp;
    public float DistanceToPlayer;

    public int PatrolIndex;
    public int AttackRemainTicks;
}

Tick

부모 노드가 자식 노드에게 “너 이번엔 뭐니?”라고 묻는 호출. 유니티 Update와 1:1일 필요는 없다. 0.1초마다 Tick해도 된다. 다만 한 Tick 안에서는 Root → 자식 → 손자 순으로 동기 호출된다.

정리

Selector / Sequence     여러 자식을 규칙으로 실행
Decorator               자식 하나의 결과를 변조
Condition / Action      실제로 판단하거나 움직인다
Blackboard              그 판단의 재료
Tick                    평가 한 번

이 다음 포스팅에는 간단한 C# 콘솔게임에 BT를 적용해 보도록 하겠다.

profile
유니티로 사료값을 벌어야 합니다

0개의 댓글