/
Creating e new Behaviour Tree task (AtomsUnreal)

Creating e new Behaviour Tree task (AtomsUnreal)

In this section, we are going to create a simple wait behaviour tree task.

Create an UAtomsBehaviourTreeTaskNode

In your project, create a new class MyWaitBehaviourTreeTaskNode and inherit from the UAtomsBehaviourTreeTaskNode .

 

You need to add these arguments to the UCLASS.

UCLASS(ClassGroup = (AtomsBehavioursTreeNode)) class UMyWaitBehaviourTreeTaskNode : public UAtomsBehaviourTreeTaskNode { GENERATED_UCLASS_BODY() }

Implement the constructor setting the AtomsBehaviourModule member. This variable contains the name of the Atoms behaviour tree node that this unreal node creates.

UMyWaitBehaviourTreeTaskNode::UMyWaitBehaviourTreeTaskNode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { NodeName = "MyWait"; }

Implement the description string method.

 

 

 

 

 

 

UCLASS(ClassGroup = (AtomsBehavioursTreeNode)) class UMyWaitBehaviourTreeTaskNode : public UAtomsBehaviourTreeTaskNode { GENERATED_UCLASS_BODY() public: virtual FString GetStaticDescription() const override; }
FString UMyWaitBehaviourTreeTaskNode::GetStaticDescription() const { return FString("My Custom Wait task"); }

Add properties

Now it is time to add some properties to the task node. If you are going to use blackboard entries on these properties you need to use the FAtomsBehaviourTreeNode*Property struct that you can find inside “BehaviourTree/AtomsBehaviourTreeNodeProperty.h” header.

For this wait task we need those 2 properties:

FAtomsBehaviourTreeNodeBoolProperty lockAgent; //used to lock the agent mutex when accessing the blackboard

FAtomsBehaviourTreeNodeDoubleProperty waitTime; // wait time

 

 

 

 

 

 

 

 

 

 

#pragma once #include "CoreMinimal.h" #include "BehaviourTree/AtomsBehaviourTreeTaskNode.h" #include "BehaviourTree/AtomsBehaviourTreeNodeProperty.h" #include "MyWaitBehaviourTreeTaskNode.generated.h" UCLASS(ClassGroup = (AtomsBehavioursTreeNode)) class UMyWaitBehaviourTreeTaskNode : public UAtomsBehaviourTreeTaskNode { GENERATED_UCLASS_BODY() public: virtual FString GetStaticDescription() const override; public: /** If on, ensures no data on the agent is written simultaneously by nodes that have this option active. */ UPROPERTY(EditAnywhere, Category = "Properties") FAtomsBehaviourTreeNodeBoolProperty lockAgent; /** Wait time */ UPROPERTY(EditAnywhere, Category = "Properties") FAtomsBehaviourTreeNodeDoubleProperty waitTime; };

Property conversion

You need to implement now the GetAtomsAttribute method to pass the properties to the atoms node.

#pragma once #include "CoreMinimal.h" #include "BehaviourTree/AtomsBehaviourTreeTaskNode.h" #include "BehaviourTree/AtomsBehaviourTreeNodeProperty.h" #include "MyWaitBehaviourTreeTaskNode.generated.h" UCLASS(ClassGroup = (AtomsBehavioursTreeNode)) class UMyWaitBehaviourTreeTaskNode : public UAtomsBehaviourTreeTaskNode { GENERATED_UCLASS_BODY() public: virtual FString GetStaticDescription() const override; virtual void GetAtomsAttribute(AtomsCore::MapMetadata* attributes) const override; public: /** If on, ensures no data on the agent is written simultaneously by nodes that have this option active. */ UPROPERTY(EditAnywhere, Category = "Properties") FAtomsBehaviourTreeNodeBoolProperty lockAgent; /** Wait time */ UPROPERTY(EditAnywhere, Category = "Properties") FAtomsBehaviourTreeNodeDoubleProperty waitTime; };

 

UMyWaitBehaviourTreeTaskNode::UMyWaitBehaviourTreeTaskNode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { NodeName = "MyWait"; } FString UMyWaitBehaviourTreeTaskNode::GetStaticDescription() const { return FString("My Custom Wait task"); } void UMyWaitBehaviourTreeTaskNode::GetAtomsAttribute(AtomsCore::MapMetadata* attributes) const { lockAgent.GetAtomsAttribute(FString("lockAgent"), attributes); waitTime.GetAtomsAttribute(FString("waitTime"), attributes); }

Create atoms node

The last step to do inside the unreal task node is to implement the GenerateAtomsBehaviour method that creates the equivalent atoms node MyWaitAtomsBehaviourTreeTaskNodewe are going to implement after this step.

#pragma once #include "CoreMinimal.h" #include "BehaviourTree/AtomsBehaviourTreeTaskNode.h" #include "BehaviourTree/AtomsBehaviourTreeNodeProperty.h" #include "MyWaitBehaviourTreeTaskNode.generated.h" UCLASS(ClassGroup = (AtomsBehavioursTreeNode)) class UMyWaitBehaviourTreeTaskNode : public UAtomsBehaviourTreeTaskNode { GENERATED_UCLASS_BODY() public: virtual FString GetStaticDescription() const override; virtual void GetAtomsAttribute(AtomsCore::MapMetadata* attributes) const override; virtual Atoms::Behaviour* GenerateAtomsBehaviour(AActor* Actor, UActorComponent* Component) override; public: /** If on, ensures no data on the agent is written simultaneously by nodes that have this option active. */ UPROPERTY(EditAnywhere, Category = "Properties") FAtomsBehaviourTreeNodeBoolProperty lockAgent; /** Wait time */ UPROPERTY(EditAnywhere, Category = "Properties") FAtomsBehaviourTreeNodeDoubleProperty waitTime; };
UMyWaitBehaviourTreeTaskNode::UMyWaitBehaviourTreeTaskNode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { NodeName = "MyWait"; } FString UMyWaitBehaviourTreeTaskNode::GetStaticDescription() const { return FString("My Custom Wait task"); } void UMyWaitBehaviourTreeTaskNode::GetAtomsAttribute(AtomsCore::MapMetadata* attributes) const { lockAgent.GetAtomsAttribute(FString("lockAgent"), attributes); waitTime.GetAtomsAttribute(FString("waitTime"), attributes); } Atoms::Behaviour* UMyWaitBehaviourTreeTaskNode::GenerateAtomsBehaviour(AActor* Actor, UActorComponent* Component) { MyWaitAtomsBehaviourTreeTaskNode* behaviour = new MyWaitAtomsBehaviourTreeTaskNode(); return behaviour; }

Implement the Atoms behaviour class.

Our UMyWaitBehaviourTreeTaskNode class is just a description of a task node, there is no logic inside, it is used to be able to expose properties used by a native Atoms behaviour node. Create a new MyWaitAtomsBehaviourTreeTaskNode class.

class MyWaitAtomsBehaviourTreeTaskNode : public Atoms::Behaviour { public: MyWaitAtomsBehaviourTreeTaskNode (): Atoms::Behaviour() {} virtual ~MyWaitAtomsBehaviourTreeTaskNode () {} static const char* staticTypeName() { return "MyWait"; } virtual const char* typeName() const override { return staticTypeName(); } public: };

Add attributes to the Atoms behaviour class

like we did with the UMyWaitBehaviourTreeTaskNode, we need to expose the same attributes here using the Atoms::Blackboard*Value classes. You don’t need to expose the lock attribute since it is already inside the base class.

class MyWaitAtomsBehaviourTreeTaskNode : public Atoms::Behaviour { public: MyWaitAtomsBehaviourTreeTaskNode (): Atoms::Behaviour(), waitTime(1.0), useSeconds(false) {} virtual ~MyWaitAtomsBehaviourTreeTaskNode () {} static const char* staticTypeName() { return "MyWait"; } virtual const char* typeName() const override { return staticTypeName(); } public: Atoms::BlackboardDoubleValue waitTime; Atoms::BlackboardBoolValue useSeconds; };

We can now initialize the attributes from the tree and blackboard definition. You need to implement the setAttributes method. It’s here that the blackboard entries are connected to each attribute.

#include <AtomsCore/Metadata/BoolMetadata.h> #include <AtomsCore/Metadata/DoubleMetadata.h> #include <Atoms/BehaviourTree/BehaviourTreeContext.h> class MyWaitAtomsBehaviourTreeTaskNode : public Atoms::Behaviour { public: MyWaitAtomsBehaviourTreeTaskNode (): Atoms::Behaviour(), waitTime(1.0), useSeconds(false) {} virtual ~MyWaitAtomsBehaviourTreeTaskNode () {} static const char* staticTypeName() { return "MyWait"; } virtual const char* typeName() const override { return staticTypeName(); } virtual void setAttributes(const AtomsCore::MapMetadata* attributes, Atoms::Blackboard* blackboard) override { if (!attributes) return; Atoms::Behaviour::setAttributes(attributes, blackboard); Atoms::getBlackboardValueFromAttributes<AtomsCore::DoubleMetadata, double>(attributes, blackboard, "waitTime", waitTime); } public: Atoms::BlackboardDoubleValue waitTime; Atoms::BlackboardBoolValue useSeconds; };

It is time to implement the node logic. For a task node, you can implement these methods:

  • initialize: method called to initialize the node instance.

  • update: method called at every tick.

  • terminate: method called when the node returned success/failure.

  • releaseData: called before the destruction of this node instance. Clear here all data allocated on the heap.

For this wait node, we need to implement the initialize to store the init time and then the update to check the timer is elapsed.

You must return a valid status that could be SUCCESS/FAILURE or RUNNING.

#include <AtomsCore/Metadata/BoolMetadata.h> #include <AtomsCore/Metadata/DoubleMetadata.h> #include <Atoms/BehaviourTree/BehaviourTreeContext.h> class MyWaitAtomsBehaviourTreeTaskNode : public Atoms::Behaviour { public: MyWaitAtomsBehaviourTreeTaskNode (): Atoms::Behaviour(), waitTime(1.0), useSeconds(false) {} virtual ~MyWaitAtomsBehaviourTreeTaskNode () {} static const char* staticTypeName() { return "MyWait"; } virtual const char* typeName() const override { return staticTypeName(); } virtual void setAttributes(const AtomsCore::MapMetadata* attributes, Atoms::Blackboard* blackboard) override { if (!attributes) return; Atoms::Behaviour::setAttributes(attributes, blackboard); Atoms::getBlackboardValueFromAttributes<AtomsCore::DoubleMetadata, double>(attributes, blackboard, "waitTime", waitTime); } virtual void initialize(Atoms::BehaviourTreeContext* context, State* data) override; virtual void update(Atoms::BehaviourTreeContext* context, State* data) override; virtual void terminate(Atoms::BehaviourTreeContext* context, State* data) override; virtual void releaseData(Atoms::BehaviourTreeContext* context, State* data) override; public: Atoms::BlackboardDoubleValue waitTime; Atoms::BlackboardBoolValue useSeconds; };

Local storage

Each node instance has local storage of 8 bytes that you can use to store data. The storage is defined inside a union inside the State structure.

 

struct State { union { int intValue; int int2Value[2]; short short4Value[4]; float float2Value[2]; double doubleValue; void* ptrValue; }; unsigned short status; unsigned short index; unsigned short tickCount; unsigned short padding; };

If you need to allocate more then 8 byte do something like this:

struct MyStruct { std::string foo1; double foo2 AtomsMath::Vector3 foo3; } data->ptrValue = context->allocateMemory<MyStruct>();

To release the allocated data call

context->releaseMemory(reinterpret_cast<MyStruct>(data->ptrValue));

Please remember to release the allocated data at least inside the releaseData method.

 

Access attribute values

To access an attribute value you need to use the context->getBlackboardValue() method. This method automatically returns a reference to the attribute value or to the blackboard entry connected to this attribute.

void MyWaitAtomsBehaviourTreeTaskNode::initialize(Atoms::BehaviourTreeContext* context, State* data) { //Set the status to RUNNING data->status = Behaviour::Status::RUNNING; // If the lock attribute is checked we need to lock the agent if (lock && context->agent) { context->agent->lock(); } // Get the simulation time from the agent group if (context->agentGroup) { // store the initali time inside the local storage. Since we need only 8 byte we don't need to allocate memory on the heap data->doubleValue = context->agentGroup->simulationTime().seconds(); } else { data->doubleValue = context->tickCount; } // If the lock attribute is checked we need to release lock the agent if (lock && context->agent) { context->agent->unlock(); } } void MyWaitAtomsBehaviourTreeTaskNode::update(Atoms::BehaviourTreeContext* context, State* data) { double currentTime = 0.0; if (lock && context->agent) { context->agent->lock(); } if (context->agentGroup) { currentTime = context->agentGroup->simulationTime().seconds(); } else { currentTime = context->tickCount; } currentTime = currentTime - data->doubleValue; // Check if we are over the wait time if (currentTime > context->getBlackboardValue(waitTime)) { data->status = Behaviour::Status::SUCCESS; } if (lock && context->agent) { context->agent->unlock(); } } void MyWaitAtomsBehaviourTreeTaskNode::terminate(Atoms::BehaviourTreeContext* context, State* data) { } void MyWaitAtomsBehaviourTreeTaskNode::releaseData(Atoms::BehaviourTreeContext* context, State* data) { }

Those are the final files:

 

 

Related content

Copyright © 2017, Toolchefs LTD.