Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 7 Next »

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.

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).

#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.

#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.

#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.

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.

#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.

#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.

#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.

#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.

#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.

UCLASS(ClassGroup = (AtomsBehaviours), meta = (BlueprintSpawnableComponent))
class ATOMSDEMO_API UMyFollowtargetBehaviourComponent : public UAtomsBehaviourComponent
{
  ...
}

You need to add the constructor and destructor.

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.

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 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.

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:

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

This is called after the automatic translation.

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!

MyFollowTargetModule.mp4

  • No labels