상태
Running : 노드 검사 진행중
Success : 조건을 만족하여 노드 실행 성공
Failure : 조건을 만족하지 못하여 노드 실행 실패
노드
Composite Node
: 합성 노드. 하나 이상의 자식을 가지는 부모 노드. 자식의 상태 결과에 따라 실행 성공과 실패 여부가 결정된다. 자식 노드는 순차적으로 실행되며 한 노드의 검사가 끝나기 전까지 다른 노드는 실행되지 않는다.
- Sequence node
: 말그대로 자식 노드의 연속적인 실행이 목적인 노드. 자식 모두 성공 시 성공 처리.
- Selector node
: 자식 중 선택된 하나의 노드 성공이 목적인 노드. 자식 중 하나가 성공 시 다음 노드로 이어가지 않고 바로 성공 처리
Decorator Node
: 자식 노드의 반환 값을 바꾸거나 동작을 추가하는 노드.
- Inverter node
: 자식 노드의 반환 값을 반대로 바꾸는 노드. 성공 -> 실패, 실패 -> 성공을 반환.
- Succeeder node
: 자식 노드 값 상관 없이 무조건 성공 반환
- Repeater (Loop)
: 미리 설정해둔 임의의 수만큼 자식 노드 반복
- Repeat until fail
: 자식 노드가 실패할 때까지 반복.
Action node (Leaf node)
: 실제 행동 노드. 자식이 없는 노드.
Root node (Top node)
: 부모 노드
시퀀스 노드와 선택자 노드의 흐름은 초반에는 꽤나 헷갈리기 때문에 예시를 보면서 흐름을 알아가보자.

예시 1의 경우,
Try to TakeCover는 Selector node 이기 때문에
Is Covered Node가 실패했어도 그 다음 노드인 Find Cover로 넘어갔고,
Find Cover 또한 Selector node 이기 때문에
자식 노드 하나가 성공하여 그 다음 노드를 검사하지 않고 바로 성공 처리.
Cover 노드 하나가 성공하였으므로 나머지 노드를 검사하지 않고 Top Node 성공 처리.

예시 2의 경우,
Cover 는 Sequence node 이기 때문에
자식 하나가 성공했어도 그 다음 노드가 실패하여 실패 처리.
Go to Cover Node 또한 Sequence node 이기 때문에
자식 하나가 실패할 경우 다음 노드를 검사하지 않고 바로 실패 처리.
Chase 또한 Seuqence node 이기 때문에
자식 모두 성공하여 성공 처리
Cover 가 실패하여 Shoot 을 검사하였고, Shoot이 실패하여 Chase를 검사한 결과,
Chase가 성공하여 Top Node도 성공 처리.
표로 정리하자면 이렇다.
자식이 2개 이상일 시, 자식의 상태 | Seletor node | Sequence node |
자식 하나 성공 | 바로 성공 | 다음 자식 검사 |
자식 하나 실패 | 다음 자식 검사 | 바로 실패 |
모두 성공 | (불가능한 상황) | 성공 |
모두 실패 | 실패 | 실패 |
코드 작성
Node 스크립트
[System.Serializable]
public abstract class Node // MonoBehaviour를 상속 안함, 추상클래스
{
protected NodeState _nodeState;
public NodeState nodeState { get { return _nodeState; } }
public abstract NodeState Evaluate(); // 추상메서드
}
public enum NodeState // 상태 enum 선언
{
RUNNING, SUCCESS, FAILURE,
}
Sequence 스크립트
public class Sequence : Node
{
protected List<Node> nodes = new List<Node>();
public Sequence(List<Node> nodes)
{
this.nodes = nodes;
}
public override NodeState Evaluate() // Node의 평가 추상 함수 오버라이드
{
bool isAnyNodeRunning = false; // 자식 노드의 진행 중 여부 변수
foreach (var node in nodes)
{
switch (node.Evaluate()) // 평가는 노드 상태 열거 유형 값 반환 후 다음 진행
{
case NodeState.RUNNING:
isAnyNodeRunning = true; // 진행 중일 경우 변수 true
break;
case NodeState.SUCCESS: // 시퀀스 노드는 성공할 경우 다음으로
break;
case NodeState.FAILURE:
_nodeState = NodeState.FAILURE; // 시퀀스 노드는 자식이 실패할 경우 실패 처리
return _nodeState; // switch 아래 코드에 도달 못함
default:
break;
}
}
// 진행 중인 노드가 없으면 모두 성공했다는 뜻이므로 성공 처리
_nodeState = isAnyNodeRunning ? NodeState.RUNNING : NodeState.SUCCESS;
return _nodeState;
}
}
Selector 스크립트
public class Selector : Node
{
protected List<Node> nodes = new List<Node>();
public Selector(List<Node> nodes)
{
this.nodes = nodes;
}
public override NodeState Evaluate() // Node의 평가 추상 함수 오버라이드
{
foreach (var node in nodes)
{
switch (node.Evaluate()) // 평가는 노드 상태 열거 유형 값 반환 후 다음 진행
{
case NodeState.RUNNING:
_nodeState = NodeState.RUNNING;
return _nodeState; // 노드 진행 중이면 진행 중 반환
case NodeState.SUCCESS:
_nodeState = NodeState.SUCCESS;
return _nodeState; // 자식 하나가 성공했으면 성공 처리 반환
break;
case NodeState.FAILURE:
break; // 자식 하나 실패했으면 다음으로
default:
break;
}
}
_nodeState = NodeState.FAILURE; // 모두 실패했을 경우에만 이 코드에 도달하므로 실패 처리
return _nodeState;
}
}
Inverter 스크립트
public class Inverter : Node
{
protected Node node; // 수정할 노드 하나로만 이루어짐
public Selector(Node node)
{
this.node = node;
}
public override NodeState Evaluate() // Node의 평가 추상 함수 오버라이드
{
switch (node.Evaluate()) // 평가는 노드 상태 열거 유형 값 반환 후 다음 진행
{
case NodeState.RUNNING:
_nodeState = NodeState.RUNNING;
break;
case NodeState.SUCCESS:
_nodeState = NodeState.FAILURE; // 성공이면 실패 반환
break;
case NodeState.FAILURE:
_nodeState = NodeState.SUCCESS; // 실패면 성공 반환
break;
default:
break;
}
return _nodeState;
}
}
이런 식으로 코드를 작성하여 BT 노드를 만들 수 있다.
다음에는 편집기를 이용하여 BT를 만드는 방법을 알아보겠다.
'유니티' 카테고리의 다른 글
터렛 업그레이드 시스템 구현 (1) | 2024.08.30 |
---|---|
[멋쟁이사자처럼 유니티 TIL] UI 슬라이더, 체력바 구현 (+ 델리게이트(delegate), 이벤트) (1) | 2024.06.11 |
[유니티 이론] 중요한 기능!! Deltatime 정의 및 사용법 (0) | 2024.01.23 |
[유니티 기초] 버튼 입력 방식으로 물체 이동하기 - GetButton, Transform (1) | 2024.01.23 |
[유니티 기초] Input 으로 키보드, 마우스 입력 기능 넣기 (1) | 2024.01.21 |