行为树基础实现

行为树(Behavior Tree,BT)是一种控制结构,通过组合不同的行为节点,能够定义复杂的行为逻辑,使自主体能够根据当前的环境状态和目标做出决策并执行相应的动作。常用于游戏中AI逻辑的实现。

一.基本概念

1.节点: 行为树由多个节点构成每个节点代表一个行为或决策步骤。节点根据其在树中的位置和功能可以分为不同类型的节点,如根节点、内部节点(控制流节点)、叶节点(执行节点)等。
2. 状态: 每个节点在执行时都会返回三种状态之一,即成功(Success)、失败(Failure)和运行(Running)。成功和失败状态会通知其父节点其操作结果,而运行状态表示节点仍在执行中。
3. 根节点: 作为行为树的入口,根节点是AI的起点。每个行为树只有一个根节点,且根节点通常只有一个子节点。

二.节点类型

1.任务节点(TaskNode): 任务节点也叫执行节点(ActionNode) 它是叶节点,是行为树中最底层的节点,负责执行具体的行为或任务。它们不会拥有子节点,而是直接返回成功、失败或运行状态。。
2. 复合节点(CompositeNode): 内部节点,也称为组合节点。它们可以有一个或多个子节点,并控制子节点的执行顺序和方式。常见的控制流节点包括选择器(Selector)、序列器(Sequence)等。
3. 装饰节点(DecoratorNode): 一种特殊的控制流节点,它们通常只有一个子节点,并对子节点的执行结果进行修饰或改变。例如,反转装饰器可以反转子节点的成功或失败状态。

三.工作原理

行为树通过从上至下、从左到右的顺序遍历节点来执行AI的行为。当行为树运行到终结状态时,会回到根节点重新开始运行。在遍历过程中,每个节点都会根据其类型和功能执行相应的行为或决策,并返回状态给其父节点。父节点根据子节点的返回状态来决定下一步的执行计划。

四.黑板

黑板是行为树架构中的一个共享数据区域,它充当了不同节点之间通信和数据交换的桥梁。黑板上的数据可以包括目标位置、敌人信息、玩家状态等,这些数据对于行为树做出决策至关重要。

#pragma once
#include<string>
#include<unordered_map>
#include<iostream>
#include<any>

class Blackboard
{
private:
	std::unordered_map<std::string, std::any> data;
public:
	template<typename T>
	void setValue(const std::string& key,const T&value)
	{
		data[key] = value;
	}

	template<typename T>
	T getValue(const std::string& key) const
	{
		return std::any_cast<T>(data.at(key));
	}

	bool hasValue(const std::string& key)
	{
		return  data.find(key) != data.end();
	}
}; 

五.节点示例

  • 节点基类
#pragma once
#include<vector>
#include<memory>
#include<iostream>
#include"Blackboard.h"
//节点状态
enum class NodeStatus
{
	Success,
	Failure,
	Running
};
//行为树节点基类
class BehaviorNode
{
public:
	virtual NodeStatus update(Blackboard& board) = 0;
	virtual ~BehaviorNode() = default;
	virtual void abort(){}//中断执行
};
  • 复合节点
#pragma once
#include "BehaviorNode.h"

class CompositeNode :public BehaviorNode
{
protected:
	std::vector<std::shared_ptr<BehaviorNode>> children;
	size_t currentIndex = 0;
public:
	void addNode(std::shared_ptr<BehaviorNode> nodePtr)
	{
		children.push_back(nodePtr);
	}
};
  • 选择节点
    按顺序执行子节点,直到找到第一个成功的子节点,否则返回失败
#pragma once
#include"CompositeNode.h"
class SelectorNode :public CompositeNode
{
public:	
	NodeStatus update(Blackboard& board) override
	{
		for (const auto& child : children)
		{
			auto childStatus = child->update(board);
			if (childStatus != NodeStatus::Failure) return childStatus;
		}
		return NodeStatus::Failure;
	}
};
  • 顺序节点
    从左到右依次执行子节点,全部成功则返回成功,任意子节点失败则立即终止
#pragma once
#include "CompositeNode.h"
//顺序节点
class SequenceNode : public CompositeNode
{
private:
	size_t currentIndex = 0;
protected:
	NodeStatus update(Blackboard& board) override
	{
		while (currentIndex < children.size())//顺序遍历子节点
		{
			auto childStatus = children[currentIndex]->update(board);//调用子节点的tick函数
			if (childStatus == NodeStatus::Running) return NodeStatus::Running;//正在执行返回执行状态
			if (childStatus == NodeStatus::Failure)//执行失败返回失败状态并重置索引
			{
				currentIndex = 0;
				return NodeStatus::Failure;
			}
			++currentIndex;
		}
		//所有子节点执行完成 重置索引并返回成功状态
		currentIndex = 0;
		return NodeStatus::Success;
	}
};
  • 并行节点
    并行节点允许其下的所有子节点一起执行,并根据设定的成功标准(如一个子节点成功则成功,或所有子节点成功则成功)来判断整个并行节点的执行结果。这种并行执行的方式使得行为树能够同时处理多个任务或行为,提高了系统的并发性和响应速度。
#pragma once
#include"CompositeNode.h"
//并行节点
class ParallelNode :public CompositeNode
{

public:
	//并行策略枚举
	enum class ParallelPolicy
	{
		All,
		ONE,
	};

	ParallelNode(ParallelPolicy _successPolicy, ParallelPolicy _failurePolicy)
		:successPolicy(_successPolicy)
		,failurePolicy(_failurePolicy){};

	NodeStatus update(Blackboard& board) override
	{
		int successCount = 0;//成功数量
		int failureCount = 0;//失败数量
		for (auto& child : children)
		{
			NodeStatus childStatus = child->update(board);
			if (childStatus == NodeStatus::Success) successCount++;
			if (childStatus == NodeStatus::Failure) failureCount++;
		}
		//成功执行策略
		if (successPolicy == ParallelPolicy::All && successCount == children.size() ||
			successPolicy == ParallelPolicy::ONE && successCount > 0)
		{
			return NodeStatus::Success;
		}
		//失败执行策略
		if (failurePolicy == ParallelPolicy::All && failureCount == children.size() ||
			failurePolicy == ParallelPolicy::ONE && failureCount > 0)
		{
			return NodeStatus::Failure;
		}

		return NodeStatus::Running;
	}
private:
	ParallelPolicy successPolicy,failurePolicy;
};
  • 条件节点
    封装逻辑判断函数,根据执行结果返回成功或失败
#pragma once
#include<functional>
#include"BehaviorNode.h"

//条件节点
class ConditionNode :public BehaviorNode
{
public:
	//定义条件函数别名
	using CheckFunc = std::function<bool(Blackboard&)>;
	;//构造函数关闭隐式转换
	explicit ConditionNode(CheckFunc _func) :func(_func) {};

	NodeStatus update(Blackboard& board)override
	{
		return func(board) ? NodeStatus::Success : NodeStatus::Failure;
	}
private:
	CheckFunc func;
};
  • 装饰节点
#pragma once
#include "BehaviorNode.h"
//装饰节点
class DecoratorNode :public BehaviorNode
{
protected:
	std::shared_ptr<BehaviorNode> child;
public:
	DecoratorNode(std::shared_ptr<BehaviorNode> node) :child(node) {};
};
  • 取反器
    将执行结果取反
#pragma once
#include "DecoratorNode.h"
//取反器
class InverterNode :public DecoratorNode
{
public:
	NodeStatus update(Blackboard& board) override
	{
		auto childStatus = child->update(board);
		if (childStatus == NodeStatus::Success) return NodeStatus::Failure;
		if (childStatus == NodeStatus::Failure)return NodeStatus::Success;
		return childStatus;
	}
};
  • 重复器
    重复执行节点n次
#pragma once
#include"DecoratorNode.h"
#include<iostream>
//重复执行节点
class RepeatNode :public DecoratorNode
{
private:
	size_t repeatCount;//0代表无限循环
public:
	RepeatNode(std::shared_ptr<BehaviorNode> node,size_t times = 0)
		:DecoratorNode(node),repeatCount(times){}

	NodeStatus update(Blackboard& board) override
	{
		for (int i = 0; !repeatCount || i < repeatCount; ++i)
		{
			std::cout << "Excute times:" << i + 1 << std::endl;
			NodeStatus childStatus = child->update(board);
			if (childStatus == NodeStatus::Failure) return NodeStatus::Failure;
		}
		return NodeStatus::Success;
	}
};
  • 任务节点
    封装AI具体的执行逻辑
#pragma once
#include<functional>
#include"BehaviorNode.h"
//任务节点
class TaskNode :public BehaviorNode
{

public:
	using TaskFunc = std::function<NodeStatus(Blackboard&)>;

	TaskNode(TaskFunc _task) :task(_task) {};

	NodeStatus update(Blackboard& board) override
	{
		return task(board);
	}
private:
	TaskFunc task;
};

以上就是行为树中一些基本节点,现在我们用以上部分节点构建一棵行为树,来执行一些简单的AI逻辑。

#pragma once
#include<memory>
#include"Blackboard.h"
#include"BehaviorNode.h"
#include"SelectorNode.h"
#include"SequenceNode.h"
#include"ParallelNode.h"
#include"ConditionNode.h"
#include"TaskNode.h"
#include"RepeatNode.h"
class BehaviorTree
{
private:
	Blackboard& board;
public:
	std::shared_ptr<SelectorNode> root;

	BehaviorTree(Blackboard& _board, std::shared_ptr<SelectorNode> _root) :board(_board), root(_root) {};
	
	void initBehavior()
	{
		//攻击行为
		auto attack = std::make_shared<SequenceNode>();
		attack->addNode(std::make_shared<ConditionNode>([](Blackboard& board) {
			float distance = board.getValue<float>("TargetPosition") - board.getValue<float>("CurrentPosition");
			float attackRange = board.getValue<float>("AttackDistance");
			std::cout << attackRange << "," << distance << std::endl;
			return distance <= attackRange;//检查攻击距离
		}));
		//补充子弹行为
		auto checkBullets = std::make_shared<SelectorNode>();
		//检查子弹是否充足
		checkBullets->addNode(std::make_shared<ConditionNode>([](Blackboard& board) {
			return board.getValue<int>("Bullets") > 0;
		}));
		//前往补充子弹
		checkBullets->addNode(std::make_shared<TaskNode>([](Blackboard& board) {
			std::cout << "Go supply bullets.\n";
			board.setValue("CurrentPosition", -3.0f);//假设补给点在-10位置 移动到补给点
			board.setValue("Bullets", 3);
			return NodeStatus::Failure;//返回Failure 重新退到根节点重新执行检测逻辑
		}));
		attack->addNode(checkBullets);
		//检测目标是否存活
		attack->addNode(std::make_shared<ConditionNode>([](Blackboard& board) {
			bool isAlive = board.getValue<int>("Health") > 0;
			if (isAlive)
				std::cout << "Target is alive start attack!\n";
			else
				std::cout << "Target is Dead! continue patrolling.\n";
			return isAlive;
		}));
		//攻击目标
		attack->addNode(std::make_shared<TaskNode>([](Blackboard& board) {
			int bullets = board.getValue<int>("Bullets");
			int hp = board.getValue<int>("Health");
			board.setValue("Bullets", bullets - 1);
			board.setValue("Health", hp - 1);
			std::cout << "Attack Enemy! Current bulltes:"<< bullets-1 <<"Current HP:"<< hp-1 << std::endl;
			return NodeStatus::Success;
		}));

		//创建巡逻分支
		auto patrol = std::make_shared<TaskNode>([](Blackboard& board) {
			float currentPosition = board.getValue<float>("CurrentPosition");
			board.setValue("CurrentPosition", currentPosition + 1.0f);
			std::cout << "Patrolling currentPosition:"<< currentPosition <<std::endl;
			return NodeStatus::Running;
		});

		root->addNode(attack);
		root->addNode(patrol);
	}
	
	void update()
	{
		root->update(board);
	}
};

以上代码按如下逻辑执行:

  • 创建选择节点作为根节点
    • 创建顺序节点作为攻击行为分支
      • 创建条件节点判断攻击距离
      • 创建选择节点作为补充创建子弹行为分支
        • 创建条件节点判断子弹是否充足
        • 创建任务节点前往补充子弹
      • 创建条件节点判断目标是否存活
      • 创建任务节点攻击目标
    • 创建任务节点作为巡逻行为分支(单节点)

执行流程图如下:
在这里插入图片描述
最后在main函数中调用行为树:

#include<thread>
#include<memory>
#include"Blackboard.h"
#include"BehaviorTree.h"

int main()
{
	Blackboard board;
	board.setValue("TargetPosition", 13.f);
	board.setValue("AttackDistance", 10.f);
	board.setValue("CurrentPosition", 0.f);
	board.setValue("Bullets", 2);
	board.setValue("Health", 3);
	//构建行为树
	BehaviorTree bt(board, std::make_shared<SelectorNode>());
	//初始化行为树
	bt.initBehavior();
	//模拟游戏帧循环逻辑一秒钟调用一次
	while (true)
	{
		bt.update();
		std::this_thread::sleep_for(std::chrono::milliseconds(1000));
	}
	return 0;
}

执行结果:
在这里插入图片描述
先巡逻,发现目标后检查目标存活并发起攻击,减少目标血量,当子弹耗尽则前往补充子弹,再次巡逻检测目标距离,进入攻击范围再次攻击,目标死亡,继续巡逻。

<think>好的,我现在需要帮助用户了解如何在Unreal Engine中实现行为树。首先,我得回忆一下行为树的基本概念和UE中的相关组件。行为树是用于控制AI决策的逻辑结构,通常由任务、装饰器、服务和复合节点组成。 根据用户提供的引用,特别是引用[1]和[3],里面提到行为树和蓝图的高级应用。需要结合这些信息来构建回答。首先,步骤应该是创建行为树资源,设置根节点,然后添加各种节点比如选择器、序列器,以及具体的任务节点。 接下来要考虑如何将行为树与AI控制器和Character蓝图关联起来。可能需要使用Blackboard来存储数据,这样行为树可以动态调整。引用中提到蓝图接口和与C++的交互,这部分可能在高级应用中才会涉及,但用户可能需要基础教程,所以暂时先不深入。 然后需要解释行为树的基本节点类型,比如Selector、Sequence、Task等,并给出一个简单的示例,比如移动到某个点。可能需要编写蓝图或任务节点的代码片段,但用户提供的引用中有Python示例,不过UE中通常是蓝图或C++,所以可能需要用伪代码或者描述步骤。 另外,用户的问题可能还涉及调试和优化,引用[1]提到调试行为树的方法,应该包含在步骤中。例如使用行为树调试器工具。 还要注意引用中的高级技巧,比如状态机与行为树结合,但用户的问题可能更关注基础实现,所以放在后续相关问题里。 最后生成相关问题,需要涵盖性能优化、高级功能、调试技巧和应用场景,参考用户提供的示例问题结构。</think>### 在Unreal Engine中实现行为树的步骤 #### 1. **创建行为树资源** - 在内容浏览器中右键选择 **Artificial Intelligence > Behavior Tree**,创建行为树资源。行为树是AI决策的核心逻辑框架,通过节点组合定义AI行为流程[^1][^3]。 #### 2. **设置AI控制器与Blackboard** - **创建Blackboard**:定义AI的共享数据(如目标位置、敌人引用等)。右键选择 **Artificial Intelligence > Blackboard**。 - **绑定AI控制器**:继承自`AIController`的蓝图类需关联Blackboard,并在`BeginPlay`中调用`RunBehaviorTree()`加载行为树[^2]。 #### 3. **构建行为树节点** - **根节点**:行为树必须从根节点(Root)开始。 - **复合节点**: - **Selector(选择器)**:从左到右执行子节点,直到某个子节点返回成功。 - **Sequence(序列)**:按顺序执行子节点,若某个子节点失败则终止。 - **任务节点**:实现具体行为(如移动、攻击),需继承自`BTTaskNode`。 - **装饰器(Decorator)**:条件判断,控制节点是否执行(例如“是否看到玩家”)。 - **服务(Service)**:后台更新数据(如定期更新目标位置)[^1]。 #### 4. **编写任务节点示例(移动至目标)** ```cpp // C++ 任务节点示例(简化) UBTTaskNode* UBTTask_MoveTo::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { AAIController* AIController = OwnerComp.GetAIOwner(); if (AIController) { AIController->MoveToLocation(TargetLocation); return EBTNodeResult::Succeeded; } return EBTNodeResult::Failed; } ``` - 在蓝图中可直接使用内置的`BTTask_MoveTo`节点,通过Blackboard绑定目标位置。 #### 5. **调试与测试** - 在编辑器中使用 **行为树调试器**(Window > Developer Tools > Behavior Tree)实时查看节点执行状态。 - 通过`Print String`节点或Blackboard值监控关键数据。 #### 6. **优化与扩展** - **分层行为树**:将复杂逻辑拆分为子行为树复用。 - **与蓝图/C++交互**:通过事件驱动或自定义任务节点实现高级逻辑[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值