Skip to content

Instantly share code, notes, and snippets.

@randyprime
Last active October 16, 2025 13:37
Show Gist options
  • Save randyprime/0878ebcbe728139e5d91d903a5e2bd0b to your computer and use it in GitHub Desktop.
Save randyprime/0878ebcbe728139e5d91d903a5e2bd0b to your computer and use it in GitHub Desktop.
Simple Particle System
package main
import "core:log"
// This extra stuff comes from https://github.com/baldgg/blueprint
import "bald:utils"
import "bald:utils/shape"
import "bald:utils/color"
import "bald:utils/string_store"
Particle_Flags :: enum {
valid,
physics,
// add more stuff as needed to get whatever effect you want
}
Particle :: struct {
flags: bit_set[Particle_Flags],
pos: Vec3,
vel: Vec3,
acc: Vec3,
col: Vec4,
lifetime: f32,
lifetime_left: f32,
fade_in_pct: f32,
fade_out_pct: f32,
z_layer: ZLayer,
size: Vec2,
bounce_factor: f32, // https://www.youtube.com/watch?v=Y-iVeTyn7n0
friction: f32,
// same deal here, add whatever you want
}
particles: [4096]Particle
particle_cursor: int
particle_create :: proc() -> ^Particle {
p := &particles[particle_cursor%len(particles)]
if .valid in p.flags {
// on ship, you'd ideally want to throttle this somehow so it doesn't fuck your logs up
// log_one_time("Overwriting particles")
log.info("overwriting particles, increase buffer size pls")
}
p^ = {}
p.flags |= {.valid}
p.col = color.WHITE
p.z_layer = draw_frame.active_z_layer
p.size = Vec2{1,1}
particle_cursor += 1
return p
}
particle_destroy :: proc(p: ^Particle) {
p^ = {}
}
// this gets called every frame
// split this out if you need to separate the update / render
particle_update_and_render :: proc() {
// update
for &p in particles {
if .valid in p.flags {
if .physics in p.flags {
if p.friction != 0 && p.pos.z == 0 {
p.acc.xy += -p.vel.xy * p.friction
}
p.acc.z -= 980.0
p.vel += p.acc * f32(get_delta_t())
p.acc = {}
p.pos += p.vel * f32(get_delta_t())
if p.pos.z <= 0 {
p.vel.z *= -1 * p.bounce_factor
p.pos.z = 0
}
}
if p.lifetime != 0 {
if p.lifetime_left == 0 {
p.lifetime_left = p.lifetime
}
p.lifetime_left -= f32(get_delta_t())
if p.lifetime_left <= 0 {
particle_destroy(&p)
continue
}
}
}
}
// render
for p in particles {
if .valid in p.flags {
z_layer := p.z_layer
if z_layer == .nil {
z_layer = ZLayer.pfx
}
push_z_layer(z_layer)
draw_pos := p.pos.xy
draw_pos.y += p.pos.z
alpha_mask := color.WHITE
if p.lifetime != 0 {
lifetime_alpha := utils.float_alpha(p.lifetime-p.lifetime_left, 0, p.lifetime)
if p.fade_in_pct != 0 {
alpha_mask.a *= utils.float_alpha(lifetime_alpha, 0.0, p.fade_in_pct)
}
if p.fade_out_pct != 0 {
alpha_mask.a *= 1.0-utils.float_alpha(lifetime_alpha, 1.0-p.fade_out_pct, 1.0)
}
}
rect := rect_make(draw_pos, p.size, pivot=Pivot.center_center)
draw_rect(rect, col=p.col*alpha_mask)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment