render/sprites/particleSystem.js

import { Sprite } from "./sprite.js"
import { Vector2, rand } from "../../math/index.js"
import { circle, fill } from "../utils/index.js"

/**
 * Its a fricking particle!
 */
export class Particle {
  /**
   * @readonly
   * @type Vector2
   */
  position = null
  /**
   * @readonly
   * @type Vector2
   */
  velocity = null
  /**
   * @type boolean
   */
  active = true
  /**
   * @type number
   */
  radius = 0
  /**
   * @type {{r:number,b:number,g:number,a:number}}
   */
  color = null
  /**
   * @private
   * @type number
   */
  _life = 0
  /**
   * @readonly
   * @type number
   */
  lifespan = 0
  /**
   * @param { Vector2} pos
   * @param {number} radius
   * @param {number} [lifespan=5] In seconds
   */
  constructor(pos, radius, lifespan = 5) {
    this.position = pos
    this.velocity = new Vector2()
    this.radius = radius
    this.color = {
      r: 100,
      g: 255,
      b: 255,
      a: 1
    }
    this.lifespan = lifespan
    this._life = 0
  }
  /**
   * Renders a particle.
   * 
   * @param {CanvasRenderingContext2D} ctx
   */
  draw(ctx) {
    ctx.beginPath()
    circle(ctx, ...this.position, this.radius)
    fill(ctx, `rgba(${this.color.r},${this.color.g},${this.color.b},${this.color.a})`)
    ctx.closePath()
  }
  /**
   * Updates a particle's lifetime
   * @inheritdoc
   * @param {CanvasRenderingContext2D} ctx
   * @param {number} dt
   */
  update(ctx, dt) {
    this._life += dt
    this.position.add(this.velocity)
    this.active = this._life < this.lifespan
  }
}

/**
 * This creates a particle system 
 * @augments Sprite
 */
export class ParticleSystemSprite extends Sprite {
  /**
   * @private
   */
  _particles = []
  /**
   * @type number
   * @default 1
   */
  initial = 0
  /**
   * @type number
   * @default 1
   */
  frameIncrease = 0
  /**
   * @type number
   * @default 1
   */
  max = 0
  /**
   * @param {number} [initial=1] Number of particles to start with.
   * @param {number} [max=100] Maximum number of particles.
   * param {number} [increment=5] Maximum number of particles.
   */
  constructor(initial = 1, max = 100, increment = 5) {
    super()
    this.initial = initial
    this.frameIncrease = increment
    this.max = max
  }

  /**
   * @protected
   * @param {number} n
   */
  initParticles(n) {
    for (var i = 0; i < n; i++) {
      this._particles.push(this.create())
    }
  }

  /**
   * override this to return an object created from your own class extending the particle class
   * 
   * @protected
   */
  create() {
    return new Particle(
      new Vector2(...this.position),
      rand(1, 10),
      rand(1, 6)
    )
  }
  /**
   * @inheritdoc
   * @param {Entity} entity
   */
  init(entity) {
    super.init(entity)
    this.initParticles(this.initial)
  }
  /**
   * @protected
   * @param {Particle} p
   * @param {number} dt
   */
  behavior(p,dt) {
    p.velocity.set(
      p.velocity.x + rand(-1, 1)*dt,
      p.velocity.y + rand(0, 0.3)*dt
    )
  }
  /**
   * @inheritdoc
   *  @param {CanvasRenderingContext2D} ctx
   * @param {number} dt
  */
  render(ctx, dt) {
    for (let i = this._particles.length - 1; i > 0; i--) {
      let p = this._particles[i]
      p.update(ctx, dt)
      this.behavior(p,dt)
      p.draw(ctx, dt)
      if (!p.active) {
        this._particles.splice(i, 1)
      }
    }
    if (this._particles.length < this.max) {
      this.initParticles(this.frameIncrease)
    }
  }
}