Phaser のゲームがカクつくのはなぜ?
あなたの Phaser のゲームは 60 fps でなめらかに動いています。ところが、ときどきそうでなくなります。敵が出現するときの一瞬の停止、新しいシーンが始まるときのヒッチ、なぜか他人のノート PC でだけ起きるカクつき。
Phaser 内蔵の FPS カウンターはフレームが落ちたことは教えてくれますが、理由は教えてくれません。Myaku は理由を教えてくれます。すべてのフレームを見張り、遅いフレームをわかりやすい言葉で説明します。
ゲームに追加する
npm install -D myaku// main.ts の 1 行目
import "myaku/auto";
import Phaser from "phaser";画面の隅に小さなフレームグラフが現れます。赤いスパイクがヒッチです。グラフにマウスを乗せると止まるので、スパイクをクリックしてみましょう。
どのシーンが遅いのかを見る
ゲームループを握っているのは Phaser なので、ブラウザから見ると遅いフレームは「Phaser の step 関数に 40 ms かかった」としか見えません。正しいけれど役に立ちません。Myaku は Phaser 自身のループにスパンを追加するので、判定はその中のどこで時間を使ったのかを教えてくれます。
update:すべてのシーンの update 処理update:<シーンのキー>:シーンごとの処理。たとえばupdate:levelやupdate:hudrender:描画
Phaser 3 や、script タグで読み込んだ Phaser なら自動です。Myaku がグローバルの Phaser を見つけて、すべてのゲームとシーンを計測します。Phaser 公式の Runner サンプルで、ゲームには一切手を加えずに試したところ、すべてのフレームで update、update:game、render が取れました。
ES モジュールで使う Phaser 4 にはグローバルがないので、ゲームを Myaku に渡してください。
import { adapt, myaku } from "myaku";
const game = new Phaser.Game(config);
adapt(myaku(), game);すると、遅いフレームの判定はたとえばこうなります。
Frame 812 took 38 ms (budget 25 ms). The game's frame code ran 35 ms. Spans: update 33 ms, update:level 31 ms, render 2.0 ms.
(フレーム 812 に 38 ms かかりました(予算 25 ms)。ゲームのフレーム処理に 35 ms。スパン:update 33 ms、update:level 31 ms、render 2.0 ms)
これで、描画ではなく level シーンの update が原因だとわかります。さらに絞り込むには、その中のシステムを自分でスパンに包みましょう。
const profiler = myaku();
class Level extends Phaser.Scene {
update(time: number, delta: number) {
profiler.span("enemies", () => this.updateEnemies(delta));
profiler.span("physics", () => this.physics.world.update(time, delta));
}
}Phaser でよくある容疑者
Phaser のゲームで Myaku がよく報告する原因と、それぞれの対策です。
upload:プレイ中に作られるテクスチャ。this.add.text()は canvas テクスチャを作ります。そのためupdateの中でテキストオブジェクトを作ったり、毎フレーム文字を変えたりすると、そのたびに GPU へのアップロードが起きます。this.textures.addCanvasや実行時のテクスチャ生成も同じです。create()で作って使い回すか、ビットマップテキストを使いましょう。Myaku はバイト数も数えます。あるテストでは 1 フレームで 64 MB がアップロードされていました。compile:シェーダーの初回使用。 ポストプロセスのパイプラインや FX は、初めて使うときにシェーダーをコンパイルします。ローディングシーンの間に温めておきましょう。gc:ホットパスでのメモリ確保。update内のnew Phaser.Math.Vector2()、毎フレーム全部の敵に対して行う配列のfilter/map、毎フレーム作られるトゥイーンのクロージャ。オブジェクトを使い回し、プーリング付きのグループ(group.get())を使いましょう。- 場所付きの
script。 遅い関数が Phaser のループの外、たとえばローダーのコールバックや入力ハンドラーで動いた場合、Chrome がどの関数かを正確に Myaku に伝えます。Myaku はそれをファイル名付きで示します。 throttled:ゲームのせいではありません。 ウィンドウが隠れていたり最小化されていたりすると、ブラウザはフレームを遅くします。Myaku はゲームのせいにせず、そう伝えます。
共有できる数字
window.__MYAKU__.report() は、フレーム時間のパーセンタイル(p50、p95、p99)、1 分あたりのヒッチ回数、判定付きのワーストのヒッチ、各スパンの平均コストを返します。ただの JSON なので、issue に貼ったり、Playwright のスクリプトから確かめたりできます。