A reference implementation for feeding live, mutable gameplay data (an ability/technique’s timing and size info) into a Niagara VFX system via a custom Niagara Data Interface (NDI) — without needing to manually rewire Niagara User Parameters every time a new field is added.
Location: Source/FXImplementations/MutableVFX/
Niagara systems can read external data via User Parameters, but the naive approach — adding one User Parameter per value (Activation Time, Duration, Size, etc.) — means every time gameplay design wants to pass in a new field, someone has to add a new User Parameter in the Niagara editor and update every call site that sets it. It also doesn’t naturally support “live” data — values that change while the effect is already playing (e.g. an ability’s Duration getting extended mid-cast).
This system instead exposes one custom Niagara function (“Get Mutable VFX Info”) backed by a single struct. Gameplay code spawns the VFX and hands it a pointer to a small carrier object; the Niagara system reads through that pointer every frame, so whatever gameplay code does to the carrier’s data shows up in the running effect without any extra wiring.
Gameplay code (ability, designer BP, etc.)
│
│ builds FMutableVFXActivationInfo (transform, attach target, timing, size)
▼
UMutableVFXFunctionLibrary::SpawnMutableVFX()
│
│ spawns the Niagara component, creates a UMutableVFXInfoCarrier
│ (outered to the spawned component so its lifetime matches the VFX),
│ copies the activation info onto it, and sets it as the
│ "User.MutableVFXObject" user parameter
▼
UNiagaraDataInterfaceMutableVFX (the NDI, set as that user parameter's value)
│
│ PerInstanceTick() (once per frame, game thread) re-reads
│ Carrier->MutableVFXActivationInfo into its own per-instance cache
▼
Niagara graph — "Mutable VFX Query" node, "Get Mutable VFX Info" function
│
│ VectorVM function GetMutableVFXInfoVM() runs per particle-batch,
│ writes the cached struct's fields out as individual float/vector pins
▼
Rest of the Niagara graph (artist-authored, uses the pins to drive the effect)
From C++ or Blueprint, call UMutableVFXFunctionLibrary::SpawnMutableVFX with
a UNiagaraSystem* template and a filled-in FMutableVFXActivationInfo:
FMutableVFXActivationInfo Info;
Info.SpawnTransform = MyActor->GetActorTransform();
Info.AttachTarget = MyActor->GetMesh();
Info.ActivationTime = 0.2f;
Info.Duration = 2.5f;
Info.Size = FVector(1.5f, 1.5f, 1.5f);
UNiagaraComponent* VFX = UMutableVFXFunctionLibrary::SpawnMutableVFX(this, MyNiagaraSystem, Info);
On the Niagara side, any system using this pattern needs:
UObject/Object Interface, named
MutableVFXObject.To update the live data mid-effect, keep a reference to the
UMutableVFXInfoCarrier (not currently returned by SpawnMutableVFX — see
Limitations below) and write to its MutableVFXActivationInfo fields
directly; the running Niagara instance picks up the change on its next tick.
MutableVFXTypes.hPlain data structs used throughout the system.
FMutableVFXActivationInfo — the main payload. Split into two logical
groups: spawn/transform info (SpawnTransform, AttachTarget,
SocketName — used by the spawn function, never touched by Niagara) and
user parameters (ActivationTime, Duration, LaunchDelay, Period,
Size — the values Niagara actually reads). This split matters: the
transform/object-pointer fields can’t be registered as a Niagara type,
only the plain-numeric fields can flow through the Data Interface.
IsValid() — sanity check called before spawning (rejects degenerate
transforms, non-positive Duration, zero Size, etc.).ToString() — debug helper, logged from the NDI’s VM function outside
shipping builds.