UE Niagara Effects
Target engine: UE 5.8. APIs below are verified against the 5.8 headers; older forms are listed under "Deprecated — do not use".
Niagara ships as the Niagara plugin (Engine/Plugins/FX/Niagara, enabled by default). Game code spawns and drives systems through UNiagaraFunctionLibrary and UNiagaraComponent, feeds them structured data through data interfaces and Niagara Data Channels, and controls cost through component pooling and UNiagaraEffectType scalability. Add "Niagara" to your module's PublicDependencyModuleNames, plus "NiagaraCore" when you subclass UNiagaraDataInterface.
Context
Read .agents/ue-project-context.md if it exists (module names, conventions, enabled plugins, GAS/networking setup). Do not stop if it is missing.
Identify the area from the request and the codebase. Ask only when two plausible readings would produce different code.
System and Component Model
Authors expose parameters to C++ by placing them in the User. namespace in the Niagara editor. Only User.* parameters can be overridden at runtime; System.*, Emitter.*, Particle.* and Module.* are simulation-internal.
Discover what an asset exposes without opening the editor:
Spawning Systems
Attached, persistent effect. Note the argument order: bAutoDestroy comes before bAutoActivate here.
A second SpawnSystemAttached overload inserts FVector Scale after Rotation and then orders the tail as LocationType, bAutoDestroy, PoolingMethod, bAutoActivate, bPreCullCheck. Pick one overload deliberately; the two are easy to confuse.
SpawnSystemAtLocationWithParams and SpawnSystemAttachedWithParams take a single FFXSystemSpawnParameters struct (Particles/ParticleSystemComponent.h) when you want named fields instead of a long argument list. That struct carries EPSCPoolMethod PoolingMethod, not ENCPoolMethod.
For an effect that repeats on one actor, own the component instead of spawning per event:
Lifecycle Control
Setting User Parameters
Setters take an FName including the namespace prefix. They are silent no-ops when the name or the type does not match the asset.
Full C++-to-Niagara type table, the array function library matrix and the pool-method values are in niagara-parameter-types.md [blocked].
Data Interfaces
Data interfaces are UNiagaraDataInterface subclasses exposed as User.* parameters of DI type. Bind them with the typed helpers in UNiagaraFunctionLibrary, which take the parameter name as const FString&.
Array DIs are the workhorse for pushing gameplay data per frame. These helpers take FName:
Fetch a DI object when you need to mutate it directly:
The non-template overload GetDataInterface(UClass* DIClass, UNiagaraComponent*, FName) covers the case where the class is only known at runtime. The built-in DI catalogue with header paths, properties and sim-target support is in niagara-data-interfaces.md [blocked].
Custom Data Interfaces
Subclass UNiagaraDataInterface (NiagaraDataInterface.h). The function list is editor-only data: the override is GetFunctionsInternal(TArray<FNiagaraFunctionSignature>& OutFunctions) const declared inside #if WITH_EDITORONLY_DATA, plus GetVMExternalFunction to bind the CPU implementations and Equals / CopyToInternal so instances compare and duplicate correctly. GPU support adds ProvidePerInstanceDataForRenderThread and PerInstanceDataPassedToRenderThreadSize. Full class template in niagara-data-interfaces.md [blocked].
Niagara Data Channels
Data Channels are the supported bridge between gameplay code and Niagara, and between independent systems. A channel is a UNiagaraDataChannelAsset describing a payload; writers publish elements, Niagara emitters and game code read them.
Reading back on the game side:
SubscribeToNiagaraDataChannel(WorldContextObject, Channel, SearchParams, UpdateDelegate, UnsubscribeToken) pushes an FOnNewNiagaraDataChannelPublish callback whenever new elements are published; its FNiagaraDataChannelUpdateContext carries Reader, FirstNewDataIndex, LastNewDataIndex and NewElementCount. Pair every subscription with UnsubscribeFromNiagaraDataChannel using the stored token.
UNiagaraDataChannelLibrary also offers GetDataChannelElementCount, single-element ReadFromNiagaraDataChannelSingle / WriteToNiagaraDataChannelSingle, and _WithContext variants that take an FNDCAccessContextInst instead of FNiagaraDataChannelSearchParameters. The search-parameter overloads are the legacy path and do not support newer channel types; prefer the context variants for new channels.
Completion Callbacks
OnSystemFinished is DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNiagaraSystemFinished, class UNiagaraComponent*, PSystem), so the handler must be a UFUNCTION() declared in a class body.
Simulation Caches
A UNiagaraSimCache records simulation frames and plays them back deterministically — useful for cinematics, network-visible hero effects and regression tests.
UNiagaraSimCache exposes IsCacheValid(), CanRead(UNiagaraSystem*), GetNumFrames(), GetStartSeconds() and GetDurationSeconds(). Data interfaces that hold their own state participate through INiagaraSimCacheCustomStorageInterface.
Lightweight Emitters
Emitters come in two modes, reported by FNiagaraEmitterHandle::GetEmitterMode(): ENiagaraEmitterMode::Standard (UNiagaraEmitter, full scripted simulation) and ENiagaraEmitterMode::Stateless (UNiagaraStatelessEmitter, a fixed-cost analytic emitter with no per-particle simulation buffers). Stateless emitters are authored in the Niagara editor and cost far less on CPU and memory; prefer them for ambient, high-instance-count effects.
UNiagaraStatelessEmitter lives in the module-internal Internal/Stateless/NiagaraStatelessEmitter.h, so game modules cannot include it. Drive stateless emitters the same way as any other system — through UNiagaraComponent and User. parameters. To detect them from game code, read bIsLightweight on FNiagaraMinimalEmitterInfo from UNiagaraFunctionLibrary::GetAllEmitters.
Component Pooling
ENCPoolMethod on every spawn call selects pool behavior: None (fresh component), AutoRelease (returned automatically on completion; do not keep the pointer past the spawning tick) and ManualRelease (you call ReleaseToPool()). ManualRelease_OnComplete and FreeInPool are hidden internal states.
Pool capacity is a per-system property on the asset (MaxPoolSize, PoolPrimeSize on UFXSystemAsset, Particles/ParticleSystem.h:126,135); priming creates min(PoolPrimeSize, MaxPoolSize) components and PoolPrimeSize defaults to 0, so set it or priming is a no-op (NiagaraComponentPool.cpp:285). Global CVars: FX.NiagaraComponentPool.Enable, FX.NiagaraComponentPool.KillUnusedTime, FX.NiagaraComponentPool.CleanTime, FX.NiagaraComponentPool.Validation.
Scalability and Tick Behavior
Scalability lives in the UNiagaraEffectType assigned to the system: SystemScalabilitySettings, EmitterScalabilitySettings, CullReaction, UpdateFrequency and an instanced SignificanceHandler. It is data-driven; no per-platform C++.
CPU vs GPU sim: CPU simulations support every data interface and can be read back; GPU simulations scale to far higher counts but support a narrower DI set and cannot be read back without an explicit readback path.
Determinism: for effects that must match across clients, enable fixed tick on the UNiagaraSystem asset (bFixedTickDelta / FixedTickDeltaTime, readable through HasFixedTickDelta() and GetFixedTickDeltaTime()) and use CPU simulation. Cosmetic effects belong on clients only — guard spawns with IsRunningDedicatedServer().
Occlusion: SetOcclusionQueryMode(ENiagaraOcclusionQueryMode) / GetOcclusionQueryMode() control occlusion queries per component (Default, AlwaysEnabled, AlwaysDisabled).
Niagara Fluids (Beta in 5.8) adds GPU fluid and gas solvers at a high per-frame cost; restrict it to hero effects.
Build Setup
"NiagaraCore" is only needed when you derive from UNiagaraDataInterface. Headers under Internal/ (for example Internal/DataInterface/NiagaraDataInterfaceStaticMesh.h) are not includable from game modules.
Deprecated — do not use
Common Mistakes
Assuming there is no C++ event path into Niagara: there is. Use Data Channels (UNiagaraDataChannelLibrary) for gameplay-to-Niagara events rather than toggling a User. bool and hoping the spawn script samples it on the right tick.
Spawning a component every tick: SpawnSystemAtLocation inside Tick creates and destroys a component per frame. Create the component once, or spawn with ENCPoolMethod::AutoRelease on discrete events only.
Wrong namespace or wrong type: SetVariableFloat(FName("Emitter.Speed"), 300.f) and SetVariableVec3 on a Color parameter both fail silently. The author must expose the value as User.Speed, the setter must match the authored type, and world positions go through SetVariablePosition so LWC is handled.
Ignoring the return value of a spawn call: it is nullptr on dedicated servers and when the pre-cull check rejects the spawn. Null-check before touching the component.
UFUNCTION() on an out-of-line definition: the macro only has meaning inside a UCLASS body. A OnSystemFinished handler marked UFUNCTION() above its .cpp definition will not bind; declare it in the header as shown above.
Holding a pointer to an AutoRelease component: the pool reclaims it as soon as the system completes, and later access is unsafe. Use ManualRelease plus ReleaseToPool() when you need a lasting handle.
Treating a data interface like a component: UpdateLUT() is WITH_EDITORONLY_DATA and must be guarded, and MarkRenderStateDirty() does not exist — UNiagaraDataInterface derives from UObject. Array changes are picked up on the next simulation tick; curve key changes only if the curve DI has bUseLUT off.
Related Skills
ue-materials-rendering— particle materials, dynamic material instances, render targets fed to DIsue-actor-component-architecture— component creation, attachment, activation and lifecycleue-animation-system— skeletal mesh sockets, notifies and bone data that drive mesh-sampling DIsue-gameplay-abilities— GameplayCue-driven effect spawning and ability-scoped VFX ownershipue-audio-system— sound submixes and spectrum analysis behind the audio data interfacesue-async-threading— render-thread and task-graph rules when feeding DIs from background workue-procedural-generation— generating the point and spline data that array and spline DIs consumeue-sequencer-cinematics— Level Sequences, playback, cine cameras and Movie Render Graph


