はじめに
この記事は 「MM2025 開発記」 シリーズ第9回です。
前回の記事ではMainクラスを利用したプロフェクト全体管理について解説しました。
今回は、Canvasを利用したゲームの毎フレーム処理を担うループ構造について解説します。
gameLoopの流れ
Tree形式で表すと以下のようになる。1フレーム内でこのTreeが上から下へ順番に処理されるイメージで考えると分かりやすい。
gameLoop()
├ 再生位置取得(TextAlive)
├ 背景描画(色・画像)
├ 状態分岐(ゴール / 未開始)
├ UI描画(スコアなど)
├ スクロール更新
├ プレイヤー更新
├ オブジェクト更新(コインなど)
└ 次のフレームを予約
処理順序
function gameLoop() {
// 現在の再生位置を取得
//...省略...
// 背景画像の描画
//...省略...
//ゴール処理
if (isGoal) {
//...省略...
}
//ゲーム開始を待つ
if (!gameStarted) return;
//ゴール旗の描画
if (goalX !== null) {
//...省略...
}
// スクロール量を更新
let currentSpeed = 7;
if (!isGoal) {
if (goalX === null) {
//...省略...
}
}
player.update(currentSpeed); //状態の更新
player.draw(ctx); //描画
//...省略...
requestAnimationFrame(gameLoop);
}
実装上は細かい描画が途中に入るが、大きな流れとしては以下の順序を意識している。
- 背景を描画
- 状態を更新
- 衝突・ゴール判定
- 結果を描画
状態更新・判定が終了した後に結果を描画という順序が大切。結果の描画を先にしてしまうと、ずれたり、判定が反映されなくなったりするので注意。
※if (!gameStarted) return;について
ここでreturnしているが、次のフレームのrequestAnimationFrame(gameLoop);は予約されているためループ自体は止まっていない。
requestAnimationFrameとは
JavaScriptにおいて繰り返し処理を行う関数としてrequestAnimationFrameとsetIntervalがある。それぞれの特徴をまとめると、以下のようになる。
requestAnimationFrame
- 画面の更新が必要なタイミングで呼び出す
- 視覚的更新に使用する
- パフォーマンスが良い(無駄なCPU消費を抑える)
setInterval
- 定期的な更新を行う
- 非視覚的更新に使用する
今回のgameLoopでは動きのあるアニメーションが含まれるためrequestAnimationFrameを採用している。
状態フラグ
if (!gameStarted) return;
if (isGoal) { ... return; }
gameLoopの中では上記のようにフラグ&if文で流れを制御している。ゲームは様々な状態の集まりになっているため、フラグは重要である。もしフラグが無いと毎回分岐を描くことになり、手間がかかる&可読性低下の原因になる。
さいごに
今回の記事では、gameLoopについて解説しました。
次回は、キャラクターの物理設計について紹介していきます。