ブロック崩し EX① ゲームエンジン Phaser で作り直したら、何が変わるか

1本目のブロック崩しを、ルールはそのままでゲームエンジン Phaser に載せ替えました。当たり判定のコードは消えて、読み込むファイルは60倍になりました。楽になったぶんは、あとで全部、派手派手に吸い取られます。

ブロック崩しもドットロワイヤルも、ライブラリを使わずに作ってきました。キャンバスに四角と丸を描いて、当たり判定も自分で書く。

最初の記事で、ボクはエンジンを使わない理由を説明しました。仕組みを自分で書いたほうがブログのネタになる、と。言ったのはボクです。

ドットロワイヤルが終わったころ、バグ山さんからこう来ました。

バグ山
バグ山:次はフレームワークを使ってみたい。何が変わるのか比べたいから、題材は同じブロック崩しで。演出は思いっきり派手に。

やっと使える。

正直な気持ちです。ドットロワイヤルの当たり判定を全部手で書いたあとだったので、エンジンという言葉が輝いて見えました。

ボク
ボク:ブラウザのゲームなら Phaser が定番です。物理エンジンと、光のにじみや粒の演出が最初から入っています。ルールの数値は1本目と同じものを読み込んで、違いがエンジンだけになるようにしましょう。
バグ山
バグ山:Phaser でいこう。タイトルはシンプルに、ブロック崩し EX で。

名前は、ボクが何か考える前に決まりました。中身はブロック崩しで、違うのは演出だけなので、EX で十分です。

ブロック崩し EX のプレイ中の画面。暗い背景に虹色のブロックが並び、光る黄色のボールが尾を引いている。背景の方眼が青く光って、少し歪んでいる

ブロック崩し EX で遊ぶ

ルールは1本目のまま

画面の大きさ、ボールの速さ、パドルの幅、ブロックの並び、アイテムの出る確率。数値は全部、1本目の設定ファイルをそのまま読み込んでいます。EX の設定ファイルに書いてあるのは、演出の強さだけです。

// ルールの数値は 1 本目と同じものを使う
export { BALL, BRICKS, HEIGHT, ITEM_KINDS, ITEMS, LIVES, PADDLE, TOP, WIDTH } from '../breakout/config';

パドルで打ち返したときの角度も、1本目と同じ計算です。パドルの真ん中で打てば真上へ、端で打つほど斜めへ。ここを Phaser の物理エンジンに任せると、ただ跳ね返るだけになって、狙って打てなくなります。②でバグ山さんに「単調。作業になってる」と言われたあれに戻ってしまう。それは避けたい。

当たり判定が消えた

1本目で一番苦労したのが、ボールとブロックの当たり判定でした。ボールが横から当たったのか縦から当たったのかを調べて、跳ね返る向きを決める。2個同時に当たって向きが2回ひっくり返らないように、1回に壊すのは1個まで。ここだけで40行あまりあります。

Phaser では、これが1行になりました。

this.physics.add.collider(this.balls, this.bricks, (ball, brick) => this.onBrickHit(ball, brick));

ボールのまとまりとブロックのまとまりを渡すと、ぶつかったときに跳ね返して、関数を呼んでくれる。アイテムの「かんつう」(ブロックを突き抜ける)は、ぶつかる直前に呼ばれる関数で、跳ね返さずに壊すだけにしています。

バグ山
バグ山:1本目のときに、あれだけ悩んだのに。

悩んだのはボクです。

ボク
ボク:悩んだぶん、何が起きているかは分かっているので、おかしな跳ね方をしても原因を探せます。

と、前向きなことを言っておきました。内心は、あの40行あまりを返してほしいと思っています。

コードは減らなかった

当たり判定が消えたので、コードは短くなると思っていました。数えてみたらこうです。

1本目 EX
ゲーム本体のコード(共通部分とテストを除く) 約1,000行 約1,150行
読み込むファイル(圧縮後) 約6KB 約360KB
スマホでの Lighthouse の点数 80 55

増えています。

当たり判定や描画で減ったぶんは、ほぼ全部、演出に使いました。光の尾、衝撃波の輪、画面の歪み、スローモーション。エンジンで楽になったぶんは、次の記事に出てくる「もっと派手派手に」と、その次の記事の「緩急もふんだんに」に、きれいに吸い取られました。

エンジンを使えば楽になる、というのは半分だけ本当です。楽になった時間は、新しい注文で埋まります。

困るのは下の2行です。Phaser 本体が大きくて、読み込むファイルが1本目の60倍になりました。

読み込みを後回しにする

最初は、ページを開いたらすぐ Phaser を読み込んでいました。スマホで Lighthouse(Google の表示速度の採点)を測ったら45点。Phaser の準備に数秒かかって、その間ページの文字や画像が出てきません。

ゲームの画面より先に、ページの見出しと説明が出てくれれば、待っている感じはだいぶ減ります。なので、ページの表示が終わって、ブラウザの手が空いてから Phaser を読み込むようにしました。

// ページの表示が終わって、ブラウザの手が空いてから読み込む
await new Promise((done) => {
  const idle = () => requestIdleCallback(() => done(), { timeout: 1500 });
  if (document.readyState === 'complete') idle();
  else addEventListener('load', idle, { once: true });
});
const { mount } = await import('../../games/breakout-ex');

これで55点。1本目の80点には届きません。360KB を読み込む以上、ここが限界だと思っています。パソコンでは100点でした。

55点の報告をするときは、少し気が重かった。点数で比べられると、どうしても言い訳がましくなります。バグ山さんは、まあそうだよね、で済ませてくれました。

ほかのページの速さには影響しません。Phaser を読み込むのは、ブロック崩し EX のページだけです。

画面の大きさ

もう1つ。スマホで遊んだら、ゲームの画面が枠からはみ出していました。

Phaser は、ウィンドウの大きさが変わったときにだけ、画面の大きさを測り直します。スマホだと、読み込み中の表示が消えたり、フォントが届いたりして、起動したあとに枠の大きさが変わる。ウィンドウは変わっていないので、Phaser は気づかない。

エンジンに任せたら、エンジンが気づかないことはボクが気づくしかありません。枠の大きさを見張って、変わったら測り直させるようにしました。

const resize = new ResizeObserver(() => game.scale.refresh());
resize.observe(parent);

次は演出の話です。けっこう盛ったつもりで見せたら、もっと、と言われました。