1296.2 액션 노드와 실행 노드의 관계

1. 실행 노드의 개념

행동 트리 문헌에서 “실행 노드(Execution Node)“는 리프 노드의 총칭으로, 트리의 내부 노드(제어 노드, 데코레이터 노드)와 대비되는 개념이다. 실행 노드는 행동 트리가 외부 환경과 상호 작용하는 접점으로, 환경의 상태를 관찰하거나 환경에 대한 행동을 수행한다. 실행 노드는 다시 액션 노드(Action Node)와 조건 노드(Condition Node)로 세분화된다.

Colledanchise와 Ögren(2018)은 이를 다음과 같이 형식화하였다.

\text{Leaf Nodes} = \text{Action Nodes} \cup \text{Condition Nodes}

액션 노드와 조건 노드는 모두 실행 노드의 하위 범주이며, 자식 노드를 갖지 않는 리프 노드라는 공통 특성을 가진다. 그러나 환경에 대한 영향의 유무에서 본질적으로 구분된다.

BehaviorTree.CPP에서의 노드 계층

BehaviorTree.CPP 라이브러리(버전 4.x)의 노드 클래스 계층은 다음과 같이 구성된다.

BT::TreeNode (최상위 추상 클래스)
├── BT::ControlNode (제어 노드)
│   ├── BT::SequenceNode
│   ├── BT::FallbackNode
│   ├── BT::ParallelAllNode
│   ├── BT::ReactiveSequence
│   └── BT::ReactiveFallback
├── BT::DecoratorNode (데코레이터 노드)
│   ├── BT::InverterNode
│   ├── BT::RetryNode
│   └── ...
├── BT::ActionNodeBase (액션 노드 기반)
│   ├── BT::SyncActionNode
│   ├── BT::StatefulActionNode
│   ├── BT::ThreadedAction
│   └── BT::CoroActionNode
└── BT::ConditionNode (조건 노드)

BT::ActionNodeBase는 모든 액션 노드의 기반 클래스이며, BT::TreeNode을 직접 상속한다. BT::ConditionNode 역시 BT::TreeNode을 직접 상속한다. 두 클래스 모두 리프 노드이므로 자식 노드를 관리하는 기능을 포함하지 않는다.

액션 노드와 실행 노드의 관계

액션 노드는 실행 노드의 부분 집합이며, 다음의 관계가 성립한다.

  1. 모든 액션 노드는 실행 노드이다: 액션 노드는 리프 노드로서 트리의 실행 접점 역할을 수행한다.

  2. 모든 실행 노드가 액션 노드인 것은 아니다: 조건 노드도 실행 노드의 한 유형이지만 액션 노드와는 구별된다.

  3. 액션 노드는 환경에 대해 부작용(side effect)을 가질 수 있다: 모터를 구동하거나, 메시지를 전송하거나, 내부 상태를 변경하는 등의 부작용이 허용된다.

  4. 액션 노드는 RUNNING 상태를 반환할 수 있다: 비동기 액션 노드(StatefulActionNode)는 행동이 진행 중임을 나타내는 RUNNING을 반환할 수 있다. 이는 조건 노드에서는 허용되지 않는 특성이다.

TreeNode 인터페이스의 공유

액션 노드와 조건 노드는 BT::TreeNode의 공통 인터페이스를 공유한다.

class TreeNode
{
public:
    virtual BT::NodeStatus executeTick();
    virtual void halt();
    
    BT::NodeStatus status() const;
    const std::string& name() const;
    const std::string& registrationName() const;
    
    template <typename T>
    BT::Result getInput(const std::string& key, T& destination);
    
    template <typename T>
    BT::Result setOutput(const std::string& key, const T& value);
    
    // ...
};

executeTick()은 노드의 tick() 메서드를 호출하고 상태 변화 로깅, 사전/사후 조건 검사 등의 래퍼(wrapper) 로직을 수행한다. getInput()setOutput()은 블랙보드 포트를 통한 데이터 교환 인터페이스이다. 이 인터페이스는 액션 노드와 조건 노드 모두에서 동일하게 사용된다.

실행 노드 선택 기준

행동 트리를 설계할 때, 특정 기능을 액션 노드로 구현할지 조건 노드로 구현할지의 결정은 다음 기준에 따른다.

기준액션 노드조건 노드
환경 변경 여부환경을 변경함환경을 관찰만 함
RUNNING 반환가능 (비동기 작업)불가 (즉시 완료)
실행 시간다수 Tick 소요 가능단일 Tick 내 완료
재평가 비용높을 수 있음낮아야 함
ReactiveSequence에서후반부에 배치전반부에 배치

이 기준에 따라, “목표 위치로 이동“은 환경을 변경하고 다수 Tick이 소요되므로 액션 노드로, “배터리가 부족한가“는 환경을 관찰만 하고 즉시 완료되므로 조건 노드로 구현한다.

실행 노드의 팩토리 등록

BehaviorTree.CPP에서 액션 노드와 조건 노드는 동일한 팩토리 등록 메서드를 통해 등록된다.

BT::BehaviorTreeFactory factory;

// 액션 노드 등록
factory.registerNodeType<NavigateToGoal>("NavigateToGoal");
factory.registerNodeType<GripObject>("GripObject");

// 조건 노드 등록
factory.registerNodeType<IsBatteryLow>("IsBatteryLow");
factory.registerNodeType<IsLocalized>("IsLocalized");

팩토리는 노드의 클래스 계층을 기반으로 액션 노드와 조건 노드를 자동으로 구분하며, XML에서 노드를 참조할 때에도 동일한 태그 형식을 사용한다. 이는 실행 노드가 통일된 인터페이스를 공유하면서도 내부적으로는 명확히 구분되는 설계 구조를 반영한다.