| name | ue-niagara-effects |
| description | Use this skill when working with Niagara particle systems, VFX, effects, emitter, Niagara component, or Niagara parameter in Unreal Engine C++. Covers spawning systems, setting parameters, data interfaces (SkeletalMesh, StaticMesh, Curve, Array), OnSystemFinished delegate, and performance tuning. See references/niagara-parameter-types.md for type mapping and references/niagara-data-interfaces.md for data interface catalogue. For particle materials, see ue-materials-rendering. |
| metadata | {"version":"1.0.0"} |
UE Niagara Effects
You are an expert in controlling Unreal Engine's Niagara VFX system from C++.
Context Check
Read .agents/ue-project-context.md before proceeding. Confirm:
- The
Niagara plugin is listed under enabled plugins (Plugins/FX/Niagara).
- The target module's
Build.cs has "Niagara" (and optionally "NiagaraCore") in PublicDependencyModuleNames.
- Platform targets: note whether mobile or dedicated-server builds are in scope, because Niagara is
typically suppressed on dedicated servers and may need LOD simplification on mobile.
Information Gathering
Before writing Niagara C++ code, clarify:
- Effect lifecycle — one-shot (fire and forget) or persistent / looping?
- Parameter needs — which Niagara User Parameters must be set from gameplay (positions, colors, scalars)?
- Data interfaces required — SkeletalMesh, StaticMesh, Curve, Array, or custom?
- Simulation target — CPU or GPU sim? (affects which DI features are available)
- Performance budget — pooling required? Mobile scalability tier?
- Completion handling — does gameplay need a callback when the effect finishes?
System Structure (UE Concept Map)
UNiagaraSystem (asset: UNiagaraSystem)
└── UNiagaraEmitter[] (per-emitter asset, referenced via FNiagaraEmitterHandle)
└── UNiagaraScript[] (Spawn / Update / Event scripts; authored in Niagara editor)
└── Modules (stack of NiagaraScript nodes; not C++ classes)
Runtime instances:
UNiagaraComponent (scene component that drives one UNiagaraSystem instance)
└── FNiagaraSystemInstance (internal runtime state; access via GetSystemInstanceController())
Key rule: authors expose parameters to C++ by setting their namespace to User. in the Niagara
editor. Only User.* parameters can be overridden at runtime from C++.
Spawning Niagara Systems
Fire-and-Forget (One-Shot) at World Location
#include "NiagaraFunctionLibrary.h"
#include "NiagaraComponent.h"
UNiagaraComponent* NiagaraComp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
this,
ImpactVFXSystem,
HitLocation,
FRotator::ZeroRotator,
FVector(1.f),
true,
true,
ENCPoolMethod::AutoRelease,
true
);
if (NiagaraComp)
{
NiagaraComp->SetVariableVec3(FName("User.HitNormal"), HitNormal);
NiagaraComp->SetVariableLinearColor(FName("User.HitColor"), DamageColor);
}
Attached to a Component (Persistent / Looping)
UNiagaraComponent* TrailComp = UNiagaraFunctionLibrary::SpawnSystemAttached(
TrailVFXSystem,
WeaponMesh,
FName("MuzzleSocket"),
FVector::ZeroVector,
FRotator::ZeroRotator,
EAttachLocation::SnapToTarget,
false,
true,
ENCPoolMethod::ManualRelease,
true
);
Persistent Component on an Actor (Preferred for Repeated Use)
UPROPERTY(VisibleAnywhere)
TObjectPtr<UNiagaraComponent> EngineTrailVFX;
EngineTrailVFX = CreateDefaultSubobject<UNiagaraComponent>(TEXT("EngineTrailVFX"));
EngineTrailVFX->SetupAttachment(GetRootComponent());
EngineTrailVFX->SetAutoActivate(false);
EngineTrailVFX->SetAsset(EngineTrailSystem);
EngineTrailVFX->Activate( true);
Lifecycle Control
NiagaraComp->Activate( false);
NiagaraComp->Activate( true);
NiagaraComp->Deactivate();
NiagaraComp->DeactivateImmediate();
NiagaraComp->ResetSystem();
NiagaraComp->ReinitializeSystem();
NiagaraComp->SetPaused(true);
NiagaraComp->SetAutoDestroy(true);
Setting Parameters from C++
All setter variants accept the parameter name as FName prefixed with its namespace.
User-exposed parameters use the User. prefix.
NiagaraComp->SetVariableFloat(FName("User.DamageAmount"), 150.f);
NiagaraComp->SetVariableInt(FName("User.ProjectileCount"), 12);
NiagaraComp->SetVariableBool(FName("User.bIsCritical"), bIsCriticalHit);
NiagaraComp->SetVariableVec2(FName("User.UVOffset"), FVector2D(0.5, 0.25));
NiagaraComp->SetVariableVec3(FName("User.TargetPosition"), TargetLocation);
NiagaraComp->SetVariableVec4(FName("User.CustomData"), FVector4(1, 0.5, 0, 1));
NiagaraComp->SetVariableLinearColor(FName("User.TintColor"), FLinearColor::Red);
NiagaraComp->SetVariableQuat(FName("User.Orientation"), GetActorQuat());
NiagaraComp->SetVariableObject(FName("User.TargetMesh"), SkeletalMeshComponent);
NiagaraComp->SetVariableActor(FName("User.SourceActor"), this);
NiagaraComp->SetVariablePosition(FName("User.WorldOrigin"), WorldSpaceOrigin);
NiagaraComp->SetVariableMaterial(FName("User.FXMaterial"), DynamicMaterial);
NiagaraComp->SetVariableTexture(FName("User.FlowMap"), FlowTexture);
bool bIsValid = false;
float CurrentValue = NiagaraComp->GetVariableFloat(FName("User.EmitRate"), bIsValid);
Blueprint-Accessible Legacy Signatures (prefer FName variants above)
NiagaraComp->SetNiagaraVariableFloat(TEXT("User.SpeedScale"), 2.f);
NiagaraComp->SetNiagaraVariableVec3(TEXT("User.ImpactPoint"), Location);
NiagaraComp->SetNiagaraVariableLinearColor(TEXT("User.Color"), FLinearColor::Blue);
Parameter Namespaces Reference
| Namespace prefix | Settable from C++ | Description |
|---|
User. | Yes | User-exposed; main runtime override |
System. | No (read-only) | System-level built-ins (Age, DeltaTime, etc.) |
Emitter. | No (internal) | Per-emitter variables |
Particle. | No (internal) | Per-particle variables |
See references/niagara-parameter-types.md for the full C++ type to Niagara type mapping.
Data Interfaces from C++
Data interfaces (DIs) are UObject-derived assets that expose structured external data to Niagara
scripts. They appear as User.* parameters of DI type in the Niagara editor, and are overridden
at runtime via SetVariableObject or the specialized function library helpers.
Binding Skeletal Mesh DI
#include "NiagaraFunctionLibrary.h"
UNiagaraFunctionLibrary::OverrideSystemUserVariableSkeletalMeshComponent(
NiagaraComp,
TEXT("User.SourceMesh"),
GetMesh()
);
UNiagaraFunctionLibrary::SetSkeletalMeshDataInterfaceFilteredBones(
NiagaraComp,
TEXT("User.SourceMesh"),
{ FName("hand_l"), FName("hand_r") }
);
UNiagaraFunctionLibrary::SetSkeletalMeshDataInterfaceSamplingRegions(
NiagaraComp,
TEXT("User.SourceMesh"),
{ FName("HeadRegion") }
);
Binding Static Mesh DI
UNiagaraFunctionLibrary::OverrideSystemUserVariableStaticMeshComponent(
NiagaraComp,
TEXT("User.ScatterMesh"),
StaticMeshComp
);
UNiagaraFunctionLibrary::OverrideSystemUserVariableStaticMesh(
NiagaraComp,
TEXT("User.ScatterMesh"),
LoadedStaticMesh
);
Reading / Modifying an Array DI at Runtime
#include "NiagaraDataInterfaceArrayFunctionLibrary.h"
TArray<float> HeatValues = ComputeHeatValues();
UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayFloat(
NiagaraComp, FName("User.HeatData"), HeatValues
);
UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayFloatValue(
NiagaraComp, FName("User.HeatData"), 5, 0.9f, false
);
Direct DI Object Access (Advanced)
UNiagaraDataInterfaceCurve* CurveDI =
UNiagaraFunctionLibrary::GetDataInterface<UNiagaraDataInterfaceCurve>(
NiagaraComp, FName("User.SpeedCurve")
);
if (CurveDI)
{
CurveDI->Curve.AddKey(0.f, 0.f);
CurveDI->Curve.AddKey(1.f, 500.f);
#if WITH_EDITORONLY_DATA
CurveDI->UpdateLUT();
#endif
}
UNiagaraDataInterface* GenericDI =
UNiagaraFunctionLibrary::GetDataInterface(
UNiagaraDataInterfaceStaticMesh::StaticClass(),
NiagaraComp,
FName("User.ImpactMesh")
);
See references/niagara-data-interfaces.md for the full built-in DI catalogue.
Custom Data Interfaces: Subclass UNiagaraDataInterface, override GetFunctions() to define
available functions, GetVMExternalFunction() to bind C++ implementations, and optionally
ProvidePerInstanceDataForRenderThread() for GPU access. Register in the module's StartupModule.
This enables game-specific data (inventory, terrain) to feed directly into Niagara systems.
Completion Callbacks
NiagaraComp->OnSystemFinished.AddDynamic(this, &UMyComponent::OnVFXFinished);
UFUNCTION()
void UMyComponent::OnVFXFinished(UNiagaraComponent* FinishedComponent)
{
FinishedComponent->DestroyComponent();
}
NiagaraComp->OnSystemFinished.RemoveDynamic(this, &UMyComponent::OnVFXFinished);
Performance: Pooling
ENCPoolMethod controls the pool behavior on every spawn call:
AutoRelease — component returns to the world pool automatically when the system finishes.
Pass bAutoDestroy=true; the pool handles actual reclaim.
ManualRelease — you control when the component returns; call ReleaseToPool() to reclaim.
None — no pooling; component is destroyed when finished if bAutoDestroy=true.
UNiagaraComponent* Comp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
this, ExplosionSystem, Location, FRotator::ZeroRotator,
FVector(1.f), true, true,
ENCPoolMethod::AutoRelease
);
TrailComp->ReleaseToPool();
if (FNiagaraWorldManager* NiagaraWorldMan = FNiagaraWorldManager::Get(GetWorld()))
{
NiagaraWorldMan->GetComponentPool()->PrimePool(ExplosionSystem, GetWorld());
}
Pool capacity is configured per-system in the UNiagaraSystem pooling settings (not a global CVar).
Relevant global pool CVars: FX.NiagaraComponentPool.Enable (1/0) and
FX.NiagaraComponentPool.KillUnusedTime (seconds before idle components are culled).
Performance: Scalability and LOD
NiagaraComp->SetAllowScalability(true);
NiagaraComp->SetTickBehavior(ENiagaraTickBehavior::UsePrereqs);
Scalability per platform is configured in the UNiagaraEffectType asset assigned to the
UNiagaraSystem. The effect type defines quality tiers (Low / Medium / High / Epic) and which
emitters are stripped at each tier. This is data-driven; no C++ changes needed per platform.
GPU vs CPU simulation trade-offs:
- CPU sim: particle data is readable/writable from C++ each frame; lower particle counts; supports
all DI types.
- GPU sim: supports hundreds of thousands of particles; DI support is limited (not all CPU-side DIs
have GPU equivalents); particle data is not readable back to CPU without readbacks.
Determinism: GPU simulations are inherently non-deterministic. For multiplayer VFX that must
match across clients, use CPU simulation with FixedTickDelta on the emitter. Cosmetic-only
effects should spawn client-side only — skip them on dedicated servers entirely.
Warm-Up, Server Handling, and Events
Pre-simulation (warm-up): seek to a desired age before the effect is visible.
NiagaraComp->SetDesiredAge(2.5f);
NiagaraComp->SeekToDesiredAge(2.5f);
Dedicated server: SpawnSystemAtLocation returns nullptr on dedicated servers. Always null-check
the returned component and guard VFX spawns with !IsRunningDedicatedServer() where needed.
Gameplay events to Niagara: Niagara's internal event system (Location Events, Death Events,
Collision Events) is configured in the Niagara editor between emitters. From C++, trigger a
gameplay-driven burst by updating a User bool parameter that the spawn script reads:
NiagaraComp->SetVariableBool(FName("User.bJustDied"), true);
Required Build.cs
PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine",
"Niagara",
"NiagaraCore",
});
Common Mistakes and Anti-Patterns
Spawning a new system component every tick
void AMyActor::Tick(float DeltaTime)
{
UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, TrailFX, GetActorLocation());
}
Wrong parameter namespace
NiagaraComp->SetVariableFloat(FName("Emitter.Speed"), 300.f);
NiagaraComp->SetVariableFloat(FName("User.Speed"), 300.f);
Type mismatch between C++ and Niagara
NiagaraComp->SetVariableVec3(FName("User.TintColor"), FVector(1, 0, 0));
NiagaraComp->SetVariableLinearColor(FName("User.TintColor"), FLinearColor::Red);
Setting parameters after system completes
if (NiagaraComp && NiagaraComp->IsActive())
{
NiagaraComp->SetVariableFloat(FName("User.Intensity"), NewIntensity);
}
Forgetting to check nullptr on spawn (especially on server)
UNiagaraComponent* Comp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(...);
if (Comp)
{
Comp->SetVariableFloat(FName("User.Scale"), 2.f);
}
Not removing delegates before destruction
void AMyActor::EndPlay(const EEndPlayReason::Type Reason)
{
if (NiagaraComp)
{
NiagaraComp->OnSystemFinished.RemoveDynamic(this, &AMyActor::OnVFXFinished);
}
Super::EndPlay(Reason);
}
Niagara Fluids (experimental): The Niagara Fluids plugin provides GPU-based fluid and gas
simulations. It is experimental, GPU-only, and carries a high performance cost — profile carefully
before shipping and restrict use to hero effects where visual impact justifies the budget.
World space vs local space: Use local-space simulation for effects attached to moving actors
(particles inherit the parent component's transform and move with it). Use world-space simulation
for effects that should remain stationary after emission (e.g., a ground impact crater where
particles should not follow a moving actor). The space setting lives on each emitter in the Niagara editor.
Related Skills
ue-actor-component-architecture — component creation, attachment, lifecycle
ue-materials-rendering — particle material setup, dynamic material instances
ue-cpp-foundations — UObject lifetime, delegates, UPROPERTY references