File size: 2,008 Bytes
dbb749a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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
  }
}