記事一覧へ戻る

ゲームの最適化を実機で確かめる:Unity、UE5、Godot の計測用ビルド

エディタ上でフレームタイムが改善しても、プレイヤーの端末で同じ効果が出るとは限りません。 数字を比べる前に、どのビルドで、何を実行した結果なのかをそろえる必要があります。

出荷するゲームの性能は、製品に近いビルドを実機で動かして確かめます。 重い処理の調査には診断機能を備えたビルドを使い、修正後は製品に近い条件へ戻して再計測します。 この記事では、製品に近い構成に必要最小限の計測処理を加えたものを計測用ビルドと呼びます。 3 つのエンジンに共通する正式なビルド構成名ではありません。

確かめたいことに合わせてビルドを選ぶ

エディタでのプロファイリングは、開発中に重いスクリプトやアセット処理、メモリ確保を調べるのに役立ちます。 Unity や Unreal Engine の Development ビルドなど、診断機能を備えた単体ビルドなら、エディタ本体の負荷を切り離し、実機上で詳しく調べられます。 ただし、そこで得た数値をそのまま製品ビルドの性能として扱うことはできません。

エディタで調査 診断用ビルドで実機解析 計測用ビルドで検証

結果にはビルド構成を添えます。構成が変わると、処理の負荷だけでなく動作自体も変わることがあります。

エディタでは、各ウィンドウの描画やアセット処理なども同じ端末のリソースを使います。 診断用ビルドには診断コードやプロファイリングの負荷が加わりますが、その大きさはエンジン、バックエンド、プラットフォーム、有効な設定によって異なります。 一律に差し引ける値はありません。 診断用ビルドで見つかった重い処理を調べ、その改善が製品向けのビルドでも効くかを確かめます。

コードを変える前に、実行条件をそろえる

ビルド設定だけでは、再現性のある比較にはなりません。 まず「セーブデータを読み込み、決めたカメラ経路を移動する」といったシナリオを 1 つ決め、結果と一緒に次の条件を残します。

  • ビルド ID とコミット、エンジンのバージョン、スクリプティングバックエンド、ビルド構成
  • 端末、GPU ドライバ、解像度、画質設定、VSync、フレームレート上限
  • シーン、開始時の状態、入力や移動経路、計測時間またはフレーム数
  • ウォームアップの条件とキャッシュの状態。特にシェーダーコンパイルや初回ロードの扱い
  • 有効にした計測処理、サンプリング間隔、欠損や破棄されたサンプル

変更前のビルドを複数回動かしてから、変更後と比べます。 変更前どうしのばらつきが期待する改善幅と同じくらいなら、先に計測条件を見直す必要があります。 再実行はばらつきを知る手掛かりになりますが、それだけで統計的な信頼性が確立するわけではありません。 スマートフォンやノート PC では、電源や温度の条件もそろえます。

初回ロードと、キャッシュが温まった後のプレイは別のテストにします。 定常状態を測るならシェーダーコンパイルを計測区間から外す意味があります。 初回起動のカクつきを調べたい場合に同じことをすると、調べるべき現象まで消えてしまいます。

エンジン別の計測用ビルド

Unreal Engine:対応するエンジンなら Test 構成を使う

Epic の説明では、Test は Shipping に一部のコンソールコマンド、統計、プロファイリング機能を残した構成です。 製品向けの最適化を使いつつ診断機能も必要な場合に候補になります。 Development でも多くの最適化は有効で、最適化されていない Debug と同じではありません。

先に、使っているエンジンが Test に対応しているか確認します。 Installed Build の既定の構成は Shipping、Development、DebugGame です。 Test には通常、ソースからビルドしたエンジンか、Test を含めたカスタム Installed Build が必要です。 コマンドで Test を指定するだけでは、不足するバイナリは補えません。

対応するエンジンで Windows 向けにパッケージ化する例です。 プロジェクトのパスを置き換え、インストール先の Automation Tool ランチャーから実行します。

RunUAT.bat BuildCookRun -project="C:/Path/MyGame.uproject" -platform=Win64 -clientconfig=Test -cook -stage -pak -package -build

できたパッケージで、必要な診断コマンドやカウンタを実際に使えるか確認します。 Test と Shipping では既定でログ出力がコンパイル時に除かれるため、UE_LOG が使えることを計測の前提にしないでください。 ログ設定の変更にはエンジンの再ビルドが必要になる場合があります。 計測コードはプロジェクト内の必要な箇所に絞ります。 Test を使えず Development で調査する場合も、最後はプロジェクトで利用できる計測手段を使い、Shipping で効果を確かめます。

Unity:製品とバックエンドとコンパイラ設定をそろえる

Unity 6 では専用のビルドプロファイルを作り、製品に近い条件で比較する際は Development Build をオフにします。 製品が IL2CPP なら IL2CPP、Mono なら Mono を使い、IL2CPP の Release / Master 設定も製品に合わせます。 コードの変更と同時にバックエンドまで変えると、差の理由を切り分けられません。

Unity Profilerで調べるための Development プロファイルは別に残します。 プロファイラー接続や Deep Profiling は調査用の設定として扱い、製品向けの比較に混ざらないようにします。 Unity 6 より前でも、BuildPipeline のスクリプトで同じように構成を分けられます。

計測用ビルドだけで診断用マーカーを出すには、プロファイル固有のスクリプティング定義シンボルを使えます。

using System.Diagnostics;

public static class Measure
{
    [Conditional("MEASUREMENT")]
    public static void Mark(string label)
    {
        UnityEngine.Debug.Log($"[measure] {label} @ {UnityEngine.Time.frameCount}");
    }
}

プロファイルの Scripting Defines に MEASUREMENT を追加し、シナリオの開始や終了などで Measure.Mark を呼びます。 これは区切りを記録するログで、フレームタイムの収集処理ではありません。 文字列の生成やログ出力にも負荷があるため、毎フレームの呼び出しは避けます。 [Conditional] はシンボルがないときに呼び出し側を除去しますが、メソッド本体をアセンブリから除くものではありません。

Godot:リリースエクスポートに計測用の機能タグを付ける

エクスポートプリセットを複製し、Export With Debug をオフにします。 レンダラー、プラットフォーム、エクスポートテンプレートは出荷予定の構成に合わせます。 計測用プリセットを作るためだけに、エンジンのカスタムテンプレートをビルドする必要はありません。

プリセットに measurement などのカスタム機能を追加します。

custom_features="measurement"

C# プロジェクトなら、シナリオの開始位置を次のように記録できます。

if (OS.HasFeature("measurement"))
{
    GD.Print($"[measure] scenario-start frame={Engine.GetFramesDrawn()}");
}

カスタム機能タグが有効になるのはエクスポート後で、通常のエディタ実行では有効になりません。 エクスポートした実行ファイルで動作を確認します。 Unity の例と同じく、このマーカーはシナリオの位置を示すだけなので、指標を収集する処理は別に必要です。 収集処理の負荷や設定も、比較する実行間でそろえます。

収集した粒度に合わせて数字を読む

60 FPS が目標なら 1 フレームの予算は約 16.67 ms、30 FPS なら約 33.33 ms です。 平均 FPS だけでなく、フレームタイムの分布を確認します。 P95 は観測値の 95% がその値以下に収まる境界です。 それよりまれな大きなカクつきは隠れるので、必要に応じて上位のパーセンタイル、予算を超えた回数、時系列も調べます。

何を集計した P95 なのかにも注意が必要です。 定期送信されたテレメトリの P95 は、収集されたサンプルの P95 です。 描画した全フレームの P95 と同じとは限りません。 レポートを比べる前に、サンプリング方法を確認します。

フレームタイム取得元と計測区間を残す。通常のフレームに加え、時間のかかったフレームを見る。
メモリカウンタ名と単位を明記する。ピークとロードとアンロードの繰り返しを見る。値が大きいだけではリークと断定できない。
ロード時間開始と完了の定義を固定する。初回とキャッシュが温まった後を分けて比べる。
ストレージ I/O読み込みや待ちをフレームの時系列と照合する。転送量だけではカクつきの原因は分からない。

場所に依存する問題なら、シーン、座標、カメラの向きも記録します。 セッション全体の平均より、「入口付近で、物の多い部屋にカメラを向けたとき」という情報のほうが再現に役立ちます。 ただし、その場所で原因となったオブジェクトや処理は別途調べる必要があります。

原因を調べ、元の条件で修正を確かめる

記録した条件を使って現象を再現し、Unity Profiler / Memory Profiler、Unreal Insights、Godot のプロファイラーで調べます。 Godot の内蔵スクリプトプロファイラーは GDScript 用なので、C# の処理を詳しく追うには対応する .NET プロファイラーを使います。

負荷の原因と考えられる箇所を変更したら、計測用ビルドで元のシナリオを再実行します。 複数回でも改善が続くか、メモリやロード時間、別のフレーム処理が悪化していないかまで確かめます。 診断用のトレースが速くなった段階では、まだ検証の途中です。

計測結果は、演出を増やす判断にも使えます。 フレーム予算に余裕があれば、エフェクトや表示物を増やす候補になります。 ただし描画スレッドに余裕があっても、GPU やメモリまで空いているとは限りません。 追加した状態を、同じ実機条件で測って判断します。

Framedash で比較の条件と結果を残す

Framedash のダッシュボードのヒートマップでは、収集した性能サンプルをマップ上で確認し、ビルド、プラットフォーム、記録された属性で絞り込めます。 SDK の設定や収集できる指標はエンジンごとに異なります。 Unity、UE5、Godot C# の手順を確認し、対象ビルドから実際に何が送られるかを確かめてからグラフを使います。

Unity なら、PC 上で性能を比較するパイロットから始められます。 自分の PC でベースライン、変更なしの再実行、候補の 3 つを収集し、条件とフレーム間隔の集計を確認する手順です。 マップは不要です。 計測対象は SDK の Update コールバック間の時間で、GPU の完了時刻や画面への表示時刻ではありません。 このパイロットは比較可能な証拠がそろったかを示すもので、性能回帰の合否を自動判定するものではありません。

まずは再現できるシナリオを 1 つ、効果を確かめたい変更を 1 つ選びます。 変更前の結果と実行条件を変更後の結果に添えて残せば、次回も同じ問いで比較できます。