Mass Framework for crowds & traffic simulation in Unreal Engine

Mass Framework for crowds and traffic simulations. Available since Unreal Engine 5.0, the code and API on this page reflect Unreal Engine 5.8 (the final UE5 release).

Vladimír NeporAuthorVladimír Nepor
Published

About Mass Framework

Mass is Unreal's in-house ECS (Entity Component System archetype-based) framework that use an efficient data-oriented design. It's created by the AI team at Epic Games to facilitate massive crowd & traffic simulations. It was designed for and featured in the Matrix Awakens demo Epic released in 2021, while it can be used also for other gameplay purposes. Since that time, MassFramework has grown to include many powerful features. As it's generally written, it may be used for many use-cases.

Difference between OOP and ECS

Mass is still under active development. Epic's documentation keeps parts of it marked as experimental, and the API changes between engine versions, so always check the API of your engine version before upgrading a project. Mass is comparable to other archetype-based ECS solutions such as Apparatus (third-party plugin for Unreal Engine), Unity Entities (DOTS) or FLECS.

What changed in Mass between UE 5.1 and 5.8

Many tutorials on the internet (including the original version of this page) were written for UE 5.0–5.3. These are the most important changes you will run into:

VersionChange
5.1UMassProcessor::Execute takes FMassEntityManager& instead of UMassEntitySubsystem&.
5.5MassEntity moved from a plugin to an engine runtime module (Engine/Source/Runtime/MassEntity), the old plugin is deprecated. The StructUtils plugin is deprecated as well - FInstancedStruct & co. live in CoreUObject (#include "StructUtils/InstancedStruct.h").
5.6+ConfigureQueries receives const TSharedRef<FMassEntityManager>& (the parameterless version is legacy), queries are registered by constructing them with the owning processor (EntityQuery(*this)) - RegisterWithProcessor() no longer exists in UE 5.8. ForEachEntityChunk(Context, …) no longer needs the entity manager. New optional simplified Processor/Query API (UE::Mass::FQueryExecutor).
5.7Processor time-slicing (UE::Mass::FExecutionLimiter), observer calls are batched while commands are flushed.
5.8Major refactor: Mass Signals are part of the core engine, entities can be created off the game thread, sparse/virtual fragments and tags (added/removed without an archetype change), reworked processor scheduling with thread-safe observer notifications, Mass events in Unreal Insights. The current observer API uses ObservedTypes (multiple types) and EMassObservedOperationFlags (incl. CreateEntity, DestroyEntity) - the old ObservedType / Operation (Add, Remove) are deprecated.

UE 5.8 (June 2026) is the last release of Unreal Engine 5 - Epic moves development to Unreal Engine 6, so expect further changes in Mass there.

Mass Plugins and Modules

Mass schema
Plugin namePlugin description
MassEntity

MassEntity is the core that manages everything regarding Entity creation and storage. It's a gameplay-focused framework for data-oriented calculations. It also ensures entities inside the world are persistent. Since UE 5.5 it's an engine runtime module (Engine/Source/Runtime/MassEntity), not a plugin - you only add "MassEntity" to your .Build.cs. Mass Signals (Runtime/MassSignals) and the rendering part MassEngine live in the engine runtime as well. Check more at MassEntity Unreal page.

  • Framework Fundamentals
  • Scheduling
  • Composition tools (Traits)
  • Basic subsystems
  • ...
MassGameplay

MassGameplay plugin extends MassEntity for compilation of a number of useful Fragments and Processors that are used in different parts of the Mass framework.

  • In World Representation
  • Movement
  • LOD Mechanisms
  • StateTree
  • Spawning
  • Replication
  • ...

Located at UE5/Engine/Plugins/Runtime/MassGameplay

MassAI

MassAI is a plugin that provides AI features for Mass. It extends MassGameplay plugin.

  • Navigation
  • AI Behaviors
  • ZoneGraph interface
  • SmartObjects
  • Animation
  • ...

Located at UE5/Engine/Plugins/AI/MassAI

MassCrowd

Plugin initially written for Crowd and Traffic behavior in CitySample project. It extends MassAI plugin

Meshes / Visuals in Mass

Mass framework works with Skeletal as well as static meshes. As so, there's not visual nor technological difference regarding the Meshes itself. There are instanced entities with Static Meshes (and, since recent UE 5 versions, also instanced skinned meshes driven by Mass) as well as standard Actors (extended for MassAgent component used in the Mass) with a Mesh of your wish. What differs is the general form of presence in the scene and behaviour controls.

Entity types
  • Regular Actor - Standard actor without Mass implementation. The actor is just commonly placed in the scene, with its own logic.
  • Mass Controlled Actor - BP_Actor(s) spawned to the scene by AMassSpawner (or through its derived class). There's an individual BP_Actor representation for each member. The BP_Actor itself has no own functionality, instead, it's controlled through attached MassAgent component (part of MassGameplay plugin, module MassActors), specifically Mass Entity Config file (DataAsset).

    As it's a standard actor in a scene, just with attached MassAgent and other components, it may be based on Static Mesh as well as Skeletal Mesh.

  • Mass Entity - All spawned objects by AMassSpawner (or through its derived class) are placed in the scene within one object - MassVisualizer actor (MassRepresentationSubsystemVisualizer), but still can move quite independently.

Usually it works so, that the closest crowd characters are fully rigged and animated MetaHuman characters (Mass Controlled Actor), and the more distant ones are custom generated, vertex-animated Static Meshes generated from the MetaHumans (Mass Entities). This is the approach of City Sample (UE 5.0). Newer engine versions add an alternative: MassRepresentation can drive instanced skinned meshes (processors such as UMassUpdateInstancedSkinnedMeshProcessor) and UE 5.8 brings the experimental MetaHuman Crowd plugin for thousands of MetaHuman characters.

Crowd Simulation sample form CitySample project

Mass Architecture Logic (MassAI plugin)

The visualization below shows a logic behind MassAI plugin use.

Mass schema

Mass Framework Entity Component System (ECS):

In Mass, some ECS terminology differs from the norm. Partly, because there is an existing patent for ECS framework (idea documented decades prior the patent itself) by Unity Engine. In the Mass framework it looks like follow:

norm ECSMass ECSUser's perspective ECS explanation
EntityEntityEntities = Indexes of data
ComponentFragmentComponents = the Data itself
SystemProcessorSystems = Behaviors definition

Beside the shown above, there are also Traits, Tags and Subsystems.

Data-Oriented design architecture of Mass framework

As it's data-oriented, we work with a "joining" various custom data each other, see an example of Entity id10 linking at 2 Traits that are composed of Fragments and Tag.

Mass Entity

Mass ECS data scheme

Mass schema
Mass Schema. Source: Unreal Engine Docs
  • Mass manages different agents called Entities. Entities are something like lightweight actors
  • Entities have a collection of Traits. Traits are something like lightweight components

    Since neither entities nor traits have an option to attach any functionality, the comparison to Actors and Components are not 100% true, but it helps to understand these components a bit more.

    • Entities defining identity only
    • Traits defining data only
  • Entities are going to be defined by a list of traits, and that definition is called an archetype.
  • Real data are stored in fragments that are small UStructs. Fragments define the state of a given functionality (it's something like the data of a component).
  • The actual functionality, the evaluation, and the alteration of each fragmented state is implemented in processors. Processors filter the entities that use the data they can operate on and will be served the fragments of those entities packed in chunks all of the same type at the same time, facilitating the manipulation of the same kind of data in sequential and contiguous blocks, making cache misses less likely to happen.

Fragment

Mass Fragment

Fragment represents an atomic piece of data (Structs) used in calculations. It may be imagined as of an TArray of elements that each entity access own index from the TArray. Common examples for Fragments include Transform, Velocity and LOD Index.

Fragments can be grouped into collections, and an instance of such collection can be associated with an ID. This collection instance is called an Entity.

As mentioned, Fragments are Data-only UStructs. As so, they are defined in .h file only and has no .cpp file in general. Fragments can be own by entities and processed with processors based on specified query.

Fragment types in Mass (to derive from on creation)

FMassFragment represents UStruct for single Entity - Each entity gets its own fragment data.

Creating a Mass fragment FMassFragment
USTRUCT(BlueprintType)
struct EXMPLPR_API FOccupantTrajectoryFragment : public FMassFragment
{
	GENERATED_BODY()
	
	UPROPERTY(EditAnywhere)
	float Time;
};

Sparse (virtual) fragments and tags - UE 5.8

Adding or removing a regular fragment or tag moves the entity to another archetype, which costs a memory copy. UE 5.8 introduced a sparse/virtual fragment system: optional fragments and tags can be added or removed without triggering an archetype change, which also reduces memory use. Use regular fragments for data every entity of the archetype has, and sparse ones for rarely present, frequently toggled data.

Attaching custom Fragment to Mass

Custom created Fragments can be attached to Traits through Assorted Fragments trait in Mass Entity Config data asset file, see below.

Attaching custom Fragment through Trait

Selected Built-in Mass Fragments

  • Transform Fragment MassGameplay (MassCommonFragments.h)
  • Mass Actor Fragment MassGameplay (MassActorSubsystem.h)
    Fragment to save the actor pointer of a mass entity if it exist
  • Agent Radius Fragment MassGameplay (MassCommonFragments.h)
  • Other fragments used in the source code
    • FMassViewerInfoFragment (MassGameplay)
    • FMassRepresentationFragment (MassGameplay)
    • FMassRepresentationLODFragment (MassGameplay)
    • FMassVisualizationChunkFragment (MassGameplay)
    • FMassRepresentationSubsystemSharedFragment (MassGameplay)
    • FMassVelocityFragment (MassGameplay)
    • FMassActorFragment (MassGameplay)

Other code-based operations with fragments

  • MontageFragment.Clear(); Clear fragment - When Clear function is defined as of:
    void FMassMontageFragment::Clear()
    {
    	*this = FMassMontageFragment();
    }
  • Context.Defer().RemoveFragment<FMassMontageFragment>(Context.GetEntity(EntityIdx)); - Remove the fragment from the entity (deferred, executed when the command buffer is flushed). The longer form Context.Defer().PushCommand<FMassCommandRemoveFragments<FMassMontageFragment>>(…) does the same.
  • Context.Defer().AddFragment<FMassMontageFragment>(Context.GetEntity(EntityIdx)); - Add the fragment to the entity (deferred).

Trait

A Trait is a collective name for Fragments and Processors supplying a given functionality. From here, Traits ~= Features.

For now, we can simplify is as follow:

Mass Trait

Creating a custom Trait

Custom trait is a standard C++ class inherited from UMassEntityTraitBase class. Note for the .uplugin / .uproject tab: since UE 5.5 you don't enable a MassEntity plugin anymore - MassEntity is part of the engine, you only enable the gameplay plugins (MassGameplay, MassAI, MassCrowd).

#include "CoreMinimal.h"
#include "MassEntityTraitBase.h"

#include "UObject/Object.h"
#include "StructUtils/InstancedStruct.h" // UE 5.5+ (CoreUObject), formerly "InstancedStruct.h" from the StructUtils plugin

#include "OccupantMovementTrait.generated.h"

UCLASS()
class PATHFINDERIMPORTER_API UOccupantMovementTrait : public UMassEntityTraitBase
{
	GENERATED_BODY()

protected:
	virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const override;

	// Other Optional properties
	UPROPERTY(EditAnywhere, Category = "Mass")
	FVector StartingForce = {0,0,100.0f};

	UPROPERTY(EditAnywhere, meta = (BaseStruct = "/Script/MassEntity.MassTag", ExcludeBaseStruct))
	TArray<FInstancedStruct> Tags;
};

Attaching Traits to Mass

Custom written trait from above example as well as any Mass defualt Trait can be attached to the scene through Mass Entity Config file attached in Mass Spawner as follow.

Mass Trait

Mass Entity Config Data Asset has an array of traits through which there can be attached numerous traits instances. Each Trait instance is then responsible for adding and configuring Fragments in a way that results in the Entity's exhibiting the behavior supplied by the Trait.

Default Mass Traits to use

  • Visualization MassCrowd

    • Crowd Visualization
      City Sample - Mass Entity Config Asset

      Take into notice, that the MassCrowdAgentConfig_MetaHuman extends MassCrowdAgentConfig with orther Traits associated.

  • Assorted Fragments MassGameplay

    Assorted Fragments allows to attach general as well as custom-made Fragments to the Mass Entity Config data asset file.

    Assorted Fragments Trait in City Sample
  • Replication MassGameplay

    Replication Trait in City Sample
  • State Tree MassAI

    State Tree Trait in City Sample
  • Crowd Server Representation MassCrowd

    Crowd Server Representation Trait in City Sample
  • Look At MassAI

    Trait without custom configuration

  • CrowdMember MassCrowd

    Trait without custom configuration

  • ZoneGraph Annotation MassAI

    Trait without custom configuration

  • SmartObject User MassGameplay

    Smart Objects are a collection of Actors placed in a level that the Mass AI agents and players can interact with, see Smart Objects section. The system is configurable and adds an unprecedented level of interactivity to the scene. See more at Smart Objects documentation.

    SmartObject User Trait in City Sample
  • SimulationLOD MassGameplay

    SimulationLOD Trait in City Sample
  • LODCollector MassGameplay

    Trait without custom configuration

  • Movement MassGameplay

    Movement Trait in City Sample
  • ZoneGraph Navigation MassAI

    ZoneGraph Navigation Trait in City Sample
  • Steering MassAI

    Steering Trait in City Sample
  • Smooth Orientation MassAI

    Proper orientation related to the movement.

    Smooth Orientation Trait in City Sample
  • Avoidance MassAI

    AvoidanceTrait in City Sample
  • Navigation Obstacle MassAI

    Trait without custom configuration

  • Debug Visualization MassGameplay

    renders the entities in the scene while in Editor.

    This Trail allows to set custom options, such as culling distances, mesh, material or wire shape.

    Mass schema
  • Agent Feet Location Sync

    indicates sync direction, in othe words the priority actor of movement, see:
    • Actor To Mass: Mass Controlled Actor is driven by its character movement component based on environment (physics) and respects (is affected based on) its collision settings
    • Mass To Actor: Mass Controlled Actor is driven completely by data fileld in Mass Processor, such as Transform, animation data and so on.
  • Agent Movement Sync

    indicates sync direction for movement.

Entity

An Entity is a data-only element (it does not contain any logic), usually in the form of small unique identifiers that points to a combination of fragments and tags in memory. Entities are mainly a simple integer ID. For example, entity with id 10 might point to a single actor with properties such as transform, velocity, and damage data.

Mass Entity

Creating an Entity is similar to class instancing in object-oriented programming. However, instead of strictly declaring a class and its functionality, Entities are built by Traits composition. These compositions can be changed at runtime. For example, an Entity's composition can include two Traits linked to Fragments such as a Transform and a Velocity.

Entities are created and placed in the scene based on Entity Config Asset by Mass Spawner.

Processors

Processors take care about behavior. They combine multiple user-defined queries with functions that compute entities.

Mass Processor schema

By default, all crated processors run automatically, it's not needed to attach / define them anywhere for running.

UMassProcessor

Processor Constructor setup

UMyProcessor::UMyProcessor()
	: MyQuery(*this) // UE 5.6+: constructing the query with the processor registers it
{
	// This processor is registered with mass by just existing! This is the default behaviour of all processors.
	bAutoRegisterWithProcessingPhases = true;
	// Setting the processing phase explicitly
	SetProcessingPhase(EMassProcessingPhase::PrePhysics);
	// Using the built-in movement processor group
	ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
	// You can also define other processors that require to run before or after this one
	ExecutionOrder.ExecuteAfter.Add(TEXT("MSMovementProcessor"));
	// This executes only on Clients and Standalone
	ExecutionFlags = (int32)(EProcessorExecutionFlags::Client | EProcessorExecutionFlags::Standalone);
	// This processor should not be multithreaded
	bRequiresGameThreadExecution = true;
}

On initialization, Mass creates a dependency graph of processors using their execution rules so they execute in order (ie: In the example above we make sure to move our entities with MSMovementProcessor before we call Execute in UMyProcessor).

  • Processing phases

    Unreal classes deriving from UMassProcessor are automatically registered with Mass and added to the EMassProcessingPhase::PrePhysics processing phase by default. Each EMassProcessingPhase relates to an ETickingGroup, meaning that, by default, processors tick every frame in their given processing phase. Users can configure to which processing phase their processor belongs by calling SetProcessingPhase() (or setting the ProcessingPhase property) in the processor's constructor:

    EMassProcessingPhaseRelated ETickingGroupDescription
    PrePhysicsTG_PrePhysicsExecutes before physics simulation starts.
    StartPhysicsTG_StartPhysicsSpecial tick group that starts physics simulation.
    DuringPhysicsTG_DuringPhysicsExecutes in parallel with the physics simulation work.
    EndPhysicsTG_EndPhysicsSpecial tick group that ends physics simulation.
    PostPhysicsTG_PostPhysicsExecutes after rigid body and cloth simulation.
    FrameEndTG_LastDemotableCatchall for anything demoted to the end.
  • Execution flags

    FlagDescription
    StandaloneThe processor should be executed on Standalone
    ServerThe processor should be executed on Server
    ClientThe processor should be executed on Client
    EditorThe processor should be executed in the Editor
    EditorWorldThe processor should be executed in the editor world (not only in PIE)
    AllNetModesStandalone | Server | Client
    AllWorldModesStandalone | Server | Client | EditorWorld
    AllStandalone | Server | Client | Editor | EditorWorld
  • Threads settings

    By default all processors are multithreaded. To run in a single-thread, set bRequiresGameThreadExecution to true. UE 5.8 reworked the processor scheduling and dependency resolution to make better use of multi-core CPUs - correct access requirements (ReadOnly vs. ReadWrite) in queries are therefore even more important, because the scheduler uses them to run processors in parallel.

Processors Queries configuration

Queries (FMassEntityQuery) filter and iterate entities given a series of rules based on Fragment and Tag presence.

Processors can define multiple FMassEntityQuerys and should override the ConfigureQueries to add rules to the different queries defined in the processor's header. Each query must be constructed with the owning processor (MyQuery(*this) in the constructor's initializer list) - otherwise it isn't registered and won't run:

// .h
UCLASS()
class UMyProcessor : public UMassProcessor
{
	GENERATED_BODY()
public:
	UMyProcessor();
protected:
	virtual void ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager) override;
	virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
private:
	FMassEntityQuery MyQuery;
};

// .cpp
void UMyProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
	// Entities must have an FTransformFragment and we are reading and writing it (EMassFragmentAccess::ReadWrite)
	MyQuery.AddRequirement<FTransformFragment>(EMassFragmentAccess::ReadWrite);

	// Entities must have an FMassForceFragment and we are only reading it (EMassFragmentAccess::ReadOnly)
	MyQuery.AddRequirement<FMassForceFragment>(EMassFragmentAccess::ReadOnly);

	// Entities must have a common FClockSharedFragment that can be read and written
	MyQuery.AddSharedRequirement<FClockSharedFragment>(EMassFragmentAccess::ReadWrite);

	// The query needs UMassDebuggerSubsystem that can be read and written
	MyQuery.AddSubsystemRequirement<UMassDebuggerSubsystem>(EMassFragmentAccess::ReadWrite);

	MyQuery.AddTagRequirement<FMoverTag>(EMassFragmentPresence::All);
	MyQuery.AddRequirement<FHitLocationFragment>(EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional);

	// No RegisterWithProcessor() call anymore (removed) - the query was registered by MyQuery(*this) in the constructor.

	ProcessorRequirements.AddSubsystemRequirement<UMassDebuggerSubsystem>(EMassFragmentAccess::ReadWrite);
}

ProcessorRequirements is a special query part of UMassProcessor that holds all the UWorldSubsystems that get accessed in the Execute function outside the queries scope. In the example above, UMassDebuggerSubsystem gets accessed within MyQuery's scope (MyQuery.AddSubsystemRequirement) and in the Execution function scope (ProcessorRequirements.AddSubsystemRequirement).

Note: Queries can also be created and iterated outside processors.

  • Access requirements

    EMassFragmentAccessDescription
    NoneNo binding required.
    ReadOnlyWe want to read the data for the fragment/subsystem.
    ReadWriteWe want to read and write the data for the fragment/subsystem.
  • Presence requirements

    EMassFragmentPresenceDescription
    AllAll of the required fragments/tags must be present. Default presence requirement.
    AnyAt least one of the fragments/tags marked any must be present.
    NoneNone of the required fragments/tags can be present.
    OptionalIf fragment/tag is present we'll use it, but it does not need to be present.

Processors Execution

Processors execute queries within their Execute function:

Queries are executed by calling the ForEachEntityChunk member function with the FMassExecutionContext and a lambda. (Older versions passed also the FMassEntityManager - ForEachEntityChunk(EntityManager, Context, …) - that overload is legacy.)

void UMyProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
	//Note that this is a lambda! If you want extra data you may need to pass it in the [] operator
	MyQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
	{
		//Loop over every entity in the current chunk and do stuff!
		for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex)
		{
			// ...
		}
		// Alternative (UE 5.6+): for (FMassExecutionContext::FEntityIterator EntityIt = Context.CreateEntityIterator(); EntityIt; ++EntityIt)
	});
}

Time-slicing heavy processors (UE 5.7+)

Long-running processors can be split across multiple frames with an execution limiter. The query then processes entities only up to the limit and continues from the last position in the next frame (it always finishes the current chunk):

// member of the processor, keeps the position between frames
UE::Mass::FExecutionLimiter Limiter; // configure the entity limit per frame

MyQuery.ForEachEntityChunk(Context, Limiter, [](FMassExecutionContext& Context)
{
	// ...
});

Entities can be skipped if the entity count or chunk layout changes between iterations, so use it for work that tolerates that (e.g. periodic updates of distant agents).

Be aware that the index we employ to iterate entities, in this case EntityIndex, doesn't identify uniquely your entities along time, since chunks' disposition may change and an entity that has an index this frame, may be in a different chunk with a different index in the next frame.

Access Requirement based on Query Conf

EMassFragmentAccessTypeFunctionDescription
ReadOnlyFragmentGetFragmentViewReturns a read only TConstArrayView containing the data of our ReadOnly fragment.
ReadWriteFragmentGetMutableFragmentViewReturns a writable TArrayView containing de data of our ReadWrite fragment.
ReadOnlyShared FragmentGetConstSharedFragmentReturns a constant reference to our read only shared fragment.
ReadWriteShared FragmentGetMutableSharedFragmentReturns a reference of our writable shared fragment.
ReadOnlySubsystemGetSubsystemCheckedReturns a read only constant reference to our world subsystem.
ReadWriteSubsystemGetMutableSubsystemCheckedReturns a reference of our writable shared world subsystem.

Notes

  • Tags do not have access requirements since they don't contain data.

Example use - updating location

// .h file
UCLASS()
class UAdvancedRandomMovementProcessor : public UMassProcessor
{
	GENERATED_BODY()
public:
	UAdvancedRandomMovementProcessor();

protected:
	virtual void ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager) override;
	virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;

private:
	FMassEntityQuery EntityQuery;
};

// .cpp file
// Constructor
UAdvancedRandomMovementProcessor::UAdvancedRandomMovementProcessor()
	: EntityQuery(*this) // registers the query with this processor
{
	bAutoRegisterWithProcessingPhases = true;
	ExecutionFlags = (int32)EProcessorExecutionFlags::AllNetModes;
	ExecutionOrder.ExecuteBefore.Add(UE::Mass::ProcessorGroupNames::Avoidance);
}

void UAdvancedRandomMovementProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
	EntityQuery.AddRequirement<FTransformFragment>(EMassFragmentAccess::ReadOnly);
	EntityQuery.AddRequirement<FMassMoveTargetFragment>(EMassFragmentAccess::ReadWrite);
	EntityQuery.AddConstSharedRequirement<FMassMovementParameters>(EMassFragmentPresence::All);
}

// Called every frame in the processing phase the processor is registered to.
void UAdvancedRandomMovementProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
	EntityQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
	{
		// ReadWrite access -> mutable view
		const TArrayView<FMassMoveTargetFragment> NavTargetsList = Context.GetMutableFragmentView<FMassMoveTargetFragment>();

		// ReadOnly access -> const view
		const TConstArrayView<FTransformFragment> TransformsList = Context.GetFragmentView<FTransformFragment>();
		const FMassMovementParameters& MovementParams = Context.GetConstSharedFragment<FMassMovementParameters>();

		for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex)
		{
			const FTransform& Transform = TransformsList[EntityIndex].GetTransform();
			FMassMoveTargetFragment& MoveTarget = NavTargetsList[EntityIndex];

			const FVector CurrentLocation = Transform.GetLocation();
			FVector TargetVector = MoveTarget.Center - CurrentLocation;
			TargetVector.Z = 0.f;
			MoveTarget.DistanceToGoal = TargetVector.Size();
			MoveTarget.Forward = TargetVector.GetSafeNormal();

			if (MoveTarget.DistanceToGoal <= 20.f || MoveTarget.Center == FVector::ZeroVector)
			{
				MoveTarget.Center = FVector(FMath::RandRange(-1.f, 1.f) * 1000.f, FMath::RandRange(-1.f, 1.f) * 1000.f, CurrentLocation.Z);
				MoveTarget.DistanceToGoal = (MoveTarget.Center - CurrentLocation).Size();
				MoveTarget.Forward = (MoveTarget.Center - CurrentLocation).GetSafeNormal();
				MoveTarget.DesiredSpeed = FMassInt16Real(MovementParams.DefaultDesiredSpeed);
			}
		}
	});
}

Simplified Processor / Query API (UE 5.6+)

Since UE 5.6 there is an optional simplified Processor/Query API that reduces boilerplate and makes the access to fragments type-safe. The requirements are declared once as template parameters of UE::Mass::FQueryDefinition inside a UE::Mass::FQueryExecutor - no ConfigureQueries and no Execute override in the processor. The classic style shown above stays supported.

// .cpp - keep the query definition private to the .cpp file
struct FVelocityExecutor : public UE::Mass::FQueryExecutor
{
	using Query = UE::Mass::FQueryDefinition<
		UE::Mass::FMutableFragmentAccess<FTransformFragment>,
		UE::Mass::FConstSharedFragmentAccess<FMyVelocitySharedFragment>,
		UE::Mass::FMassTagRequired<FMyMoverTag>>;

	Query Accessors{ *this };

	virtual void Execute(FMassExecutionContext& Context) override
	{
		ForEachEntityChunk(Context, Accessors, [](FMassExecutionContext& Ctx, Query& Data)
		{
			const auto Transforms = Ctx.GetMutableFragmentView<FTransformFragment>();
			const FMyVelocitySharedFragment& Velocity = Ctx.GetConstSharedFragment<FMyVelocitySharedFragment>();
			const FVector Offset = Velocity.Value * Ctx.GetDeltaTimeSeconds();

			for (int32 i = 0; i < Ctx.GetNumEntities(); ++i)
			{
				Transforms[i].GetMutableTransform().AddToTranslation(Offset);
			}
		});
	}
};

// The executor is still owned by a regular UMassProcessor wrapper (no ConfigureQueries / Execute override)
// .h members: FMassEntityQuery EntityQuery; TSharedPtr<FVelocityExecutor> Executor;
UMyVelocityProcessor::UMyVelocityProcessor()
	: EntityQuery(*this)
	, Executor(UE::Mass::FQueryExecutor::CreateQuery<FVelocityExecutor>(EntityQuery, this))
{
	AutoExecuteQuery = Executor;
	ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
	SetProcessingPhase(EMassProcessingPhase::PrePhysics);
	ExecutionFlags = (int32)EProcessorExecutionFlags::AllWorldModes;
}

Besides ForEachEntityChunk, the executor offers ForEachEntity, ParallelForEachEntity and ParallelForEachEntityChunk. The per-entity variants are convenient, but fetching fragment views per entity has an overhead - prefer the chunk variants for hot code.

UMassObserverProcessor

Observer processors help initialize fragments that depend on other fragments' information or other systems that may not be initialized at construction time.

The UMassObserverProcessor is a type of processor that runs for entities that have just been subject of an observed operation over the observed Fragment/Tag types. The observer declares the types in the ObservedTypes array and the operations in ObservedOperations (EMassObservedOperationFlags):

EMassObservedOperationFlagsDescription
AddElementThe observed Fragment/Tag was added to an existing entity.
RemoveElementThe observed Fragment/Tag was removed from an existing entity.
CreateEntityAn entity containing the observed Fragment/Tag was created.
DestroyEntityAn entity containing the observed Fragment/Tag is being destroyed.
Add / Remove / AllCombinations: Add = AddElement | CreateEntity, Remove = RemoveElement | DestroyEntity, All = Add | Remove.

The older API (ObservedType + Operation = EMassObservedOperation::Add/Remove) is deprecated. Inside Execute, the execution context's auxiliary data (Context.GetAuxData()) holds an FMassObserverExecutionContext telling which type and operation triggered the observer.

Examples of use

  • Updating Radius value in Fragment

    UCLASS()
    class BUILDYMASS_API UBuildyMassAgentRadiusInitializerProcessor : public UMassObserverProcessor
    {
    	GENERATED_BODY()
    public:
    	UBuildyMassAgentRadiusInitializerProcessor();
    protected:
    	virtual void ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager) override;
    	virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
    
    	FMassEntityQuery EntityQuery;
    };
  • Updating Color

    This observer changes the color to the entities that just had an FSampleColorFragment added:

    UMSObserverOnAdd::UMSObserverOnAdd()
    	: EntityQuery(*this)
    {
    	ObservedTypes = { FSampleColorFragment::StaticStruct() };
    	ObservedOperations = EMassObservedOperationFlags::Add;
    	ExecutionFlags = (int32)(EProcessorExecutionFlags::All);
    }
    
    void UMSObserverOnAdd::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
    {
    	EntityQuery.AddRequirement<FSampleColorFragment>(EMassFragmentAccess::ReadWrite);
    }
    
    void UMSObserverOnAdd::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
    {
    	EntityQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
    	{
    		const TArrayView<FSampleColorFragment> Colors = Context.GetMutableFragmentView<FSampleColorFragment>();
    		for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex)
    		{
    			// When a color is added, make it random!
    			Colors[EntityIndex].Color = FColor::MakeRandomColor();
    		}
    	});
    }
Use cases:
  • Change default value (from 40 to 20) like in the example above
  • An observer processor could have been used in the simple movement example to assign an initial movement target instead of having the origin as the initial target. Like, for example, the current location. So in that way, the movement processor will have branched to finding a new random target automatically.

When are observers triggered

Observers are triggered by the entity manager whenever an entity composition changes in a way that matches the observed types and operations - entity creation and destruction, adding or removing fragments and tags, both through direct (batched) FMassEntityManager calls and through deferred commands once the command buffer is flushed.

  • Since UE 5.7, observers are locked while commands are flushed and their calls are batched - in City Sample this reduced the number of observer executions roughly 20 times.
  • Since UE 5.8, observer notifications are thread-safe as part of the reworked processor scheduling.

Observing multiple Fragment/Tags

An observer can watch several types and operations at once - just list them in the constructor. When multiple observed types are added simultaneously, Execute fires once per entity batch (duplicates are removed). Overriding Register() and calling UMassObserverRegistry manually is no longer needed.

UMyMassObserverProcessor::UMyMassObserverProcessor()
	: EntityQuery(*this)
{
	ObservedTypes = { FSampleColorFragment::StaticStruct(), FSampleMaterialFragment::StaticStruct() };
	ObservedOperations = EMassObservedOperationFlags::Add | EMassObservedOperationFlags::RemoveElement;
	ExecutionFlags = (int32)(EProcessorExecutionFlags::All);
}

Tags

Tags are empty UScriptStructs that processors can use to filter entities in Processor to process based on their presence/absence.

Tags require #include "MassEntityTypes.h" and can be defined in any file.

Creating a tag in any file

#include "MassEntityTypes.h"
								
/** Special tag to differentiate the TrafficVehicle from the rest of the other entities */
USTRUCT()
struct MASSTRAFFIC_API FMassTrafficVehicleTag : public FMassTag
{
	GENERATED_BODY()
};

Example of Creating a Mass tag in Trait file and attaching it to the Trait

#include "CoreMinimal.h"
#include "MassEntityTypes.h"
#include "MassEntityTraitBase.h"
#include "SomeTrait.generated.h"

/** Tag that represents ... */
USTRUCT()
struct PATHFINDERIMPORTER_API FSomeMyTag : public FMassTag
{
	GENERATED_BODY()
};

UCLASS()
class PATHFINDERIMPORTER_API USomeTrait : public UMassEntityTraitBase
{
	GENERATED_BODY()

protected:
	virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const override;
};
  • A good example of the use of tags to exclude certain entities (filtering application) is e.g. the LOD system

Filtration based on Tag

Data-Oriented design schema for Mass after filtration by Processor
Data-Oriented design schema for Mass
Drag to compare.
Default structure / Structure after filtration by Processor

Tag filtration definition can be specified for each processor in its ConfigureQueries function, see:

void USomeProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
	EntityQuery.AddTagRequirement<FMSInOctreeGridTag>(EMassFragmentPresence::All);
}

See EMassFragmentPresence for all presence options.

Dynamical adding / Removal tags during execution

  • Context.Defer().AddTag<FSomeTag>(Context.GetEntity(i));
  • Context.Defer().RemoveTag<FSomeTag>(Context.GetEntity(i));

Other methods are defined in Engine/Source/Runtime/MassEntity/Public/MassCommandBuffer.h file (before UE 5.5 the file lived in Plugins/Runtime/MassEntity).

Tip: if a tag is toggled often, consider the sparse tags introduced in UE 5.8 - they don't move the entity to another archetype.

Example: Muting entities (adding to tag filtration) with Defer()

Within the ForEachEntityChunk we have access to the current execution context. FMassExecutionContext enables us to get entity data and mutate their composition. The following code adds the tag FDead to any entity that has a health fragment with its Health variable less or equal to 0, at the same time, as we define in ConfigureQueries, after the FDead tag is added, the entity won't be considered for iteration (EMassFragmentPresence::None):

UDeathProcessor::UDeathProcessor()
	: DeclareDeathQuery(*this) // registers the query
{
}

void UDeathProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
	// All the entities processed in this query must have the FHealthFragment fragment
	DeclareDeathQuery.AddRequirement<FHealthFragment>(EMassFragmentAccess::ReadOnly, EMassFragmentPresence::All);
	// Entities processed by this query shouldn't have the FDead tag, as this query adds the FDead tag
	DeclareDeathQuery.AddTagRequirement<FDead>(EMassFragmentPresence::None);
}

void UDeathProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
	DeclareDeathQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
	{
		const TConstArrayView<FHealthFragment> HealthList = Context.GetFragmentView<FHealthFragment>();

		for (int32 EntityIndex = 0; EntityIndex < Context.GetNumEntities(); ++EntityIndex)
		{
			if (HealthList[EntityIndex].Health <= 0.f)
			{
				// Adding a tag to this entity when the deferred commands get flushed
				FMassEntityHandle EntityHandle = Context.GetEntity(EntityIndex);
				Context.Defer().AddTag<FDead>(EntityHandle);
			}
		}
	});
}

In order to Defer Entity mutations we require to obtain the handle (FMassEntityHandle) of the Entities we wish to modify. FMassExecutionContext holds an array with all the Entity handles. We can access it through two different methods:

PluralityCode
SingularFMassEntityHandle EntityHandle = Context.GetEntity(EntityIndex);
Pluralauto EntityHandleArray = Context.GetEntities();

Source files structure

Entities, Fragments, Tags, Processors and other Mass classes can be defined and declared within one file, see the example below:

#include "CoreMinimal.h"

#include "CrowdCharacterDefinition.h"
#include "MassEntityTypes.h"
#include "MassObserverProcessor.h"

#include "CrowdVisualizationFragment.generated.h"

struct FMassEntityQuery;

USTRUCT()
struct CITYSAMPLE_API FCitySampleCrowdVisualizationFragment : public FMassFragment
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, Category = "")
	FCrowdVisualizationID VisualizationID;

	UPROPERTY(EditAnywhere, Category = "")
	uint32 TopColor = 0;

	UPROPERTY(EditAnywhere, Category = "")
	uint32 BottomColor = 0;

	UPROPERTY(EditAnywhere, Category = "")
	uint32 ShoesColor = 0;

	UPROPERTY(EditAnywhere, Category = "")
	uint8 SkinAtlasIndex = 0;
};

UCLASS()
class CITYSAMPLE_API UCitySampleCrowdVisualizationFragmentInitializer : public UMassObserverProcessor
{
	GENERATED_BODY()

public:
	UCitySampleCrowdVisualizationFragmentInitializer();

protected:
	virtual void ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager) override;
	virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;

protected:
	FMassEntityQuery EntityQuery;

	uint32 FindColorOverride(FCrowdCharacterDefinition& CharacterDefinition, USkeletalMesh* SkelMesh);
	UAnimToTextureDataAsset* GetAnimToTextureDataAsset(TSoftObjectPtr<UAnimToTextureDataAsset> SoftPtr);
};

// Others

Mass Character BP class

As mentioned, Mass controlled actors are just a Blueprint derived actors of C++ class with MassAgent component.

Mass Agents

MassAgent Component is a way to comunicate with Mass Gameplay Framework - it provides a nice way of adding entities to actors, so that they are considered as such.

In the entity config, we can add agent specific traits and decide if the information is going to be synchronized from the actor to the Mass system, from the Mass system to the actor, both ways, or only for initialization.

Mass Agent Component

Setting Character as Mass agents

In a case of human based crowds, usually the MassAgent component is palced to ACharacter BP class.

Mass Character MassAgent actor
Data-Oriented design schema for Mass
Drag to compare.
Mass Character actor | MetaHuman Mass actor

MassCharacter class is a custom class derived from ACharacter. City Sample Crowd Character class is derived from ACitySampleCharacter (inherited from ACharacter), IMassCrowdActorInterface and IMassActorPoolableInterface.

MassCharacter example

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AnimToTextureDataAsset.h"
#include "MassCharacter.generated.h"

UCLASS()
class PROJECTM_API AMassCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, Category = "Mass")
	TObjectPtr<UAnimToTextureDataAsset> AnimToTextureDataAsset;

	UFUNCTION(BlueprintImplementableEvent)
	void BP_OnCharacterDeath();
};

MetaHuman Mass example



	

Smart Objects in Mass (MassAI)

Smart objects are type of objects with which AI agents can interact with (usually based on StateTree). An example of such object is a bench.

Smart object is a common BP_Actor with attached Mass's SmartObject component.

Mass Smart Object - bench

Smart Object Definition File

Mass Smart Object - bench

Trait attachment

Interaction with Smart Objects is made through Smart Object User trait.

Mass Smart Object - bench

UWorldSubsystem

The MassEntity framework is divided into several subsystems for encapsulation and code organization. All of the subsystems are World subsystems, which means their lifetime is bound to that of the World where they were created.

UWorldSubsystem serves for handling positioning for the workers and for the different elements, like resources or buildings.

SubsystemExample

A grid (plain two-dimensional array that lives within a subsystem) representing the world, where each cell is going to be 2 by 2 meters and can contain either an empty ground, a building with the builder's ID associated, resources, either a tree or a boulder with a number of slots associated to the current gatherer's ID.

UCLASS()
class BUILDYMASS_API UBuildyMassGridSubsystem : public UWorldSubsystem
{
	GENERATED_BODY()

public:
	typedef FBuildyMassGridCellContent BuildyMassGrid[GridDimensions][GridDimensions];

private:
	BuildyMassGrid Grid;
};

Subsystems are automatically instanced classes whose lifetimes are managed for us and are easily accessible from almost anywhere - so also from the processors, that can ask for the location of resources or the height of the current cell.

UCLASS()
class BUILDYMASS_API UBuildyMassBuilderProcessor : public UMassProcessor
{
	GENERATED_BODY()
	
	TObjectPtr<UBuildyMassGridSubsystem> GridSubsystem;
	TObjectPtr<UBuildyMassBuildingSubsystem> BuildingSubsystem;
};

There is a cached pointer to the grid subsystem in the relevant processors and the actual subsystem is grabbed when the processor is initialized - in UE 5.8 by overriding InitializeInternal(UObject& Owner, const TSharedRef<FMassEntityManager>& EntityManager) (the older Initialize(UObject&) is deprecated). A cleaner alternative is to declare the subsystem as a query/processor requirement (AddSubsystemRequirement) and fetch it with Context.GetSubsystemChecked<T>() / GetMutableSubsystemChecked<T>() - the scheduler then knows about the access and can run processors in parallel safely.

There's a need to be very cautious about memory in this regard, since accessing every frame to this grid for each entity would cause a big impact, because it will likely mean random memory accesses, meaning that we will have to ask the main RAM to fetch the data for us, because it can't live altogether in the cache, along with the fragments that we are currently iterating on.

To palliate that, an example below show storing a subgrid of 3 by 3 cells in the MovementTargetFragment. And only when an entity is outside its cached grid, we'll request new data to the grid subsystem. That will save us many cycles every frame, since knowing if an entity is out or not from its cached grid doesn't need any data from the grid subsystem itself.

typedef FBuildyMassGridCellContent BuildyMassGrid[3][3];
USTRUCT()
struct BUILDYMASS_API FBuildyMassMovementTargetFragment : public FMassFragment
{
	GENERATED_BODY()

	FVector TargetLocation;
	uint32 CachedGridX;
	uint32 CachedGridY;
	UBuildyMassGridSubsystem::BuildyMassSubGrid CachedGrid;
	bool bStartGoing;
}

The exact way of caching is used in the Mass NavSubsystem, see default struct FMassMoveTargetFragment inherited from FMassFragment. The built-in movement system works by updating the values in FMassMoveTargetFragment. And the already mentioned processors will work out the displacement.

/** Move target. */
USTRUCT()
struct MASSNAVIGATION_API FMassMoveTargetFragment : public FMassFragment
{
	GENERATED_BODY()
	// ...
public:
	FVector Center = FVector::ZeroVector; // Target location to reach
	FVector Forward = FVector::ZeroVector; // desired facing vector
	float DistanceToGoal = 0.0f; // Distance to center
	float SlackRadius = 0.0f;

	FMassInt16Real DesiredSpeed = FMassInt16Real(0.0f);
	EMassMovementAction IntentAtGoal = EMassMovementAction::Move;
	// ...
}

UBuildyMassBuildingSubsystem

Mass supports also entities that don't move (trees, boulders, and buildings...) but have the navigation obstacle, So every entity using avoidance will consider them for the calculations.

UCLASS()
class BUILDYMASS_API UBuildyMassBuildingSubsystem : public UWorldSubsystem
{
	GENERATED_BODY()

public:
	// At some point, we are going to want to interact among entities or with the external systems. In this example, the builders generate new instance static meshes when adding parts to their buildings. And for that, they are communicating with their subsystem, dropping messages in a queue. That queue is a basic array, where I'm adding those messages.
	UFUNCTION(BlueprintCallable, Category = Mass)
	bool AddBuildingMessage(const unit8& Floor, const int32& CellX, const int32& CellY, const bool& bFinished, const uint8& Orientation);
	UFUNCTION(BlueprintCallable, Category = Mass)
	bool IsBuildingInboxEmpty() const;
	UFUNCTION(BlueprintCallable, Category = Mass)
	// the subsystem will be processing all those petitions and adding instances to the proper instance static mesh components.
	FBuildyMassBuildingMessage ConsumeBuildingMessage();
private:
	TArray<FBuildyMassBuildingMessage, TInlineAllocator<32768>> BuildingMessages;
}
Proper instanced Static Mesh components

FBuildyMassBuildingMessage ConsumeBuildingMessage(); is linked to following

void UBuildyMassBuildingsComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	while(!BuildingSubsystem->IsBuildingInboxEmpty())
	{
		// Spawn Building Parts and Particles
	}
}

Spawning entities to the world

Spawn entities with runtime data

Mass Spawner (MassGameplay)

The Mass Spawner is an entry point to bring Mass Entities into the world. The Mass Spawner dictates two things: what type of entity is spawned and where it is spawned. As so, Mass Spawner is useful to spawn entities with predefined data. Mass Spawner can be added through Quick add to project button.

Mass actors

Mass Spawners require set a few things to spawn entities:

  • Count - number of entities to spawn
  • EntityConfig - An array of entity types: Specifies what type of entity to spawn. It's made with UMassEntityConfigAsset.
  • Spawn Data Generators - An array of (FMassSpawnDataGenerator), that specifies where to spawn (transform) the entities in the world.
Mass EQS Spawner

Mass Spawners are placed in the level and they can beset to start spawn automatically (Auto Spawn On Begin Play) or be queried in runtime based on the request. AMassSpawner class contain a bunch of public blueprint collable functions to do that, such as:

FunctionDescription
DoSpawning()Performs the spawning of all the agent types of this spawner.
DoDespawning()Despawns all mass agents spawned by this spawner.
ScaleSpawningCount(float Scale)Scales the spawning counts. Scale is the number to multiply the all counts of each agent types.
GetCount()Returns the unscaled count of entities to spawn.
GetSpawningCountScale()Returns the number to multiply the all counts of each agent types.

Mass Spawners are useful to spawn entities with static data in the world (predefined CDO and spawning transform).

The best practice of using Mass spawners is so to use each Mass Spawner for entities of a certain property, see an example from City Sample below:

Mass actors

Spawning various entities

Through a single Mass Spawner (or it's BP_ derived class in this case), there's possible to set various entities to spawn, see 7 types of cars in the example below:

Mass Spawner actor

As shown, there's set 3844 entities to spawn, while these entities are of 7 types defined by Entity Data Asset files. Exact the same files are palced also in the Array of 7 called "Entity Type to Parking T..." in the Generator Instance.

Parking spots themselves are defined by DataAsset "CitySampleSmallCityParkingSpaces" in this case. The DataAsset file links to Point Cloud data asset "CITY cars parked", see the image below.

Point Cloud

BP Mass Traffic Parked Vehicle Spawn Data Generator placed in the Generator Instance is a part of MASSTRAFFIC_API plugin (it's derived class of UMassEntitySpawnDataGeneratorBase).

Mass Spawner allows supports various types of Spawn Data Generators, see more at Spawn Data Generators section.

Mass Entity Config Asset

Mass Entity Definition asset is used to specify what should be spawned by the Mass Spawner. It dictates Traits for that entity being spawned, such as visuals, level of detail, behaviors and more.

City Sample - Mass Entity Config Asset

It can be created by clicking with right mouse button at Content Drawer → Miscellaneous → Data Asset and selecting the data asset of Mass Entity Config Asset (requires enabled MassGameplay plugin). Name the newly created data asset with DA_ prefix.

Mass schema

Spawn Data Generators

Spawn data generators define where to spawn entities by Mass Spawner. It provides 3 types of Generator Instance:

  • EQS SpawnPoints Generator

    EQS SpawnPoints Generator works with EQSRequest query template, see below:

    Environment Query

    Environment Query

    The easies way of creating Environment Query if from the EQS SpawnPoint Generator component once there's necessaty to attach Environment Query. Press create new (as shown above) and name it with EQ_ prefix.

    Environment Query allows placing various nodes, such as Points: Grid. This basically defines the size of grid where the entities should be placed and how. You can select one of followings Generators:

    • Actors of class
    • Composite - Allows placing multiple Generator types at once
    • Current Location
    • Perceived Actors
    • Points: Circle
    • Points: Cone
    • Points: Donut
    • Points: Grid
      Mass actors
    • Points: Pathing Grid
    • Smart Objects
  • Zone Graph SpawnPoints Generator

    The ZoneGraph is a lightweight design-driven flow for AI that follows a point-by-point corridor structure. It can store meaningful tags (static and dynamic) that can be leveraged for AI behaviors.

    Zone Graph SpawnPoints Generator workws with build Zone Graphs in the project, see following screenshot and video.

    Zone Graph Generator

    When deciding where to spawn entities in City Sample demo, distribution along a ZoneGraph provided through the procedural data from Houdini is used to generate spawn points along the ZoneGraph for the Crowd and Traffic Systems to use.

    See the crowd and traffic entities overview in the City Sample documentation.

  • Custom Written Generator

    There's possible to write own generators using UMassEntitySpawnDataGeneratorBase class. To do so, simply create custom a generator class and attach it to Generator Instance of Mass Spawner as below:

    Custom Mass Spawn Generator

    The sample above takes data right from the MassSpawner-derivied class (BP_OccupantsSpawner). Anyway, of course, there's possible to take data also from data assets, such as shown on the BP Mass Traffic Parked Vehicle Spawn Data Generator linking to Cloud Point data, shown below.

    Custom Spawn Generator

Destroying entities

  • Deferred

    The preferred way of destroying entities is to defer it (especially when processing, to stay safe.)

    // inside a processor
    Context.Defer().DestroyEntities(Entities);
    Context.Defer().DestroyEntity(Entity);
    
    // outside of processing
    EntityManager.Defer().DestroyEntity(Entity);
  • Directly

    BatchDestroyEntityChunks is preferred as it calls the observer manager for you. This is only truly safe to call outside of processing on the main thread, like other direct composition changes. UMassSpawnerSubsystem::DestroyEntities calls this as well.

    EntityManager.BatchDestroyEntityChunks(Collection)

LODs in Mass

LODs can be used not just for setting textures quality, but for the behaviour as well. E.g. activating / disabling avoidance based on camera distance.

Internally, the LOD uses tags to filter out the farther levels or to select simpler functionality for them.

USampleProcessor::USampleProcessor()
	: HighLODQuery(*this)
	, MediumLODQuery(*this)
{
}

void USampleProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
	HighLODQuery.AddTagRequirement<FMassHighLODTag>(EMassFragmentPresence::All);
	MediumLODQuery.AddTagRequirement<FMassMediumLODTag>(EMassFragmentPresence::All);

	// Similar for LowLOD and OffLOD
}

void USampleProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
	HighLODQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
	{
		// More complex code
	});

	MediumLODQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
	{
		// Simpler code
	});

	// Even simpler code for LowLOD and OffLOD
}

As shown, there's possible to set up different functionality in the processors for different levels using different queries with different requirements

This system is the other key to scalability, since it will allow us to leverage the amount of code we run. It allows to reach almost 100,000 units processing different amounts of code at different ticking rates.

To make it work in entities, there is an additional trait called LODCollector that can be added to Mass Entity Config Asset. Take into notice, that LODCollector requires registration in the Project settings. Go to Project Settings → Mass → Module Settings / Mass Entity / Processors CDOs and search for MassLODCollectorProcessor. In it, check Auto Register with Processing Phases box.

For work that doesn't need to run every frame for all LODs, combine LOD tags with the processor time-slicing introduced in UE 5.7.

FMassEntityView

FMassEntityView is a struct that eases all kinds of Entity operations. One can be constructed with a FMassEntityHandle and a FMassEntityManager. On construction, the FMassEntityView caches the Entity's archetype data, which will later reduce repeated work needed to retrieve information about the Entity.

Following next, we expose some of the relevant functions of FMassEntityView. In the following contrived processor example, we check if NearbyEntity is an enemy, if it is, we damage it:

FMassEntityView EntityView(EntityManager, NearbyEntity.Entity);

// Check if we have a tag
if (EntityView.HasTag<FEnemyTag>())
{
	if (const FDamageOnHit* DamageOnHitFragment = EntityView.GetFragmentDataPtr<FDamageOnHit>())
	{
		// Now we defer something to do to the other entity!
		FDamageFragment DamageFragment;
		DamageFragment.Damage = DamageOnHitFragment->Damage * AttackPower;
		Context.Defer().PushCommand<FMassCommandAddFragmentInstances>(EntityView.GetEntity(), DamageFragment);
	}
}

Debugging Mass behaviour

Mass Framework offers a bunch of tools and console commands to debug the Mass function.

  • Mass Debugger - editor tool showing processors, their execution order, archetypes, entities and fragment values of a running (PIE) session.
  • Unreal Insights - UE 5.8 adds Mass trace events (archetype creation, entities moved between archetypes) next to the regular CPU timing of processors.
  • EnableGDT - Gameplay Debugger (has a Mass category)
  • VisLog - Visual Logger
  • mass.Debug

More to know

There is a MassCrowd plugin in the AI folder that is specific for creating crowds in your projects.

Also state trees have been introduced into Unreal Engine 5. And they connect pretty easily to the Mass system. Just by creating one state tree asset and selecting it as it's schema Mass Behavior, we are going to be able to control entities' states through different tasks.

Tasks are going to be as processors, but that output a result to the processing of the fragments. So that the new transition to a state can be evaluated. This is a cleaner way to handle a state for our entities.

And also, it can work in a level of abstraction where less technical team members can feel more comfortable. Tasks for the Mass system still have to be defined in C++. But tasks for actors can be created in Blueprint in the same way that we can create tasks for behavior trees.

Newer additions worth knowing about:

  • Mass Signals - event-like notifications that wake up processors only for affected entities; part of the core engine since UE 5.8.
  • Mass Relations - built-in support for relationships between entities (e.g. child-of hierarchies) with their own creation/destruction processors and observers.
  • Instanced skinned meshes in MassRepresentation and the experimental MetaHuman Crowd plugin (UE 5.8) for large animated crowds.
  • Off-game-thread entity creation (UE 5.8) - entities can be created from worker threads thanks to the archetype-based, lock-free scheduling.

Other resources

Anything unclear?

Let us know!

Contact Us