行为树基础实现

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

行为树(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;
}

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

行为树(Behavior Tree) 行为树中,根节点是AI的起点,通过遍历子节点来决策AI的行为。前两个通知其父节点其操作是成功还是失败。下次树被选择时,该节点将再次被选择,此时它将再次有机会成功,失败或继续运行。行为树(Behavior Trees)是一种在游戏开发中广泛使用的AI设计模式,主要用于描述AI的行为和决策过程,实现更加智能和自然的游戏AI。它由多个节点组成,每个节点代表一个行为或决策,按照特定的方式连接在一起,形成一个树状结构。行为树通过模拟树状结构来描述AI的行为和决策过程,使得AI的行为更加灵活、自然和智能。 阅读详情

相关推荐

基于Unity行为树设计与实现的尝试

查阅了一些行为树资料,目前最主要是参考了这篇文章,看完后感觉行为树实乃强大,绝对是替代状态机的不二之选。但从理论看起来很简单的行为树,真正着手起来却发现很多细节无从下手。 总结起来,就是: 1、行为树只是单纯的一棵决策树,还是决策+控制树。为了防止不必要的麻烦,我目前设计成单纯的决策树。 2、什么时候执行行为树的问题,也就是行为树的Tick问题,是在条件变化的时候执行一次,还是只要对

3万+

行为树原理简介

行为树原理简介 本篇博客整理总结自行为树(Behavior Tree)详细介绍。 前言 首先需要说明的是行为树和状态机都是实现游戏AI的一种手段,二者各有利弊。相对于行为树,经典状态机实现起来更简单,且执行效率更高,但是随着状态数量的增加,状态机的结构将会向着不可控的方向发展。而经典行为树则是使用起来结构更加清晰,对于复杂AI的开发效率更高,但是实现起来复杂,且执行效率较低。所以说二者各有千秋。不过现在行为树已经逐渐成为游戏AI开发的主流,具体原因可以看由赖勇浩翻译的《有限状态机时代终结的10大理由》这篇博

BraveRunTo的博客 2971

行为树(Behavior trees)

文章目录行为树组合节点次序节点选择节点并行节点修饰节点逆变节点成功节点重复节点叶子节点 行为树 行为树由多种不同类型的节点组成,这些节点都会返回三种状态中的一种作为节点的运行结果。三种状态分别是: 成功 - Success 失败 - Failure 运行中 - Running 前两个,正如它们的名字一样,是用来向它们的父节点通知运行的成功或失败。第三种是指还在运行中,结果还未确定,会在下一个 Tick 的时候再去检查这个节点的运行结果。这个功能非常重要,它可以让一个节点持续运行一段时间来维持某些行为。比

念去去 6894

行为树 Behavior Tree 原理

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

Leo_starr的博客 2412

游戏AI——行为树理论及实现

https://www.fgba.net/sitemap.xml

wubaohu1314的博客 943

游戏AI-行为树理论及实现

从上古卷轴中形形色色的人物,到NBA2K中挥洒汗水的球员,从使命召唤中诡计多端的敌人,到刺客信条中栩栩如生的人群。游戏AI几乎存在于游戏中的每个角落,默默构建出一个令人神往的庞大游戏世界。 那么这些复杂的AI又是怎么实现的呢?下面就让我们来了解并亲手实现一下游戏AI基础架构之一的行为树行为树简介 行为树是一种树状的数据结构,树上的每一个节点都是一个行为。每次调用会从根节点开始遍历,通过检查...

ctbinzi的专栏 770

Godot AI行为树:智能决策系统实现

游戏开发中,创建智能且响应式的非玩家角色(NPC)一直是开发者面临的核心挑战。传统的状态机(State Machine)虽然简单易用,但随着AI复杂度的增加,状态之间的转换会变得难以维护。行为树(Behavior Tree)作为一种更加灵活和可扩展的AI架构,正在成为现代游戏AI开发的首选方案。 Godot引擎虽然没有内置的行为树系统,但其强大的节点架构和脚本系统为我们提供了实现自定义行为树的...

gitblog_00081的博客 1161

游戏思考25:behaviorTree源码剖析行为树(3.8版本,2023-11-22)

行为树节点分类1)序列Sequence节点:顺序执行所有子节点,若都成功则返回成功,若有一个失败则返回失败2)循环Loop节点循环执行子节点到指定次数后返回成功,如果循环次数为"-1",则无限循环3)条件Condition节点根据条件的比较结果,返回成功或失败4)动作Action节点根据动作结果返回”成功“、”失败“或”运行“5)等待Wait节点返回"运行",一直到指定的时间过去后返回”成功"

谢白羽 1648

打造行为树:巡逻、追踪与攻击AI的实现

本章节深入探讨了如何在游戏AI中实现巡逻、追踪和攻击行为。通过构建行为树的组件,如任务(Task)、装饰器(Decorator)和服务(Service),我们能够控制AI的行为和决策流程。文中详细描述了创建自定义行为树组件的步骤,并以巡逻、追踪和攻击AI为例,展示了如何使用这些组件来构建复杂的行为模式。

weixin_28931449的博客 527

游戏AI设计经验分享——行为树研究

http://bbs.gameres.com/thread_493700.html 文/Chris Simpson   1、 简介   因为网上有太多的行为树的教程和手册,当我在决定哪一个适合Zomboid项目时,总是反复遇到相同的问题。我看的很多手册都很严重地依赖于具体代码的实现,或者简单地基于通用的节点的工作流,都没有实际的实现案例,就像下面这张图:  

caoshulin1989的专栏 4719

【UE5】AI随机漫游蓝图两种实现方法(角色蓝图、行为树

角色蓝图快速实现行为树原理,寻路网格体的调整

Zhanganzhen的博客 5443

**发散创新:行为树在AI决策过程中的应用与实现**随着人工智能技术的飞速发展,行为树作为一

行为树是一种决策结构,用于描述AI在特定环境下的行为和决策过程。它由节点和边组成,节点表示任务或条件,边表示节点之间的控制流。行为树的主要目的是将复杂的决策过程分解为一系列较小的、易于管理的任务。行为树的运行过程可以看作是一个递归过程。当根节点被激活时,它将根据其子节点的条件执行相应的操作。否则,将执行失败操作或回退到上一个节点重新尝试。随着人工智能技术的飞速发展,行为树作为一种重要的决策模型,在AI领域的应用愈发广泛。本文将详细介绍行为树的基本原理、应用场景,并通过实例展示如何在编程中实现行为树

YVZONG1234的博客 393

游戏AI—行为树研究及实现

从上古卷轴中形形色色的人物,到NBA2K中挥洒汗水的球员,从使命召唤中诡计多端的敌人,到刺客信条中栩栩如生的人群。游戏AI几乎存在于游戏中的每个角落,默默构建出一个令人神往的庞大游戏世界。 那么这些复杂的AI又是怎么实现的呢?下面就让我们来了解并亲手实现一下游戏AI基础架构之一的行为树行为树简介 行为树是一种树状的数据结构,树上的每...

weixin_34248118的博客 748

基础入门:5分钟学会行为树基础

拖拽选择节点和序列节点到画布上,再连接几个简单动作(比如移动、跳跃),点击运行就能看到角色按你的逻辑行动。行为树就是这样用树状结构组织「如果...就...」的判断逻辑,只不过每个判断或动作都变成了可复用的节点。这种能实时看到角色反馈的环境,调试时改个节点连接方式,立刻就能在右侧预览窗口观察到行为变化,对建立编程直觉帮助很大。今天就用新手视角,带大家用两个最常用节点入门。用选择节点时,只要「看到敌人」成立,后面两项就不会执行。作为一名刚接触行为树的开发者,最初看到那些复杂的节点和连线时,我也曾一头雾水。

OnyxPanther23的博客 511

游戏AI之行为树(上)

游戏AI之行为树(上) Introduction 简介 因为网上有太多的行为树的教程和手册,当我在决定哪一个适合Zomboid项目时,总是反复遇到相同的问题。我看的很多手册都偏重于具体代码的实现,或者简单地基于通用的节点的工作流,都没有实际的实现案例,就像下面这张图: 因为那些教程对于我理解行为树的核心规则没有用处,我发现我尽管知道行为树是如何操作的,但对于在游戏中应当使用

aaa 1327

AI行为树理解

基于前面几次的面试,自己进行行了总结: 在问道我项目里的怪物AI逻辑的时候,我自己项目里用的是状态机制,算是行为树的一层,但是复杂的行为树有很多很多层,比较能完成的实习NPC或者怪物行为。自己也在百度上找了许多资料,对此谈下我自己的理解! @TOC 一级目录 二级目录 三级目录 基础-Basics 1、行为树的名字很好地解释了它是什么。不像有限状态机(Finite State Machine)或其他用于 AI 编程的系统,行为树是一棵用于控制 AI 决策行为的、包含了层级节点的树结构。树的最末端——叶子,

Gxy_w的博客 1676

C++ 版本的 行为树的简单实现

如果你想转载这篇文章呢,请严格按照以下格式注明出处和作者 出处:http://www.cnblogs.com/anxin1225/p/4827294.html 作者:Anxin1225、Bianchx、Linker(其实他们都是一个人。。) 行为树是一种简洁明了的整理业务逻辑的有效方法。至于他的好处,不做赘述。 由于项目的需要,所以实现了一个非常简单的行为树,...

weixin_34383618的博客 993

探索行为树框架:Behavior3Lua

探索行为树框架:Behavior3Lua 在这篇文章中,我们将深入探讨一个强大的开源行为树框架——Behavior3Lua。这是一个基于Lua语言的行为树库,专为游戏AI、机器人控制和任何需要动态决策系统的设计者打造。 项目简介 Behavior3Lua是 Behavior3 的 Lua 实现,原版 Behavior3 是一款由 JavaScript 编写的流行行为树库。此项目旨在提供一个灵活且...

gitblog_00080的博客 1053
上一篇: 快速排序算法
下一篇: 有限状态机的简单实现
破坏之王C
博客等级 码龄10年 229粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值