Creating e new Behaviour Tree composite (AtomsUnreal)
In this section, we are going to create a random sequence behaviour tree composite.
Create an UAtomsBehaviourTreeCompositeNode
In your project, create a new class UMyRandomSequenceCompositeNode and inherit from the UAtomsBehaviourTreeCompositeNode.
UCLASS()
class UMyRandomSequenceCompositeNode: public UAtomsBehaviourTreeCompositeNode
{
GENERATED_UCLASS_BODY()
};
You need to add these arguments to the UCLASS.
UCLASS(ClassGroup = (AtomsBehavioursTreeNode))
class UMyRandomSequenceCompositeNode: public UAtomsBehaviourTreeCompositeNode
{
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.
UMyRandomSequenceCompositeNode::MyRandomSequenceAtomsCompositeNode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
NodeName = "MyRandomSequence";
}
Implement the description string method.
UCLASS(ClassGroup = (AtomsBehavioursTreeNode))
class UMyRandomSequenceCompositeNode: public UAtomsBehaviourTreeCompositeNode
{
GENERATED_UCLASS_BODY()
public:
virtual FString GetStaticDescription() const override;
}
FString UMyRandomSequenceCompositeNode::GetStaticDescription() const
{
return FString("My Custom Random Sequence");
}
Add properties
Now it is time to add some properties to the composite 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 composite we need those 2 properties:
FAtomsBehaviourTreeNodeBoolProperty lockAgent
; //used to lock the agent mutex when accessing the blackboard
FAtomsBehaviourTreeNodeIntProperty seed
; // random seed
#pragma once
#include "CoreMinimal.h"
#include "BehaviourTree/AtomsBehaviourTreeCompositeNode.h"
#include "BehaviourTree/AtomsBehaviourTreeNodeProperty.h"
#include "MyRandomSequenceCompositeNode.generated.h"
UCLASS(ClassGroup = (AtomsBehavioursTreeNode))
class UMyRandomSequenceCompositeNode: public UAtomsBehaviourTreeCompositeNode
{
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;
/** Random seed */
UPROPERTY(EditAnywhere, Category="Properties")
FAtomsBehaviourTreeNodeIntProperty seed;
};
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/AtomsBehaviourTreeCompositeNode.h"
#include "BehaviourTree/AtomsBehaviourTreeNodeProperty.h"
#include "MyRandomSequenceCompositeNode.generated.h"
UCLASS(ClassGroup = (AtomsBehavioursTreeNode))
class UMyRandomSequenceCompositeNode: public UAtomsBehaviourTreeCompositeNode
{
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;
/** Random seed */
UPROPERTY(EditAnywhere, Category="Properties")
FAtomsBehaviourTreeNodeIntProperty seed;
};
UMyRandomSequenceCompositeNode::UMyRandomSequenceCompositeNode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
NodeName = "MyRandomSequence";
}
FString UMyRandomSequenceCompositeNode::GetStaticDescription() const
{
return FString("My Custom Random Sequence");
}
void UMyRandomSequenceCompositeNode::GetAtomsAttribute(AtomsCore::MapMetadata* attributes) const
{
lockAgent.GetAtomsAttribute(FString("lockAgent"), attributes);
seed.GetAtomsAttribute(FString("seed"), attributes);
}
Create atoms node
The last step to do inside the unreal composite node is to implement the GenerateAtomsBehaviour method that creates the equivalent atoms node MyRandomSequenceAtomsCompositeNode
we are going to implement after this step.
#pragma once
#include "CoreMinimal.h"
#include "BehaviourTree/AtomsBehaviourTreeCompositeNode.h"
#include "BehaviourTree/AtomsBehaviourTreeNodeProperty.h"
#include "MyRandomSequenceCompositeNode.generated.h"
UCLASS(ClassGroup = (AtomsBehavioursTreeNode))
class UMyRandomSequenceCompositeNode: public UAtomsBehaviourTreeCompositeNode
{
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;
/** Number of times to execute the child */
UPROPERTY(EditAnywhere, Category="Properties")
FAtomsBehaviourTreeNodeIntProperty seed;
};
UMyRandomSequenceCompositeNode::UMyRandomSequenceCompositeNode(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
NodeName = "MyRandomSequence";
}
FString UMyRandomSequenceCompositeNode::GetStaticDescription() const
{
return FString("My Custom Random Sequence");
}
void UMyRandomSequenceCompositeNode::GetAtomsAttribute(AtomsCore::MapMetadata* attributes) const
{
lockAgent.GetAtomsAttribute(FString("lockAgent"), attributes);
seed.GetAtomsAttribute(FString("seed"), attributes);
}
Atoms::Behaviour* UMyRandomSequenceCompositeNode::GenerateAtomsBehaviour(AActor* Actor, UActorComponent* Component)
{
MyRandomSequenceAtomsCompositeNode * behaviour = new MyRandomSequenceAtomsCompositeNode();
return behaviour;
}
Implement the Atoms composite class.
Our UMyRandomSequenceCompositeNode is just a description of a composite node, there is no logic inside, it is used to be able to expose properties used by a native Atoms composite node. Create a new MyRandomSequenceAtomsCompositeNode
.
#include <Atoms/BehaviourTree/Composite.h>
class MyRandomSequenceAtomsCompositeNode: public Atoms::Composite
{
public:
MyRandomSequenceAtomsCompositeNode(): Atoms::Composite()
{}
virtual ~MyRandomSequenceAtomsCompositeNode() {}
static const char* staticTypeName() { return "MyRandomSequence"; }
virtual const char* typeName() const override { return staticTypeName(); }
public:
};
Add attributes to the Atoms composite class
like we did with the UMyRandomSequenceCompositeNode
, 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.
#include <Atoms/BehaviourTree/Composite.h>
class MyRandomSequenceAtomsCompositeNode: public Atoms::Composite
{
public:
MyRandomSequenceAtomsCompositeNode(): Atoms::Composite(), seed(0)
{}
virtual ~MyRandomSequenceAtomsCompositeNode() {}
static const char* staticTypeName() { return "MyRandomSequence"; }
virtual const char* typeName() const override { return staticTypeName(); }
public:
Atoms::BlackboardIntValue seed;
};
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 <Atoms/BehaviourTree/Composite.h>
#include <AtomsCore/Metadata/IntMetadata.h>
#include <Atoms/BehaviourTree/BehaviourTreeContext.h>
class MyRandomSequenceAtomsCompositeNode: public Atoms::Composite
{
public:
MyRandomSequenceAtomsCompositeNode(): Atoms::Composite(), seed(0)
{}
virtual ~MyRandomSequenceAtomsCompositeNode() {}
static const char* staticTypeName() { return "MyRandomSequence"; }
virtual const char* typeName() const override { return staticTypeName(); }
virtual void setAttributes(const AtomsCore::MapMetadata* attributes, Atoms::Blackboard* blackboard) override
{
if (!attributes)
return;
Atoms::Composite::setAttributes(attributes, blackboard);
Atoms::getBlackboardValueFromAttributes<AtomsCore::IntMetadata, int>(attributes, blackboard, "seed", seed);
}
public:
Atoms::BlackboardIntValue seed;
};
It is time to implement the node logic. For a composite 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.
onChildUpdated: called when a child changes its status.
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.
For decorators and composites node always set at the initialization a SUSPENDED state. Because it needs to wait for for a child update to know if it need to switch to RUNNING/SUCCESS or FAILURE state.
#include <Atoms/BehaviourTree/Composite.h>
#include <AtomsCore/Metadata/IntMetadata.h>
#include <Atoms/BehaviourTree/BehaviourTreeContext.h>
class MyRandomSequenceAtomsCompositeNode : public Atoms::Composite
{
public:
MyRandomSequenceAtomsCompositeNode() : Atoms::Composite(), seed(0)
{}
virtual ~MyRandomSequenceAtomsCompositeNode() {}
static const char* staticTypeName() { return "MyRandomSequence"; }
virtual const char* typeName() const override { return staticTypeName(); }
virtual void setAttributes(const AtomsCore::MapMetadata* attributes, Atoms::Blackboard* blackboard) override
{
if (!attributes)
return;
Atoms::Composite::setAttributes(attributes, blackboard);
Atoms::getBlackboardValueFromAttributes<AtomsCore::IntMetadata, int>(attributes, blackboard, "seed", seed);
}
virtual void initialize(Atoms::BehaviourTreeContext* context, State* data) override;
virtual void update(Atoms::BehaviourTreeContext* context, State* data) override;
virtual void onChildUpdated(Atoms::BehaviourTreeContext* context, State* data, unsigned int childIndex, unsigned short childStatus) override;
virtual void terminate(Atoms::BehaviourTreeContext* context, State* data) override;
virtual void releaseData(Atoms::BehaviourTreeContext* context, State* data) override;
public:
Atoms::BlackboardIntValue seed;
};
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.
#include <random>
struct RandomSequenceData
{
std::vector<unsigned short> children;
unsigned int index;
};
void MyRandomSequenceAtomsCompositeNode::initialize(Atoms::BehaviourTreeContext* context, State* data)
{
data->status = Status::FAILURE;
data->ptrValue = nullptr;
// Check fi there are children connected
if (m_Children.size() > 0)
{
data->status = Status::SUSPENDEND;
// Allocate the local storage
auto newData = context->allocateMemory<RandomSequenceData>();
newData->children = m_Children;
{
if (lock)
{
context->agent->lock();
}
// Get the seed from the attribute/blackboard
unsigned int seedValue = context->getBlackboardValue(seed);
if (lock)
{
context->agent->unlock();
}
std::default_random_engine e(seedValue);
// shuffle the children
std::shuffle(newData->children.begin(), newData->children.end(), e);
}
newData->index = 0;
data->ptrValue = newData;
context->start(newData->children[0]);
}
}
void MyRandomSequenceAtomsCompositeNode::update(Atoms::BehaviourTreeContext* context, State* data)
{
}
void MyRandomSequenceAtomsCompositeNode::onChildUpdated(Atoms::BehaviourTreeContext* context, State* data, unsigned int childIndex, unsigned short childStatus)
{
if (childStatus == Status::RUNNING)
{
data->status = childStatus;
return;
}
if (!data->ptrValue || childStatus == Status::FAILURE)
{
data->status = childStatus;
return;
}
auto newData = reinterpret_cast<RandomSequenceData*>(data->ptrValue);
// increase the current child index and check if there are any other child
if (++newData->index >= m_Children.size())
{
data->status = childStatus;
}
else
{
context->start(newData->children[newData->index]);
}
}
void MyRandomSequenceAtomsCompositeNode::terminate(Atoms::BehaviourTreeContext* context, State* data)
{
}
void MyRandomSequenceAtomsCompositeNode::releaseData(Atoms::BehaviourTreeContext* context, State* data)
{
if (data->ptrValue)
{
// release the local storage
auto newData = reinterpret_cast<RandomSequenceData*>(data->ptrValue);
context->releaseMemory(newData);
data->ptrValue = nullptr;
}
}
Those are the final files:
Related content
Copyright © 2017, Toolchefs LTD.