Particles are a perfect fit for Chasm's memory model. The pool lives at script lifetime, individual particle state lives in the pool, and all per-frame math uses frame-lifetime locals.
Particle Struct
defstruct Particle do
x :: float = 0.0
y :: float = 0.0
vx :: float = 0.0
vy :: float = 0.0
life :: float = 0.0 # remaining lifetime in seconds
max_life :: float = 1.0
active :: int = 0
endPool Setup
@particles :: script = array_fixed(128, Particle{})array_fixed allocates from the script arena at startup, no malloc, no GC.
Spawning a Particle
Find the first inactive slot and write into it:
defp emit(x :: float, y :: float, vx :: float, vy :: float, life :: float) do
i = 0
while i < @particles.len do
p = @particles.get(i)
if p.active == 0 do
@particles.set(i, Particle{
x: x, y: y, vx: vx, vy: vy,
life: life, max_life: life, active: 1
})
return
end
i = i + 1
end
endBurst Emitter
Emit a ring of particles from a point, useful for explosions:
defp burst(x :: float, y :: float, count :: int, speed :: float, life :: float) do
i = 0
while i < count do
angle :: frame = to_float(i) / to_float(count) * 6.2831853
vx :: frame = cos(angle) * speed
vy :: frame = sin(angle) * speed
emit(x, y, vx, vy, life)
i = i + 1
end
endUpdating Particles
defp update_particles(dt :: float) do
i = 0
while i < @particles.len do
p :: frame = @particles.get(i)
if p.active == 1 do
nx :: frame = p.x + p.vx * dt
ny :: frame = p.y + p.vy * dt
nl :: frame = p.life - dt
alive :: frame = 1
if nl <= 0.0 do alive = 0 end
@particles.set(i, p with { x: nx, y: ny, life: nl, active: alive })
end
i = i + 1
end
endAll locals, nx, ny, nl, alive, are :: frame. They exist for one iteration of the loop and vanish.
Drawing Particles
Fade out by scaling alpha with the remaining life ratio:
defp draw_particles() do
i = 0
while i < @particles.len do
p :: frame = @particles.get(i)
if p.active == 1 do
t :: frame = p.life / p.max_life
size :: frame = 3.0 * t
draw_circle(p.x, p.y, size, :white)
end
i = i + 1
end
endGravity & Drag
Add gravity and drag to the update loop for more natural motion:
defp update_particles(dt :: float) do
gravity :: frame = 200.0
drag :: frame = 0.95
i = 0
while i < @particles.len do
p :: frame = @particles.get(i)
if p.active == 1 do
nvx :: frame = p.vx * drag
nvy :: frame = (p.vy + gravity * dt) * drag
nx :: frame = p.x + nvx * dt
ny :: frame = p.y + nvy * dt
nl :: frame = p.life - dt
alive :: frame = 1
if nl <= 0.0 do alive = 0 end
@particles.set(i, p with { x: nx, y: ny, vx: nvx, vy: nvy, life: nl, active: alive })
end
i = i + 1
end
end