| Header | Link |
|---|---|
| Purpose | Purpose |
| Use Cases | Use Cases |
| Example | Example |
| Reference | Reference |
The particle system builds effects like fire, smoke, sparks, magic, and weather from math instead of pre-baked sprite sheets. An emitter node (ParticleEmitter3D / ParticleEmitter2D) decides when and how many particles spawn; a .ppart profile defines what each particle does over its lifetime through presets and per-axis expressions. Splitting the two means one profile powers many emitters, each tuned with its own seed, rate, and params.
- Campfire or torch flame: a
spiralpreset.ppartwithforce = (0, 2.5, 0)upward drift andcolor_start/color_endfading orange to smoke, spawned at a steadyspawn_rate. - Muzzle flash / impact burst:
looping = false, high one-shotspawn_rate,prewarm = false, and a shortlifetime_min/lifetime_max. - Ground dust or embers:
flat_diskpreset seeding particles across a radius, withsize_min/size_maxvariance. - Reusable effect tuned per instance: one
.ppartreadsparams[0]as flame height so each emitter passes a differentparams = (...). - GPU-heavy weather (rain, snow, ash): set
sim_mode = "gpu"andrender_mode = "billboard"for high particle counts; fall back tosim_mode = "cpu"on low-end targets. - Deterministic layouts (rings, fountains): expressions using
ring_u,rand, andidplace particles without per-frame CPU work.
Use one reusable .ppart plus instance params when effects share behavior but
vary in size/color. Use separate profiles when simulation or render mode differs.
GPU simulation fits high counts; CPU fits low-count behavior that needs CPU-side
control. A one-shot emitter owns presentation, not the gameplay event that
caused it.
Define res://particles/campfire.ppart:
preset = spiral
preset_param_a = 8.0
preset_param_b = 1.1
lifetime_min = 0.8
lifetime_max = 1.7
speed_min = 1.2
speed_max = 3.4
spread_radians = 0.49
size = 5.0
force = (0.0, 2.8, 0.0)
color_start = (1.0, 0.68, 0.20, 1.0)
color_end = (0.95, 0.08, 0.02, 0.0)
emissive = (1.0, 0.38, 0.05)
spin = 4.0
y = t * params[0]Spawn it from a scene, passing a per-instance flame height through params[0]:
[Campfire]
[ParticleEmitter3D]
active = true
looping = true
prewarm = true
spawn_rate = 180.0
seed = 41
sim_mode = "gpu"
render_mode = "billboard"
profile = "res://particles/campfire.ppart"
params = (1.8, 0.0, 0.0, 0.0)
[/ParticleEmitter3D]
[/Campfire]
Set a project-wide default backend in project.toml (per-emitter sim_mode overrides it):
[graphics]
particle_sim_default = "cpu" # cpu | hybrid | gpuPerro exposes a flexible particle system centered around:
ParticleEmitter3Dfor spawning/controlling 3D particlesParticleEmitter2Dfor spawning/controlling 2D particles.ppartprofiles for per-particle mathematical behavior
.ppart authoring details are documented here:
Perro particles are math-driven. You author equations per particle (x, y, z) and combine them with presets and built-in variables/functions.
ParticleEmitter2D reads x and y; z, force_z, dir_z, vel_z, and emitter_z do not affect 2D output.
The emitter handles spawn orchestration:
- active/looping/prewarm control
- spawn rate
- random seed
- param injection (
params[i]) for reusable profile logic - simulation backend and render mode selection
ParticleEmitter3D.sim_mode supports:
cpu: CPU simulation and submissionhybrid: GPU vertex-driven path for supported non-custom workloadsgpu: full GPU compute-driven simulation/render pathdefault: resolves fromproject.toml(graphics.particle_sim_default)
This lets you choose between maximum compatibility (cpu) and high-throughput GPU execution (gpu) per emitter.
ParticleEmitter3D.render_mode supports:
pointbillboard
You can pair render mode with any simulation mode; choose based on visual style and cost.
ParticleEmitter3D / ParticleEmitter2D define when/how many particles spawn.
.ppart defines what each particle does over its lifetime.
That split keeps effects composable:
- one profile can be reused by many emitters
- emitters can supply different seeds/params/rates to get distinct looks from the same profile
Set a project default backend in project.toml:
[graphics]
particle_sim_default = "cpu" # cpu | hybrid | gpuPer-emitter sim_mode can override this.