本文へスキップ

draw.sprites でパーティクルと弾を描く ​

弾幕シューティングなら画面上に 5,000 発の弾、魔法のエフェクトなら 20,000 個の火花があってもおかしくありません。draw.sprite() を 20,000 回呼んでも動きますが、呼び出しのたびに少しずつ JavaScript が走ります。オプションを読み、変形を計算し、インスタンスを書き込む。draw.sprites() は 1 枚の画像をたくさんの位置に 1 回の呼び出しで描きます。WebGL2 なら、スプライトごとの JavaScript 処理はまったくありません。

基本 ​

パーティクルは型付き配列で持っておき、位置を spryte に渡します。

ts
const MAX = 20_000;
const pos = new Float32Array(MAX * 2); // x0, y0, x1, y1, …
const vel = new Float32Array(MAX * 2);
let live = 0;

function update(dt: number) {
  for (let i = 0; i < live; i++) {
    pos[i * 2] += vel[i * 2] * dt;
    pos[i * 2 + 1] += vel[i * 2 + 1] * dt;
  }
}

function render(draw: Draw) {
  draw.sprites(spark, pos, live, { tint: "#ffd36b", alpha: 0.9 });
}
  • positions には x, y のペアが入ります。count は描く数です。省略するとすべて描きます。
  • オプション(tint、alpha、scaleX、rotation、origin、flash、smooth など)はすべてのコピーに適用されます。1 回の draw.sprite() に渡したときとまったく同じです。
  • 毎フレーム同じ Float32Array を渡してください。WebGL2 では spryte がそれを直接アップロードするので、パーティクルごとの処理はあなた自身の更新ループだけになります。

パーティクルごとに変える:each ​

縮んでいく火花、回転する破片、消えていく残り火。コピーごとの配列を 5 番目の引数に渡しましょう。

ts
const scale = new Float32Array(MAX);
const rotation = new Float32Array(MAX);
const color = new Uint32Array(MAX); // 0xRRGGBBAA

// 例:パーティクルの寿命に合わせてフェードアウト
color[i] = (0xffd36b00 | Math.round(255 * lifeLeft)) >>> 0;

draw.sprites(spark, pos, live, {}, { scale, rotation, color });
  • scale は共通の scaleX/scaleY に掛け合わされます。rotation(ラジアン)は共通の rotation に足されます。color は共通の tint とアルファに掛け合わされます。
  • 必要な配列だけ渡せば大丈夫です。渡さなかったものはスケール 1、回転なし、白になり、アップロードもされません。
  • 色は Uint32Array に 0xRRGGBBAA で詰めます。spryte が GPU 上で展開するので、JavaScript でバイトを並べ替える必要はありません。作るときは符号なしのままにするため >>> 0 を忘れずに。
  • count は渡した配列のうち一番短いものに合わせて切り詰められます。短い配列のせいで spryte がゴミを読むことはありません。

sprite() とまったく同じものを描く ​

draw.sprites(img, pos, n, o, each) は、対応する値で draw.sprite() を n 回呼んだときと同じピクセルを描きます。ピクセルアートのスナップ(回転していないコピーは整数ピクセルに揃う)も同じです。spryte のテストスイートは、実際の GPU 上で、WebGL2 と Canvas 2D の両方のバックエンドについて、これをピクセル単位で確認しています。だからエフェクトをループから sprites() に切り替えても、見た目は変わりません。

コストはどれくらいか ​

ベンチマーク用のノート PC で、すべてのプロパティが毎フレーム変わる 200,000 スプライトを描きました。かかった時間は draw.sprites が 7.7 ms、PixiJS の ParticleContainer は 28.9 ms でした(詳細は ベンチマークページ)。位置のみなら、draw.sprites は 800,000 スプライトで 60 fps を保ちました。

draw.sprite を使うべきとき ​

  • 画像がばらばらなとき。 sprites() が描くのは 1 つのフレームだけです。たくさんのテクスチャから数百枚のスプライトを描くなら、draw.sprite() のループのほうがシンプルですし、どのみちうまくバッチされます。
  • withShader の中。 カスタムシェーダーは汎用のスプライトプログラムで動くので、sprites() はコピーごとに 1 枚のクアッドを描く方式に切り替わります。結果は正しいままですが、一括描画の速さは出ません。
  • Canvas 2D フォールバックでは(WebGL2 が使えない環境でだけ使われます)、普通の Canvas 2D のコードと同じく、コピーを 1 つずつ描きます。

描画順 ​

sprites() は 1 回の描画呼び出しで、それより前に描いたものの後、それより後に描くものの前に描かれます。レイヤー(奥に煙、手前に火花など)ごとに呼び出しを分け、描きたい順に並べてください。

全体像は spryte 登場 に、すべてのオプションは API リファレンス にあります。