1. はじめに
前回の記事「ゲームエンジンとは何か?比較する」では、Unity・Unreal Engine・Godotなど、代表的なエンジンの特徴を比較しました。ただ、比較しているうちに一つ疑問が浮かびました。「そもそもゲームエンジンは、内部でどういう仕組みでゲームを動かしているのか」ということです。
普段使っているエディタは、コードを書く道具ではあっても、それ自体がプログラムを実行する仕組みは持っていません。一方でゲームエンジンは、書いたコードを実際に画面上で動かす仕組みそのものを内部に持っています。今回は、その中身を分解して整理してみます。
2. この記事はこんな方におすすめ
- 前回の記事「ゲームエンジンとは何か?比較する」を読んで、エンジンの中身が気になった方
- ゲームエンジンが「何をしてくれているのか」を、内部構造のレベルで知りたい方
- 「ゲームループ」「レンダリング」「物理エンジン」といった言葉を聞いたことはあるが、それぞれの役割を整理できていない方
- 普段のIDEと、ゲームエンジンの違いが気になっている方
3. 内容
心臓部にある「ゲームループ」
ゲームエンジンの中核にあるのが、ゲームループと呼ばれる仕組みです。シンプルな2Dゲームから大規模なオープンワールドRPGまで、すべて同じ基本構造の上で動いています。「入力を受け取る→状態を更新する→画面に描画する」という一連の処理を、1秒間に何十回も繰り返す、という構造です。
このループが1秒間に繰り返される回数が、いわゆる「フレームレート(fps)」です。60fpsなら、1秒間にこの一連の処理を60回繰り返していることになります。
ここで一つ問題があります。「更新」の処理を、描画とまったく同じ頻度・同じ間隔で行ってしまうと、動作するマシンの性能によって物理演算の結果が変わってしまいます。高性能なPCでは物が速く落ち、低スペックな端末では同じ物がゆっくり落ちる、という不具合が起きるのです。この問題を避けるため、多くのエンジンでは「物理演算などのロジックは、決まった間隔(固定タイムステップ)で必ず一定回数実行し、画面の描画はハードウェアが許す限りできるだけ滑らかに行う」という、2つの異なる速度を組み合わせた設計を採用しています。
各エンジンでの実装例
この「固定タイムステップ+可変フレームレートの描画」という考え方は、Unity・Unreal・Godotのいずれにも共通する設計です。ただし、呼び方や具体的な実装は少しずつ異なります。
-
Unity:
Updateという関数がフレームごとに呼ばれる一方、物理演算などはFixedUpdateという別の関数で処理されます。既定値では0.02秒(1秒間に50回)間隔で実行され、フレームレートが低いときは1フレームの間にFixedUpdateが複数回まとめて呼ばれることもあります。 -
Godot:Unityの
Update/FixedUpdateにあたるものが、_process(毎フレーム)と_physics_process(固定間隔)という2つの関数に分かれています。考え方はUnityとほぼ同じです。 -
Unreal Engine(UE):Unity・Godotのように「専用の関数を書けば済む」形にはなっておらず、
Project Settings > PhysicsにあるMax Physics Delta Time(物理シミュレーションが1回に進める最大の時間)や、Max Substeps(1フレームを最大何回に分割して計算するか)といった設定値を通じて、開発者自身がどこまで細かく制御するかを選ぶ仕組みになっています。
同じ「固定間隔で計算する」という目的に対して、UnityとGodotは「関数を分けることで、開発者に意識させずに済ませる」方向を、Unreal Engineは「設定を公開することで、開発者に委ねる」方向を選んでいる、というのが対照的で興味深い点です。デフォルトのままでも動きますが、UEでは必要に応じて数値を調整する前提の作りになっています。
呼び方は違っても、「ロジックは一定間隔で、描画はできる限り滑らかに」という設計思想そのものは、主要なエンジンでほぼ共通しています。
「見えるようにする」レンダリングエンジン
ゲームループの中で、実際に画面に絵を描く役割を担うのが、レンダリングエンジン(描画エンジン)です。3Dモデルの頂点座標やテクスチャといった情報をもとに、どの物体が見えていて、どの物体が隠れているかを計算し、最終的に1枚の画像(1フレーム分の映像)としてまとめ上げます。
この処理は、「どのオブジェクトが画面に映るかを絞り込む」「影を計算する」「奥にある物体から手前の物体の順に描画する」「透明なオブジェクトを処理する」「最後に画面全体のエフェクトをかける」といった、いくつもの段階を経て行われます。この一連の流れは「レンダリングパイプライン」と呼ばれ、エンジンごとに最適化の方法が異なります。前回の記事で触れたUnreal Engineの「Lumen」や「Nanite」も、このレンダリングパイプラインをより高精細・高効率にするための技術です。
「動きを計算する」物理エンジン
キャラクターがジャンプすれば重力で落ちてくる、物同士がぶつかれば跳ね返る、こうした、現実世界の物理法則に近い動きを計算するのが物理エンジンです。重力・摩擦・衝突判定などのルールに基づいて、オブジェクトが次の瞬間にどう動くべきかを計算します。
このとき裏側で行われているのが「衝突判定(コリジョン検出)」です。すべてのオブジェクトの正確な形状同士を毎回ぶつけて判定すると計算が重くなりすぎるため、多くのエンジンでは、まず球や箱など単純な形で「大まかに近くにあるかどうか」を絞り込み、そこで初めて詳細な形状同士の判定を行う、という2段階の処理をしています。キャラクターの当たり判定が、見た目より少しだけ小さい・大きい箱や円で表現されていることが多いのは、この計算負荷を抑えるための工夫です。
素材をまとめる「アセット管理」
画像、3Dモデル、音声、アニメーションといった素材(アセット)を、プロジェクトの中で読み込み・管理する仕組みも、ゲームエンジンの重要な機能です。素材をどのタイミングでメモリに読み込み、いつ解放するかという管理が不十分だと、ゲームの動作が重くなったり、メモリ不足につながったりすることがあります。
IDEとの違いを、改めて整理する
冒頭の疑問に戻ります。EclipseやVSCodeは、コードを書き、保存し、実行を指示するための道具です。実行そのものは別のランタイムに委ねます。
一方でゲームエンジンは、コードを書くエディタ機能に加えて、ゲームループ・レンダリングエンジン・物理エンジン・アセット管理という、ゲームを実際に動かすための仕組みそのものを内部に抱えています。 書いたコードは、Updateや_processといった形で、これらの仕組みの中に「毎フレーム呼び出される処理」として組み込まれ、エンジンが用意した土台の上で実行されます。IDEが「編集した結果を、実行する仕組みの外部に投げ渡す」道具だとすれば、ゲームエンジンは「実行する仕組みそのものを内側に抱え込んでいる」道具だ、という違いです。
4. まとめ
ゲームエンジンの中身は、ゲームループを中心に、レンダリングエンジン・物理エンジン・アセット管理といった仕組みが組み合わさって成り立っています。fpsという言葉自体は知っていましたが、その裏で「ロジックは固定間隔、描画は可変速度」という2段構えの仕組みが動いていることまでは、今回調べてみるまで意識していませんでした。
特に印象的だったのは、UnityのFixedUpdateとGodotの_physics_processが、名前こそ違えど驚くほど似た設計思想を採用していたことです。今後、どれかのエンジンを触るときは、表面的な機能だけでなく、中身の仕組みを意識しながら動かしてみようと思います。