import { Container, Graphics } from 'pixi.js' interface Particle { x: number y: number vx: number vy: number life: number maxLife: number color: number size: number alpha: number } export interface EmitConfig { count: number x: number y: number color: number spread: number // velocity spread px/s speed: number // base speed px/s size: number life: number // seconds gravity?: number } export class ParticleSystem { container: Container private particles: Particle[] = [] private g: Graphics private _running = false constructor(parent: Container) { this.container = new Container() parent.addChild(this.container) this.g = new Graphics() this.container.addChild(this.g) } emit(cfg: EmitConfig): void { for (let i = 0; i < cfg.count; i++) { const angle = Math.random() * Math.PI * 2 const speed = cfg.speed * (0.5 + Math.random() * 0.5) this.particles.push({ x: cfg.x, y: cfg.y, vx: Math.cos(angle) * speed + (Math.random() - 0.5) * cfg.spread, vy: Math.sin(angle) * speed + (Math.random() - 0.5) * cfg.spread, life: cfg.life, maxLife: cfg.life, color: cfg.color, size: cfg.size * (0.5 + Math.random() * 0.5), alpha: 1, }) } this._running = true } update(dt: number): void { if (!this._running) return const gravity = 200 let alive = false this.g.clear() for (const p of this.particles) { p.life -= dt if (p.life <= 0) continue p.x += p.vx * dt p.y += p.vy * dt p.vy += gravity * dt p.alpha = Math.max(0, p.life / p.maxLife) this.g.circle(p.x, p.y, p.size * p.alpha) this.g.fill({ color: p.color, alpha: p.alpha }) alive = true } this.particles = this.particles.filter((p) => p.life > 0) this._running = alive } clear(): void { this.particles.length = 0 this.g.clear() this._running = false } }