PixiJSでパーティクルを作ってみる

パーティクルに興味をもったので色々調べて作ってみた。
PixiJSのParticle-Containerを試してみたが、パーティクルの操作にかなり縛りがあったので使用せず。

また、この記事のコードはICSさんの下記のURLを参考にPixiJSを使ってコーディングしました(要PixiJSだったため)

JavaScriptとWebGLで取り組む クリエイティブコーディング
https://www.slideshare.net/clockmaker_jp/javascriptwebgl-107429033

テスト的にやってみただけ＆パーティクルを出したり引っ込めたかったのでオブジェクトプーリングはしてません。
追々対応予定。

作ったばかりでコードを整理していないので、微妙なとこがあるかもしれません（特に命名規則

```ts
import * as PIXI from 'pixi.js'

export class ParticleStage extends Dispatcher {
    private app
    private container
    private particles = []

    // 1フレームごとに生成するパーティクルの数
    private totalSpritesPerTick = 4
    // パーティクルのライフ
    private MAX_LIFE = 100

    public constructor(){
        super()
        
        this.app = new PIXI.Application({
            resolution: devicePixelRatio,
            transparent: true,
        })

        // パーティクルを追加していくコンテナインスタンスを作成
        this.container = new PIXI.Container()
        // ステージに追加
        this.app.stage.addChild(this.container)
        return this.app
    }

    // パーティクルを開始するメソッド
    public startTick() {
        //パーティクル作成tickを登録
        this.app.ticker.add(this.createTick, this)
        this.app.ticker.start()
    }
    // パーティクルを停止するメソッド
    public stopTick() {
        this.app.ticker.remove(this.createTick, this)
    }

    // 1フレーム毎に呼び出されるパーティクルを生成するメソッド
    private createTick() {
        for (let i = 0; i < this.totalSpritesPerTick; i++) {

            // PIXI.Spriteを拡張したパーティクルインスタンスを作成
            const particle = new Particle()

            // なんとなくぼかす
            particle.filters = [new PIXI.filters.BlurFilter(5)]
            // パーティクルの透明度をランダムで設定
            particle.alpha = Math.random() * 0.2 + 0.5
            // パーティクルの中心を設定
            particle.anchor.set(0.5)
            // 浮き上がる速度を設定
            particle.vy = 20 * (Math.random() - 0.5)
            // パーティクルのライフを設定
            particle.life = this.MAX_LIFE

            // パーティクルのシェイプを決定
            particle.type = Math.floor(Math.random() * 2).toString()

            // パーティクルのシェイプをtypeごとに生成
            const g = new PIXI.Graphics()
            switch (particle.type) {
                case '0':
                    g.beginFill(0xFFFFFF)
                    g.drawCircle(0, 0, 15)
                    g.endFill()
                    break
                case '1':
                    g.lineStyle(3, 0xFFFFFF)
                    g.drawCircle(0, 0, 15)

                    break
            }
            g.endFill()
            // GraphicsをTextureに変換
            const t = this.app.renderer.generateTexture(g)
            particle.texture = t

            // パーティクルの出現位置を設定(今回はnormal)
            const randomValue = this.createRandom('normal')
            particle.x = window.innerWidth * (randomValue)
            particle.y = window.innerHeight + 10

            // パーティクルを配列に保存
            this.particles.push(particle)
            // コンテナにaddChild
            this.container.addChild(particle)
        }
    }

    // パーティクルを動かすメソッド
    private tick() {
        for (let i = 0; i < this.particles.length; i++) {
            // オブジェクトの作成
            const particle = this.particles[i]
            // 重力
            particle.vy -= 2
            // 摩擦
            particle.vy *= 0.92
            // 速度を位置に適用
            particle.y += particle.vy

            // パーティクルのサイズをライフ依存にする
            const scale = particle.life / this.MAX_LIFE;
            particle.scale.x = scale
            particle.scale.y = scale

            // パーティクルの透過度をライフ依存にする(0.6が下限)
            particle.alpha = (1-scale) + 0.6

            // 寿命を減らす
            particle.life -= 1;
            // 寿命の判定
            if (particle.life <= 0) {
                // 変数を初期化
                particle.reset()
                // コンテナから削除
                this.container.removeChild(particle)
                // 配列からも削除
                this.particles.splice(i, 1)
                i -= 1
            }
        }
    }
    private createRandom(randomType) {
        switch (randomType) {
            case 'center':
                // 中央に配置
                return (Math.random() + Math.random()) / 2
            case 'center-strong':
                // さらに中央に配置
                const valueA = (Math.random() + Math.random()) / 2
                const valueB = (Math.random() + Math.random()) / 2
                return (valueA + valueB) / 2
            case 'edge':
                // 乗算の乱数
                const base = Math.random() * Math.random() * Math.random()
                const inverse = 1.0 - base
                return Math.random() < 0.5 ? base : inverse
            case 'normal':
            default:
                // 一様分布
                return Math.random()
        }
    }
}
export class Particle extends PIXI.Sprite {
    public vx = 0
    public vy = 0
    public life = 0
    public size = 0
    public type
    constructor() {
        super()
    }

    public reset() {
        this.vx = 0
        this.vy = 0
        this.life = 0
        this.size = 0
        this.type = 0
        this.texture = null
        this.destroy()
    }
}
```

試してみる

```ts
import { ParticleStage } from './ParticleStage '

const particles = new ParticleStage()
// 適当なdivのクラス名
const div = document.getElementsByClassName('canvas-stage')

// canvasを追加
div.appendChild(particles.view)

//パーティクルを開始
particles.startTick()

// 1秒後に停止
setTimeout(particles.stopTick(), 1000)
```

実際のものはHSLでランダムに色を変更したりしています。
パーティクルおもしろいなぁ。