Skip to content

C++ gameplay quick start

Mass Forge exposes the same supported gameplay path to C++ and Blueprint. The public Blueprint function libraries are ordinary public C++ APIs, so a project module can call them directly and receive the same generation-safe handles and structured outcomes shown in the Blueprint tutorials.

This page uses designer-authored Data Assets for configuration and C++ for orchestration. It does not construct schemas, abilities, or damage formulas in code.

Mass Forge C++ gameplay flow from project-owned spawning through generation-safe handles, abilities or effects, a Mass projectile, and impact-driven damage

Read this left to right before copying code. Project C++ owns encounter rules and presentation, editable Data Assets own configuration, and Mass Forge owns generation-safe identity, validation, bounded state, and structured transactions.

Module setup

Add the plugin modules used by the project to the project's Build.cs:

csharp
PublicDependencyModuleNames.AddRange(new[]
{
    "MassForge",
    "MassEntity",
    "MassSpawner"
});

Visual Studio Code showing the MassForgeSamples Build.cs public dependency list with MassEntity, MassForge, and MassSpawner together

Add the modules to the consumer module that owns the gameplay code. MassForge exposes the supported gameplay API, while Unreal's MassEntity and MassSpawner provide the native entity configuration and spawn types used by the example.

Include only public headers. A gameplay class that follows the complete example below needs:

cpp
#include "Blueprint/MF_AbilityBlueprintLibrary.h"
#include "Blueprint/MF_AbilityTargetingBlueprintLibrary.h"
#include "Blueprint/MF_AttributeBlueprintLibrary.h"
#include "Blueprint/MF_DamageBlueprintLibrary.h"
#include "Blueprint/MF_EntityBlueprintLibrary.h"
#include "Blueprint/MF_EntityInspectionBlueprintLibrary.h"
#include "Blueprint/MF_EntityStateBlueprintLibrary.h"
#include "Blueprint/MF_ProjectileBlueprintLibrary.h"
#include "Data/MF_AbilityData.h"
#include "Data/MF_DamageDefinitionData.h"
#include "Data/MF_InstantEffectData.h"
#include "MassEntityConfigAsset.h"

Never include a file from a Private directory. FMFEntityHandle is the supported index-and-serial identity; do not store a raw entity index or rebuild a handle from partial data.

Visual Studio Code showing the public Mass Forge Blueprint-library includes and generation-safe entity handles in the compile-checked quick-start source

The include paths begin at the plugin's public boundary. The checked source intentionally includes and instantiates every public type used below, so public-header, module-dependency, and signature drift fails the release compile test.

Author the data first

Use the Entity Config wizard or duplicate the assets in /MassForge/BlueprintExamples/Data into project Content. Keep these as editable project-owned inputs:

Mass Forge Entity Config Wizard with Combat Ready selected and the complete dependency-safe creation plan visible

C++ orchestration consumes the same editable Entity Config, schemas, definitions, effects, abilities, and projectile config as Blueprint. Combat Ready is a starting package, not generated gameplay code.

  • a Combat Ready Mass Entity Config with the Transform, Attributes, Gameplay Tags, Effects, and Ability traits required by the intended gameplay;
  • Vital, Combat, and Support schemas;
  • a Damage Definition;
  • Instant/Persistent Effects;
  • Ability Data Assets;
  • a projectile Entity Config with the Mass Forge Transform and Projectile traits.

Mass Forge Global Attribute Schemas project settings with Vital, Combat, and Support schema assets assigned

Verify these project-wide assignments before resolving attribute IDs in C++. The names used below must exist uniquely in the configured schemas.

The C++ examples below assume those assets are assigned through UPROPERTY references. Do not synchronously load gameplay assets inside a Mass processor loop.

Spawn generation-safe entities

cpp
FMFEntityHandle Source;
const EMFEntitySpawnResult SpawnResult =
    UMF_EntityBlueprintLibrary::SpawnMassForgeEntity(
        this,
        CombatEntityConfig,
        GetActorTransform(),
        Source);

if (SpawnResult != EMFEntitySpawnResult::Success)
{
    // Surface the exact enum in diagnostics or UI.
    return;
}

Visual Studio Code showing two SpawnMassForgeEntity calls returning explicit spawn enums and complete FMFEntityHandle values

Spawning returns two things with different jobs: an explicit outcome for the current request and a complete generation-safe handle for future work. Preserve both; never infer success from a raw index.

this is any object with the intended gameplay world. Use SpawnMassForgeEntities for a bounded list of transforms. That convenience path is capped at 10,000 returned handles; large population creation belongs in a project-owned C++ Mass spawning path.

Before a later operation, IsMassForgeEntityValid(this, Source) is a convenient predicate. Every Mass Forge operation still revalidates the full handle itself. DestroyMassForgeEntity is immediate and therefore valid only while Mass is idle.

Read and change an attribute

cpp
FMFAttributeId Health;
Health.Domain = EMFAttributeDomain::Vital;
Health.Name = TEXT("Health");

float CurrentHealth = 0.0f;
const EMFAttributeAccessResult ReadResult =
    UMF_AttributeBlueprintLibrary::GetMassForgeAttribute(
        this, Source, Health, CurrentHealth);

if (ReadResult == EMFAttributeAccessResult::Success)
{
    const FMFAttributeChangeResult Change =
        UMF_AttributeBlueprintLibrary::ChangeMassForgeAttribute(
            this,
            Source,
            Health,
            EMFAttributeOperation::Add,
            -10.0f,
            TEXT("Example.DirectChange"));

    if (!Change.WasSuccessful())
    {
        // Inspect Change.Result and Change.Attribute.
    }
}

The direct path is for a known Mass-idle point. For general game-thread orchestration, queue the request and retain the receipt ID:

cpp
const FMFAttributeRequestReceipt Receipt =
    UMF_AttributeBlueprintLibrary::QueueMassForgeAttributeChange(
        this,
        Source,
        Health,
        EMFAttributeOperation::Add,
        -10.0f,
        TEXT("Example.QueuedChange"));

if (!Receipt.WasAccepted())
{
    // No completion will arrive. Handle queue-full or validation failure now.
}

Bind once to UMF_AttributeSubsystem::OnAttributeRequestCompletedNative and correlate its completion by RequestId. Acceptance is not execution success: the target and schema are validated again in the deterministic deferred phase.

Visual Studio Code showing attribute read and direct or queued change calls, a reusable Instant Effect, deterministic damage, ability admission, and queued activation

The compile-checked counterpart keeps the ordinary gameplay sequence in one readable frame. Every operation retains its typed result or receipt; none is collapsed into an unchecked boolean.

Apply reusable healing and effects

cpp
const FMFInstantEffectResult HealResult =
    UMF_AttributeBlueprintLibrary::ApplyMassForgeInstantEffect(
        this,
        Source,
        HealEffect,
        TEXT("Example.Heal"));

Use the queued equivalent when overlap with Mass processing is possible. Prefer an Effect over a raw attribute change when the operation has several atomic modifiers, gameplay tags, source context, or a reusable designer-authored identity.

Deal deterministic damage

cpp
FMFDamageRequest Request;
Request.SourceEntity = Source;
Request.TargetEntity = Target;
Request.BaseDamage = 25.0f;
Request.CriticalRoll = 1.0f;
Request.Context.Origin = GetActorLocation();
Request.Context.bHasOrigin = true;

const FMFDamageApplicationResult Damage =
    UMF_DamageBlueprintLibrary::ApplyMassForgeDamage(
        this,
        DamageDefinition,
        Request);

if (Damage.WasSuccessful())
{
    // Damage.HealthAfter, Damage.ShieldAfter, Damage.bCritical, Damage.bKilled.
}

The result contains the resolved formula stages, policy decision, attribute transaction, death request, and failure location. Use QueueMassForgeDamage outside a guaranteed Mass-idle point and correlate OnDamageRequestCompletedNative by request ID.

Ordinary Actors can use ApplyMassForgeActorDamageToEntity; entity-to-entity combat uses ApplyMassForgeEntityDamageToEntity. These are convenience constructors around the same canonical request.

Grant and activate an ability

cpp
const EMFAbilityResult GrantResult =
    UMF_AbilityBlueprintLibrary::GrantMassForgeAbility(
        this, Source, Ability);

if (GrantResult == EMFAbilityResult::Success)
{
    const FMFAbilityActivationResult Check =
        UMF_AbilityBlueprintLibrary::CanActivateMassForgeAbility(
            this, Source, Target, Ability);

    if (Check.WasSuccessful())
    {
        const FMFAbilityActivationResult Activation =
            UMF_AbilityBlueprintLibrary::ActivateMassForgeAbility(
                this, Source, Target, Ability);
    }
}

Activation is atomic across costs, cooldowns, charges, tags, effects, and authored execution behavior. A cast or channel returns a started lifecycle rather than pretending the final effect committed immediately.

For ordinary runtime orchestration, prefer QueueMassForgeAbilityGrant and QueueMassForgeAbilityActivation. Bind once to UMF_AbilitySubsystem::OnAbilityOperationRequestCompletedNative, then match the positive request ID. Cancellation and interruption share that ordered request channel.

Select actorless or represented targets

cpp
FMFAbilityRadiusTargetRequest Query;
Query.SourceEntity = Source;
Query.Origin = GetActorLocation();
Query.Radius = 1200.0f;
Query.MaxTargets = 16;
Query.bExcludeSource = true;

const FMFAbilityTargetSelection Selection =
    UMF_AbilityTargetingBlueprintLibrary::FindMassForgeAbilityTargetsInRadius(
        this, Query);

for (const FMFAbilityTargetCandidate& Candidate : Selection.Targets)
{
    const FMFEntityHandle TargetEntity = Candidate.Entity;
    // Candidate.RepresentedActor is optional; actorless entities remain valid targets.
}

Radius and segment queries are bounded and deterministic. Use SelectMassForgeAbilityTargetsWithProvider when a project owns a more specialized C++/Blueprint targeting rule; Mass Forge validates, de-duplicates, and bounds the returned handles.

Launch a gameplay projectile

cpp
const FMFProjectileLaunchReceipt Launch =
    UMF_ProjectileBlueprintLibrary::LaunchMassForgeEntityProjectile(
        this,
        ProjectileEntityConfig,
        Source,
        Target,
        MuzzleLocation,
        TargetLocation,
        1800.0f,             // speed
        120.0f,              // arc height
        0.0f,                // launch delay
        8.0f,                // maximum lifetime
        0,                   // presentation channel
        TEXT("Impact.Arc"),
        DamageDefinition,
        25.0f,
        FMFEffectContext());

if (!Launch.WasSuccessful())
{
    // No projectile exists; inspect Launch.Result.
}

Damage commits when Mass Forge resolves impact, not at launch. Gameplay state remains in the projectile subsystem; visuals consume GetMassForgeProjectileSnapshots. Use Niagara, HISM, or an Actor pool for production populations. PresentFirstMassForgeProjectile is intentionally only a small-scene teaching helper.

Visual Studio Code showing bounded radius target selection followed by a Mass Forge entity-projectile launch with source, target, trajectory, cue, damage definition, and effect context

Selection produces bounded generation-safe candidates; launch owns flight and impact resolution. The authored damage definition is passed into the projectile so damage is committed at impact instead of being applied invisibly when the button is pressed.

Inspect and persist state

cpp
FMFEntityInspectionSnapshot Inspection;
const EMFEntityInspectionResult InspectionResult =
    UMF_EntityInspectionBlueprintLibrary::CaptureMassForgeEntityInspection(
        this, Source, Inspection, true);

FMFEntityStateSnapshot SavedState;
const FMFEntityStateSnapshotOperationResult Capture =
    UMF_EntityStateBlueprintLibrary::CaptureMassForgeEntityState(
        this, Source, SavedState);

Store the snapshot in the project's save format, not the entity handle. Recreate an entity in the destination world, then restore at a known Mass-idle point with RestoreMassForgeEntityState, or queue QueueMassForgeEntityStateRestore. Active externally targeted casts/channels require the documented project-owned relationship key and lifecycle target binding.

Visual Studio Code showing projectile launch beside entity inspection and entity-state snapshot capture in the public compile test

Inspection is an observation surface; persistence captures a portable value. The current world handle is deliberately not the save identity and must not be serialized as if it survives recreation.

Blueprint and C++ parity

TaskBlueprintOrdinary C++Processor/high-volume C++
Spawn a small populationEntity spawn nodesSame static public functionsProject Mass spawning path
Read/change one attributeDirect or async nodesDirect or queued public APICompiled attribute + fragment view
Effects/damage/abilitiesDirect or async nodesDirect or queued public APIBounded commands or high-volume effect table
Target selectionRadius, trace, provider nodesSame public APIsProject query/processor with bounded handoff
Projectile gameplayLaunch async/convenience nodesSame public APIs/subsystemProjectile trait + built-in processor
PresentationBlueprint events/snapshotsNative delegates/snapshotsCollect bounded presentation state; render elsewhere
Save/restoreSnapshot nodesSame public APIsPerform outside processor hot loops
InspectionInspector and capture nodeCapture API/native providersNever inspect every entity every frame

Intentionally C++-only work

Blueprint is supported for authoring and ordinary gameplay orchestration. C++ is required or strongly preferred when the work itself is high-volume: custom Mass processors, direct fragment iteration, compiled attribute bindings, populations above the Blueprint batch limit, project representation processors, and renderer-specific Niagara/HISM upload paths.

That boundary prevents per-entity reflection, allocation, delegate broadcasts, and large Blueprint arrays from becoming the scale limit. It does not create a second gameplay model; both paths use the same schemas, Data Assets, entity identity, and result contracts.

Continue with C++ high-volume attribute access, architecture, deferred commands, Mass projectiles, and entity-state persistence.

Mass Forge documentation — generated from the shipping Markdown source.