draw.sprites でパーティクルと弾を描く
弾幕シューティングなら画面上に 5,000 発の弾、魔法のエフェクトなら 20,000 個の火花があってもおかしくありません。draw.sprite() を 20,000 回呼んでも動きますが、呼び出しのたびに少しずつ JavaScript が走ります。オプションを読み、変形を計算し、インスタンスを書き込む。draw.sprites() は 1 枚の画像をたくさんの位置に 1 回の呼び出しで描きます。WebGL2 なら、スプライトごとの JavaScript 処理はまったくありません。
基本
パーティクルは型付き配列で持っておき、位置を spryte に渡します。
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 番目の引数に渡しましょう。
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 リファレンス にあります。