每个叶子节点(nil)是黑色。_UE4 C++自定义AI行为树的Decorator和Task节点

目录:

一、创建C++类

二、关键函数

三、实际代码举例


一、创建C++类

Decorator应继承UBTDecorator,Task应继承UBTTaskNode。如果希望使用黑板键,可以继承自相应的有_BlackboardBase后缀的类,这个类会提供一个黑板键成员变量,当然也可以不继承这个_BlackboardBase,自己写

(以下代码中中文仅代表自定义名称占位符,实际代码中请不要出现中文

protected:
	/** blackboard key selector */
	UPROPERTY(EditAnywhere, Category=Blackboard)
	struct FBlackboardKeySelector 黑板键成员变量名;

然后在构造函数里加上黑板键的类型筛选,例如如果只希望可以选择float类型的黑板键,则:

BlackboardKey.AddFloatFilter(this, GET_MEMBER_NAME_CHECKED(你的类名, 黑板键成员变量名));

并且

/*声明:*/
virtual void InitializeFromAsset(UBehaviorTree& Asset) override;

/*实现:*/
Super::InitializeFromAsset(Asset);

UBlackboardData* BBAsset = GetBlackboardAsset();
if (ensure(BBAsset))
{
	黑板键成员变量名.ResolveSelectedKey(*BBAsset);
}

如果希望自定义节点名,如

982537c6c34f74412ca2ab1ee299302a.png
这里的Wait就是节点名

就在构造函数里

NodeName = "Wait";

如果希望节点有详细的描述,如

35ac3171f3e689bc83cf4c9cf9e55ac9.png
这里的 Wait: 1.0s

就重写

virtual FString GetStaticDescription() const override;

返回值就是这个描述。如果希望调试时能实时得知运行情况,如:

946ec906271a806a24c66aa89bb423c4.png
remaining: 0.115943s

就需要重写

virtual void DescribeRuntimeValues(const UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTDescriptionVerbosity::Type Verbosity, TArray<FString>& Values) const override;

输出值为参数Values,如

Values.Add(想要输出的FString);

二、关键函数

Task开始执行函数为:

/** starts this task, should return Succeeded, Failed or InProgress
 *  (use FinishLatentTask() when returning InProgress)
 * this function should be considered as const (don't modify state of object) if node is not instanced! */
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;

其可用的返回值有三种

EBTNodeResult::Failed
EBTNodeResult::Succeeded
EBTNodeResult::InProgress

前两种自然就是立刻返回当前节点执行成功与否,最后一种是表示节点虽然已经开始执行,但暂时还没执行结束,当希望结束执行InProgress的节点(以成功执行举例),就需要调用

FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);

一般在TickTask中调用,也就是每个Tick都去检查是否执行完成。

TickTask函数原型:

/** ticks this task
 * this function should be considered as const (don't modify state of object) if node is not instanced! */
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds);

(注意,只有当设置bNotifyTick = true时TickTask才会执行,同理还有bNotifyActivation 于OnNodeActivation、bNotifyDeactivation 于OnNodeDeactivation)

Decorator的判断函数为

virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;

重写这个函数,返回值则是能否执行子节点。

当有黑板键的值发生变化时,会调用

virtual EBlackboardNotificationResult OnBlackboardKeyValueChange(const UBlackboardComponent& Blackboard, FBlackboard::FKey ChangedKeyID) override;

如果希望控制Decorator父节点的哪个子节点被执行,可以调用:

GetParentNode()->SetChildOverride(SearchData, ChildIndex);

其中SearchData是函数OnNodeActivation或OnNodeDeactivation的参数,ChildIndex是签名为UBTAuxiliaryNode::ChildIndex,UBTDecorator就是继承UBTAuxiliaryNode的,这个成员变量的意义是正在执行的子节点索引。这类功能函数不能一一列举,建议自行查看源码。

额外一句,GetParentNode()的返回值是UBTCompositeNode,而任意节点的父节点都是一个CompositeNode,因为叶子节点(行为树中只有一种也就是TaskNode)是没有子节点的。

Task和Decorator还提供了

virtual void InitializeMemory(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTMemoryInit::Type InitType) const override;
virtual uint16 GetInstanceMemorySize() const override;

两个函数,个人理解是为了方便此节点在堆上构造数据,有点像C语言先malloc然后再init,也即C++中new一个对象的过程,先分配,再构造 。

【修改:Node Memory设计理念:BTTask NodeMemory??】

其中GetInstanceMemorySize返回数据大小,一般用法为

uint16 你的类名::GetInstanceMemorySize() const
{
	return sizeof(自定义的结构体名);
}

InitializeMemory则是初始化,NodeMemory就是分配好的GetInstanceMemorySize()长度的内存指针,InitType有两种,

Initialize,		// first time initialization
RestoreSubtree,	// loading saved data on reentering subtree

分别是 第一次初始化,以及 已经进入过而且已经离开了,现在又一次进入时。

三、实际代码举例

理论讲的够多了,接下来实际看代码。我选的例子是UBTDecorator_CoolDown,作用大家应该都清楚,让节点有冷却时间,也就是执行过一个节点,特定时间内不会再次执行。

.h

//这个结构体需要在堆上构造的,可以稍后在后续代码中得知,
//无论是GetInstanceMemorySize返回了这个结构体的size,
//或是InitializeMemory、CalculateRawConditionValue、DescribeRuntimeValues这三个函数都
//将参数uint8* NodeMemory给
//FBTCooldownDecoratorMemory* DecoratorMemory = CastInstanceNodeMemory<FBTCooldownDecoratorMemory>(NodeMemory);
//(可以理解为Cast)
//都可以证明这一点
struct FBTCooldownDecoratorMemory
{
	//上一次执行时的时间戳
	//由这个变量就可以推导出CoolDown的判断实现为
	//判断当前世界时间戳与此值差值是否大于期望
	float LastUseTimestamp;
	//一个flag,判断是否已经渡过冷却时间
	//已渡过冷却时间后就不计算是否已渡过
	//节省性能
	//同时确保第一次执行不会失败
	uint8 bRequestedRestart : 1;
};

/**
 * Cooldown decorator node.
 * A decorator node that bases its condition on whether a cooldown timer has expired.
 */
UCLASS(HideCategories=(Condition))
class AIMODULE_API UBTDecorator_Cooldown : public UBTDecorator
{
	GENERATED_UCLASS_BODY()

	/** max allowed time for execution of underlying node */
	//暴露给行为树,以可以在Detail Panel中设置冷却时间
	UPROPERTY(Category=Decorator, EditAnywhere)
	float CoolDownTime;

	//~ Begin UObject Interface
	virtual void PostLoad() override;
	//~ End UObject Interface

	virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
	virtual void InitializeMemory(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTMemoryInit::Type InitType) const override;
	virtual uint16 GetInstanceMemorySize() const override;
	virtual void DescribeRuntimeValues(const UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTDescriptionVerbosity::Type Verbosity, TArray<FString>& Values) const override;
	virtual FString GetStaticDescription() const override;

#if WITH_EDITOR
	virtual FName GetNodeIconName() const override;
#endif // WITH_EDITOR

protected:

	virtual void OnNodeDeactivation(FBehaviorTreeSearchData& SearchData, EBTNodeResult::Type NodeResult) override;
	virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};

.cpp

UBTDecorator_Cooldown::UBTDecorator_Cooldown(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
	NodeName = "Cooldown";
	CoolDownTime = 5.0f;
	
	// aborting child nodes doesn't makes sense, cooldown starts after leaving this branch
	bAllowAbortChildNodes = false;

	bNotifyTick = false;
	bNotifyDeactivation = true;
}

void UBTDecorator_Cooldown::PostLoad()
{
	Super::PostLoad();
	//这一句是,只要Abort方式不是None,那就需要Tick
	//因为只有在有可能Abort掉子节点或同级低优先节点的情况下才需要逐tick检查
	bNotifyTick = (FlowAbortMode != EBTFlowAbortMode::None);
}

bool UBTDecorator_Cooldown::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
	FBTCooldownDecoratorMemory* DecoratorMemory = CastInstanceNodeMemory<FBTCooldownDecoratorMemory>(NodeMemory);
	const float TimePassed = (OwnerComp.GetWorld()->GetTimeSeconds() - DecoratorMemory->LastUseTimestamp);
	return TimePassed >= CoolDownTime;
}

void UBTDecorator_Cooldown::InitializeMemory(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTMemoryInit::Type InitType) const
{
	FBTCooldownDecoratorMemory* DecoratorMemory = CastInstanceNodeMemory<FBTCooldownDecoratorMemory>(NodeMemory);
	if (InitType == EBTMemoryInit::Initialize)
	{
		DecoratorMemory->LastUseTimestamp = -FLT_MAX;
	}

	DecoratorMemory->bRequestedRestart = false;
}

void UBTDecorator_Cooldown::OnNodeDeactivation(FBehaviorTreeSearchData& SearchData, EBTNodeResult::Type NodeResult)
{
	FBTCooldownDecoratorMemory* DecoratorMemory = GetNodeMemory<FBTCooldownDecoratorMemory>(SearchData);
	DecoratorMemory->LastUseTimestamp = SearchData.OwnerComp.GetWorld()->GetTimeSeconds();
	DecoratorMemory->bRequestedRestart = false;
}

void UBTDecorator_Cooldown::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	FBTCooldownDecoratorMemory* DecoratorMemory = CastInstanceNodeMemory<FBTCooldownDecoratorMemory>(NodeMemory);
	if (!DecoratorMemory->bRequestedRestart)
	{
		const float TimePassed = (OwnerComp.GetWorld()->GetTimeSeconds() - DecoratorMemory->LastUseTimestamp);
		if (TimePassed >= CoolDownTime)
		{
			DecoratorMemory->bRequestedRestart = true;
			OwnerComp.RequestExecution(this);
		}
	}
}

FString UBTDecorator_Cooldown::GetStaticDescription() const
{
	// basic info: result after time
	return FString::Printf(TEXT("%s: lock for %.1fs after execution and return %s"), *Super::GetStaticDescription(),
		CoolDownTime, *UBehaviorTreeTypes::DescribeNodeResult(EBTNodeResult::Failed));
}

void UBTDecorator_Cooldown::DescribeRuntimeValues(const UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTDescriptionVerbosity::Type Verbosity, TArray<FString>& Values) const
{
	Super::DescribeRuntimeValues(OwnerComp, NodeMemory, Verbosity, Values);

	FBTCooldownDecoratorMemory* DecoratorMemory = CastInstanceNodeMemory<FBTCooldownDecoratorMemory>(NodeMemory);
	const float TimePassed = OwnerComp.GetWorld()->GetTimeSeconds() - DecoratorMemory->LastUseTimestamp;
	
	if (TimePassed < CoolDownTime)
	{
		Values.Add(FString::Printf(TEXT("%s in %ss"),
			(FlowAbortMode == EBTFlowAbortMode::None) ? TEXT("unlock") : TEXT("restart"),
			*FString::SanitizeFloat(CoolDownTime - TimePassed)));
	}
}

uint16 UBTDecorator_Cooldown::GetInstanceMemorySize() const
{
	return sizeof(FBTCooldownDecoratorMemory);
}

#if WITH_EDITOR

FName UBTDecorator_Cooldown::GetNodeIconName() const
{
	return FName("BTEditor.Graph.BTNode.Decorator.Cooldown.Icon");
}

#endif	// WITH_EDITOR

Task我推荐的是UBTTask_Wait,需要了解黑板键的可以去UBTTask_WaitBlackboardTime

f898e44aef11ecc84cba4836230a51bb.png

可以自行去阅读源码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值