本文へスキップ

spryte vs Phaser ​

2dbench の同じマシン・同じシーンで、spryte は動くスプライトを Phaser 4 の Image オブジェクトより 2.2〜3.1 倍速く、バルク描画の手段より 5〜11 倍速く描きます。JavaScript の量は約 24 分の 1(起動時 11.3 kB 対 276.6 kB)です。計測の前に、すべてのフレームがピクセル単位で一致することを確認しています。spryte はレンダラーだけで、Phaser はフレームワーク全体なので、これは描画の比較であってフレームワークの比較ではありません。Phaser のほうが向いている場面は最後にまとめています。

結果 ​

RTX 5060 搭載ノート PC(Ryzen 7 260)、Chrome 155、AC 電源、シーンごとに交互実行で 5 ラウンド。シミュレーションから GPU 完了までのフレーム時間の中央値です。spryte 0.2.1、Phaser 4.2.1、参考として PixiJS 8.22.0。生データ:results/community/*-rtx-5060-laptop-phaser-*.json。

負荷、スプライト 5 万個sprytePhaser 4PixiJS 8spryte / Phaser
Bunnymark、位置のみ:draw.sprite / Image / Sprite4.8 ms15.0 ms13.5 ms3.1 倍
Mixed:テクスチャ 4 種、回転、拡大縮小、色、透明度7.4 ms20.0 ms23.0 ms2.7 倍
Particles:すべてのプロパティが変化6.9 ms19.9 ms21.4 ms2.9 倍
Particles、バルク:draw.sprites / SpriteGPULayer / ParticleContainer1.9 ms9.5 ms7.0 ms5.0 倍

バルクで 20 万個になると差は広がります。位置だけが動くバニーでは 2.7 ms 対 SpriteGPULayer の 23.2 ms、Blitter の 29.9 ms(8.6〜11 倍)、すべてが動くパーティクルでは 6.2 ms 対 35.2 ms(5.7 倍)です。

60 fps で描けるスプライト数 ​

シーンsprytePhaser 4
bunny、スプライトごとに1オブジェクトまたは1呼び出し100,00050,000
mixed100,00020,000
particles100,00020,000
bunny、バルク(Blitter または SpriteGPULayer)800,000100,000
particles、バルク(SpriteGPULayer)400,00050,000
raycaster(敵の数)10,0005,000

数は最大 2 倍刻みの段階で測っているので、下限値です。raycaster(数千のテクスチャの帯で描く一人称視点の迷路)では、敵 1 万体のフレームが spryte 10.1 ms、Phaser 22.3 ms(2.2 倍)、PixiJS 37.3 ms でした。

Phaser の良いところも書いておきます。Phaser の Image は mixed と particles で PixiJS の Sprite に勝ち(5 万個で 20.0 ms 対 23.0 ms、19.9 対 21.4)、bunnymark でもわずかな差でした。raycaster では大きく上回っています。「PixiJS は速く、Phaser は重い」というよくある思い込みは、スプライトごとの描画については当てはまりません。

ダウンロードサイズ ​

回転したスプライト1枚を描くアプリ、Vite 8 の本番ビルド、brotli 品質 11(bench/size):

起動時の JS
spryte 0.3.011.3 kB
PixiJS 8.2287.8 kB
Phaser 4.2.1276.6 kB

Phaser の npm エントリはすべての機能を含むビルド済みバンドル1つなので、バンドラーでツリーシェイキングできません。カスタムビルドならもっと小さくなりますが、標準の使い方ではありません。また、そのサイズの大半は spryte が持たないもの、つまり物理、入力、オーディオ、トゥイーン、ローダー、シーンです。

差が生まれる理由 ​

Phaser の Image はゲームオブジェクトです。Phaser は毎フレーム表示リストをたどり、トランスフォームを更新し、カリングし、バッチ処理します。spryte はオブジェクトを持ちません。draw.sprite の呼び出しは型付き配列にいくつかの数値を書くだけで、draw.sprites は位置の Float32Array をそのままアップロードし、スプライトごとの JavaScript はまったく走りません。

Phaser のバルク描画には、それぞれ制約があります。Blitter は位置だけの Bob を描き、回転と拡大縮小はできません。SpriteGPULayer は回転、拡大縮小、色を扱えますが、めったに変わらないメンバーのための仕組みです。ドキュメントにも頻繁な編集は高コストとあり、毎フレーム全メンバーを書き換えるとバッファ全体が再アップロードされます。動くパーティクルのシーンではそれが必要なので、ここでの数字は Phaser で使える手段の公平な評価であって、SpriteGPULayer の本来の用途の評価ではありません。patchMember で生のワードを書けば速くなるかもしれません。まだ計測していないので、それを試す 2dbench への PR を歓迎します。

計測方法 ​

  • 同じ処理: シード付きの共通シミュレーションと同じテクスチャ。960×540、ニアレストフィルタ、アンチエイリアスなし、整数ピクセルへの丸め(Phaser は pixelArt: true、roundPixels: true)。
  • まずピクセル一致: スプライト 2 千個と 5 万個で、各アダプターのフレームを基準と比較します。Phaser はすべてのシーンでスプライト内部の差が 0% で、縁の差も spryte と一致しました。
  • Phaser はゲームと同じように動かす: Phaser 自身のループを止め、ハーネスが毎フレーム game.step() を呼びます。これは描画に加えてシーンの更新リストとカメラも処理します。PixiJS にはない小さなコストですが、Phaser のゲームはすべてこれを払っています。
  • WebGL1: Phaser 4 のレンダラーは WebGL1 コンテキストを作り、WebGL2 を選ぶ設定はありません。spryte は WebGL2 を使います。

Phaser アダプターは公開しています。もっと速い正攻法を知っている Phaser ユーザーからの提案を歓迎します。

この結果からわからないこと ​

  • 1台のマシンだけ。 これは RTX 5060 搭載ノート PC 1台の数字です。spryte の PixiJS との基準結果と同じマシンですが日が違うので、ミリ秒ではなく同じ実行内の比率で比べてください。同じノート PC の内蔵 GPU(Radeon 780M)では差は小さめでした(2.0〜2.3 倍と 3.0〜4.6 倍、生データ)。お手元のデバイスで 2dbench を実行できます。
  • 静的なシーン。 差は毎フレームのスプライトごとの処理から生まれます。動くものが少なければ縮まります。
  • 描画以外のすべて。 物理、入力、オーディオ、シーン管理は計測しておらず、spryte はそれらを持ちません。
  • Phaser でまだ計測していないもの: 2dbench のタイルマップ、churn、クリッピング、アップロード、テキストのシーン。

Phaser を選ぶべきとき ​

すべてをこなすフレームワークが欲しいなら Phaser です。Arcade と Matter の物理、トゥイーン、ローダー、シーン、入力、オーディオ、ビジュアルエディタ、そして長年の事例があります。ほとんどのゲームはスプライト数で詰まることはなく、Phaser のスプライトごとの描画も十分速いです。

描画がボトルネックなら(弾幕、パーティクル、群衆、大きなタイルマップ)、ダウンロードサイズが大事なら、あるいは自分のコードや debuff の下にレンダラーを置きたいなら spryte です。debuff はループ、入力、オーディオ、テストを加えます。

すでに Phaser を使っているなら、Phaser からの移行に対応表と、何も書き換えずに一番重い演出で spryte を半日で測る方法があります。