DreamFXLang

Overview

DreamFXLang is a text-first language for Unreal Niagara effects, compiled into standard Niagara assets by the DreamFX plugin — and decompiled back out again.

DreamFXLang is a text-first authoring language for Unreal Engine Niagara. You write .dfs, .dfe and .dfm files, and the DreamFX plugin compiles them into standard assets — UNiagaraSystem, UNiagaraEmitter, UNiagaraScript — which a level, a blueprint or a sequencer references exactly like any other.

The source is what you maintain. The .uasset is a build product: it can always be thrown away and regenerated, and it is never edited by hand.

The reverse direction holds too: any existing Niagara system decompiles back into a .dfs. That is not a convenience export, it is a contract — anything the language cannot express is written into the file header as an explicit gap, never dropped in silence.

Plugin version1.0.0
EngineUnreal Engine 5.8 (a source build and the installed engine share one source tree with zero #if forks)
ModulesDreamFX (Runtime), DreamFXEditor (Editor)
Source extensions.dfs · .dfe · .dfm
Project settingsProject Settings ▸ Plugins ▸ DreamFX
Diagnostics143 DFXnnnn codes, each with file, line and column
LicenseMIT

Three kinds of source file

ExtensionDeclaresProduces
.dfsSystema UNiagaraSystem
.dfeEmitternothing on its own — merged into a .dfs by from
.dfmModule / DynamicInputa UNiagaraScript (works on the installed engine through a reflection backend)

All three share one header:

<Kind>(Name="<path under the root>", Root="<root token>")
{
    ...
}

Kind has to agree with the file extension (DFX2021). Root is Game, empty (the same thing), or Plugin.<PluginName>; Name is the asset path relative to that root's content directory. See the file model.

A minimal system

System(Name="Effects/NS_Hello", Root="Game")
{
    Properties = {
        float Speed = 150.0 [ Group="Motion" ];   // exposed as User.Speed
    }

    Emitter Motes
    {
        Settings = { SimTarget = CPU; Determinism = true; RandomSeed = 1; }

        EmitterUpdate = {
            EmitterState(LifeCycleMode = Self, LoopBehavior = Infinite);
            SpawnRate(SpawnRate = 20.0);
        }

        ParticleSpawn = {
            Spawn/Initialization/V2/InitializeParticle(
                LifetimeMode = DirectSet, Lifetime = 2.0,
                SpriteSizeMode = Uniform, UniformSpriteSize = 8.0
            );
            SystemLocation();
            AddVelocityInCone(ConeAngle = 30.0, VelocityStrength = User.Speed);
        }

        ParticleUpdate = {
            ParticleState();
            GravityForce(Gravity = (0, 0, -400));
            SolveForcesAndVelocity();
        }

        SpriteRenderer Core
        {
            Alignment = Unaligned;  FacingMode = FaceCamera;  SortMode = ViewDepth;
        }
    }
}
pwsh -File Plugins/DreamFX/.skill/dfx.ps1 build DFX/Effects/NS_Hello.dfs

That writes /Game/Effects/NS_Hello with no editor involved. With the editor open, saving the source rebuilds it — leave the generated asset open in the Niagara editor and it is the live preview for this workflow.

Commands that write packages (build, corpus, mirror-diff, decompile-all) want the editor closed. Two processes saving the same packages race silently, and whichever saves second wins. See the daily loop.

What the language covers

  • User parameters, including data-interface parameters with their JSON configuration (DI<X>);
  • the six stacks: SystemSpawn / SystemUpdate / EmitterSpawn / EmitterUpdate / ParticleSpawn / ParticleUpdate;
  • event handlers OnEvent(...) and named simulation stages Stage <Name>(...);
  • renderers, with schema-driven properties and Bind;
  • every value mode: literals, links, enums, nested dynamic inputs, hlsl { }, curve { } with tangent modes, and static switches in every position.

Not covered, by design or not yet: Scratch Pad, module-internal graph lowering, GPU/CPU branch conditions, Scalability conditions, and true emitter inheritance (from is a copy). Every degradation is diagnosed rather than silent — see limits.

Round-trip, verified four ways

The correctness of a decompile is measured, not claimed:

LayerQuestion it answers
L1 mirror-diffdoes the mirror's export match the original's, line by line?
L2does the mirror compile clean?
L3does the rebuilt system simulate like the original? (fixed-step SimCache, per-frame counts)
asset-diffdo the two agree as assets — reflection-walked facts, independent of the exporter?

The detail is in round trip and equivalence.

Where to start

On this page