Table of Contents
A particle system is an asset: a node graph that says what one particle is — where it sits, how big it is, how it is turned, what colour it has — as a function of its age and of a random number drawn once for it. Unlike a texture or a shader it can be placed: an object made from it is a source, and its rows say how many particles go out and for how long they live.
Nothing is simulated. A particle is not pushed by forces frame after frame; it is a formula evaluated at whatever second is being drawn. Three things follow from that, and they are the reason the model was chosen:
- The same second always looks the same. Two runs at the same frame rate, or a different one, draw the same picture — the field is a function of the clock, not of how many frames have gone by.
- Any second can be shown at once. The preview panel in the tab has a slider: drag it to 2 s and the particles stand where they are at 2 s, exactly, with nothing simulated on the way there.
- Collision is out, for good. "Sparks bounce off the ground", wind, drag mid-flight — none of these can be written as a function of age, and no later addition will change that.
The Particles tab
+ in the Assets panel makes a particle asset and opens the Particles tab on it (a
double-click on an existing one does the same; a click only selects). The left part is the same
canvas the Behavior and Shader tabs have: right-click places a node, drag moves, drag from a port
links, the magnet snaps to the 24 px grid, Fit frames the graph, undo takes every step back. The
readout on the right says N parameters while the graph compiles, otherwise why it does not
(No Output node, a bad parameter name, Feeds itself). A node that keeps the graph from
compiling shows the reason on its body and a red frame; a source whose graph does not compile emits
nothing.
Right of it, past a divider that drags, the preview panel emits the asset's own particles from
its middle, wearing the rows the asset itself carries — edit Rate or Lifetime on the asset
and the panel changes with them, including how far the slider reaches (three lifetimes, which a
Time scale of 2 covers in half the seconds). Rows the
asset leaves alone fall back to the type's defaults: 20 a second, one second each, a plain 16 px
square. It stands beside the graph rather than under it because a graph is read across: width is
what it can spare, height is not. Hold stops the clock where it is and turns into Run; the
slider jumps to any second in the first three lifetimes — and holds the panel too, so the button
reads Run after a drag as well; Restart begins the stream again at particle 0, which a held
panel shows as 0.00 s rather than keeping the second it stood on. A hold owes nothing when it
ends: letting the panel run again carries on from the second it was stopped at, however long it
stood there — the field's origin is moved, rather than the library clock being allowed to run on
underneath the freeze.
The nodes
Every value is a number. A colour is a number too — packed 0xRRGGBB, as everywhere else in the
project — which is why blending two of them needs Mix color rather than Mix: mixing the packed
numbers of red and blue would run through green.
| Node | Ports | What it does |
|---|---|---|
| Age | out age |
How far through its life the particle is: 0 at birth, 1 when it dies. The axis most graphs are drawn along. |
| Age in seconds | out seconds |
The same in seconds — the t of a formula of motion. |
| Time | out seconds |
Seconds since the source started emitting. The same for every particle alive, so it drifts them together rather than apart. |
| Index | out index |
Which particle this is, counting up from the first the source emitted: a fan or a ring instead of a scatter. |
| Random | in min, max · out value |
A number drawn once per particle and kept for its whole life. Two Random nodes draw differently, so the direction and the speed of one particle scatter apart instead of being the same number. |
| Gravity | in strength · out x, y |
How far a steady pull has moved the particle by now: strength * age * age / 2, down positive. Add it to the offset. |
| Start velocity | in angle, speed · out x, y |
How far a constant speed has carried it: angle in degrees (0 right, 90 down), speed in pixels per second. |
| Output | in x, y, scaleX, scaleY, rotation, color, alpha |
What the particle is drawn as, all of it relative to the source. One per graph. An input left unconnected stays what a particle without a graph would be: at the source, full size, upright, the source's own colour, opaque. |
| Property | out value |
Declares a number the source sets: Name is a row on every object placed from this asset, Default, Min, Max are its range. One word (letters, digits, _), unique in the graph, and not the name of one of the source's own rows. |
| Number, Color | out value / color |
Constants, typed or picked on the node. |
| Add, Subtract, Multiply, Divide | in a, b · out result |
Arithmetic. Division by zero reads as 0 — infinity would draw as nothing at all. |
| Min, Max, Clamp | · out result |
The smaller, the larger, and a value held between two bounds. |
| Mix | in a, b, t · out result |
Blends two numbers: t = 0 is a, 1 is b, beyond that it carries on. |
| Mix color | in a, b (colour), t · out color |
The same for colours, channel by channel — a flame fading to smoke. |
| Sin, Cos | in value · out result |
Of an angle in degrees, one full wave every 360. Degrees, not radians, because rotation and Start velocity are in degrees too. |
| Sqrt | in value · out result |
Rises fast and then slows — how smoke drifts up. Below 0 it is 0. |
| Abs, Floor, Fract | in value · out result |
Distance from 0; rounded down; the part after the decimal point (a rising value becomes a sawtooth). |
| Step | in edge, value · out result |
0 below the edge, 1 from it on: a hard cut. |
| Pow | in value, exponent · out result |
To a power: 2 squares, 0.5 is the square root. Below 0 the value counts as 0. |
The source
A placed source carries a position and the transform rows every drawn object has (scale, rotation, mirror), so hanging one on a moving ship turns the whole spray with it. Its own rows are:
| Row | Default | What it means |
|---|---|---|
| Emitting | on | Whether new particles go out. Switched off, the ones in flight live out their lives — unlike Enabled, which takes them away at once along with everything parented below. |
| Mode | Continuous | Continuous is a steady stream; Burst throws the whole count at once. |
| Rate | 20 | Particles per second, for a continuous source. |
| Burst count | 20 | How many go out per burst. |
| Lifetime | 1 s | How long one particle lives — the scale that gives Age its meaning. |
| Time scale | 1 | How fast the source's own clock runs: 2 is twice the speed, so it emits twice as often and a particle is gone in half the time. Every row above is read on that clock, which is why none of them has to be touched to slow a whole effect down. 0 holds the picture where it is. This is this source's scale; the global one (which a pause sets to 0) multiplies on top of it. |
| Prewarm | off | Start one lifetime in, which is the second a stream first stands full. Without it a fire lit by a scene fade-in begins as a single spark and takes a lifetime to look like a fire. Nothing to a Burst, whose particles are all gone by that second. |
| Max particles | 500 | The most alive at once; past it the oldest are dropped. |
| Size | 16 | The particle's size in pixels, which the graph's scaleX/scaleY multiply. |
| Texture | none | The picture each particle is drawn with. Without one it is a plain square. |
| Color | white | The tint, and what an unconnected color output reads. |
| Move with source | off | Off: a particle stays where it was born, so a moving source leaves a trail (rocket smoke). On: it hangs on the source and travels with it (sparks on a carried torch). The difference shows only while the source moves. |
| Shader | none | A shader asset worn by every particle of this source, the way a sprite wears one. Its Property rows appear below, like a sprite's. |
| Field shader | none | A shader asset laid over the whole field at once (see A shader over the whole field). Its rows appear below as well. |
| Field width, Field height | 256 | The frame the field shader covers, in units, centred on the source. Shown only while a field shader is picked. |
Below them come the rows the asset's graph declares, one per Property node — so two fountains of
the same asset can spray at different strengths, exactly the way two sprites wearing the same shader
can. Overriding one of them on a placed object is an ordinary override, with reset and apply.
Wearing a shader. The Shader row puts a shader asset on every particle of the source, and the shader's own rows follow underneath — the same picker a sprite has. It is the cheapest place in the editor to wear one: a shaded sprite gives up its batch and costs a draw call of its own, while a source is already a single draw call for its whole field, so five hundred shaded particles cost exactly what five hundred plain ones did.
What the shader sees is one particle's picture, not its life. The fragment gets the quad's texel, its own UV and the tint the graph computed — but not the particle's age, index or random number: the buffer a field is drawn from carries five fixed attributes and takes no sixth. That is less of a loss than it sounds, because colour, alpha, size and rotation over age are what the particle graph itself is for; the shader adds what that graph cannot do — sampling, distorting UVs, reading the texture's own size.
A source is the one asset drawing rows from two graphs, so a name can arrive twice. The source's own row wins and the uniform of that name keeps its declared default: two rows called Rate would be one row with two meanings. Nothing is rejected on the shader's side — the same shader on a sprite is perfectly fine, it is only this source that already has the name.
A shader over the whole field (#170). The Field shader row lays a shader over everything the
source draws at once: the particles are drawn into a picture of a fixed frame, Field width by
Field height units centred on the source, and the shader draws that picture. So it can do what a
shader per particle cannot: a glow or blur where particles overlap, an outline round the whole
cloud, a ripple through all of it. It is an ordinary shader asset; its u/v run from 0 to 1
across the frame and never jump, however the cloud changes, and Texture color, Texture alpha
and Sample texture read the picture of the particles (Texture size is Field width by Field
height: a pixel of a field shader is a unit of the frame).
The frame is the edge: whatever flies past it is cut off, so make it as large as the effect reaches.
The scene view draws it as a dashed rectangle round the selected source. It turns and grows with the
source.
Both shaders may be worn together: the one per particle draws into the picture, the field shader over it. A name both of them declare is one row, and both read it. The field shader's Padding and Vertex output do nothing here: the frame already is the room around the field, and its corners stay put, as with a nine-sliced sprite.
The picture has as many pixels as the frame covers on screen, so it stays sharp at any zoom, but at
most 2048 per side. Still the shader counts in units of the frame, not in those pixels: a Blur of
4 reaches 4 units at 50 % and at 400 %, and a step of 1 / Texture size is one unit. Zoomed far in,
the samples of a Blur therefore lie several pixels apart. A source whose frame is off screen draws
no picture at all. What it costs is one
extra pass per source, sized by the frame, not by the number of particles; see What it costs in
Shaders for the measurement.
Measured in the checks (step 31): a field shader that changes nothing draws the field where it was and the right way up, with a picture of 256 pixels at zoom 1; a flat red fills the frame 100 units off the source where no particle is, and stops before 140; a Blur of 8 across a flat picture fades the frame's edge over 8 units at 50 % and at 200 % alike; taken off again, the particles draw as before.
A behavior can start a source over with the Emit node (its source input names which one): in Burst
mode that is another burst, in Continuous mode the stream begins again at particle 0.
Its icon in the Scene tab. Like a behavior or a container, a source is marked by an icon of its own — a ring with sparks, white in a dark outline like every symbol, upright at its position (or beside it when another icon wants the same spot), with the object's name beside it and a dashed line to its owner when it is parented. The ring is hollow because, unlike those two, a source does draw something: the effect shows through the middle of its own mark. The icon is editor decoration and never reaches the running game, but the source is not an editor symbol: its particles are scene content, and what is grabbed and framed is the ring-sized square around its position rather than anything that grows with the source's scale.
Recipes
Rain. Age in seconds → Multiply(b: 400) → Output.y, and a Random(-300, 300) into
Output.x to spread the drops across the screen. Three nodes.
A spark fountain. Random(0, 360) → Start velocity.angle, Random(100, 300) → .speed, its
x/y into two Adds together with a Gravity(800), and Age → Subtract(a: 1) → Output.alpha
to fade them. This is where the formula model asks its price: gravity has to be written as the
offset it has produced (g * t * t / 2), not as a force — which is what the Gravity node is for.
Smoke. Age → Sqrt → Multiply(b: -60) → Output.y — rising and slowing without a damping
constant anyone has to guess. Add Time → Multiply(b: 180) → Sin → Multiply(b: 20) → Output.x for
drift, Age → Add(b: 1) → Output.scaleX/scaleY to let it grow, and Mix color from a warm grey
to a cold one over Age.
What it cannot do
- No collision, no wind, no drag. See the top of this page: they are not functions of age.
- A source is culled by nothing. Its particles may be anywhere on screen, so the source itself is never skipped for being out of frame — with many sources in one scene that costs.
- Particle time is the global clock (see Architecture), so pausing the game freezes every spray with it, and starting or stopping a run puts them back to their first second.
- The preview shows the asset, not an instance: the rows the asset carries, never the ones a particular placed source was given — every one of them may be overridden where it is placed.
- A source with
Emittingoff draws nothing at all once the last particle has died. In the Scene tab its icon still marks the spot; in the running game there is nothing to see.
Using the editor
- Concepts
- Accounts
- Agents (MCP)
- Scenes
- Scene editing
- Behaviors
- Structs
- Colliders
- Physics
- Textures
- Vector art
- Pixel art
- Fonts
- Videos
- Sounds
- Instruments
- Patterns
- Songs
- Animations
- Curves
- Tiles
- Tilemaps
- State machines
- Shaders
- Particles
- Containers
- Compositions
- Keyboard
Working on the code