In this section we are going to create a new behaviour module: a follow target module.
Set up Atoms
Add the AtomsUnreal plugin to your unreal projects.
Open the Plugins dialog from the Edit menu. Go inside the “Installed” section and enable the AtomsUnreal plugin.
Before you can start to create the behaviour module, you need to add the AtomsUnreal and AtomsRealtime dependency to your build config file.
Open your Build.cs file in your project, then add the “AtomsUnreal“ and “AtomsRealtime” modules.
Code Block | ||
---|---|---|
| ||
using UnrealBuildTool; public class AtomsDemo : ModuleRules { public AtomsDemo(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange( new string[] { "Core", "CoreUObject", "Engine", "InputCore", "AtomsRealtime", "AtomsUnreal" }); PrivateDependencyModuleNames.AddRange(new string[] { }); } } |
Create the Atoms module
In your project, create a new header file called MyFollowTargetBehaviourModule.h.
In this file create a new class and inherit from the base Atoms::BehaviourModule class. Define a constructor, a destructor and a static creator function (needed by Atoms to create an instance of this module).
Code Block | ||
---|---|---|
| ||
#pragma once #include <Atoms/BehaviourModule.h> class MyFollowTargetModule: public Atoms::BehaviourModule { public: MyFollowTargetModule(); virtual ~MyFollowTargetModule(); static Atoms::BehaviourModule* creator(const std::string& parameter); }; |
Now add a MyFollowTargetBehaviourModule.cpp file to your project.
In this file implement the constructor, destructor and creator functions.
Code Block | ||
---|---|---|
| ||
#include "MyFollowTargetBehaviourModule.h" MyFollowTargetModule::MyFollowTargetModule() : Atoms::BehaviourModule() { } MyFollowTargetModule::~MyFollowTargetModule() { } Atoms::BehaviourModule* MyFollowTargetModule::creator(const std::string& parameter) { return new MyFollowTargetModule(); } |
Register the behaviour module
Now you need to register your new module. Go inside the StartupModule() function of your game module and register your new behaviour module.
Code Block | ||
---|---|---|
| ||
#include "MyFollowTargetBehaviourModule.h" #include <Atoms/BehaviourModules.h> void FAtomsDemoGameModule::StartupModule() { Atoms::BehaviourModules& moduleManager = Atoms::BehaviourModules::instance(); moduleManager.registerBehaviourModule( "MyFollowTarget", &MyFollowTargetModule::creator, Atoms::BehaviourModules::kNative ); } |
Now de-register the module inside the ShutdownModule() function of your game module.
Code Block | ||
---|---|---|
| ||
void FAtomsDemoGameModule::ShutdownModule() { Atoms::BehaviourModules& moduleManager = Atoms::BehaviourModules::instance(); moduleManager.deregisterBehaviourModule("MyFollowTarget"); } |
Add input Attributes
You need to add now some input attributes.
For this module, you need only a target position attribute. The attributes of the module are stored inside a MapMetadata object, you can add new attributes using the "addAttribute" function. Attributes should always be added inside the constructor.
The third attribute of the addAttribute method defines if the attribute is overridable per agent or not. In this example, we'll make it overridable.
Code Block |
---|
#include <AtomsCore/Metadata/Vector3Metadata.h> MyFollowTargetModule(): Atoms::BehaviourModule() { AtomsCore::Vector3Metadata targetPosition(AtomsCore::Vector3(0.0, 0.0, 0.0)); // The last argumet specify if this attribute can be override per agent addAttribute("targetPosition", &targetPosition, true); } |
Implement the behaviour logic
Now you need to compute the new agent direction at each frame before the agent pose is computed. This will be done inside the initFrame method.
For this example we won't need to override any other method.
Please have a look to the Behaviour Module documentation if you want implement other methods.
Code Block | ||
---|---|---|
| ||
#pragma once #include <Atoms/BehaviourModule.h> class MyFollowTargetModule : public Atoms::BehaviourModule { public: MyFollowTargetModule(); virtual ~MyFollowTargetModule(); void initFrame(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup = nullptr) override; static Atoms::BehaviourModule* creator(const std::string& parameter); }; |
In the initFrame function get the targetPosition value, iterate over each agent to read the position and set their new direction.
Code Block |
---|
#include <Atoms/Agent.h> #include <Atoms/GlobalNames.h> void MyFollowTargetModule::initFrame(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup) { // Get the target position from the moduel attribute AtomsCore::MapMetadata& attributeMap = attributes(); AtomsCore::Vector3& targetPosition = attributeMap.getTypedEntry<AtomsCore::Vector3Metadata>("targetPosition")->get(); // iterate over each agent for (Atoms::Agent* agent : agents) { if (!agent) continue; //get the agent position AtomsPtr<AtomsCore::Vector3Metadata> positionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>( Atoms::GlobalNameKeys::ATOMS_AGENT_POSITION_KEY ); AtomsPtr<AtomsCore::Vector3Metadata> directionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>( Atoms::GlobalNameKeys::ATOMS_AGENT_DIRECTION_KEY ); if (!positionPtr || !directionPtr) continue; // compute the new direction directionPtr->set((targetPosition - positionPtr->get()).normalized()); } } |
Use per agent attributes
The target position attribute is overridable per agent, but the code to handle it isn't there yet. When you override an attribute, a new map metadata named "AttributeName_override" is created. The keys of this map metadata are the agent group ids, but they will be present only when the user specifies an override from inside his 3D application.
Use the function getAttributePerAgent
to get the value per agent. This function checks if there is an override and returns the value, otherwise returns the default value.
Code Block | ||
---|---|---|
| ||
#include <AtomsCore/Metadata/IntMetadata.h> void MyFollowTargetModule::initFrame(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup) { // Get the target position from the moduel attribute AtomsCore::MapMetadata& attributeMap = attributes(); AtomsCore::Vector3& targetPosition = attributeMap.getTypedEntry<AtomsCore::Vector3Metadata>("targetPosition")->get(); //get the override map metadata AtomsPtr<AtomsCore::MapMetadata> targetPositionOverride = attributeMap.getTypedEntry<AtomsCore::MapMetadata>("targetPosition_override"); // iterate over each agent for (Atoms::Agent* agent : agents) { if (!agent) continue; //get the agent position AtomsPtr<AtomsCore::Vector3Metadata> positionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>( Atoms::GlobalNameKeys::ATOMS_AGENT_POSITION_KEY ); AtomsPtr<AtomsCore::Vector3Metadata> directionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>( Atoms::GlobalNameKeys::ATOMS_AGENT_DIRECTION_KEY ); if (!positionPtr || !directionPtr) continue; // get the agent group id AtomsPtr<AtomsCore::IntMetadata> groupIdPtr = agent->metadata().getTypedEntry<AtomsCore::IntMetadata>( Atoms::GlobalNameKeys::ATOMS_AGENT_GROUPID_KEY); if (!groupIdPtr) continue; //convert the groupId to a string since the map metadata use only strings as key std::string groupIdStr = std::to_string(groupIdPtr->value()); auto overrideTargetPos = getAttributePerAgent(targetPosition, targetPositionOverride.get(), groupIdStr); directionPtr->set((overrideTargetPos - positionPtr->get()).normalized()); } } |
Improve performance
If you want to improve the performance of the module you can parallelize the main for loop.
Code Block |
---|
#include <AtomsCore/Metadata/IntMetadata.h> #include <AtomsUtils/TaskScheduler.h> void MyFollowTargetModule::initFrame(const std::vector<Atoms::Agent*>& agents, Atoms::AgentGroup* agentGroup) { // Get the target position from the moduel attribute AtomsCore::MapMetadata& attributeMap = attributes(); AtomsCore::Vector3& targetPosition = attributeMap.getTypedEntry<AtomsCore::Vector3Metadata>("targetPosition")->get(); //get the override map metadata AtomsPtr<AtomsCore::MapMetadata> targetPositionOverride = attributeMap.getTypedEntry<AtomsCore::MapMetadata>("targetPosition_override"); // iterate over each agent AtomsUtils::parallel_for(AtomsUtils::ParallelForRange(0, agents.size()), [&](const AtomsUtils::ParallelForRange& r TASK_PARTITION_EXTRA_ARGS) { std::string groupIdStr; for (unsigned int i = r.begin(); i < r.end(); i++) { Atoms::Agent* agent = agents[i]; if (!agent) continue; //get the agent position AtomsPtr<AtomsCore::Vector3Metadata> positionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>( Atoms::GlobalNameKeys::ATOMS_AGENT_POSITION_KEY ); AtomsPtr<AtomsCore::Vector3Metadata> directionPtr = agent->metadata().getTypedEntry<AtomsCore::Vector3Metadata>( Atoms::GlobalNameKeys::ATOMS_AGENT_DIRECTION_KEY ); if (!positionPtr || !directionPtr) continue; // get the agent group id AtomsPtr<AtomsCore::IntMetadata> groupIdPtr = agent->metadata().getTypedEntry<AtomsCore::IntMetadata>( Atoms::GlobalNameKeys::ATOMS_AGENT_GROUPID_KEY); if (!groupIdPtr) continue; //convert the groupId to a string since the map metadata use only strings as key groupIdStr = std::to_string(groupIdPtr->value()); auto overrideTargetPos = getAttributePerAgent(targetPosition, targetPositionOverride.get(), groupIdStr); directionPtr->set((overrideTargetPos - positionPtr->get()).normalized()); } }); } |
Create an UAtomsBehaviourComponent
The atoms behaviour module is now ready. Now you need to create an UAtomsBehaviourComponent in order to use your behaviour module in unreal.
In your project create a new class MyFollowtargetBehaviourComponent and inherit from the UAtomsBehaviourComponent.
You need to add these arguments to the UCLASS.
Code Block |
---|
UCLASS(ClassGroup = (AtomsBehaviours), meta = (BlueprintSpawnableComponent)) class ATOMSDEMO_API UMyFollowtargetBehaviourComponent : public UAtomsBehaviourComponent { ... } |
You need to add the constructor and destructor.
Code Block |
---|
UCLASS(ClassGroup = (AtomsBehaviours), meta = (BlueprintSpawnableComponent)) class ATOMSDEMO_API UMyFollowtargetBehaviourComponent : public UAtomsBehaviourComponent { GENERATED_BODY() public: UMyFollowtargetBehaviourComponent(); ~UMyFollowtargetBehaviourComponent(); }; |
Implement the constructor setting the AtomsBehaviourModule
member. This variable contains the name of the Atoms behaviour module that this component creates.
Code Block |
---|
UMyFollowtargetBehaviourComponent::UMyFollowtargetBehaviourComponent(): UAtomsBehaviourComponent() { AtomsBehaviourModule = "MyFollowTarget"; } UMyFollowtargetBehaviourComponent::~UMyFollowtargetBehaviourComponent() { } |
Add attributes uproperty
You need now to expose the target position property and pass it’s value to the atoms behaviour module.
Add an FVector property to your class and call it “targetPosition”. This is the same name of the attribute inside the atoms behaviour module. Set the property as BlueprintReadOnly and EditAnywhere. Add the “Behaviour“ category. Then add as meta, AtomsType=”5”. The value 5 is the atoms metadata type id for the vector3metadata (you can find all the type id below).
This value is important because the component tries to translate automatically the uproperty on the component to the attributes of the atoms module. For each atoms attribute it searches for a property with the same name, then it checks the AtomsType to be sure that it can be converted.
To expose the per-agent override attributute you need to add a uproperty for type TMap<int32, FVector>. The arguments of the uproperty are similar, except that The AtomsType=”1” since it is a MapMetadata and add AtomsOverride = "targetPosition" to the meta. This tell atoms which is the attribute that is overriding.
The In Then inside the constructor you need to fill the AtomsTypes and AtomsOverrides map passing the data type for each property and the override information like in the example on the righright.
Code Block | ||
---|---|---|
| ||
UCLASS(ClassGroup = (AtomsBehaviours), meta = (BlueprintSpawnableComponent)) class ATOMSDEMO_API UMyFollowtargetBehaviourComponent : public UAtomsBehaviourComponent { GENERATED_BODY() public: UMyFollowtargetBehaviourComponent(); ~UMyFollowtargetBehaviourComponent(); UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Behaviour", meta = (AtomsType = "5")) FVector targetPosition; UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Behaviour", meta = (AtomsType = "1", AtomsOverride = "targetPosition")) TMap<int32, FVector> targetPositionOverride; }; UMyFollowtargetBehaviourComponent::UMyFollowtargetBehaviourComponent(): UAtomsBehaviourComponent() { if (!AtomsTypes.Contains("targetPosition")) AtomsTypes.Add("targetPosition", 5); if (!AtomsTypes.Contains("targetPositionOverride")) AtomsTypes.Add("targetPositionOverride", 1); if (!AtomsOverrides.Contains("targetPositionOverride")) AtomsOverrides.Add("targetPositionOverride", "targetPosition"); } |
The property translation system and the OnBehaviourModuleCreated
method are called only once at the beginning of the simulation. If you want to update the attributes during the simulation you need to turn on the UpdateOnTick property on the behaviour component and implement the method OnBehaviourModuleUpdated for custom translations. If you want update the attributes manually outside the behaviour component use the SetModuleProperty_As* blueprint function.
The module is now ready to be used. Create an agent group with a grid layout and a clipReader or state machine module. Add the new MyFollowTarget component and set the target position property. Press play and you are done!