DOCUMENTATION

Niagara

Common mistakes with Niagara systems through MifBridge: user parameters, compiles, the readyToRun flag, stack edits and effects that look dead in the editor. Read it when a Niagara edit answers ok and the effect does not run, or runs wrong.

Niagara is built from scripts that compile, often on other threads, and several signals that look like a verdict are not one. The rule on this page: judge an edit with compile_niagara_system, then look at the effect running.

Judging a system

readyToRun does not mean the scripts compiled

describe_niagara_system can report ready for a system whose scripts failed to compile. For example, an event generator added to an emitter without persistent IDs leaves both particle scripts in Error while the description reads ready. The compiler message is "Before the Particles.ID parameter can be used, the 'Requires persistent IDs' option has to be activated".

Call compile_niagara_system and read its status, errorCount and each script's compiler events. That is the verdict.

A system can finish building unable to run, with every script up to date (UE 5.3 to 5.5)

On 5.3 to 5.5 a large system can finish its build with compile_niagara_system answering UpToDate, every script synchronized and no events, while readyToRun is false and the placed component never starts. The cause is two system scripts that disagree on their attributes (one measured pair held 118 and 120). The engine will not run a system whose spawn and update scripts disagree, and it explains why only in a cooked build.

compile_niagara_system now detects this itself: after a compile that completes with the system not ready, it compares the two lists and, when they differ, forces one compile. It reports systemScriptsDisagreed with both counts, the names only one list holds and a mended flag. A forced compile took 14 s and 91 s on 5.3.2 in two runs. UE 5.6 to 5.8 compile in a different default mode and did not show the problem.

An edit made while the system compiles can be lost to that compile

Some edits start a compile themselves: a static switch, and set_niagara_emitter_properties when it changes SimTarget or the local-space flag (the answer carries engineCompilePending). That compile works from the graph as it was when it started. If it finishes after later edits, those edits count as compiled: every script reads synchronized, a later compile_niagara_system answers UpToDate and compiles nothing, and a module added in between has no rapid-iteration parameters, so each input left at its default reads 0. The effect then draws nothing or draws differently from run to run. It depends on timing, so a rebuild usually comes out right, which hides it.

What MifBridge does about it: every stack write, and add_niagara_emitter, remove_niagara_emitter and set_niagara_emitter, first finishes the system's outstanding compile, waiting up to 120 s. The answer's settledCompileSeconds says how long it waited. compile_niagara_system is the one write that does not wait.

If you suspect a system was already hit, run compile_niagara_system with force: true. To check, compare the stack's rapid-iteration inputs with a good build of the same plan: describe_niagara_emitter_stack shows a lost one as binding:"default". A good build shows that for inputs behind a static switch too, so the read alone proves nothing. describe_niagara_system reports compilePending, but asking again does not help: it is also true for a fresh system that only needs a compile nobody has requested.

GPU shaders take more of the editor's frame than scripts do

A compile waited on from inside a call makes no progress by itself, because parts of it run from the editor's own per-frame tick. The compile tools pump those steps while they wait. A GPU emitter's shader compile finished in 0.9 s that way on 5.3.2 and made no progress in 300 s without it. The practical lesson: if you wait on a compile in your own loop, ask compile_niagara_system again instead of polling a flag.

GPU shader warnings arrive in the same list as errors. The engine counts only messages it marked as errors, and compile_niagara_system follows that rule. A shader map loaded from the derived data cache carries no messages at all.

Reading and writing parameters

One offset list covers three arrays

A Niagara parameter store keeps value bytes, data interfaces and objects in three parallel arrays, behind a single sorted list of offsets. A parameter's Offset is a byte position for values and an array index for the other two, and nothing in the entry says which. On one stock system three different parameters all reported Offset=0.

A width worked out as "distance to the next offset" is wrong the moment a system has any data interface or object parameter: it gave a float one byte. list_niagara_user_parameters separates the three with a rule it can prove, then checks that the result tiles the value bytes exactly. It reports parameterLayoutVerified and withholds values when the check fails. Treat a missing value as a refusal to guess.

A value on the wrong side is one the script never reads

The stack editor keeps a rapid-iteration input's value as a constant in the stage scripts, and a bool, enum or object on the override pin. A value written to the pin of a rapid-iteration input leaves the engine's own constant behind it, which the describer reports as shadowedConstant. set_niagara_module_input follows the editor's rule, including for inputs whose default is read in several places.

A flipbook can show only its first frame

A sprite renderer with the right Sub Image Size can still show frame 0. The SubUVAnimation module's automatic setup reads its grid from the sprite renderer its Sprite Renderer input selects. A module added with add_niagara_module selects none and falls back to a 1 x 1 grid, so every particle stays on frame 0 for its whole life. The compile is clean, and the editor warns only in its own stack view ("No Sprite Renderer selected").

Set Define SubUV Setup Manually to Manual, Manual SubImage Size to the renderer's grid and Is SubUV Blending Enabled On Renderer to the renderer's SubImageBlend, using set_niagara_module_input.

Distance-field modes need mesh distance fields at first compile

With r.GenerateMeshDistanceFields at 0, a distance-field query does not compile (The mesh distance field generation is currently not enabled), and a system compiled that way stays broken after the setting is turned on. The stack writes warn with distanceFieldWarning, and a copy from a template lists the modules that reach such a query as distanceFieldModules. A module whose query sits behind a switch it does not select is listed too, so read the list as "may reach". compile_niagara_system is the verdict.

Events are CPU only

A GPU emitter with an event handler never reports ready to run, and the translator refuses event scripts on a GPU target. The tools refuse a handler on a GPU receiver or from a GPU source, and a switch to the GPU while events flow.

An emitter also spawns from events only while it is Active. After a system is deactivated its emitters go inactive with it, so nothing an event would spawn is spawned again, however many events still arrive.

A hand-renamed Map Get pin leaves its input behind

Connecting a pin to a Niagara Map Get's add pin makes a typed pin named Module.<name> and registers that graph parameter. Renaming the pin by writing its name leaves the Module.<name> parameter in the graph, so the stack shows an input nothing reads. The node's own rename commit renames the parameter with the pin.

Effects that look dead in the editor

A placed effect advances only while a viewport is realtime

An unfocused editor with Editor Preferences, "Use Less CPU when in Background" on (the default on UE 5.3 to 5.8) is held non-realtime by "Background Process", and the world then stops ticking its components. On a 5.3.2 editor a Fountain ticked five times and then stood still for 30 s. get_niagara_component_stats reports that truthfully, and get_viewport_camera names the override. Sample a running effect in a focused editor, or with that preference off. The same cause makes captures black and runs two to three times slower on a hidden editor: one set of 18 effect looks took 589 s with the preference on and 234 s with it off. See Common mistakes with captured images.

SetDesiredAge steps nothing unless the component runs solo

In DesiredAge mode a component seeks its age through its solo simulation only. A placed component is batched, so its own tick is off, the world ages it in real time, and SetDesiredAge only records the number. On 5.8.2 a campfire asked for 0.5 s read 1.08 s and ran on at a second per second. Frames "stepped" that way are taken at whatever time the capture loop reached, and two runs agree whenever the loop runs steadily, which makes a determinism check pass wrongly.

Call SetForceSolo(true) first, then the age calls, and read age from get_niagara_component_stats before each capture (the seek runs on the next tick). Its solo field says which mode a component is in, and desiredAgeNote says when a desired age is being ignored. SetForceSolo(false) and TickDeltaTime put it back.

A module's dependencies are met by the system stack too

The stack editor reads an emitter's dependencies over the system's stages as well as the emitter's. The stock Emitter State requires SystemState, and the Fountain template meets it in System Update, so the editor shows no error. A walk over the emitter's own stages alone reports it unmet.

A Niagara light rides every live particle

A light renderer hangs one light on every live particle of its emitter, each as bright as that particle. A dense layer therefore sums to a very bright light on the floor: one fire effect with about 530 flames lit the floor with 1,672 lux. No emitter of luminance L lights a floor above pi x L lux, so cap an effect's light under that, and compare a light with its flames at the same age. Measure the light in linear units. A tone-mapped PNG clips a channel at about 2.6 nits (red) and 7.5 (green), so no light-to-flame ratio can be read from it.

A light on a particle born at the effect's ground point sits in the floor's plane and lights nothing.

A sprite lying on a surface fades away

Soft particle fade is on by default. A flat sprite 2 cm above a floor with a 16 cm depth fade draws as a ghost of itself: 1.44% of the frame rose above the floor's brightest pixel, against 5.12% with a 2 cm fade. A layer that lies on a surface wants a depth fade no longer than its lift.