Creating a new Behaviour Tree decorator
In this section, we are going to create a simple repeat behaviour tree decorator.
Implement the Atoms decorator class.
Create a new MyRepeatAtomsBehaviourTreeDecoratorNode
class.
#include <Atoms/BehaviourTree/Decorator.h>
#include <Atoms/BehaviourTree/BlackboardValue.h>
#include <Atoms/BehaviourTree/ConditionValue.h>
class MyRepeatAtomsBehaviourTreeDecoratorNode: public Atoms::Decorator
{
public:
MyRepeatAtomsBehaviourTreeDecoratorNode(): Atoms::Decorator()
{}
virtual ~MyRepeatAtomsBehaviourTreeDecoratorNode() {}
static const char* staticTypeName() { return "MyRepeat"; }
virtual const char* typeName() const override { return staticTypeName(); }
static Atoms::Behaviour* creator() { return new MyRepeatAtomsBehaviourTreeDecoratorNode(); }
public:
};
Add attributes to the Atoms decorator class
We need to expose the same attributes here using the Atoms::Blackboard*Value classes.
#include <Atoms/BehaviourTree/Decorator.h>
#include <Atoms/BehaviourTree/BlackboardValue.h>
#include <Atoms/BehaviourTree/ConditionValue.h>
class MyRepeatAtomsBehaviourTreeDecoratorNode: public Atoms::Decorator
{
public:
MyRepeatAtomsBehaviourTreeDecoratorNode(): Atoms::Decorator(), repeatTimes(1), repeatForever(false)
{}
virtual ~MyRepeatAtomsBehaviourTreeDecoratorNode() {}
static const char* staticTypeName() { return "MyRepeat"; }
virtual const char* typeName() const override { return staticTypeName(); }
static Atoms::Behaviour* creator() { return new MyRepeatAtomsBehaviourTreeDecoratorNode(); }
public:
Atoms::BlackboardIntValue repeatTimes;
Atoms::BlackboardBoolValue repeatForever;
};
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.
Then implement the getAttributes and getAttributesProperty. These methods are used by the behaviour tree editor to inspect and customize the widget for this node.
#include <Atoms/BehaviourTree/Decorator.h>
#include <AtomsCore/Metadata/IntMetadata.h>
#include <AtomsCore/Metadata/DoubleMetadata.h>
#include <AtomsCore/Metadata/StringMetadata.h>
#include <Atoms/BehaviourTree/BehaviourTreeContext.h>
class MyRepeatAtomsBehaviourTreeDecoratorNode: public Atoms::Decorator
{
public:
MyRepeatAtomsBehaviourTreeDecoratorNode(): Atoms::Decorator(), repeatTimes(1), repeatForever(false)
{}
virtual ~MyRepeatAtomsBehaviourTreeDecoratorNode() {}
static const char* staticTypeName() { return "MyRepeat"; }
virtual const char* typeName() const override { return staticTypeName(); }
static Atoms::Behaviour* creator() { return new MyRepeatAtomsBehaviourTreeDecoratorNode(); }
virtual void setAttributes(const AtomsCore::MapMetadata* attributes, Atoms::Blackboard* blackboard) override
{
if (!attributes)
return;
Atoms::Decorator::setAttributes(attributes, blackboard);
Atoms::getBlackboardValueFromAttributes<AtomsCore::IntMetadata, int>(attributes, blackboard, "repeatTimes", repeatTimes);
Atoms::getBlackboardValueFromAttributes<AtomsCore::BoolMetadata, bool>(attributes, blackboard, "repeatForever", repeatForever);
}
virtual void getAttributes(AtomsCore::MapMetadata* attributes, Blackboard* blackboard) override
{
if (!attributes)
return;
Decorator::getAttributes(attributes, blackboard);
BT_BEHAVIOUR_DEFINE_GET_ATTRIBUTE(AtomsCore::IntMetadata, repeatTimes);
BT_BEHAVIOUR_DEFINE_GET_ATTRIBUTE(AtomsCore::BoolMetadata, repeatForever);
}
virtual void getAttributeProperties(AtomsCore::MapMetadata* attributes) override
{
if (!attributes)
return;
Decorator::getAttributeProperties(attributes);
auto parameters = attributes->getTypedEntry<AtomsCore::MapMetadata>(Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_NODE_PARAMETERS_KEY);
AtomsCore::StringMetadata description("Repeats the child node either a number of times or forever.<br/><br/><b>Success</b>: if the child node return Success on the last repeat.<br/><br/><b>Failure</b>: if the child node return Failure on the last repeat.<br/><br/><b>Running</b>: As long as it is repeating.");
attributes->addEntry(GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_NODE_DESCRIPTION_KEY, &description);
AtomsCore::StringMetadata tooltip;
AtomsCore::MapMetadata attributeP;
tooltip.set("Number of times to execute the child");
attributeP.addEntry(Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_PARAMETER_DESCRIPTION_KEY, &tooltip);
parameters->addEntry("repeatTimes", &attributeP);
tooltip.set("Repeat the child forever");
attributeP.addEntry(Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_PARAMETER_DESCRIPTION_KEY, &tooltip);
parameters->addEntry("repeatForever", &attributeP);
}
public:
Atoms::BlackboardIntValue repeatTimes;
Atoms::BlackboardBoolValue repeatForever;
};
It is time to implement the node logic. For a decorator 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/Decorator.h>
#include <AtomsCore/Metadata/IntMetadata.h>
#include <AtomsCore/Metadata/DoubleMetadata.h>
#include <AtomsCore/Metadata/StringMetadata.h>
#include <Atoms/BehaviourTree/BehaviourTreeContext.h>
class MyRepeatAtomsBehaviourTreeDecoratorNode: public Atoms::Decorator
{
public:
MyRepeatAtomsBehaviourTreeDecoratorNode(): Atoms::Decorator(), repeatTimes(1), repeatForever(false)
{}
virtual ~MyRepeatAtomsBehaviourTreeDecoratorNode() {}
...
virtual void initialize(BehaviourTreeContext* context, State* data) override;
virtual void update(BehaviourTreeContext* context, State* data) override;
virtual void onChildUpdated(BehaviourTreeContext* context, State* data, unsigned int childIndex, unsigned short childStatus) override;
public:
Atoms::BlackboardIntValue repeatTimes;
Atoms::BlackboardBoolValue repeatForever;
};
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 MyRepeatAtomsBehaviourTreeDecoratorNode::initialize(Atoms::BehaviourTreeContext* context, State* data)
{
// It is a decorator so put it in SUSPENDED mode when it is initialized
data->status = Status::SUSPENDEND;
// Local storage, int2Value[0] will contains the number of repeats,
// data->int2Value[1] contains 1 it is waiting for the child
data->int2Value[0] = 0;
data->int2Value[1] = 0;
}
void MyRepeatAtomsBehaviourTreeDecoratorNode::update(Atoms::BehaviourTreeContext* context, State* data)
{
// Check if it is already waiting for a child status
if (data->int2Value[1] == 0)
{
// check if it need to repeat again the child
if (context->getBlackboardValue(repeatForever) || (data->int2Value[0] <= context->getBlackboardValue(repeatTimes)))
{
// Flag as wait for child
data->int2Value[1] = 1;
// Start che child
if (!context->start(childIndex))
data->status = Status::FAILURE;
}
}
}
void MyRepeatAtomsBehaviourTreeDecoratorNode::onChildUpdated(Atoms::BehaviourTreeContext* context, State* data, unsigned int childIdx, unsigned short childStatus)
{
// If the child is running than wait for a different status
if (childStatus == Status::RUNNING)
{
data->status = Status::RUNNING;
return;
}
// A child returned success or failure, it has terminated. Reset the wait status
if (childStatus != Status::RUNNING)
data->int2Value[1] = 0;
if (childStatus == Status::SUCCESS)
{
// Lock the agent if the lock is active
if (lock)
{
context->agent->lock();
}
if (!context->getBlackboardValue(repeatForever) && ++data->int2Value[0] == context->getBlackboardValue(repeatTimes))
{
// All the repeats are terminated, so return the child status
data->status = childStatus;
}
else
{
// there are still repeats to do so keep in running mode
data->status = Status::RUNNING;
}
// Unlock the agent if the lock is active
if (lock)
{
context->agent->unlock();
}
return;
}
// Child returnsa failure so set the failure state on this decorator as well
if (childStatus == Status::FAILURE)
{
data->status = childStatus;
return;
}
}
Now it is time to register the node in a plugin
extern "C"
{
ATOMSPLUGIN_EXPORT bool initializePlugin()
{
AtomsUtils::Logger::info() << "Loading MyRepeatBTNode plugin";
// Register the node to the factory
auto& factory = Atoms::BehaviourFactory::instance();
factory.registerBehaviour(MyRepeatAtomsBehaviourTreeDecoratorNode::staticTypeName(), &MyRepeatAtomsBehaviourTreeDecoratorNode::creator, Atoms::BehaviourFactory::kNative, true, "Decorator");
return true;
}
ATOMSPLUGIN_EXPORT bool unitializePlugin()
{
AtomsUtils::Logger::info() << "Unloading MyRepeatBTNode plugin";
// Deregister the node from the node factory
auto& factory = Atoms::BehaviourFactory::instance();
manager.deregisterBehaviour(MyRepeatAtomsBehaviourTreeDecoratorNode::staticTypeName());
return true;
}
}
Compile
Windows:
In visual studio create a dll projects, add the atoms includes and add BUILD_ATOMSPLUGIN to the preprocessor definitions.
Linux:
Compile as a shared object adding the atoms includes and BUILD_ATOMSPLUGIN to the preprocessor definitions.
Related content
Copyright © 2017, Toolchefs LTD.