WorksHub

BROWSER GAME BASICS

ブラウザゲームはどう動いているか: Canvasとゲームループ入門How Browser Games Work: An Introduction to Canvas and the Game Loop

ボタンを押すと機体が動き、敵と弾が飛び、次の瞬間には衝突の結果が表示される。その裏では、入力・計算・描画を短い周期で繰り返す仕組みが動いています。Press a button and the craft moves; enemies and bullets cross the screen; a moment later, the result of a collision appears. Behind these actions is a system that repeats input, calculation, and drawing in short cycles.

公開日: 2026年7月14日Published: July 14, 2026

1枚の画面を何度も描き直しているRedrawing the same screen many times

ブラウザゲームの表示には、HTMLの<canvas>要素がよく使われます。Canvasは、JavaScriptから長方形、画像、文字などを座標指定で描ける領域です。たとえば横座標100、縦座標200へ自機を描き、次の更新で横座標104へ描けば、目には右へ移動したように見えます。Browser games often use the HTML <canvas> element for display. Canvas is an area where JavaScript can draw rectangles, images, text, and other elements at specified coordinates. For example, if the player craft is drawn at x-coordinate 100 and y-coordinate 200, then at x-coordinate 104 on the next update, it appears to have moved to the right.

一般的な処理順は「入力を読む→物体の位置を更新する→衝突を調べる→画面を消して描き直す」です。この一周をゲームループと呼びます。60Hzの画面では、およそ16.7ミリ秒ごとに新しい絵を用意できると滑らかです。ただし、常に60回動くとは限りません。端末の性能や別タブの状態によって間隔は変わるため、移動量をフレーム数だけに依存させない設計が必要です。A typical sequence is “read input, update object positions, check collisions, clear the screen, and draw it again.” One pass through this sequence is called a game loop. On a 60 Hz display, preparing a new image about every 16.7 milliseconds produces smooth motion. The loop will not always run 60 times per second, however. Its timing changes with device performance and the state of other tabs, so movement should not depend only on the number of frames.

requestAnimationFrameで画面更新に合わせるSynchronizing updates with requestAnimationFrame

requestAnimationFrameは、ブラウザが次に描画できるタイミングで指定した関数を呼びます。単純な骨格は次のようになります。requestAnimationFrame calls a specified function when the browser is ready to draw the next frame. A simple structure looks like this.

let previous = performance.now();
function loop(now) {
  const dt = Math.min((now - previous) / 1000, 0.05);
  previous = now;
  update(dt);
  draw();
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

dtは前回から経過した秒数です。速度が毎秒240ピクセルなら、位置へ240 * dtを加えます。30fpsへ落ちても、同じ実時間でほぼ同じ距離を進めます。例ではdtの最大値を0.05秒に抑えています。タブを戻した直後に数秒分を一度に計算し、自機が画面外へ飛ぶ事故を防ぐためです。dt is the number of seconds elapsed since the previous frame. If the speed is 240 pixels per second, add 240 * dt to the position. Even if the rate falls to 30 fps, the object travels almost the same distance over the same real time. In this example, dt is capped at 0.05 seconds. This prevents the game from calculating several seconds at once when a tab is restored and sending the player craft off-screen.

入力は「押した瞬間」と「押している間」を分けるDistinguishing a key press from a held key

キーボードならkeydownkeyupを受け、現在押されているキーを集合へ記録します。移動は押している間ずっと参照し、メニュー決定やボムは押した瞬間だけ処理すると、意図しない連続発動を避けられます。For keyboard input, listen for keydown and keyup, and record the currently held keys in a set. Check held keys continuously for movement, but process menu confirmation and bombs only at the moment a key is pressed. This avoids unintended repeated activation.

タッチ操作では、指の座標をCanvas内の座標へ変換します。CSSで表示を縮小している場合、画面上の幅とCanvas内部の幅は異なります。getBoundingClientRect()で表示領域を取得し、内部サイズとの比率を掛けるのが要点です。画面外へ指が出た場合や、端末が回転した場合の解除処理も欠かせません。For touch controls, convert the finger position into coordinates within the Canvas. If CSS scales the display, its on-screen width differs from the Canvas's internal width. The key is to obtain the display area with getBoundingClientRect() and apply the ratio to the internal size. The controls must also be released when a finger leaves the screen or the device rotates.

当たり判定は見た目より単純にするKeeping collision detection simpler than the artwork

すべての画像の輪郭を1ピクセルずつ比較すると負荷が高く、プレイヤーにも境界が読みにくくなります。そこで、長方形同士や円同士の判定へ置き換えます。円なら、2つの中心間距離が半径の合計より小さいかを調べます。平方根を計算せず、「距離の二乗」と「半径合計の二乗」を比べれば軽量です。Comparing every image outline pixel by pixel is expensive and also makes the boundary difficult for players to read. Instead, use rectangle-to-rectangle or circle-to-circle tests. For circles, check whether the distance between their centers is less than the sum of their radii. Comparing the squared distance with the squared sum of the radii avoids calculating a square root and costs less.

シューティングでは、自機の見た目より当たり判定を小さくすることがあります。重要なのは小ささそのものではなく、中心がどこかをプレイヤーが把握できることです。自機中心に点を表示する、被弾時に判定位置と合う反応を返すなど、視覚情報と計算結果を一致させます。In shooting games, the player craft's hitbox may be smaller than its artwork. Its size alone is not the important point; players need to understand where its center is. A dot at the center of the craft, or a hit response aligned with the collision position, keeps the visual information consistent with the calculation.

物体が増えたときは、画面外の弾を配列から外します。1000個の弾と100個の敵を総当たりで比べると10万回の判定になります。格子状に領域を分け、近い物体だけ比較する方法もありますが、小規模なゲームでは、まず不要な物体の削除と簡単な図形判定から始める方が実装を確かめやすくなります。As the number of objects grows, remove off-screen bullets from the array. Comparing 1,000 bullets against 100 enemies exhaustively requires 100,000 checks. The play area can also be divided into a grid so that only nearby objects are compared. For a small game, however, it is easier to verify the implementation by first removing unnecessary objects and using simple geometric tests.

ゲームの状態を分けるSeparating game states

タイトル、プレイ中、ポーズ、結果画面を同じ処理へ詰め込むと、ゲームオーバー後も敵が動くといった不具合が起きます。現在の状態をtitleplayingresultのように明示し、状態ごとに入力と更新を限定します。If the title screen, active play, pause, and result screen are all packed into the same process, errors can occur, such as enemies continuing to move after game over. Make the current state explicit with values such as title, playing, and result, and limit input and updates according to the state.

プレイ中のデータも役割を分けます。自機は位置・速度・残機、敵は種類・体力・行動時間、弾は位置・半径・所属を持つ、と決めると見通しが良くなります。スコア表示は計算結果を読むだけにし、描画処理から得点を増やさないことも大切です。描画回数が変わってもルールが変化しないためです。Separate the responsibilities of in-game data as well. The player craft can hold its position, speed, and remaining lives; enemies their type, health, and action time; and bullets their position, radius, and owner. This makes the system easier to follow. The score display should only read a calculated result, rather than adding points from the drawing process. The rules then remain unchanged even if the number of rendered frames changes.

localStorageで小さなデータを保存するSaving small amounts of data with localStorage

ハイスコアや設定はlocalStorageへ文字列として保存できます。同じブラウザ、同じサイトで次回も読み出せ、サーバーを用意せずに済みます。オブジェクトはJSON.stringifyで文字列へ変換し、読み出すときはJSON.parseを使います。High scores and settings can be stored as strings in localStorage. They can be read on a later visit in the same browser on the same site, with no server required. Convert objects to strings with JSON.stringify, and use JSON.parse when reading them.

const data = { highScore: 12800, volume: 0.6, version: 1 };
localStorage.setItem("game-save", JSON.stringify(data));

読み込みでは、データが存在しない場合と壊れている場合を考え、try...catchで初期値へ戻します。将来項目を増やせるよう、保存形式にバージョン番号を持たせると更新しやすくなります。When loading, account for missing or damaged data and return to initial values with try...catch. Adding a version number to the storage format makes future updates easier when fields are added.

localStorageは機密情報の保管場所ではありません。ページ上のJavaScriptから読め、ブラウザのサイトデータ削除、プライベートブラウズ、端末故障でも失われます。ハイスコアには向きますが、復元できない重要記録にはエクスポート機能など別の備えが必要です。また、端末間で自動同期されない点も、利用者へ明記します。localStorage is not a place for confidential information. JavaScript on the page can read it, and it can be lost when browser site data is cleared, during private browsing, or through device failure. It suits high scores, but important records that cannot otherwise be restored need another safeguard, such as an export feature. Users should also be told that it does not synchronize automatically between devices.

小さな試作から確認する手順A process for checking a small prototype

  1. Canvasへ自機を1つ描き、キーで動かす。Draw one player craft on the Canvas and move it with the keyboard.
  2. 画面端を越えないよう座標を制限する。Constrain its coordinates so it cannot cross the edge of the screen.
  3. 弾を1種類追加し、画面外で削除する。Add one type of bullet and remove it when it leaves the screen.
  4. 円同士の衝突を追加し、判定範囲を線で可視化する。Add circle-to-circle collisions and show the collision areas with outlines.
  5. タイトル・プレイ中・結果の状態を分ける。Separate the title, active play, and result states.
  6. 最後にハイスコアを保存し、再読み込みで復元を試す。Finally, save the high score and confirm that reloading restores it.

最初から敵を何十種類も置くより、1つの入力と1つの衝突が安定してから広げる方が、原因を切り分けやすくなります。開発中は座標、経過時間、当たり判定を一時表示し、目で見える動きと内部の数値が一致しているかを確認します。Rather than adding dozens of enemy types at the outset, stabilize one input and one collision first, then expand. This makes causes easier to isolate. During development, temporarily display coordinates, elapsed time, and hitboxes, and confirm that the visible motion agrees with the internal values.

関連リンクRelated links