レースゲームの作り方:曲がる感覚とコース判定
遊びをつくる執筆にClaudeを使用しています。

レースゲームを作るとき、最初に直面する壁は「曲がる感覚が出ない」という問題です。キー入力で角度を変えても、車がその場でくるくる回るだけで、速度感も重さもありません。プレイヤーが「走っている」と感じるには、物理的な慣性と視覚的なフィードバックを組み合わせる必要があります。 もうひとつの壁がコース判定です。コースの外に出たらペナルティを与えたい、壁に当たったら弾き返したい、でも複雑な形状のコースをどう判定すればいいかわからない——この二つの問いに、この記事では順を追って答えます。 扱う環境はブラウザで動くJavaScript(Canvas API)を想定しています。フレームワークは使わず、素のコードで原理を確認してから、必要に応じてライブラリに移行する流れを取ります。
曲がる感覚を作る前に:車の動き方を決める
レースゲームの操作感は、車の動きモデルによって大きく変わります。まずモデルを選んでから実装に入ると、後で迷いません。
タンクモデルは、車が常に自分の向いている方向へ進むモデルです。方向キーで角度を変え、アクセルで前進します。実装は簡単ですが、スライドや流れる感覚がありません。トップビューのアーケード系に向いています。
慣性モデルは、速度ベクトルと車の向きを別々に持ち、ステアリングが速度ベクトルをゆっくり引き寄せるモデルです。カーブで外に膨らむ、ブレーキをかけると慣性でズレる、という感覚がここから生まれます。本記事ではこちらを扱います。
物理エンジンモデルは、Box2DやMatter.jsのような外部ライブラリに車体・タイヤの物理を任せるモデルです。リアリティは最も高いですが、チューニングに時間がかかります。まず慣性モデルで感覚をつかんでから移行するのが現実的です。
慣性モデルの実装:変数と更新ループ
慣性モデルに必要な変数は次の5つです。
- x, y:車の現在位置
- angle:車の向いている方向(ラジアン)
- speed:現在の速度(スカラー)
- vx, vy:速度ベクトルの成分
毎フレームの更新は以下の流れで行います。
- 入力に応じてangleを変える。左キーでangle -= steerRate * speed / maxSpeed、右キーで加算します。speed / maxSpeedを掛けることで、低速時は曲がりにくく、高速時は少しの入力で角度が変わるようになります。
- アクセル・ブレーキでspeedを増減する。最高速度と最低速度(0)でクランプし、何も押さなければ摩擦係数を掛けて少しずつ減速させます。
- 目標速度ベクトルを計算する。targetVx = Math.cos(angle) * speed、targetVy = Math.sin(angle) * speedです。
- 実際の速度ベクトルを目標に近づける。vx += (targetVx - vx) * gripのように、grip(0〜1の値)で補間します。gripが1に近いほど即座に追従し、小さいほど慣性が強くなります。0.15〜0.25あたりから試すと「それらしい」感覚になります。
- 位置を更新する。x += vx、y += vyです。
この実装の核心は「angleは即座に変わるが、速度ベクトルは遅れてついてくる」という分離にあります。この遅れがドリフトやコーナリングの感覚を生みます。
コース判定の3つの方法と選び方
コース判定には複数のアプローチがあります。コースの形状と用途に応じて選んでください。
ピクセル色判定
コースを画像として用意し、車の位置のピクセル色を読んで「コース上か否か」を判定する方法です。CanvasRenderingContext2D.getImageData()でピクセルデータを取得し、RGBが路面の色であればOK、草や壁の色であればペナルティ、という単純なロジックになります。
利点は、複雑な形状のコースを絵として描けるため、コースデザインの自由度が最も高いことです。欠点は毎フレームgetImageDataを呼ぶとパフォーマンスが落ちやすいことです。対策として、ゲーム開始時にコース画像全体をピクセル配列としてメモリに読み込んでおき、フレームごとには配列へのアクセスだけで済ませます。
ポリゴン(ベクター)判定
コースを数値的なポリゴンとして定義し、点がポリゴン内かどうかをレイキャスティングなどで判定する方法です。コースの輪郭を外側のポリゴンと内側のポリゴン(内壁)の2本の多角形として用意します。車の位置が外側の中にあって内側の外にあれば「コース上」です。
この方法は判定の精度が高く、壁との衝突方向(法線ベクトル)を求めやすいため、「壁に当たったら弾き返す」処理が実装しやすいです。コースを手動で頂点座標として定義するか、SVGパスから変換するツールを使うのが一般的です。
チェックポイント方式(補助的に使う)
コース上の特定地点にチェックポイントを並べ、車が順番に通過しているかを確認する方法です。これ単体ではコースアウトは検出できませんが、逆走防止・ラップカウント・ショートカット防止に使います。ピクセル判定やポリゴン判定と組み合わせるのが実際の設計です。
判定方法の選び方をまとめると、コースが画像として作られているならピクセル判定が素直です。プログラムでコースを生成したい、壁バウンドを実装したいならポリゴン判定が向いています。両者を組み合わせることも可能で、走行面はピクセル判定・壁の弾き返しだけポリゴンで処理する設計もよく使われます。
壁に当たったときの弾き返し処理
コースアウトを検出したら、単に速度をゼロにするだけではゲームになりません。「壁に当たって跳ね返る」処理が必要です。
ポリゴン判定を使っている場合、壁の辺の法線ベクトルを求め、速度ベクトルを法線方向に反射させます。反射ベクトルの求め方は「入射ベクトルから法線成分を2倍引く」という標準的な式を使います。反射後に速度に0.4〜0.6程度の係数を掛けて減衰させると、ドスンと当たる感触が出ます。
ピクセル判定の場合は法線が直接求まりません。簡易的な方法として、コースアウトを検出したフレームでvxとvyを反転させ、速度を大きく落とすことで「壁に食い込んだら押し返される」感覚を作れます。より正確にしたい場合は、車の少し前の複数点のピクセルを読んでどの方向から外に出たかを推定します。
どちらの方法でも、弾き返し後のフレームで再びコースアウト判定が動くと無限ループになりやすいため、弾き返し後に数フレームの「無敵時間」を設けるか、車を路面の中央方向へ少しオフセットする処理を入れることが安定につながります。
体感品質を上げる追加要素
物理モデルとコース判定が動いたら、次の要素を順番に足すと体感が大きく変わります。
まずカメラの追従です。車を画面中央に固定してコースを動かすとき、追従を遅延させると「カメラが車に引きずられる」感覚が生まれ、速さを感じやすくなります。cameraX += (car.x - cameraX) * 0.1のような補間が定石です。
次に車体の傾き表示です。ステアリング入力に応じて車のスプライトを2〜5度傾けると、曲がっている感覚が視覚的に強まります。実際の物理には影響させず、描画だけ傾けるのがシンプルです。
スキッドマーク(タイヤ痕)も効果的です。速度ベクトルと車の向きのズレが一定以上になったフレームで、車の後方に短い線を描き続けます。ドリフトしているかどうかが見える化され、プレイヤーが操作を覚えるヒントにもなります。
最後に速度に応じた視野角の変化です。高速になるほど画面をわずかにズームアウトすると、前方の見通しが広がって「速い」という感覚が強くなります。逆に低速時はズームインして細かい操作に集中しやすくなります。
次の行動
まず慣性モデルの車だけを動かす最小構成を作り、gripの値を0.05から0.5まで変えながら操作感の違いを確認してみてください。コース判定はその後、コースを用意してから実装する順番が遠回りに見えて確実です。完成したゲームはHTMLファイルまたはZIPにまとめてアソバースへ投稿できます。他のプレイヤーの反応を見ながら、チューニングを続けてみてください。

