400行代码实现一个轻量级的行为树Behavior Tree(二)

前面一个教程简单了讲了下行为树的实现。下来我们实现一个巡逻加攻击的功能。
该功能为:

  1. 时刻进行攻击范围的判断,一旦进入攻击范围,那么就进行攻击操作。
  2. 如果没有怪物在攻击范围内,那么就继续巡逻。
    巡逻:
  3. 屏幕上随机取一个点,让玩家移动到该点。
  4. 到达该点后idle一段时间。
  5. 重复1、2操作
    攻击:
  6. 播放攻击动作
  7. 攻击动作完成后idle一段时间
  8. 重复1、2操作。

怎么通过这些控制节点把各个叶子节点组织起来呢
1、选择节点和序列节点是带有优先级的,越先添加的节点优先级越高,所以我们应该把优先级高的节点放在前面。比如我们的攻击节点的优先级应该是大于巡逻节点的,一旦攻击条件满足(即进入攻击范围时,我们就应该进入攻击相关的一些逻辑)。攻击条件不满足时候才执行巡逻逻辑。

上面的节点放在越上面的节点优先级越高,比如该图的攻击节点的优先级大于巡逻节点。
MemSeq表示带记忆的序列节点(因为巡逻下面的idle和移动的优先级应该是一样的,所以选择带记忆的序列节点)。
2、下来我们看看攻击节点怎么组织
攻击节点是由下面两部分组成的
a、条件节点(叶子节点):是否进入攻击范围
b、攻击(攻击动作和idle组成,这两部分应该是并列的,优先级一样,所以应该用MemSeq)
攻击节点如下图所示


3、巡逻节点(并列关系,优先级一样,所以选择MemSeq)
a、移动
b、idle
下图所示

所以整个行为树如下图所示

下面我们准备来构建该行为树:
后续行为树可能需要到各个模块去搜罗一些需要的数据。有的行为树的框架是通过一块外部世界的黑板数据,将外部所有的逻辑收集到黑板中,然后把整块黑板传到行为树里面。这样做的缺点是需要把整棵行为树需要的所有数据都收集到一起,存在大量的数据冗余。我目前这边是通过一个Agent类在行为树需要的时候通过函数的形式从各个模块取数据和逻辑。简单点说就是一个连接行为树和外部世界的一个中转站,需要跟外部世界打交道时调用Agent里面的函数进行获取。
独立出这样一个类可以降低耦合性,若行为树的和外部世界的交互全部通过AI单元从各处去收集信息的话,增大了ai单元的负担。
下来开始上代码:
首先定义一个Agent基类

/**********************************************************************/
/*	author:稀土                                                               */
/**********************************************************************/
#ifndef __Agent_H__
#define __Agent_H__

namespace BT
{
	class Agent
	{
	public:
		Agent(){}
		~Agent(){}

		virtual void update(float fDeltaTime) = 0;
	};

}
#endif

下来就是具体的AI单元的Agent了,他们继承于Agent

//.h
/**********************************************************************/
/*	author:稀土                                                               */
/**********************************************************************/
#ifndef __PlayerAgent_H__
#define __PlayerAgent_H__

#include "BevTree/BevComm.h"
#include "Player.h"

USING_NS_CC;
using namespace BT;
class PlayerAgent : public Agent
{
public:
	PlayerAgent();
	~PlayerAgent();

	static PlayerAgent* create(CPlayer* pPlayer);
	bool init(CPlayer* pPlayer);

	virtual void update(float fDelta);

	CC_SYNTHESIZE(CPlayer*, m_pPlayer, Player);
	CC_SYNTHESIZE_READONLY(BevNode*, m_pRoot, Root);

private:
	void createBevTree();

};
#endif

//.cpp
#include "PlayerAgent.h"


PlayerAgent::PlayerAgent()
	: m_pPlayer(nullptr)
{
}

PlayerAgent::~PlayerAgent()
{
	if (m_pRoot)
	{
		delete m_pRoot;
		m_pRoot = nullptr;
	}
}

PlayerAgent* PlayerAgent::create(CPlayer* pPlayer)
{
	PlayerAgent* pAgent = new PlayerAgent();
	pAgent->init(pPlayer);
	return pAgent;
}

bool PlayerAgent::init(CPlayer* pPlayer)
{
	m_pPlayer = pPlayer;
	this->createBevTree();
	return true;
}
//构建整棵行为树,后续若有做行为树编辑器可在这个类里面加载文件,解析创建行为树
void PlayerAgent::createBevTree()
{
	//根节点
	m_pRoot = new Selector();
	//攻击节点
	BevNode* pAckNode = new SequenceNode();
	//攻击节点的条件节点
	LeafNode* pWithIn = new LeafNode();
	//用lamda表达式更方便。注意lamda里面的这个参数LeafNode* pLeafNode,
	//主要用于外部世界可能需要打断行为树正在执行的节点,方便外部世界可以强制打断行为树的逻辑
	pWithIn->setExecuteFunc([=](LeafNode* pLeafNode, float fDelta)->eBevState{
		return this->getPlayer()->withIn(pLeafNode, fDelta);
	});
	//攻击
	BevNode* pAck = new memorySequence();
	//攻击idle
	LeafNode* pAckIdleNode = new LeafNode();
	pAckIdleNode->setEnterFunc([=](LeafNode* pLeafNode){
		this->getPlayer()->playIdle(pLeafNode);
	});
	//攻击动作
	LeafNode* pAckAction = new LeafNode();
	pAckAction->setEnterFunc([=](LeafNode* pLeafNode){
		this->getPlayer()->attack(pLeafNode);
	});
	pAck->addChild(pAckAction);
	pAck->addChild(pAckIdleNode);

	//这边要注意添加的顺序
	pAckNode->addChild(pWithIn);
	pAckNode->addChild(pAck);

	//巡逻节点
	BevNode* pPatrol = new memorySequence();
	//巡逻idle
	LeafNode* pIdleNode = new LeafNode();
	pIdleNode->setEnterFunc([=](LeafNode* pLeafNode){
		this->getPlayer()->playIdle(pLeafNode);
	});
	//巡逻move
	LeafNode* pMoveNode = new LeafNode();
	pMoveNode->setEnterFunc([=](LeafNode* pLeafNode){
		this->getPlayer()->playRun(pLeafNode);
	});
	pPatrol->addChild(pIdleNode);
	pPatrol->addChild(pMoveNode);

	//这边要注意添加的顺序
	m_pRoot->addChild(pAckNode);
	m_pRoot->addChild(pPatrol);
}

void PlayerAgent::update(float fDelta)
{
	if (m_pRoot)
	{
		m_pRoot->execute(fDelta);
	}
}

好了,基本上把该行为树的大概使用方法。本人水平有限,在实现上若有什么不妥的欢迎大家批评指正,或者有什么好的建议可以告诉我下。

6赞

这个比上篇更通俗易懂了,图文配合比代码更好的阐述啊

我又帮你改markdown格式了。记得在代码前后加

```cpp
这里是C++代码段
```

谢谢王总,代码拷贝进去格式就出问题了。

嗯,这个代码不知道怎么上传,就没传上来了,不过主要代码也都贴上来了

楼主,我根据你的思路,写了一个lua版的行为树,代码我放[githup] (https://github.com/iyexiao/BehaviorTree-lua)上了

1赞

哦,分享个地址

不错不错,赞一个