Movement with Mass Framework

How to process movement with Mass Framework. Mass Framework is a ECS for managing crowds and traffic in Unreal 5.0 and above.

Processing Movement with MassGameplay built-in resources

  • Movement Trait

    Built-in MassGameplay Trait located At UE5/Engine/Plugins/Runtime/MassGameplay/Source/MassMovement

    UCLASS(meta = (DisplayName = "Movement"))
    class MASSMOVEMENT_API UMassMovementTrait : public UMassEntityTraitBase
    {
    	GENERATED_BODY()
    
    protected:
    	virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const override;
    
    	UPROPERTY(Category="Movement", EditAnywhere)
    	FMassMovementParameters Movement;
    };
    void UMassMovementTrait::BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const
    {
    	FMassEntityManager& EntityManager = UE::Mass::Utils::GetEntityManagerChecked(World);
    
    	BuildContext.RequireFragment<FAgentRadiusFragment>();
    	BuildContext.RequireFragment<FTransformFragment>();
    
    	BuildContext.AddFragment<FMassVelocityFragment>();
    	BuildContext.AddFragment<FMassForceFragment>();
    
    	const FConstSharedStruct MovementFragment = EntityManager.GetOrCreateConstSharedFragment(Movement);
    	BuildContext.AddConstSharedFragment(MovementFragment);
    }

    Check Built-in Movement processor to get knwoledge of processing the movement from the trait

  • Fragments for movement

    As you can see, the built-in Movement Trait of MassGameplay plugin works with the 4 following fragments:

    Located at UE5/Engine/Plugins/Runtime/MassGameplay/Source/MassMovement

    USTRUCT()
    struct MASSMOVEMENT_API FMassVelocityFragment : public FMassFragment
    {
    	GENERATED_BODY()
    
    	FVector Value = FVector::ZeroVector;
    };

    Located at UE5/Engine/Plugins/Runtime/MassGameplay/Source/MassMovement

    USTRUCT()
    struct MASSMOVEMENT_API FMassForceFragment : public FMassFragment
    {
    	GENERATED_BODY()
    
    	FVector Value = FVector::ZeroVector;
    };

    Located at UE5/Engine/Plugins/Runtime/MassGameplay/Source/MassCommon

    USTRUCT()
    struct MASSCOMMON_API FAgentRadiusFragment : public FMassFragment
    {
    	GENERATED_BODY()
    	UPROPERTY(EditAnywhere, Category = "")
    	float Radius = 40.f;
    };

    Located at UE5/Engine/Plugins/Runtime/MassGameplay/Source/MassCommon

    USTRUCT()
    struct MASSCOMMON_API FTransformFragment : public FMassFragment
    {
    	GENERATED_BODY()
    
    	const FTransform& GetTransform() const { return Transform; }
    	void SetTransform(const FTransform& InTransform) { Transform = InTransform; }
    	FTransform& GetMutableTransform() { return Transform; }
    
    protected:
    	UPROPERTY(Transient)
    	FTransform Transform;
    };
  • Movement Processor

    Built-in MassGameplay Processor located at UE5/Engine/Plugins/Runtime/MassGameplay/Source/MassMovement

    Now, as you know the fragments and their values, you can check how they are used for automatical processing of movement. The point is, in a case of using the built-in Movement Trait (MassGameplay), all you need for processing movements is to set (update) the traits values. You can do that e.g. from a custom Mass processor.

    UCLASS()
    class MASSMOVEMENT_API UMassApplyMovementProcessor : public UMassProcessor
    {
    	GENERATED_BODY()
    
    public:
    	UMassApplyMovementProcessor();
    
    protected:
    	virtual void ConfigureQueries() override;
    	virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
    
    private:
    	FMassEntityQuery EntityQuery;
    };
    UMassApplyMovementProcessor::UMassApplyMovementProcessor()
    	: EntityQuery(*this)
    {
    	ExecutionFlags = (int32)EProcessorExecutionFlags::All;
    	ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
    	ExecutionOrder.ExecuteAfter.Add(UE::Mass::ProcessorGroupNames::Avoidance);
    }
    
    void UMassApplyMovementProcessor::ConfigureQueries()
    {
    	EntityQuery.AddRequirement<FMassVelocityFragment>(EMassFragmentAccess::ReadWrite);
    	EntityQuery.AddRequirement<FTransformFragment>(EMassFragmentAccess::ReadWrite);
    	EntityQuery.AddRequirement<FMassForceFragment>(EMassFragmentAccess::ReadWrite);
    	EntityQuery.AddTagRequirement<FMassOffLODTag>(EMassFragmentPresence::None);
    	EntityQuery.AddConstSharedRequirement<FMassMovementParameters>(EMassFragmentPresence::All);
    }
    
    void UMassApplyMovementProcessor::Execute(FMassEntityManager& EntityManager,
    													FMassExecutionContext& Context)
    {
    	// Clamp max delta time to avoid force explosion on large time steps (i.e. during initialization).
    	const float DeltaTime = FMath::Min(0.1f, Context.GetDeltaTimeSeconds());
    
    	QUICK_SCOPE_CYCLE_COUNTER(HighRes);
    
    	EntityQuery.ForEachEntityChunk(EntityManager, Context, [this, DeltaTime](FMassExecutionContext& Context)
    	{
    		const int32 NumEntities = Context.GetNumEntities();
    
    		const FMassMovementParameters& MovementParams = Context.GetConstSharedFragment<FMassMovementParameters>();
    
    		const TArrayView<FTransformFragment> LocationList = Context.GetMutableFragmentView<FTransformFragment>();
    		const TArrayView<FMassForceFragment> ForceList = Context.GetMutableFragmentView<FMassForceFragment>();
    		const TArrayView<FMassVelocityFragment> VelocityList = Context.GetMutableFragmentView<FMassVelocityFragment>();
    
    		for (int32 EntityIndex = 0; EntityIndex < NumEntities; ++EntityIndex)
    		{
    			FMassForceFragment& Force = ForceList[EntityIndex];
    			FMassVelocityFragment& Velocity = VelocityList[EntityIndex];
    			FTransform& CurrentTransform = LocationList[EntityIndex].GetMutableTransform();
    
    			// Update velocity from steering forces. (Force default value is ZeroVevtor)
    			Velocity.Value += Force.Value * DeltaTime;
    
    			FVector CurrentLocation = CurrentTransform.GetLocation();
    			CurrentLocation += Velocity.Value * DeltaTime;
    			CurrentTransform.SetTranslation(CurrentLocation);
    		}
    	});
    }