Unity) Behavior Tree

강성현
∙∙조회 5

Tree 의 기본 구조

  1. 순환하지 않을 것
  2. 부모는 무조건 하나만 존재할 것

 

 


상태패턴과 Behavior Tree의 차이

상태패턴

장점 :

  1. 로직마다 해야할 일들이 명확하고 전이 조건이 확실하기 때문에 로직 컨트롤이 단순해진다
  2. 메인 컨트롤러 단에서도 현재 상태를 알 필요 없이 현재 상태를 호출해주기만 하면 된다

단점 :

  1. 규모가 커져 상태가 많아지고 상태간 전이가 많아지게 되면 복잡하여 알아보기 힘들다
  2. 유연성이 떨어져 재사용이 힘들

 

Behavior Tree

이미 도식화가 완료된 상황에서 쓰기 좋음.

장점 :

  1. 어떤 상황들이 어떤 조건으로 걸려있는가 구상하기 좋은 구조이다
  2. 상태머신에 비해 트리 순회방향을 따라가기만 하면 되어 구조를 이해하기 쉽다
  3. 모듈화 및 재사용성 면에서 다른 AI에 노드단위로 붙이기 쉽다

단점:

  1. 트리를 효율적으로 구성하지 못한다면, 순회비용이 높아질 수 있다
  2. 노드의 탐색 순서가 행동에 영향을 미치기 때문에 주의하여야 한다

 

 


Behavior Tree에 관하여

1. 트리의 구성

트리(tree) 의 구조

  1. 루트(root) : 맨 위 노드 하나, 부모가 없습니다.
  2. 부모와 자식 : 부모하나는 자식을 여럿 가질 수 있고, 자식의 부모는 하나입니다.
  3. 형제 : 같은 부모 아래의 자식들입니다. 왼쪽부터 순서가 있습니다.
  4. 잎(leaf) : 자식이 없는 노드입니다.

 

2. 노드의 결과

모든 노드는 실행되면 결과를 부모에게 돌려줍니다. 결과는 셋 중 하나입니다.

결과

뜻

성공 (Success)

맡은 일을 해냈습니다

실패 (Failure)

해내지 못했습니다. 조건이 맞지 않았습니다

실행 중 (Running)

아직 끝나지 않았습니다. 다음 프레임에도 이어서 실행됩니다

==실패는 오류가 아닌 검사결과입니다.==

 

결과가 돌아오는 구조

부모는 자식을 하나 실행하고, 자식이 돌려준 결과를 보고 다음 자식을 실행할지 거기서 멈출지 정합니다.

 

  1. Sequence

Sequence는 자식을 왼쪽부터 차례로 실행합니다. 자식이 성공하면 다음 자식으로 넘어가고, 하나라도 실패하면 거기서 멈추고 실패를 돌려줍니다. 모두 성공해야 성공입니다.

자식이 돌려준 결과

Sequence가 하는 일

Sequence의 결과

성공

다음 자식을 실행합니다

마지막 자식까지 성공하면 성공

실패

남은 자식을 실행하지 않고 멈춥니다

실패

실행 중

그 자식이 끝나기를 기다립니다

실행 중

  • Ex) "공격 거리 안인가 → 바라본다 → 공격한다"를 Sequence로 묶으면, 첫 검사가 실패할 때 바라보기와 공격은 실행되지 않습니다. Sequence는 "이것들을 순서대로 모두 한다"입니다.

 

  1. Try In Order

Try In Order도 자식을 왼쪽부터 차례로 실행하지만 멈추는 조건이 반대입니다. 자식이 실패하면 다음 자식을 시도하고, 하나라도 성공하면 거기서 멈추고 성공을 돌려줍니다. 모두 실패해야 실패입니다.

자식이 돌려준 결과

Try In Order가 하는 일

Try In Order의 결과

성공

남은 자식을 실행하지 않고 멈춥니다

성공

실패

다음 자식을 시도합니다

마지막 자식까지 실패하면 실패

실행 중

그 자식이 끝나기를 기다립니다

실행 중

 

3. Behavior 패키지와 Behavior Graph

  • 설치 방법
  1. Window > Package Manager를 엽니다.
  2. Unity Registry 에 들어가 behavior 를 검색합니다.
  3. Import 하여 패키지를 추가합니다.
  4. Project 창의 빈 곳을 우클릭하고 Create > Behavior > Behavior Graph를 고릅니다.

 

3-1) Behavior Graph UI

Blackboard : 그래프가 쓰는 값을 담는 곳입니다.

Inspector : 그래프 영역에서 고른 노드의 설정을 보여주는 곳으로, Unity 에디터의 Inspector와 다릅니다.

  • ==On Start== 는 그래프의 입구, 시작지점입니다.
    Repeat 옵션을 키면 아래 노드가 모두 끝날때마다 그래프가 처음부터 다시 실행됩니다.

 

3-2) 노드 종류

종류

자식

하는 일

예

Action

없음

일을 하고 결과를 돌려줍니다

Log Message, Wait (Seconds), Look At, Rotate, 6장의 커스텀 노드

Sequencing

하나 이상

가지의 뿌리가 되어 자식을 실행하는 순서와 결과 규칙을 정합니다

Sequence, Try In Order

Modifier

하나

자식 하나의 실행이나 결과를 바꿉니다

Repeat, Inverter

3-3) 조건과 Conditional Guard

Unity Behavior 에는 조건 노드 가 따로 없고,

==Conditional Guard== 에 붙인 조건이 참이면 성공, 거짓이면 실패를 돌려주는 Action 노드입니다.

  • ==Conditional Guard==를 Sequence의 첫 자식으로 두면 조건이 거짓일 때 그 가지 전체가 멈춥니다.
  • 대표적으로 사용하는 조건으로 거리를 비교하는 ==Check Distance== 와 값을 비교하는 ==Variable Comparison==이 있습니다.

 

3-4) 커스텀 Action 노드

기본 노드로 할 수 없는 일은 Action 노드를 직접 만들어 사용합니다.

  1. 그래프 영역의 빈 곳을 우클릭하고 Create new > Action을 고릅니다.
  2. 문장의 단어마다 자료형을 고르거나 노드칸(Regular Text)으로 쓸 수 있습니다.

  • 해당 Action을 생성하게 되면 Script이 생성되는데 내부 코드를 수정하여 쓸 수 있습니다.
  • ==public== 필드, ==partial, using 별칭==, ==[SerializeReference]==는 생성 도구가 요구하는 형태입니다.
    고치지 않고, 메서드 본문만 채웁니다.
서드불리는 때돌려주는 것
OnStart노드가 실행되기 시작할 때마다 한 번Status
OnUpdate노드가 실행 중인 동안 매번Status
OnEnd노드가 멈출 때마다 한 번없음
unity
using System;
using Unity.Behavior;
using UnityEngine;
using Action = Unity.Behavior.Action;
using Unity.Properties;

[Serializable, GeneratePropertyBag]
[NodeDescription(name: "Chase", story: "Chase [Target] at [Speed]", category: "Enemy", id: "695d4612479da6594bf0c94858769755")]
public partial class ChaseAction : Action
{
    [SerializeReference] public BlackboardVariable<GameObject> Target;
    [SerializeReference] public BlackboardVariable<float> Speed;

    protected override Status OnStart()
    {
        return Status.Running;
    }

    protected override Status OnUpdate()
    {
        Transform self = GameObject.transform;
        Transform target = this.Target.Value.transform;
        Vector3 toTarget = target.position - self.position;
        
        self.LookAt(target);
        self.position += toTarget.normalized * Speed.Value * Time.deltaTime;
        
        return Status.Success;
    }

    protected override void OnEnd()
    {
    }
}

 

기본적인 추적, 공격, 죽음에 대한 로직을 Behavior Tree로 구성해보았을때는 이런 방식으로도 가능합니다.