前回の1編では「なぜこのゲームを作ったのか」という話を書きました。
今回はその続きとして、「どうやって作ったのか」—— 技術的な面を中心に振り返ってみようと思います。

私は普段、Django・PostgreSQL・Redis・Docker といったバックエンド寄りの技術を扱うことが多く、どちらかといえばフロントエンドは避けて通ってきたタイプです。
ところが今回のプロジェクトでは、ほぼすべてを バニラJavaScript + HTML5 Canvas で作ることになりました。

いつもなら深く触れなかった領域なのに、実際に作り始めると…

ものすごく楽しかった。

豆柴の大群が大好きだからこそ、普段は苦手意識のある作業も夢中になって進められたのだと思います。


1. HTML5 Canvas ― 「絵を描きながらゲームを作る」ような感覚



MAME RUN!! の土台となっているのは HTML5 Canvas です。
Canvas はウェブページ上の「自由に絵が描ける領域」で、ここに毎フレーム絵を描き直すことでゲームらしい動きを作ります。

一般的なウェブ開発が

  • HTMLでボタンやテキストを配置し
  • CSSで見た目を整え
  • JavaScriptで少し動きを付ける

という流れなのに対し、Canvas は全く違う世界です。

Canvasは1秒間に30〜60回、以下の処理を繰り返します。

  1. 背景を消す
  2. 新しい背景を描く
  3. レオナを描く
  4. 障害物を描く
  5. 衝突しているか判定する
  6. スコアを更新する
  7. 次のフレームを予約する

これを延々と繰り返しているだけなのに、ちゃんと「ゲーム」になるから不思議です。
最新のウェブブラウザは、ちょっとしたゲームエンジン顔負けの性能を持っていることを実感しました。


2. ジャンプと重力 ― たった数行で“ゲームの動き”が生まれる

ジャンプの仕組みは、とてもシンプルな物理計算で成り立っています。

dino.vy += gravity * dt;  // 重力を加える
dino.y  += dino.vy * dt;  // 位置を更新

if (dino.y >= ground) {
    dino.y = ground;
    dino.vy = 0;
}

たったこれだけで、レオナが自然に跳んで、落ちて、着地する動作になります。

ゲームエンジンを使わなくても、基本的な物理だけで“それっぽい動き”が出ることに感動しました。


3. スプライトアニメーション ― レオナが走り出す瞬間



レオナの走るモーションは、6枚の画像が横に並んだ「スプライトシート」を使っています。

JavaScriptでフレームごとに切り取って描画するだけで、まるで本当に走っているようなアニメーションになります。

const frameWidth = sprite.width / 6;
ctx.drawImage(
  sprite,
  frameIndex * frameWidth, 0, frameWidth, sprite.height,
  dino.x, dino.y, dino.width, dino.height
);

最初にレオナが「動いた」瞬間は、自分でも驚くほど嬉しかったです。
ゲーム開発においてアニメーションがどれほど重要なのか、身をもって知ることになりました。


4. 障害物のランダム生成 ― 単調さをなくす重要な仕掛け

MAME RUN!! にはいくつかの障害物があります。

  • オレンジ色のコーン
  • 赤いコーン
  • 黄色のコーン
  • バイク(2種類)
  • 車(2種類)
  • 標識(3種類)

これらを単純に並べるだけだとすぐに飽きてしまいますが、間隔や種類をランダムにするだけでプレイ感が一気に変わります。

const key = OBSTACLE_KEYS[Math.random() * length];
const interval = 0.8 + Math.random() * 1.2;

ほんの少しのランダム性でも、ゲーム体験は驚くほど豊かになります。


5. 衝突判定 ― 苦労したけど一番楽しかった部分

衝突判定は、いわゆる「AABB(Axis Aligned Bounding Box)」という方法を使いました。

if (dino.x < ob.x + ob.width &&
    dino.x + dino.width > ob.x &&
    dino.y < ob.y + ob.height &&
    dino.y + dino.height > ob.y) {
    // 衝突!
}

ただし、判定が厳しすぎるとイライラしますし、緩すぎると簡単になりすぎます。
そこで、実際の画像より10〜15%ほど小さな“当たり判定”を設定して、気持ちの良い難易度になるように調整しました。

この細かい調整がとても楽しくて、ゲーム開発者が難易度にこだわる理由が少し分かった気がします。


6. ステージ構成 ― 50秒で「もう一回遊びたい」長さに

Stage 1 は、約50秒でクリアできるように設定しました。
そのためレオナの移動速度は1秒あたり16mにしています。

目標距離:800m  
速度:16 m/s → 約50秒

数字だけでは決められないので、30〜40回ほど自分でプレイしながら
「これならテンポよく遊べて、もう一回やりたくなるか?」
という感覚をひたすら探りました。


7. サーバー連携 ― ランキング更新の気持ちよさ

サーバー部分は、普段から使い慣れている Django + DRF で実装しました。

  • POSTでスコアを送信
  • Serializerでバリデーション
  • 未ログインユーザーは guest_id で識別
  • ログインユーザーは最高スコアを専用テーブルで管理
  • TOP5 を HTML 部分として返し、JS側で描画を更新

ゲームオーバーになった瞬間にランキングがリアルタイムで切り替わる感じは、自分で作っていても気持ちよかったです。

そして、ゲームの中でもバックエンドの構造がしっかりしていると体験が大きく向上することを改めて実感しました。


8. モバイル対応 ― 一番手ごわかった部分

ウェブゲームを作ってみて分かったのは、モバイル対応が想像以上に難しいということでした。

  • 画面解像度
  • タッチ操作
  • Canvas サイズ調整
  • スマホのレンダリング性能
  • 背景スクロール速度の補正

PCでは滑らかに動くのに、スマホでは微妙なカクつきが出たり、タッチ判定の遅延が出たり…。
最後まで調整し続ける根性勝負でした。

この作業をしながら、本気で「フロントエンドのエンジニアを尊敬します…」と思いました。


9. 終わりに

今回のプロジェクトは、バックエンド中心でやってきた自分にとって大きな挑戦でした。
特に HTML5 Canvas をここまでしっかり使ったのは初めてです。

でも作ってみて分かったのは、

  • ウェブは想像以上にパワフルで
  • 敬遠しがちだったJavaScriptも触ってみると意外と面白くて
  • ゲーム開発は小さな技術の組み合わせでできている

ということでした。

豆柴の大群が好きという気持ちだけで始めたゲームですが、
技術的にも大きな学びと成長があり、自分でも驚くほど充実した制作体験でした。


次回予告(3編:ストーリー編)

次の投稿では、

  • なぜ渋谷からスタートするのか?
  • なぜ目標は東京ドームなのか?
  • メンバーごとのキャラクター設定はどのように決めたのか?

といった、世界観やストーリー制作の裏側をお話しする予定です。

前の投稿を読む