Skip to content
Website logo
YawLighthouse

Unreal Engine's Mass: Actors

Author:
Nicholas Helish

Nicholas Helish

Game Developer

You already read the About Me  page! I’m Nicholas Helish, the author for this article, website, cool stuff, and fan of cats!

July 04, 2026 · 6 min read

A description of how Actor's are handled within Unreal Engine's Mass.

This blog post was written in reference to Unreal Engine 5.8

Overview

In Mass because you’re dealing with Entities are not Actors directly it requires having a Fragment pointing to its Actor if needed, the engine provides a common Fragment for pointing to an Actor with FMassActorFragment.

Actor LOD Handling

The engine also provides another system for having a data-oriented LOD Actor swapping/spawning system that you can hook into using FMassRepresentationFragment(added via a Visualization Trait) which provides the Actor(if applicable) as its current representation but can automatically change to another LOD supporting Instanced Meshes for both Static Meshes and Skinned Meshes.

These properties are configured from a Visualization Trait. Note that the shared base UMassVisualizationTrait is soft-deprecated in 5.8(it shows in the editor as “DEPRECATED Visualization”), so pick UMassMovableVisualizationTrait for anything that moves or UMassStationaryVisualizationTrait for static placements.

There are distance-only variants too, but UMassDistanceVisualizationTrait is also deprecated, so use UMassStationaryDistanceVisualizationTrait if you want that path. It also requires the LOD Trait applied in that same Entity which handles connecting the Entity to the LOD system. It currently is intended as a single representation per Trait, how that Fragment is used at runtime is dependent on the systems and framework that is using it(basically on you the developer).

The Visualization Trait’s support:

  • High Resolution Actor: Intended as the full Actor’s functionality.
  • Low Resolution Actor: Intended as the lower processing version of the Actor, could be the same Actor just executing at a lower tick-rate with fewer features enabled.
  • Instanced Static Mesh: Intended as just a visual representation with no collision.
  • Instanced Skinned Mesh: Intended as just a visual representation with no collision.

You are able to create representation configurations at runtime and create your own Fragments as well or modify existing configurations to make new Fragments. The system that does this is through a UMassRepresentationSubsystem that registers representation descriptions and uses handles to pass that information around.

For example, you can setup a Representation Trait on an Entity. In the data asset you can also have somewhere else multiple materials for a Static Mesh that are used to swap that information for the LOD system at runtime(say you want a different LOD 3 representation material after it’s spawned). In this case you would need to do these steps:

  1. Get the Mass Representation Subsystem.
  2. Call ::FindOrAddStaticMeshDesc() and provide a runtime created FStaticMeshInstanceVisualizationDesc, whose Meshes array holds the individual FMassStaticMeshInstanceVisualizationMeshDesc entries (prepare for a lot of long names in Mass).
  3. Cache the outputted FStaticMeshInstanceVisualizationDescHandle from the ::FindOrAddStaticMeshDesc().
  4. Later in a Processor’s ::Execute() function while iterating Entities, get the FMassRepresentationFragment for that Entity on the specific LOD you want and set its StaticMeshDescHandle to the handle you created in the earlier step.

Managing LOD Swapping

There are multiple Traits that manage LOD swapping, but for any basic functionality it requires a combination of two; a LOD Trait and a Visualization Trait. They branch from those two categories below:

LOD Traits Table

Collector Type

Fragments Added

Fragments Required

Description

LOD Collector

UMassLODCollectorTrait

  • FMassViewerInfoFragment
  • FMassCollectLODViewerInfoTag

FTransformFragment

Collects viewer info into FMassViewerInfoFragment: the closest viewer distance(ClosestViewerDistanceSq) and the closest distance to that viewer’s frustum(ClosestDistanceToFrustum).

It does not pick the LOD itself, UMassVisualizationLODProcessor(or UMassSimulationLODProcessor) reads that Fragment afterwards and writes the LOD.

Heavier, because it runs the frustum distance test for every viewer.

Distance LOD Collector

UMassDistanceLODCollectorTrait

  • FMassViewerInfoFragment
  • FMassCollectDistanceLODViewerInfoTag

FTransformFragment

Collects the closest viewer distance only(ClosestViewerDistanceSq) into FMassViewerInfoFragment, with no frustum test, so ClosestDistanceToFrustum is left at its default. LOD selection still happens later in the same Visualization LOD Processor.

Lighter due to not checking frustum visibility.

For managing how an Actor’s LOD swaps or spawning is handled, you have to inherit from UMassRepresentationActorManagement which is a UObject that is never actually instanced, the Trait writes your class into FMassRepresentationParameters::RepresentationActorManagementClass, and ::ComputeCachedValues() caches that class’s Class Default Object on the const shared Fragment as CachedRepresentationActorManagement. Processors then use that CDO directly.

This is why every overridable function is const and why you must not store per-Entity mutable state on it) where it has multiple virtual functions that can be overridden. You have to specify this within the Trait for the Representation Actor Management Class to use, you can also specify the subsystem in the Trait, but I haven’t found a need to do that yet unless you are implementing a completely different LOD swapping framework.

Representation Actor Manager Class

You also have to inherit from these Processors for connecting it to the system because the base classes have their auto registration disabled. UMassRepresentationProcessor(which UMassVisualizationProcessor derives from), UMassVisualizationLODProcessor and UMassLODCollectorProcessor each set bAutoRegisterWithProcessingPhases = false in their own constructor, which is what the engine calls a Dynamically Registered Processor: one that isn’t automatically added to the processing graph.


You can see an example of this in the engine with the Mass Crowd’s module, where UMassCrowdVisualizationProcessor, UMassCrowdVisualizationLODProcessor and UMassCrowdLODCollectorProcessor each inherit from one of those base Processors and set bAutoRegisterWithProcessingPhases = true in their constructors. Otherwise, leave the flag alone and dynamically register your Processor instance at runtime, which achieves the same effect.

These are the Processors to inherit from and enable auto register in their constructors for:

  • UMassLODCollectorProcessor
  • UMassVisualizationLODProcessor
  • UMassVisualizationProcessor

When swapping between an Instanced Mesh to a real Actor(High or Low res), the Actor Manager will call its virtual ::SetActorEnabled() function for both directions. On an initial spawn ::OnPostActorSpawn() runs first: ::GetOrSpawnActor() only queues a spawn request with the UMassActorSpawnerSubsystem and returns nullptr on that pass, and the subsystem fires the post-spawn delegate when it later processes the request.

::SetActorEnabled() only gets called on a subsequent Processor pass once the request has succeeded and ::GetOrSpawnActor() actually returns the spawned Actor. It is recommended to override both of these, and it will handle many cases. There are other overridable events within the class, so I encourage you to take a look and see what works for your needs.

LOD Tags

When Mass swap’s LOD’s between High/Medium/Low/Off, it swaps Tags on the Entity to track it, with each tag representing each corresponding LOD:

  • High: FMassHighLODTag
  • Medium: FMassMediumLODTag
  • Low: FMassLowLODTag
  • Off: FMassOffLODTag

You may see considerations in Processors to account for this by using AddTagRequirement<FMassOffLODTag>(EMassFragmentPresence::None), this is so the Processor doesn’t execute on Entities too far away to visualize anything. This can help with optimizing for performance by removing unnecessary CPU cycles.

World Partition

Regarding World Partition, the system currently checks if the runtime cell is loaded and collision is enabled before swapping from an Instanced Mesh to Actor to enforce valid state for each cell.

Share this post

Other Mass Blog Posts