nun_game0

ブラウザ横スクロールアクションゲーム開発記|TypeScript×Canvasで作るネオンランナー

ネオンランナー

ポートフォリオサイトにブラウザゲームを載せる企画の第2弾。今回はマリオ風の横スクロールアクション「ネオンランナー」を作った。物理エンジンから衝突判定、操作性のチューニングまで、プラットフォーマー開発で学んだことを書く。

ネオンランナーはこちらから遊べる。

なぜ横スクロールアクションを作ったか

1作目のゲームを実装してみて、Canvas描画の基本は掴めた。次は「ゲームらしいゲーム」を作りたかった。横スクロールアクションは、物理演算、衝突判定、レベルデザイン、敵AI、アイテムシステムとゲーム開発の要素が詰まっている。勉強としてこれ以上の題材はない。

テーマはサイト全体と統一してネオン。紫(#a855f7)とシアン(#06b6d4)を基調に、黒背景(#0a0a0a)の上で光るビジュアルを目指した。

技術スタック

  • 描画: HTML5 Canvas 2D
  • 言語: TypeScript(DOM非依存のゲームエンジン + React UIの分離構成)
  • フレームワーク: Next.js 16 + Cloudflare Workers
  • 操作: PC向けキーボード入力 + モバイル向けバーチャルパッド

ゲームエンジン部分はDOM操作を一切しない設計にした。Canvas描画とゲームロジックを純粋なTypeScriptで書き、UIのオーバーレイ(スコア表示、ポーズメニューなど)はReactコンポーネントとして分離している。この構成のおかげでテストが書きやすく、ロジックの見通しもいい。

物理エンジンの実装

プラットフォーマーの物理で最も重要なのは「重力」と「衝突応答」の2つ。

重力とジャンプ

毎フレーム、プレイヤーのY速度に重力加速度を加算する。ジャンプは初速度を負方向(上向き)に設定するだけ。シンプルだが、数値のチューニングでゲームの手触りが大きく変わる。

重力が強すぎるとジャンプが低くなってステージの自由度が下がるし、弱すぎるとフワフワして気持ち悪い。何度も調整を繰り返して、マリオに近い「しっかり跳ぶけど着地は速い」感覚に近づけた。

AABB衝突判定

衝突判定にはAABB(Axis-Aligned Bounding Box)を採用した。各オブジェクトを軸に沿った矩形で囲み、矩形同士の重なりを検出する方式。回転を考慮しなくていいので計算が速い。

タイルマップとの衝突解決では、X軸とY軸を分離して処理するのがポイント。両軸を同時に処理すると、壁に沿って滑り落ちるときに引っかかるバグが起きやすい。X方向の移動を処理してから衝突解決、次にY方向の移動を処理して衝突解決、という順序で処理することで安定した挙動になる。

Swept Collisionによる踏みつけ判定

敵の踏みつけ判定には通常のAABBだけでは不十分だった。プレイヤーが高速で落下すると、1フレームで敵をすり抜けることがある。いわゆるトンネリング問題。

これを解決するためにSwept Collisionを導入した。前フレームの位置から現フレームの位置までの軌跡を追跡し、その間に衝突が発生したかを判定する。プレイヤーが敵の上部に接触した場合は「踏みつけ」、側面や下部に接触した場合は「ダメージ」として処理を分岐させている。

操作性の工夫

プラットフォーマーで最も大事なのは操作性。自分がプレイしてストレスを感じない手触りにするために、2つのテクニックを実装した。

ジャンプバッファ(120ms)

ジャンプボタンを地面に着く直前に押してしまうことはよくある。ジャンプバッファは「着地の120ms前までに押されたジャンプ入力を、着地時に自動で発動させる」仕組み。これがないと「ジャンプボタンを押したのに跳ばない」というフラストレーションが頻発する。

コヨーテタイム(80ms)

足場から落ちた直後の80ms間、まだジャンプできるようにする仕組み。名前の由来はワイリー・コヨーテが崖から落ちた後に一瞬空中で静止するアレ。プレイヤーの視覚と操作にはどうしてもラグがあるので、この猶予があるだけで「理不尽に落ちた」と感じることが激減する。

この2つは多くの商用プラットフォーマーでも採用されているテクニックで、実装コストの割に効果が大きい。プラットフォーマーを作るなら絶対に入れるべき。

ハテナブロック(?ブロック)とアイテムシステム

マリオといえば?ブロック。ネオンランナーにも実装した。プレイヤーが下からブロックに頭突きすると、ブロックが反応してアイテムが出現する。ブロックは一度叩くと使用済みになり、見た目も変わる。

シールドアイテム

?ブロックから出てくるのはシールドアイテム。シアンに光るダイヤモンド型のアイテムで、取得するとプレイヤーの周囲にオーラリングが表示される。シールド装備中は敵からの攻撃を1回だけ防御できる。ヒットするとシールドが消滅し、代わりにダメージを受けない。

シールドの配置はレベルデザインと密接に関わっていて、難所の手前にシールドブロックを置くことで「保険をかけてから挑戦する」プレイスタイルが生まれる。

敵AIとバウンスメカニクス

敵は左右にパトロールする基本的なAI。足場の端に到達するか壁にぶつかると反転する。シンプルだが、配置次第で十分な難易度を作れる。

踏みつけで敵を倒すと、プレイヤーは通常ジャンプの2倍の高さでバウンスする。これは単なる演出ではなく、レベルデザインに組み込まれた仕掛け。通常のジャンプでは届かない高所にアイテムやルートを配置して、敵を踏み台にすることで到達できるようにしている。

このバウンスメカニクスは、敵を「障害物」ではなく「ツール」として活用する設計で、プレイヤーに発見の楽しさを提供する。

ネオン演出

ビジュアル面では、Canvas 2Dのshadowプロパティを活用してネオンの発光表現を実現した。テキスト、ブロック、アイテム、プレイヤーの各要素にshadowBlurとshadowColorを設定することで、追加のレイヤーなしでグロウ効果が出せる。

背景には星が流れるパーティクルエフェクトを配置して、疾走感を演出している。パフォーマンスを考慮して、パーティクルの数はデバイスの負荷に応じて調整している。

遊び方

PC操作

  • ←→キー: 左右移動
  • Space / Enter / ↑キー: ジャンプ
  • P / Escキー: ポーズ

モバイル操作

画面下部にバーチャルパッドが表示される。

  • ←→ボタン: 左右移動
  • JUMPボタン: ジャンプ

ゲームルール

  • ライフ3つでスタート
  • 敵に当たるとライフが減る(シールドがあれば1回防御)
  • 敵を踏みつけるとスコア獲得+高バウンス
  • ?ブロックを下から叩くとアイテム出現
  • ライフがゼロでゲームオーバー

ネオンランナーを遊んでみる

プラットフォーマー開発のTips

この開発で学んだことをまとめておく。

  1. 物理の数値調整は体感で決める。数式よりもプレイして「気持ちいいか」で判断した方がいい結果になる。
  2. XY軸分離の衝突解決は必須。同時処理だと必ずバグる。
  3. ジャンプバッファとコヨーテタイムは最優先で実装。操作性が劇的に変わる。
  4. ゲームエンジンとUIを分離する。DOM非依存にしておくとテストもデバッグも楽。
  5. 敵の配置がレベルデザインの8割。同じ敵でも配置次第で難易度が全く変わる。

まとめ

横スクロールアクションは、実装してみると想像以上に多くの要素が絡み合っている。物理エンジン、衝突判定、操作性チューニング、レベルデザイン、アイテムシステム。どれか一つが欠けてもゲームとして成立しない。

特に操作性は、コードの良し悪しではなく「触ったときの手触り」がすべて。ジャンプバッファとコヨーテタイムという小さな工夫が、プレイ体験を大きく左右することを実感した。

ブラウザだけで動くプラットフォーマーとしては、それなりに遊べるものになったと思う。ぜひ試してみてほしい。

ネオンランナーを遊ぶ

関連記事: ブラウザで遊べるテトリス風パズルをTypeScriptで作った話

SharePost

他の記事