スポンサーリンク
最適化・パフォーマンス

【Unity GC最適化】ガベージコレクションを減らして処理落ち・カクつきを防ぐ実践テクニック大全

最適化・パフォーマンス

Unityでゲームを動かしていると、普段は快適なのに「一瞬だけ止まる」「敵が増えた瞬間にカクつく」といった現象に悩まされることがあります。このような症状は、ガベージコレクション(GC)が関係しているケースも少なくありません。

ただし、「GCを減らせばいい」と聞いても、何から手を付ければよいのか迷ってしまいますよね。やみくもにコードを書き換えても、思ったほど改善しないこともあります。

この記事では、Unity 6対応の内容をもとに、まずGCが本当に原因なのかを確認する方法から、優先して改善すべきコードの書き方、設定でできる対策まで、実際に判断しながら進められる手順をわかりやすく解説します。


UnityのカクつきがGCか判断する

ゲームがカクつく原因は1つではありません。まずは「本当にガベージコレクション(GC)が原因なのか」を確認することが大切です。

もし原因が描画負荷や物理演算なのにGC対策だけを進めてしまうと、時間をかけても期待した効果は得られません。最初に原因を切り分けることで、最短で改善につながります。

ゲームプレイ中に次のような症状がある場合は、GCが影響している可能性があります。

  • 一定間隔で一瞬だけ画面が止まる
  • 敵や弾を大量に生成したタイミングでカクつく
  • スコアやUIを更新した瞬間に引っかかる
  • Unity Profilerで「GC Alloc」や「GC.Collect」が表示される

反対に、次のような症状であれば、GC以外の原因を疑ったほうが改善につながることが多いです。

  • 常にFPSが低い
  • カメラを動かすと常に重い
  • ライトやポストエフェクトを有効にすると重くなる
  • 物理演算のオブジェクトが増えるほど処理が遅くなる

判断に迷った場合は、「一瞬だけ止まる」のか、「常に重い」のかを意識してみましょう。

一瞬だけ止まる場合はGCによる停止時間(GCスパイク)の可能性があります。一方で、常にフレームレートが低い場合は、CPUやGPUの処理負荷が原因であるケースが多く、GCを最適化しても改善しないことがあります。

より広い視点でUnityのパフォーマンス改善方法を知りたい場合は、こちらの記事も参考になります。

GCが原因の可能性が高そうであれば、次はUnity Profilerを使って、どの処理でGC Allocが発生しているのかを確認していきましょう。




ProfilerでGC Allocを見つける

GC対策で最初にやることは、Unity Profilerで「どの処理がメモリ割り当てを発生させているか」を見ることです。感覚だけで直そうとすると、効果の小さい場所に時間を使ってしまうことがあります。

まずはUnityエディタ上部のメニューから、次の順番でProfilerを開きます。

  • Window を選ぶ
  • Analysis を選ぶ
  • Profiler を開く

Profilerを開いたら、CPU Usageを確認します。ゲームを再生しながら、カクつきが起きたタイミングでGC AllocGC.Collectが出ていないか見てみましょう。

特に確認したいのは、次のような処理です。

  • UpdateLateUpdateFixedUpdate の中
  • 弾や敵を生成する処理
  • スコアやHPバーなどのUI更新処理
  • 毎秒何度も呼ばれる判定処理

次に、Profilerの表示をHierarchyに切り替えます。そこでGC Allocの列をクリックすると、メモリ割り当てが多い処理を見つけやすくなります。

「どの関数が怪しいか」はHierarchyで見つけられますが、「コードのどの行か」まで知りたい場合は、Call Stacksを有効にして確認します。環境によって表示内容や負荷は変わるため、普段から常にオンにするというより、原因を追うときに使うくらいが扱いやすいです。

見る場所分かること
CPU UsageGC AllocやGC.Collectが発生しているか
Hierarchyどの関数で割り当てが多いか
Call Stacksどのコード行が原因か

判断基準はシンプルです。ゲームプレイ中に何度も呼ばれる処理でGC Allocが出ているなら、そこを優先して直します。反対に、シーン開始時やロード中だけ発生しているGC Allocは、すぐに問題にならないこともあります。

原因の場所が分かったら、次は実際にコードを書き換えて、毎フレーム発生するGC Allocを減らしていきましょう。




毎フレームのGC Allocを減らす

GC Allocを減らすときは、まず毎フレーム呼ばれる処理から見直します。特にUpdateの中で新しいオブジェクトを作っていると、少しずつメモリ割り当てが増えて、カクつきの原因になりやすいです。

たとえば、次のように毎回newしている処理は注意したいポイントです。

void Update()
{
    List<GameObject> enemies = new List<GameObject>();
}

このような場合は、Listをフィールドとして用意しておき、必要なタイミングで中身だけを消して使い回します。

private List<GameObject> enemies = new List<GameObject>();

void Update()
{
    enemies.Clear();

    // enemiesを使った処理
}

Clear()はListの中身を空にしますが、Listそのものを毎回作り直すわけではありません。そのため、毎フレームの不要な割り当てを減らしやすくなります。

UIのスコア表示などで文字列を何度も連結している場合も、GC Allocが出ることがあります。

scoreText.text = "Score: " + score;

スコアが変わった時だけ更新する、毎フレーム更新しない、必要に応じてStringBuilderを使う、といった形にすると負荷を抑えやすいです。

また、Update内でLINQやラムダ式を多用している場合も注意しましょう。書き方によっては一時的なオブジェクトが作られ、GC Allocにつながることがあります。

var target = enemies.Where(e => e.activeSelf).FirstOrDefault();

このような処理を毎フレーム行うなら、通常のfor文に置き換えたほうが確認しやすく、不要な割り当ても避けやすくなります。

GameObject target = null;

for (int i = 0; i < enemies.Count; i++)
{
    if (enemies[i].activeSelf)
    {
        target = enemies[i];
        break;
    }
}

LINQやラムダ式そのものが悪いわけではありません。初期化処理やエディタ用ツールなど、実行回数が少ない場所では便利に使えます。ただ、ゲームプレイ中に何度も呼ばれる処理では、ProfilerでGC Allocを確認しながら使うのがおすすめです。

ラムダ式やLINQの使いどころをもう少し整理したい場合は、こちらの記事も参考になります。

判断基準は、「ロード中だけ」か「ゲームプレイ中に何度も」かです。ゲームプレイ中に毎フレームGC Allocが出ている処理は優先して直し、ロード中や初期化時だけの割り当ては後回しにしても大丈夫なことが多いです。




生成と破棄はObjectPoolにする

弾や敵、エフェクトのように何度も出したり消したりするPrefabは、InstantiateDestroyを繰り返すほど負荷が増えやすくなります。こうした処理がゲーム中に何度も起きるなら、ObjectPoolを使う候補です。

ObjectPoolは、オブジェクトを毎回新しく作るのではなく、あらかじめ用意しておいたものを使い回す考え方です。使い終わったら消すのではなく、非表示にして待機させ、必要になったらまた表示します。

ObjectPool向きObjectPool不要になりやすい
シーン開始時だけ作る背景
爆発エフェクト一度だけ表示する説明UI
敵の大量スポーンたまに生成する小物
ダメージ表示管理が複雑になるだけの単発オブジェクト

判断基準は、1秒間に何度も生成・破棄するかです。たとえばシューティングゲームの弾、敵が倒れたときのエフェクト、連続で表示されるダメージ数値などは、ObjectPoolにすると効果を感じやすい場面です。

逆に、1回しか出ないオブジェクトまでObjectPoolにすると、管理するコードが増えてかえって分かりにくくなることがあります。最適化は「重い場所だけに使う」くらいで大丈夫です。

簡単な流れは次のようになります。

  1. ゲーム開始時にPrefabをいくつか生成して非表示にしておく
  2. 必要になったら、非表示のオブジェクトを探して表示する
  3. 使い終わったらDestroyせず、また非表示にする

ObjectPoolの実装方法を詳しく確認したい場合は、こちらの記事で具体的な作り方を解説しています。

まずは、ProfilerでGC Allocが出ていた生成処理だけをObjectPool化してみましょう。全部を一気に直そうとせず、弾やエフェクトなど効果が出やすいところから始めるのが安心です。




Incremental GCでカクつきを緩和する

コードを見直しても、すぐにすべてのGC Allocを減らせないことがあります。そんなときに検討したいのが、UnityのIncremental GCです。

Incremental GCは、ガベージコレクションの処理を一度にまとめて行うのではなく、複数フレームに分けて少しずつ進める仕組みです。これにより、GCによる一瞬の大きな停止を緩和できる場合があります。

設定は、UnityのPlayer Settingsから確認できます。

  • Edit を選ぶ
  • Project Settings を開く
  • Player を選ぶ
  • Other Settings 内の Use Incremental GC を確認する

ただし、ここで大切なのは、Incremental GCを有効にしてもGC Allocそのものが減るわけではないという点です。あくまでGCの停止時間を分散する設定なので、毎フレーム無駄な割り当てが出ている場合は、コード側の改善も必要になります。

状況優先する対策
GC Allocが毎フレーム出ているコード修正を優先
弾や敵の生成でGC Allocが多いObjectPoolを検討
GC.Collect時の停止が目立つIncremental GCで緩和を検討
常にFPSが低い描画・物理・Update処理も確認

判断基準としては、ProfilerでGCの停止が目立つけれど、すぐにコード全体を直せない場合に、Incremental GCを補助的に使うイメージです。

「設定をオンにしたから終わり」ではなく、Profilerで前後を見比べて、カクつきが実際に減っているか確認しましょう。体感だけで判断すると、別の原因を見落とすことがあります。

ここまで確認できたら、最後にこの記事で取るべき行動を整理しておきます。


まとめ|次にやることは1つ

UnityのGC最適化で大切なのは、最初から全部を直そうとしないことです。まずはProfilerを開いて、ゲームプレイ中にGC Allocが出ている処理を1つ見つけましょう。

毎フレームのnew、Listの作り直し、文字列更新、頻繁なInstantiateDestroyが見つかったら、そこから順番に改善していけば大丈夫です。

コード修正でGC Allocを減らし、生成と破棄が多いPrefabはObjectPool化する。さらに必要ならIncremental GCでカクつきを緩和する。この順番で進めると、迷わず対策しやすくなります。


※当サイトはアフィリエイト広告を利用しています。リンクを経由して商品を購入された場合、当サイトに報酬が発生することがあります。

※本記事に記載しているAmazon商品情報(価格、在庫状況、割引、配送条件など)は、執筆時点のAmazon.co.jp上の情報に基づいています。
最新の価格・在庫・配送条件などの詳細は、Amazonの商品ページをご確認ください。

スポンサーリンク