Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Particles

Flint’s particle system provides GPU-instanced visual effects through the flint-particles crate. Fire, smoke, sparks, dust motes, magic effects — any volumetric visual that needs hundreds or thousands of small, short-lived elements.

Note: Particle effects are dynamic simulations that accumulate over time. Use flint play to see them in action — headless flint render captures a single frame and won’t show accumulated particles.

How It Works

Each entity with a particle_emitter component owns a pool of particles simulated on the CPU and rendered as camera-facing quads via GPU instancing. The pipeline is:

TOML component                CPU simulation              GPU rendering
particle_emitter   ──►  ParticleSync reads config  ──►  ParticlePipeline
  emission_rate         spawn/integrate/kill             instanced draw
  gravity               pack into instance buffer        storage buffer
  color_start/end       (swap-remove pool)               alpha or additive

Unlike billboard sprites (which are individual ECS entities), particles are pooled per-emitter — a single entity can own thousands of particles without overwhelming the ECS.

Adding Particles to a Scene

Add a particle_emitter component to any entity:

[entities.campfire]
[entities.campfire.transform]
position = [0, 0.2, 0]

[entities.campfire.particle_emitter]
emission_rate = 40.0
max_particles = 200
lifetime_min = 0.3
lifetime_max = 0.8
speed_min = 1.5
speed_max = 3.0
direction = [0, 1, 0]
spread = 20.0
gravity = [0, 2.0, 0]
size_start = 0.15
size_end = 0.02
color_start = [1.0, 0.7, 0.1, 0.9]
color_end = [1.0, 0.1, 0.0, 0.0]
blend_mode = "additive"
shape = "sphere"
shape_radius = 0.15
autoplay = true

Emission Shapes

The shape field controls where new particles spawn relative to the emitter:

ShapeFieldsDescription
point(none)All particles spawn at the emitter origin
sphereshape_radiusRandom position within a sphere
coneshape_angle, shape_radiusParticles emit in a cone around direction
boxshape_extentsRandom position within an axis-aligned box

Blend Modes

ModeUse CaseDescription
alphaSmoke, dust, fogStandard alpha blending — particles fade naturally
additiveFire, sparks, magicColors add together — bright, glowing effects

Additive blending is order-independent, making it ideal for dense effects. Alpha blending looks best for soft, diffuse effects.

Value Over Lifetime

Particles interpolate linearly between start and end values over their lifetime:

  • size_start / size_end — particles can grow (smoke expanding) or shrink (sparks dying)
  • color_start / color_end — RGBA transition. Set color_end alpha to 0 for fade-out

Sprite Sheet Animation

For textured particles (flame sprites, explosion frames), use sprite sheets:

[entities.explosion.particle_emitter]
texture = "explosion_sheet.png"
frames_x = 4
frames_y = 4
animate_frames = true   # Auto-advance frames over particle lifetime

With animate_frames = true, each particle plays through the sprite sheet from birth to death.

Bursts and Duration

For one-shot effects (explosions, impacts), combine bursts with limited duration:

[entities.explosion.particle_emitter]
emission_rate = 0.0      # No continuous emission
burst_count = 50         # 50 particles on each burst
duration = 0.5           # Emitter runs for 0.5 seconds
looping = false          # Don't repeat
autoplay = true          # Fire immediately

For periodic bursts (fountain, heartbeat), set looping = true with a duration.

Component Schema

FieldTypeDefaultDescription
emission_ratef3210.0Particles per second (0 = burst-only)
burst_counti320Particles fired on each burst/loop start
max_particlesi32256Pool capacity (max 10,000)
lifetime_minf321.0Minimum particle lifetime in seconds
lifetime_maxf322.0Maximum particle lifetime in seconds
speed_minf321.0Minimum initial speed
speed_maxf323.0Maximum initial speed
directionvec3[0,1,0]Base emission direction (local space)
spreadf3215.0Random deviation angle in degrees
gravityvec3[0,-9.81,0]Acceleration applied per frame (world space)
dampingf320.0Velocity decay per second
size_startf320.1Particle size at birth
size_endf320.0Particle size at death
color_startvec4[1,1,1,1]RGBA color at birth
color_endvec4[1,1,1,0]RGBA color at death
texturestring“”Sprite texture (empty = white dot)
frames_xi321Sprite sheet columns
frames_yi321Sprite sheet rows
animate_framesboolfalseAuto-advance frames over lifetime
blend_modestring“alpha”"alpha" or "additive"
shapestring“point”"point", "sphere", "cone", "box"
shape_radiusf320.5Radius for sphere/cone shapes
shape_anglef3230.0Half-angle for cone shape (degrees)
shape_extentsvec3[0.5,0.5,0.5]Half-extents for box shape
world_spacebooltrueParticles detach from emitter transform
durationf320.0Emitter duration (0 = infinite)
loopingbooltrueLoop when duration expires
playingboolfalseCurrent playback state
autoplaybooltrueStart emitting on scene load

Scripting Integration

Particles can be controlled from Rhai scripts:

FunctionDescription
emit_burst(entity_id, count)Fire N particles immediately
start_emitter(entity_id)Start continuous emission
stop_emitter(entity_id)Stop emission (existing particles finish)
set_emission_rate(entity_id, rate)Change emission rate dynamically
#![allow(unused)]
fn main() {
// Rhai script: burst of sparks on impact
fn on_collision() {
    let me = self_entity();
    emit_burst(me, 30);
}

// Rhai script: toggle emitter with interaction
fn on_interact() {
    let me = self_entity();
    let playing = get_field(me, "particle_emitter", "playing");
    if playing {
        stop_emitter(me);
    } else {
        start_emitter(me);
    }
}
}

Architecture

  • ParticlePool — swap-remove array for O(1) particle death, contiguous alive iteration
  • ParticleSync — bridges ECS particle_emitter components to the simulation, auto-discovers new emitters each frame
  • ParticleSystem — top-level RuntimeSystem that ticks simulation in update() (variable-rate, not fixed-step)
  • ParticlePipeline — wgpu render pipeline with alpha and additive variants, storage buffer for instances

The particle system runs after animation (emitter transforms may be animated) and before the renderer refresh. Instance data is packed contiguously and uploaded to a GPU storage buffer for efficient instanced drawing.

Recipes

Fire

emission_rate = 40.0
gravity = [0, 2.0, 0]
color_start = [1.0, 0.7, 0.1, 0.9]
color_end = [1.0, 0.1, 0.0, 0.0]
blend_mode = "additive"
shape = "sphere"
shape_radius = 0.15

Smoke

emission_rate = 8.0
gravity = [0, 0.5, 0]
damping = 0.3
size_start = 0.1
size_end = 0.6
color_start = [0.4, 0.4, 0.4, 0.3]
color_end = [0.6, 0.6, 0.6, 0.0]
blend_mode = "alpha"

Sparks

emission_rate = 15.0
speed_min = 3.0
speed_max = 6.0
spread = 45.0
gravity = [0, -9.81, 0]
size_start = 0.03
size_end = 0.01
color_start = [1.0, 0.9, 0.3, 1.0]
color_end = [1.0, 0.3, 0.0, 0.0]
blend_mode = "additive"

Dust Motes

emission_rate = 5.0
speed_min = 0.05
speed_max = 0.2
spread = 180.0
gravity = [0, 0.02, 0]
damping = 0.5
size_start = 0.02
size_end = 0.02
color_start = [1.0, 1.0, 0.9, 0.5]
color_end = [1.0, 1.0, 0.9, 0.0]
shape = "box"
shape_extents = [2.0, 1.0, 2.0]

Further Reading

  • Scripting — full scripting API including particle functions
  • Animation — animate emitter transforms with property tweens
  • Rendering — the GPU pipeline that draws particles
  • Physics and Runtime — the game loop that drives particle simulation