@modoki/engine / index / ParticleEffectDef
Interface: ParticleEffectDef
A complete particle effect definition (the .particle.json payload).
v1 covers the core authoring surface (emission, shape, start values, gravity, size/color/opacity over life). Advanced behaviors (forces, collision, trails, sub-emitters, sprite-sheet animation) extend this in Phase 3 — additively, so old assets keep loading.
Particles
collision?
optionalcollision?:CollisionConfig
collision against a solid plane / sphere / box / cylinder collider
colorOverLife?
optionalcolorOverLife?:Gradient
drag?
optionaldrag?:number
linear velocity damping per second (0 = none)
duration
duration:
number
loop period in seconds
emission
emission:
object
Particles
bursts?
optionalbursts?:EmissionBurst[]
fillPool?
optionalfillPool?:boolean
Continuous full-pool emission: keep every slot alive at all times (ages are staggered at start so deaths spread over time), ignoring rateOverTime and bursts. Effective rate ≈ maxParticles ÷ lifetime. Both backends honor this identically — it is the only emission model the GPU compute backend implements, so an effect must set fillPool to be eligible for simulation: 'gpu'. Ideal for dense ambient fields (galaxies, starfields, drifting dust/motes).
rateOverTime
rateOverTime:
number
forces?
optionalforces?:ForceField[]
external force fields (wind, attractors/repellers)
gravity
gravity:
number| [number,number,number]
Constant acceleration, world units / s². Two forms (see resolveGravity in simSpec.ts): a scalar g = a downward pull of magnitude g along -Y (legacy 3D authoring, maps to (0,-g,0)); or an explicit [x,y,z] vector applied as-is (axis-neutral). 2D effects use the vector form — [0,+G,0] falls toward screen-down (PixiJS +Y) with no Y flip, [0,-G,0] rises. normalizeParticleDef migrates a loaded scalar to [0,-g,0] so old assets re-save in vector form.
id?
optionalid?:string
Stable asset GUID, stored in-file (same convention as mesh/material/prefab/scene id). Lets scenes + sub-emitters reference this effect by GUID so the reference survives the file being moved/renamed. Assigned on first save if absent.
looping
looping:
boolean
maxParticles
maxParticles:
number
hard cap on simultaneously-alive particles (sizes the instance buffer)
name?
optionalname?:string
noise?
optionalnoise?:NoiseConfig
turbulence/curl noise acceleration
opacityOverLife?
optionalopacityOverLife?:Curve
prewarm?
optionalprewarm?:boolean
pre-simulate one duration so the effect starts "full"
render
render:
RenderConfig
rotationOverLife?
optionalrotationOverLife?:Curve
rotationSpeed?
optionalrotationSpeed?:MinMax
shape
shape:
EmitterShape
simulation?
optionalsimulation?:"cpu"|"gpu"
Simulation backend. 'cpu' (default) = deterministic JS sim, full feature set. 'gpu' = TSL compute shader for very high counts (100k+). The GPU backend only implements continuous full-pool emission, so a GPU effect must also set emission.fillPool. It supports forces, single-plane collision and mesh-primitive rendering, but NOT trails or sub-emitters. An effect that isn't GPU-eligible (no fillPool, uses trails/sub-emitters, or no WebGPU compute backend) transparently falls back to the CPU sim — which honors fillPool identically, so the look matches.
sizeOverLife?
optionalsizeOverLife?:Curve
space?
optionalspace?:"3d"|"2d"
Editor-only authoring hint: which preview canvas the Particle Editor shows for this asset ('2d' = PixiJS, '3d' = Three.js) and which property sections it exposes. It does NOT affect runtime rendering — a live emitter renders in 2D iff it has a Canvas2D ancestor (the same rule as Renderable2D), and in 3D otherwise. Default '3d'. Stamped '2d' when a particle is created under a Canvas2D node.
startColor
startColor:
RGB
startLifetime
startLifetime:
MinMax
startOpacity?
optionalstartOpacity?:number
startRotation?
optionalstartRotation?:MinMax
startSize
startSize:
MinMax
startSpeed
startSpeed:
MinMax
subEmitters?
optionalsubEmitters?:SubEmitter[]
nested effects spawned on parent-particle birth/death (depth-1)
trail?
optionaltrail?:TrailConfig
motion trail drawn from each particle's recent position history
version
version:
1
worldSpace
worldSpace:
boolean
true = particles persist in world space after emission; false = follow the emitter