Creating a new Behaviour Tree task
In this section, we are going to create a simple wait behaviour tree task.
Implement the Atoms behaviour class.
Create a new MyWaitAtomsBehaviourTreeTaskNode
class.
#include <Atoms/BehaviourTree/Behaviour.h>
#include <Atoms/BehaviourTree/BlackboardValue.h>
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(); }
static Atoms::Behaviour* creator();
public:
};
Atoms::Behaviour* MyWaitAtomsBehaviourTreeTaskNode ::creator() { return new MyWaitAtomsBehaviourTreeTaskNode (); }
Add attributes to the Atoms behaviour class
We need to expose the same attributes here using the Atoms::Blackboard*Value classes.
#include <Atoms/BehaviourTree/Behaviour.h>
#include <Atoms/BehaviourTree/BlackboardValue.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(); }
static Atoms::Behaviour* creator();
public:
Atoms::BlackboardDoubleValue waitTime;
bool useSeconds;
};
Atoms::Behaviour* MyWaitAtomsBehaviourTreeTaskNode ::creator() { return new MyWaitAtomsBehaviourTreeTaskNode (); }
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);
auto useSecondsPtr = attributes->getTypedEntry<AtomsCore::BoolMetadata>("useSeconds");
if (useSecondsPtr)
useSeconds = useSecondsPtr->value();
}
static Atoms::Behaviour* creator();
public:
Atoms::BlackboardDoubleValue waitTime;
bool useSeconds;
};
Atoms::Behaviour* MyWaitAtomsBehaviourTreeTaskNode ::creator() { return new MyWaitAtomsBehaviourTreeTaskNode (); }
Then we need to implement the getAttributes and getAttributesProperties. Those methods are used by the behaviour tree editor. The editor calls the getAttributes to know which attributes the node has. The getAttributesProperties is used to customize the appearance of the attributes inside the editor.
class MyWaitAtomsBehaviourTreeTaskNode : public Atoms::Behaviour
{
public:
...
virtual void getAttributes(AtomsCore::MapMetadata* attributes, Blackboard* blackboard) override
{
if (!attributes)
return;
Behaviour::getAttributes(attributes, blackboard);
AtomsCore::DoubleMetadata waitTimeMeta(waitTime.value);
attributes->addEntry("waitTime", &waitTimeMeta);
AtomsCore::BoolMetadata useSecondsMeta(useSeconds);
attributes->addEntry("useSeconds", &useSecondsMeta);
}
virtual void getAttributeProperties(AtomsCore::MapMetadata* attributes) override
{
if (!attributes)
return;
Behaviour::getAttributeProperties(attributes);
auto parameters = attributes->getTypedEntry<AtomsCore::MapMetadata>(Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_NODE_PARAMETERS_KEY);
AtomsCore::StringMetadata description("Makes the agent wait for the given time.<br/><br/><b>Success</b>: When the timer is expired.<br/><br/><b>Failure</b>: When the timer could not be created.<br/><br/><b>Running</b>: While the timer is running.");
attributes->addEntry(GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_NODE_DESCRIPTION_KEY, &description);
AtomsCore::StringMetadata tooltip;
AtomsCore::MapMetadata attributeP;
tooltip.set("Wait time");
attributeP.addEntry(Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_PARAMETER_DESCRIPTION_KEY, &tooltip);
parameters->addEntry("waitTime", &attributeP);
tooltip.set("Use time in seconds. If off, the node uses the time in tickCount(Unreal) or frame (Maya/Houdini)");
attributeP.addEntry(Atoms::GlobalNameKeys::ATOMS_BEHAVIOUR_TREE_PARAMETER_DESCRIPTION_KEY, &tooltip);
parameters->addEntry("useSeconds", &attributeP);
}
...
};
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:
...
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 unlock 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)
{
}
Now it is time to register the node in a plugin
extern "C"
{
ATOMSPLUGIN_EXPORT bool initializePlugin()
{
AtomsUtils::Logger::info() << "Loading MyWaitBTNode plugin";
// Register the node to the factory
auto& factory = Atoms::BehaviourFactory::instance();
factory.registerBehaviour(MyWaitAtomsBehaviourTreeTaskNode::staticTypeName(), &MyWaitAtomsBehaviourTreeTaskNode::creator, Atoms::BehaviourFactory::kNative, true, "Behaviour");
return true;
}
ATOMSPLUGIN_EXPORT bool unitializePlugin()
{
AtomsUtils::Logger::info() << "Unloading MyWaitBTNode plugin";
// Deregister the node from the node factory
auto& factory = Atoms::BehaviourFactory::instance();
manager.deregisterBehaviour(MyWaitAtomsBehaviourTreeTaskNode::staticTypeName());
return true;
}
}
Compile
Use this cmake template:
cmake_minimum_required (VERSION 3.17)
project ("MyAtomsPlugin")
if (MSVC)
cmake_policy(SET CMP0091 NEW)
endif()
enable_language(CXX)
set(CMAKE_CXX_STANDARD 14)
add_definitions(-DUSE_ATOMSAVOIDANCEPLUGIN)
add_definitions(-DBUILD_ATOMSAVOIDANCE)
add_definitions(-DTBB_USE_DEBUG=0)
add_definitions(-D__TBB_LIB_NAME=tbb)
add_definitions(-DNOMINMAX)
add_definitions(-DBOOST_ALL_NO_LIB)
if (UNIX)
add_definitions(-DLINUX)
add_definitions(-DPIC)
add_definitions(-D_FILE_OFFSET_BITS=64)
add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0)
add_compile_options(-std=c++14 -ffloat-store -fvisibility=hidden -fPIC)
endif()
if(DEFINED ATOMS_MAYA_FOLDER)
include_directories(${ATOMS_MAYA_FOLDER}/include)
link_directories(${ATOMS_MAYA_FOLDER}/lib/${MAYA_MAJOR_VERSION})
set(ATOMS_CUSTOM_PLUGINS_FOLDER ${ATOMS_MAYA_FOLDER}/plugins/${MAYA_MAJOR_VERSION})
endif()
if(DEFINED ATOMS_HOUDINI_FOLDER)
include_directories(${ATOMS_HOUDINI_FOLDER}/include)
link_directories(${ATOMS_HOUDINI_FOLDER}/lib)
set(ATOMS_CUSTOM_PLUGINS_FOLDER ${ATOMS_HOUDINI_FOLDER}/plugins)
endif()
include_directories(include)
set(SOURCES
"MyAtomsPlugin.cpp")
add_library(${PROJECT_NAME} SHARED ${SOURCES})
if (MSVC)
set_property(TARGET ${PROJECT_NAME} PROPERTY
MSVC_RUNTIME_LIBRARY MultiThreadedDLL)
endif()
set_target_properties(${PROJECT_NAME}
PROPERTIES
INSTALL_RPATH_USE_LINK_PATH True)
target_link_libraries(${PROJECT_NAME}
PUBLIC Atoms AtomsCore AtomsGraph AtomsUtils AtomsMath
)
install(TARGETS ${PROJECT_NAME} DESTINATION ${ATOMS_CUSTOM_PLUGINS_FOLDER})
Related content
Copyright © 2017, Toolchefs LTD.