In this section, we are going to create a random sequence behaviour tree composite.
Implement the Atoms composite class.
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(); } static Atoms::Behaviour* creator() { return new MyRandomSequenceAtomsCompositeNode(); } public: };
Add attributes to the Atoms composite class
We need to expose the same attributes here using the Atoms::Blackboard*Value classes.
#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(); } static Atoms::Behaviour* creator() { return new MyRandomSequenceAtomsCompositeNode(); } public: Atoms::BlackboardIntValue m_seed; bool m_useAgentIdAsSeed; bool m_useAnimatedSeed; };
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/Composite.h> #include <AtomsCore/Metadata/IntMetadata.h> #include <Atoms/BehaviourTree/BehaviourTreeContext.h> class MyRandomSequenceAtomsCompositeNode: public Atoms::Composite { public: ... virtual void setAttributes(const AtomsCore::MapMetadata* attributes, Atoms::Blackboard* blackboard) override { if (!attributes) return; Composite::setAttributes(attributes, blackboard); getBlackboardValueFromAttributes<AtomsCore::IntMetadata, int>(attributes, blackboard, "seed", m_seed); auto useAgentIdAsSeedMeta = attributes->getTypedEntry<AtomsCore::BoolMetadata>("useAgentIdAsSeed"); if (useAgentIdAsSeedMeta) m_useAgentIdAsSeed = useAgentIdAsSeedMeta->value(); auto useAnimatedSeedMeta = attributes->getTypedEntry<AtomsCore::BoolMetadata>("useAnimatedSeed"); if (useAnimatedSeedMeta) m_useAnimatedSeed = useAnimatedSeedMeta->value(); } virtual void getAttributes(AtomsCore::MapMetadata* attributes, Blackboard* blackboard) override { Composite::getAttributes(attributes, blackboard); AtomsCore::IntMetadata seedMeta(m_seed.value); attributes->addEntry("seed", &seedMeta); AtomsCore::BoolMetadata useAgentIdAsSeedMeta(m_useAgentIdAsSeed); attributes->addEntry("useAgentIdAsSeed", &useAgentIdAsSeedMeta); AtomsCore::BoolMetadata useAnimatedSeedMeta(m_useAnimatedSeed); attributes->addEntry("useAnimatedSeed", &useAnimatedSeedMeta); } virtual void getAttributeProperties(AtomsCore::MapMetadata* attributes) override { if (!attributes) return; Composite::getAttributeProperties(attributes); auto parameters = attributes->getTypedEntry<AtomsCore::MapMetadata>(Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_NODE_PARAMETERS_KEY); AtomsCore::StringMetadata description("Runs the child nodes from highest to lowest priority (top to bottom). Returns Success if all child nodes return Success while returns Failure if any of child nodes return Failure. It will shuffle its child nodes on each reset and execute them in the new order.<br/><br/><b>Success</b>: When all children finish in Success<br/><br/><b>Failure</b>: When any child returns Failure<br/><br/><b>Running</b> : When the current child is Running"); attributes->addEntry(GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_NODE_DESCRIPTION_KEY, &description); AtomsCore::StringMetadata tooltip; AtomsCore::MapMetadata attributeP; tooltip.set("The random generator seed"); attributeP.addEntry(Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_PARAMETER_DESCRIPTION_KEY, &tooltip); parameters->addEntry("seed", &attributeP); tooltip.set("Use the simulation to to compute the random generator seed"); attributeP.addEntry(Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_PARAMETER_DESCRIPTION_KEY, &tooltip); parameters->addEntry("useAnimatedSeed", &attributeP); tooltip.set("Use the agent id to compute the random generator seed"); attributeP.addEntry(Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_PARAMETER_DESCRIPTION_KEY, &tooltip); parameters->addEntry("useAgentIdAsSeed", &attributeP); } ... };
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: ... 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; ... };
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; if (m_Children.size() > 0) // check if there is any child { data->status = Status::SUSPENDEND; // put in susped mode since no child is started auto newData = context->allocateMemory<RandomSequenceData>(); //allocate memory for the local custom storage newData->children = m_Children; // copy the children vectors - we are going to shuffle it { //BehaviourTreeContext::AgentScopeLock guard(context->agent); if (lock) { context->agent->lock(); } unsigned int seed = context->getBlackboardValue(m_seed); // mget the seed from the default value or the blackboard if (lock) { context->agent->unlock(); } if (m_useAnimatedSeed) seed += context->tickCount; if (m_useAgentIdAsSeed && context->agent && context->agent->groupId()) seed += context->agent->groupId()->value(); seed += context->tickCount; std::default_random_engine e(seed); std::shuffle(newData->children.begin(), newData->children.end(), e); //chuffle randomly the children vector } 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) // the current child is in runnign state so put the sequence in runnign as well { data->status = childStatus; return; } if (!data->ptrValue || childStatus == Status::FAILURE) // the child failed, so set the state to failure as well { data->status = childStatus; return; } auto newData = reinterpret_cast<RandomSequenceData*>(data->ptrValue); if (++newData->index >= m_Children.size()) //a child is finish so go to the next one or return succes if there is no more children { 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; } }
Now it is time to register the node in a plugin
extern "C" { ATOMSPLUGIN_EXPORT bool initializePlugin() { AtomsUtils::Logger::info() << "Loading MyRandomSequenceBTNode plugin"; // Register the node to the factory auto& factory = Atoms::BehaviourFactory::instance(); factory.registerBehaviour(MyRandomSequenceAtomsCompositeNode::staticTypeName(), &MyRandomSequenceAtomsCompositeNode::creator, Atoms::BehaviourFactory::kNative, true, "Composite"); return true; } ATOMSPLUGIN_EXPORT bool unitializePlugin() { AtomsUtils::Logger::info() << "Unloading MyRandomSequenceBTNode plugin"; // Deregister the node from the node factory auto& factory = Atoms::BehaviourFactory::instance(); manager.deregisterBehaviour(MyRandomSequenceAtomsCompositeNode::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.