- Vrealmatic
- Unreal Engine Wiki
- Mass Framework
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).

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
- In object-oriented programming (OOP), game code is expressed as an
Actorobject that inherit from parent classes to change their data and functionality based on what they are. - In an Entity Component System (ECS), an
entityis only composed offragmentsthat get manipulated byprocessorsbased on which ECS components they have.
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:
| Version | Change |
|---|---|
| 5.1 | UMassProcessor::Execute takes FMassEntityManager& instead of UMassEntitySubsystem&. |
| 5.5 | MassEntity 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.7 | Processor time-slicing (UE::Mass::FExecutionLimiter), observer calls are batched while commands are flushed. |
| 5.8 | Major 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

| Plugin name | Plugin description |
|---|---|
| MassEntity |
|
| MassGameplay |
Located at |
| MassAI |
Located at |
| MassCrowd | Plugin initially written for Crowd and Traffic behavior in CitySample project. It extends |
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.

- 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 byAMassSpawner(or through its derived class). There's an individualBP_Actorrepresentation for each member. TheBP_Actoritself has no own functionality, instead, it's controlled through attachedMassAgentcomponent (part ofMassGameplayplugin, moduleMassActors), specifically MassEntity Configfile (DataAsset).As it's a standard actor in a scene, just with attached
MassAgentand other components, it may be based onStatic Meshas well asSkeletal Mesh. - Mass Entity - All spawned objects by
are placed in the scene within one object -AMassSpawner(or through its derived class)MassVisualizeractor (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.
Mass Architecture Logic (MassAI plugin)
The visualization below shows a logic behind MassAI plugin use.

- Whole scene behavior is configured through
MassEntityConfigasset. - Based on
MassEntityConfigasset,Mass SpawnerspawnsMass entitiesandMass controlled Actors(Mass Agents) to the scene. Mass agentsmove within a specifiedZoneGraphand behave in a base ofStateTree, including interaction withSmartObjectsplaced in the scene.
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 ECS | Mass ECS | User's perspective ECS explanation |
|---|---|---|
| Entity | Entity | Entities = Indexes of data |
| Component | Fragment | Components = the Data itself |
| System | Processor | Systems = 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 ECS data scheme

- Mass manages different agents called
Entities.Entitiesare something like lightweightactors Entitieshave a collection ofTraits.Traitsare something like lightweightcomponentsSince neither
entitiesnortraitshave an option to attach any functionality, the comparison toActorsandComponentsare not 100% true, but it helps to understand these components a bit more.Entitiesdefining identity onlyTraitsdefining data only
Entitiesare going to be defined by a list oftraits, and that definition is called anarchetype.- Real data are stored in
fragmentsthat are smallUStructs.Fragmentsdefine 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
fragmentedstate is implemented inprocessors.Processorsfilter the entities that use the data they can operate on and will be served thefragmentsof 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

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;
};FMassSharedFragment allows to share data across many entities - it's a type of fragment that multiple entities can point to. The typical usage is for the configuration of a group of entitities for LODs or replication settings.
Creating a shared Mass fragment FMassSharedFragment
USTRUCT()
struct MASSCOMMUNITYSAMPLE_API FVisibilityDistanceSharedFragment : public FMassSharedFragment
{
GENERATED_BODY()
UPROPERTY()
float Distance;
};In the FVisibilityDistanceSharedFragment example above, all the entities containing the FVisibilityDistanceSharedFragment will see the same Distance value. If an entity modifies the Distance value, the rest of the entities with this fragment will see the change as they share it through the archetype. Shared fragments are generally added from Mass Traits.
Shared fragments must be Crc hashable, otherwise there's not possible to create a new instance with GetOrCreateSharedFragmentByHash call. With that call, there's also possible to pass in your own hash which helps if there's a preference to control what makes each one unique.
Thanks to this sharing data requirement, the Mass entity manager only needs to store one Shared Fragment for the entities that use it.
If the shared data never change at runtime (typical for configuration), derive from FMassConstSharedFragment instead and read it with Context.GetConstSharedFragment<T>().
FMassChunkFragment is associated with a memory chunk of an archetype instead of a single entity. It stores per-chunk data used in managerial processing, such as LOD or visualization calculations (e.g. FMassVisualizationChunkFragment). Chunk fragments are part of the entity composition and are read with Context.GetChunkFragment<T>() / Context.GetMutableChunkFragment<T>().
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.

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)
- FMassViewerInfoFragment (
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 formContext.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:

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;
};#include "Mass/Traits/OccupantMovementTrait.h"
#include "MassCommonFragments.h"
#include "MassEntityTemplateRegistry.h"
#include "Mass/Fragments/OccupantFragments.h"
void UOccupantMovementTrait::BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const
{
// Add Fragment
BuildContext.AddFragment<FOccupantTrajectoryFragment>();
// Require Fragment
BuildContext.RequireFragment<FTransformFragment>();
// Get Fragment Ref
BuildContext.AddFragment_GetRef<FMassForceFragment>().Value = StartingForce;
// Add Single Tag
BuildContext.AddTag<FMSBasicMovement>();
// Add Tags
for (const FInstancedStruct& Tag : Tags)
{
if(!Tag.IsValid()) continue;
const UScriptStruct* StructType = Tag.GetScriptStruct();
if (StructType->IsChildOf(FMassTag::StaticStruct())) BuildContext.AddTag(*StructType);
}
}PrivateDependencyModuleNames.AddRange(
new string[] {
"MassEntity", // engine runtime module since UE 5.5
// "StructUtils" is deprecated since UE 5.5 - FInstancedStruct is part of CoreUObject
"MassCommon",
"MassMovement",
"MassActors",
"MassSpawner",
"MassSignals", // engine runtime module in UE 5.8
"MassCrowd",
"MassRepresentation",
"MassReplication",
"MassNavigation",
"MassSimulation",
//needed for replication setup
"NetCore",
"AIModule",
"ZoneGraph",
"MassGameplayDebug",
"MassZoneGraphNavigation",
"Niagara",
"DeveloperSettings",
"GeometryCore",
"MassAIBehavior",
"StateTreeModule",
"MassLOD",
"NavigationSystem",
"Chaos",
"PhysicsCore",
"ChaosCore",
"ChaosSolverEngine", "CADKernel",
"RHI"
}
);"Plugins": [
{
"Name": "MassGameplay",
"Enabled": true
},
{
"Name": "MassAI",
"Enabled": true
},
{
"Name": "MassCrowd",
"Enabled": true
},
{
"Name": "CodeView",
"Enabled": true
}
]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 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

Take into notice, that the
MassCrowdAgentConfig_MetaHumanextendsMassCrowdAgentConfigwith ortherTraitsassociated.
- Crowd Visualization
Assorted Fragments
MassGameplayAssorted Fragments allows to attach general as well as custom-made
Fragmentsto theMass Entity Configdata asset file.
Replication
MassGameplay
State Tree
MassAI
Crowd Server Representation
MassCrowd
Look At
MassAITrait without custom configuration
CrowdMember
MassCrowdTrait without custom configuration
ZoneGraph Annotation
MassAITrait without custom configuration
SmartObject User
MassGameplaySmart 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.

SimulationLOD
MassGameplay
LODCollector
MassGameplayTrait without custom configuration
Movement
MassGameplay
ZoneGraph Navigation
MassAI
Steering
MassAI
Smooth Orientation
MassAIProper orientation related to the movement.

Avoidance
MassAI
Navigation Obstacle
MassAITrait without custom configuration
Debug Visualization
renders the entities in the scene while in Editor.MassGameplayThis
Trailallows to set custom options, such as culling distances, mesh, material or wire shape.
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.

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.

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 theProcessingPhaseproperty) 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 | ClientAllWorldModesStandalone | Server | Client | EditorWorldAllStandalone | Server | Client | Editor | EditorWorldThreads settings
By default all processors are multithreaded. To run in a single-thread, set
bRequiresGameThreadExecutionto true. UE 5.8 reworked the processor scheduling and dependency resolution to make better use of multi-core CPUs - correct access requirements (ReadOnlyvs.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 caseEntityIndex, 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
EMassFragmentAccess | Type | Function | Description |
|---|---|---|---|
ReadOnly | Fragment | GetFragmentView | Returns a read only TConstArrayView containing the data of our ReadOnly fragment. |
ReadWrite | Fragment | GetMutableFragmentView | Returns a writable TArrayView containing de data of our ReadWrite fragment. |
ReadOnly | Shared Fragment | GetConstSharedFragment | Returns a constant reference to our read only shared fragment. |
ReadWrite | Shared Fragment | GetMutableSharedFragment | Returns a reference of our writable shared fragment. |
ReadOnly | Subsystem | GetSubsystemChecked | Returns a read only constant reference to our world subsystem. |
ReadWrite | Subsystem | GetMutableSubsystemChecked | Returns a reference of our writable shared world subsystem. |
Notes
Tagsdo 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);
}
}
});
}/** 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;
// ...
}PrivateDependencyModuleNames.AddRange(
new string[] {
"MassEntity", // FMassEntityQuery, UMassProcessor, FMassExecutionContext
"MassCommon", // FTransformFragment, ProcessorGroupNames
"MassMovement", // FMassMovementParameters
"MassNavigation" // FMassMoveTargetFragment
}
);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):
EMassObservedOperationFlags | Description |
|---|---|
AddElement | The observed Fragment/Tag was added to an existing entity. |
RemoveElement | The observed Fragment/Tag was removed from an existing entity. |
CreateEntity | An entity containing the observed Fragment/Tag was created. |
DestroyEntity | An entity containing the observed Fragment/Tag is being destroyed. |
Add / Remove / All | Combinations: 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
.hUCLASS() 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; };.cpp// Adjusts the avoidance radius (FAgentRadiusFragment) of builder entities // (FBuildyMassBuilderTag only) from the default 40 to 20 units. UBuildyMassAgentRadiusInitializerProcessor::UBuildyMassAgentRadiusInitializerProcessor() : EntityQuery(*this) { // Instead of an execution order, an observer defines what it observes ObservedTypes = { FAgentRadiusFragment::StaticStruct() }; ObservedOperations = EMassObservedOperationFlags::Add; // added fragment or created entity } void UBuildyMassAgentRadiusInitializerProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager) { EntityQuery.AddRequirement<FAgentRadiusFragment>(EMassFragmentAccess::ReadWrite); // Filtering definition EntityQuery.AddTagRequirement<FBuildyMassBuilderTag>(EMassFragmentPresence::All); /* EMassFragmentPresence ::All - only entities with the tag pass ::None - entities with the tag are excluded */ } // Runs once for the entities that just received the observed fragment. void UBuildyMassAgentRadiusInitializerProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) { EntityQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context) { const TArrayView<FAgentRadiusFragment> RadiusList = Context.GetMutableFragmentView<FAgentRadiusFragment>(); for (FAgentRadiusFragment& RadiusFragment : RadiusList) { RadiusFragment.Radius = 20.f; } }); }.Build.csPrivateDependencyModuleNames.AddRange( new string[] { "MassEntity", "MassCommon" // FAgentRadiusFragment } );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;
};#include "Mass/Traits/SomeTrait.h"
//#include "MassActorSubsystem.h"
#include "MassEntityTemplateRegistry.h"
void USomeTrait::BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const
{
// ..
BuildContext.AddTag<FSomeMyTag>();
}- A good example of the use of tags to exclude certain entities (filtering application) is e.g. the LOD system
Filtration based on Tag


ProcessorTag 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:
| Plurality | Code |
|---|---|
| Singular | FMassEntityHandle EntityHandle = Context.GetEntity(EntityIndex); |
| Plural | auto 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#include "CrowdVisualizationFragment.h"
#include "MassExecutionContext.h"
#include "CrowdCharacterDataAsset.h"
#include "CrowdPresetDataAsset.h"
#include "MassCrowdRepresentationSubsystem.h"
#include "MassRepresentationTypes.h"
#include "MassRepresentationFragments.h"
#include "MassCrowdAnimationTypes.h"
#include "CitySampleCrowdSettings.h"
#include "CrowdCharacterActor.h"
UCitySampleCrowdVisualizationFragmentInitializer::UCitySampleCrowdVisualizationFragmentInitializer()
: EntityQuery(*this)
{
// City Sample (UE 5.0) used ObservedType/Operation - current API:
ObservedTypes = { FCitySampleCrowdVisualizationFragment::StaticStruct() };
ObservedOperations = EMassObservedOperationFlags::Add;
}
void UCitySampleCrowdVisualizationFragmentInitializer::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
EntityQuery.AddRequirement<FCitySampleCrowdVisualizationFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddRequirement<FMassRepresentationFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddRequirement<FCrowdAnimationFragment>(EMassFragmentAccess::ReadWrite);
}
uint32 UCitySampleCrowdVisualizationFragmentInitializer::FindColorOverride(FCrowdCharacterDefinition& CharacterDefinition, USkeletalMesh* SkelMesh)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_CitySampleCrowdVisualizationFragmentInitializer_FindColorOverride);
if (SkelMesh == nullptr) return FColor::White.ToPackedRGBA();
// ...
}
void UCitySampleCrowdVisualizationFragmentInitializer::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
// ...
};
UAnimToTextureDataAsset* UCitySampleCrowdVisualizationFragmentInitializer::GetAnimToTextureDataAsset(TSoftObjectPtr<UAnimToTextureDataAsset> SoftPtr)
{
if (SoftPtr.IsNull()) return nullptr;
if (SoftPtr.IsValid()) return SoftPtr.Get();
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_CitySampleCrowdVisualizationFragmentInitializer_GetAnimToTextureDataAsset_LoadSync);
return SoftPtr.LoadSynchronous();
}
}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.

Setting Character as Mass agents
In a case of human based crowds, usually the MassAgent component is palced to ACharacter BP class.


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();
};#include "Character/MassCharacter.h"
// Empty classMetaHuman 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.

Smart Object Definition File

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

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 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 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:
| Function | Description |
|---|---|
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:

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:

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.

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.

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.

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 Generatorworks with EQSRequest query template, see below:
Environment Query
The easies way of creating
Environment Queryif from theEQS SpawnPoint Generatorcomponent once there's necessaty to attachEnvironment Query. Press create new (as shown above) and name it withEQ_prefix.Environment Queryallows placing various nodes, such asPoints: 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 classComposite- Allows placing multiple Generator types at onceCurrent LocationPerceived ActorsPoints: CirclePoints: ConePoints: DonutPoints: Grid
Points: Pathing GridSmart 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 Generatorworkws with build Zone Graphs in the project, see following screenshot and video.
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
UMassEntitySpawnDataGeneratorBaseclass. To do so, simply create custom a generator class and attach it toGenerator Instanceof Mass Spawner as below:Mass Spawner
TrajectorySpawnGenerator.h#pragma once #include "CoreMinimal.h" #include "MassSpawnerTypes.h" #include "StructUtils/InstancedStruct.h" #include "MassEntitySpawnDataGeneratorBase.h" #include "TrajectorySpawnGenerator.generated.h" /** * Spawn-data generator that places each occupant at its first trajectory point. */ UCLASS(EditInlineNew, meta=(DisplayName="Trajectory Spawn Generator")) class SAMPLE_API UTrajectorySpawnGenerator : public UMassEntitySpawnDataGeneratorBase { GENERATED_BODY() public: /** * @param QueryOwner actor with attached data (AOccupantsSpawner) * @param EntityTypes spawn type * @param Count amount of entities to spawn * @param OnComplete delegate */ virtual void Generate( UObject& QueryOwner, TConstArrayView<FMassSpawnedEntityType> EntityTypes, int32 Count, FFinishedGeneratingSpawnDataSignature& OnComplete) const override; };TrajectorySpawnGenerator.cpp#include "Mass/TrajectorySpawnGenerator.h" #include "MassSpawnerTypes.h" #include "StructUtils/InstancedStruct.h" #include "MassSpawnLocationProcessor.h" #include "Mass/OccupantsSpawner.h" #include "MassEntitySpawnDataGeneratorBase.h" #include "Struct/TrajectoryListArrayStruct.h" void UTrajectorySpawnGenerator::Generate( UObject& QueryOwner, TConstArrayView<FMassSpawnedEntityType> EntityTypes, int32 Count, FFinishedGeneratingSpawnDataSignature& OnComplete) const { TArray<FMassEntitySpawnDataGeneratorResult> Results; BuildResultsFromEntityTypes(Count, EntityTypes, Results); const AOccupantsSpawner* Spawner = Cast<AOccupantsSpawner>(&QueryOwner); check(Spawner); for (FMassEntitySpawnDataGeneratorResult& R : Results) { FMassTransformsSpawnData TransformData; TransformData.bRandomize = false; TransformData.Transforms.Reserve(R.NumEntities); // Fill the transforms with the first points of the trajectories for (int32 i = 0; i < R.NumEntities; ++i) { if (Spawner->OccupantsTrajectoriesArr.IsValidIndex(i) && Spawner->OccupantsTrajectoriesArr[i].Trajectory.Num() > 0) { const FOccupantsTrajectory& FirstStep = Spawner->OccupantsTrajectoriesArr[i].Trajectory[0]; TransformData.Transforms.Add(FTransform(FirstStep.Rotation, FirstStep.Location)); } else { // fallback to the origin TransformData.Transforms.Add(FTransform::Identity); } } R.SpawnData = FInstancedStruct::Make(MoveTemp(TransformData)); R.SpawnDataProcessor = UMassSpawnLocationProcessor::StaticClass(); } OnComplete.ExecuteIfBound(Results); }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 theBP Mass Traffic Parked Vehicle Spawn Data Generatorlinking to Cloud Point data, shown below.
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
BatchDestroyEntityChunksis 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::DestroyEntitiescalls 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
}void USampleProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
EntityQuery.AddTagRequirement<FMassLowLODTag>(EMassFragmentPresence::None);
EntityQuery.AddTagRequirement<FMassOffLODTag>(EMassFragmentPresence::None);
}
void USampleProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
EntityQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
{
// Code that will run only for HighLOD and MediumLOD
});
}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 Loggermass.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
MassRepresentationand 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
- MassEntity documentation (Epic) - overview, MassGameplay, Mass Avoidance, Mass Debugger, simplified Processor API
- Unreal Engine 5.8 release notes - Mass refactor details
- Your First 60 Minutes with Mass
- CitySample project (Fab)
- Crowds documentation for City Sample project
- MassSample Github
MassSample Github page comes with a great explanation of the ECS used for the Mass on that page (why it's incredibly high performance) and Mass settings and options. Best of last, a sample project included! Check which engine version the sample targets - the Mass API changed significantly in UE 5.5–5.8.
- On All Fronts game GitHub
- MassAI framework explanation in Japanese
- Chinesse articles about MassSystem
Anything unclear?
Let us know!