Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

In this section, we are going to create a new behaviour module: a the 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 creating 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
languagec#
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
languagecpp
#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
languagecpp
#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
languagecpp
#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
languagecpp
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 module's 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 at the Behaviour Module documentation if you want to implement other methods.

Code Block
languagecpp
#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
languagecpp
#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 it returns the default value.

Code Block
languagecpp
#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()
{

}
The property translation system

Add attributes uproperty

You need now to expose the target position property and pass it’s its 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 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 righ.

Code Block
languagecpp
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");
}

Add custom property

The automatic property translation system works only with these types:

  • bool: BoolMetadata, AtomsType: 13

  • int: IntMetadata, AtomsType: 3

  • float: DoubleMetadata, AtomsType: 4

  • FString: StringMetadata, AtomsType: 10

  • FVector: Vector3Metadata, AtomsType: 5

  • FVector2D: Vector2Metadata, AtomsType: 27

  • FVector4: Vector4Metadata, AtomsType: 6

  • FMatrix: MatrixMetadata, AtomsType: 7

  • FRotator: EulerMetadata, AtomsType: 9

  • FBox: Box3Metadata, AtomsType: 21

  • FBox2D: Box2Metadata, AtomsType: 29

  • TArray<bool>: BoolMetadata, AtomsType: 14

  • TArray<int>: IntArrayMetadata, AtomsType: 11

  • TArray<float>: DoubleArrayMetadata, AtomsType: 12

  • TArray<FString>: StringArrayMetadata, AtomsType: 17

  • TArray<FVector>: Vector3ArrayMetadata, AtomsType: 15

  • TArray<FVector2D>: Vector2ArrayMetadata, AtomsType: 28

  • TArray<FVector4>: Vector4ArrayMetadata, AtomsType: 16

  • TArray<FMatrix>: MatrixArrayMetadata, AtomsType: 19

  • TArray<FRotator>: EulerArrayMetadata, AtomsType: 18

  • TArray<FBox>: Box3ArrayMetadata, AtomsType: 69

  • TArray<FBox2D>: Box2ArrayMetadata, AtomsType: 69

  • AAtomsSplineActor*: CurveMetadata, AtomsType: 103

  • TArray<AAtomsSplineActor*>: CurveArrayMetadata, AtomsType: 104

  • AStaticMeshActorP* MeshMetadata, AtomsType: 101

  • TArray<AStaticMeshActor*>: MeshArrayMetadata, AtomsType: 105

  • AAtomsAgentGroup*: StringMetada, AtomsType: 9900

  • TArray<AAtomsAgentGroup*>: StringArrayMetadata, AtomsType: 9901

  • UAtomsAgentTypeAsset*: StringMetadata, AtomsType: 9902

  • TArray<UAtomsAgentTypeAsset*>: StringArrayMetadata, AtomsType: 9903

  • UAtomsStateMachine*: StringMetadata, AtomsType: 9904

  • UAtomsCache*: StringMetadata, AtomsType: 9905

  • UAnimSequence*: StringMetadata, AtomsType: 9906

  • TArray<UAnimSequence*>: StringArrayMetadata, AtomsType: 9907

If you need to translate more complex data you need to override the method:

Code Block
virtual void UAtomsBehaviourComponent::OnBehaviourModuleCreated(AtomsPtr<Atoms::BehaviourModule>& module);

This is called after the automatic translation.

To expose the per-agent override attribute you need to add a uproperty for type TMap<int32, FVector>. The int32 key contains the agent group Id, while the vector value contains the override for that agent.

Now you need to implement the GetAtomsAttributes member to translate the unreal properties to the atoms module attributes. Luckily there are some defines that you can use to translate the properties.

Code Block
languagecpp
UCLASS(ClassGroup = (AtomsBehaviours), meta = (BlueprintSpawnableComponent))
class ATOMSDEMO_API UMyFollowtargetBehaviourComponent : public UAtomsBehaviourComponent
{
	GENERATED_BODY()

public:

	UMyFollowtargetBehaviourComponent();

	~UMyFollowtargetBehaviourComponent();
	
	virtual void GetAtomsAttributes(AtomsPtr<AtomsCore::MapMetadata>& attributes) override;

	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Behaviour")
	FVector targetPosition;
	
	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Behaviour")
	TMap<int32, FVector> targetPositionOverride;	
};

Code Block
languagecpp
#include "AtomsModuleGeneratorUtils.h"
#include "AtomsAttributeUtils.h"
#include "Actors/AtomsAgentGroup.h"

UMyFollowtargetBehaviourComponent::UMyFollowtargetBehaviourComponent(): UAtomsBehaviourComponent()
{
}

void UMyFollowtargetBehaviourComponent::GetAtomsAttributes(AtomsPtr<AtomsCore::MapMetadata>& attributes)
{
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(targetPosition, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META_OVERRIDE(targetPosition_override, targetPositionOverride, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
}

Add Setter functions

The last step is to expose functions to set the component properties and update the atoms module attributes simultaneously.

We need to add a SetTargetPosition/SetTargetPositionOverride and RemoveSetTargetPositionOverride. The sequencer will also use the SetTargetPosition. So you need to implement this function if you want to drive this property by the sequencer.

Code Block
virtual void UAtomsBehaviourComponent::OnBehaviourModuleCreated(AtomsPtr<Atoms::BehaviourModule>& module);

This is called after the automatic translation.

Code Block
languagecpp
UCLASS(ClassGroup = (AtomsBehaviours), meta = (BlueprintSpawnableComponent))
class ATOMSUNREAL26CPPTEST_API UMyFollowtargetBehaviourComponent : public UAtomsBehaviourComponent
{
	GENERATED_BODY()
	
public:

	UMyFollowtargetBehaviourComponent();

	~UMyFollowtargetBehaviourComponent();

	virtual void GetAtomsAttributes(AtomsPtr<AtomsCore::MapMetadata>& attributes) override;

	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Behaviour")
	FVector targetPosition;

	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Behaviour")
	TMap<int32, FVector> targetPositionOverride;


	/** Set target position */
	UFUNCTION(BlueprintCallable, Category = "FollowTargetBehaviourComponent", meta = (CallInEditor = "true"))
	void SetTargetPosition(const FVector& Value);

	/** Set target position override */
	UFUNCTION(BlueprintCallable, Category = "FollowTargetBehaviourComponent", meta = (CallInEditor = "true"))
	void SetTargetPositionOverride(const FVector& Value, const int32 AgentId);

	/** Remove target position override */
	UFUNCTION(BlueprintCallable, Category = "FollowTargetBehaviourComponent", meta = (CallInEditor = "true"))
	void RemoveSetTargetPositionOverride(const int32 AgentId);
};
Code Block
languagecpp
#include "AtomsModuleGeneratorUtils.h"
#include "AtomsAttributeUtils.h"
#include "Actors/AtomsAgentGroup.h"

void UMyFollowtargetBehaviourComponent::SetTargetPosition(const FVector& Value)
{
	targetPosition = Value;
	ATOMS_BEHAVIOUR_COMPONENT_GET_ATTRIBUTES()
	ATOMS_BEHAVIOUR_COMPONENT_CONVERT_META(targetPosition, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3);
}


void UMyFollowtargetBehaviourComponent::SetTargetPositionOverride(const FVector& Value, const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_SET_META_OVERRIDE(targetPosition_override, targetPositionOverride, AtomsCore::Vector3Metadata, AtomsConverter::ToVector3, AgentId);
}


void UMyFollowtargetBehaviourComponent::RemoveSetTargetPositionOverride(const int32 AgentId)
{
	ATOMS_BEHAVIOUR_COMPONENT_REMOVE_OVERRIDE(targetPosition_override, targetPositionOverride, AgentId);
}

The GetAtomsAttributes 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 to update the attributes manually outside the behaviour component, use the SetModuleProperty_AsSet* blueprint functionfunction.

Final source files:

View file
nameMyFollowTargetBehaviourModule.h
View file
nameMyFollowTargetBehaviourModule.cpp
View file
nameMyFollowtargetBehaviourComponent.h
View file
nameMyFollowtargetBehaviourComponent.cpp

.

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!

MyFollowTargetModule.mp4