UnityのuGUI(Canvasシステム)は、直感的で柔軟なUI構築を可能にする強力なツールです。しかし、ゲームの規模が大きくなり、メニュー画面、インベントリ、ショップ、HUDなどのUI要素が増加するにつれて、謎のパフォーマンス低下に悩まされるケースが非常に多く発生します。

特に多くの開発者を困らせるのが、「UIを一切操作していない(クリックもしていない)状態なのに、CPUの処理負荷(EventSystem.Update / GraphicRaycaster.Raycast)が跳ね上がる」という現象です。Profilerでプロファイリングを行うと、この2つのメソッドがCPU負荷の最上位に居座っていることがあります。

本記事では、この入力レイキャスト負荷が急増する根本原因を解説し、「Raycast Targetプロパティの一括無効化」および「Canvasの適切な分割」を用いて、UIのCPU負荷を劇的に削減する具体的な実装手順とベストプラクティスを詳しく解説します。

1. 根本原因:毎フレーム実行される「全走査型」レイキャストの仕様と罠

uGUIがプレイヤーの入力を検知する仕組み(イベントシステム)は、私たちが想像するよりも遥かに泥臭い処理を行っています。

画面へのマウスホバーやタップが発生すると、EventSystemはシーン内の有効なGraphicRaycasterコンポーネントに対し、「現在どのUI要素の上にポインタがあるか」の判定を要求します。これを受けたGraphicRaycasterは、以下のステップで衝突判定を行います:

GraphicRaycasterがRaycast Targetを毎フレーム走査して衝突判定を行う仕組み

図:何百もの不要なRaycast Targetを毎フレーム総当たりで走査する非効率なレイキャスト処理

  1. Canvas内の全UIグラフィック要素のリストアップ: CanvasアタッチされたGameObjectの配下にある、すべての有効なUIグラフィック要素(Image, RawImage, Text, TextMeshProUGUI など)をリストアップします。
  2. 「Raycast Target」が有効な要素のフィルタリング: リストアップされた要素のうち、Raycast Targetプロパティが有効(true)になっているものだけを抽出します。
  3. ポインタ座標との衝突判定(総当たり): 抽出されたすべてのUIグラフィック要素に対して、現在のポインタ座標がその矩形(RectTransform)の内側にあるかどうかの交差判定をループ処理します。

この仕様における致命的な罠は、「Unityで新規にImageやTextを作成すると、デフォルトで『Raycast Target』がオン(有効)になる」という点です。

背景の飾り画像、枠線、各種ラベルテキストなど、本来プレイヤーからのクリックやホバーを検知する必要が一切ない静的なビジュアル要素であっても、初期状態のままだとすべて毎フレームのレイキャスト判定処理の対象になってしまいます。UI要素数が数百〜数千に及ぶと、たとえポインタが静止していても、この総当たり判定ループによって恐ろしいCPUスパイクが発生します。

2. 負荷の特定方法:Unity Profilerを用いたボトルの見極め

この問題が発生しているかどうかは、Unity Profilerで簡単に見分けることができます。

  1. ゲームを起動し、Profiler(Window > Analysis > Profiler)を開きます。
  2. CPU Usageモジュールを選択し、UIが密集している画面を開きます(ポインタを激しく動かすとより顕著になります)。
  3. Timelineビューから階層(Hierarchy)ビューに切り替え、ソートを「Total %」にしてボトルネックを探します。
  4. EventSystem.Update という項目を展開し、その配下にある GraphicRaycaster.Raycast のミリ秒(ms)単位の処理時間を確認します。

もしこの GraphicRaycaster.Raycast0.5ms〜2ms以上 を消費している場合、明らかに不要なレイキャスト処理が大量に走っています。一見小さな処理時間に見えますが、モバイル環境やVR環境、あるいはフレームレートを60fps/120fpsに保ちたいゲームにおいては、これだけで致命的なフレーム落ちを引き起こします。

3. 解決策①:不要な Raycast Target の一括無効化

最も基本的かつ効果的な対策は、「ボタンやスライダー、スクロールビューのドラッグ領域など、クリック判定が必要な場所以外の Raycast Target をすべてオフにする」ことです。

しかし、作成済みの無数のUI要素をインスペクターから手動で一つずつチェックしてオフにするのは極めて非効率で、設定漏れも発生します。
そこで、現在アクティブなシーンや選択中のプレハブ内から、不要なRaycast Targetを検知して一括でオフにできる便利なエディタ拡張スクリプトを導入しましょう。

レイキャストターゲット一括無効化エディタツールスクリプト

以下のスクリプトをプロジェクトの Assets/Editor/ フォルダに保存してください。

using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using TMPro;

public class RaycastTargetOptimizer : EditorWindow
{
    [MenuItem("Tools/UI/Optimize Raycast Targets")]
    public static void ShowWindow()
    {        GetWindow<RaycastTargetOptimizer>("UI Optimizer");
    }

    private void OnGUI()
    {
        GUILayout.Label("uGUI Raycast Target Optimizer", EditorStyles.boldLabel);
        GUILayout.Space(10);

        if (GUILayout.Button("Disable Unnecessary Raycast Targets in Selection"))
        {            OptimizeSelectedUI();
        }
    }

    private static void OptimizeSelectedUI()
    {
        int modifiedCount = 0;
        GameObject[] selectedObjects = Selection.gameObjects;

        if (selectedObjects.Length == 0)
        {            Debug.LogWarning("No GameObject selected. Please select UI roots in the Hierarchy.");
            return;
        }

        foreach (var root in selectedObjects)
        {
            // Imageコンポーネントの処理
            Image[] images = root.GetComponentsInChildren<Image>(true);
            foreach (var img in images)
            {
                // ボタンやスクロールビューなどの入力コンポーネントが同じオブジェクト、または親にないか簡易チェック
                if (img.raycastTarget && !IsInteractiveElement(img.gameObject))
                {                    Undo.RecordObject(img, "Disable Raycast Target");
                    img.raycastTarget = false;
                    modifiedCount++;
                }
            }

            // TextMeshProUGUIコンポーネントの処理
            TextMeshProUGUI[] tmps = root.GetComponentsInChildren<TextMeshProUGUI>(true);
            foreach (var tmp in tmps)
            {
                if (tmp.raycastTarget && !IsInteractiveElement(tmp.gameObject))
                {                    Undo.RecordObject(tmp, "Disable Raycast Target");
                    tmp.raycastTarget = false;
                    modifiedCount++;
                }
            }
        }

        Debug.Log($"Optimization Completed! Disabled Raycast Target on {modifiedCount} components.");
    }

    private static bool IsInteractiveElement(GameObject go)
    {
        // クリックやドラッグイベントを検知する各種コンポーネントがアタッチされているか判定
        if (go.GetComponent<Button>() != null ||
            go.GetComponent<Toggle>() != null ||
            go.GetComponent<Slider>() != null ||
            go.GetComponent<ScrollRect>() != null ||
            go.GetComponent<InputField>() != null ||
            go.GetComponent<TMP_InputField>() != null ||
            go.GetComponent<UnityEngine.EventSystems.EventTrigger>() != null)
        {            return true;
        }
        return false;
    }
}

使い方: ヒエラルキーで最適化したいUIルートオブジェクト(Canvasやメニューの親オブジェクト)を選択し、メニューの Tools > UI > Optimize Raycast Targets を開いてボタンを押すだけで、インタラクティブでない要素の Raycast Target が自動的に一括オフになります(Undoにも対応しています)。

4. 解決策②:Canvas の適切な分割による「走査ノイズ」のカット

次に有効な設計パターンが、「Canvas(キャンバス)の分割」です。

GraphicRaycaster はアタッチされているCanvasごとに機能します。1つの巨大なCanvasの中に、頻繁に位置や色が変わる動的なUI(プレイヤーのHPバー、ダメージ数値、アニメーションするアイコンなど)と、背景や枠線などの静的なUIが混在していると、以下の2つの問題が発生します:

  1. Canvas Rebuildの連鎖負荷: 1つの要素が動くだけで、同じCanvas内のすべてのUI要素のメッシュ再構築(Canvas Rebuild)が走り、グラフィックス負荷(CPU/GPU)が増大します。
  2. レイキャストの無駄な走査: インタラクションが必要ないエリアをポインタが通るたびに、Canvas内の全要素が走査されます。

ベストプラクティス:マルチCanvas設計

UIを以下のように機能ごとに Sub-Canvas(子キャンバス) または独立したCanvasとして分割することを推奨します。

  • Static Canvas(背景・装飾): 背景画像、枠線、固定テキストなど。GraphicRaycaster は不要なのでコンポーネントごと削除します。
  • Dynamic Canvas(動的表示・クリック不可): ダメージテキスト、HPバー、エフェクトなど。GraphicRaycaster は不要なのでコンポーネントごと削除します。
  • Interactive Canvas(ボタン・入力要素): ボタン、スクロールビュー、スライダーなど。GraphicRaycaster をアタッチしたままにし、クリック判定が必要な最小限の要素のみを格納します。
[Root Canvas] (ポインタ検知不要)
  ├── [Static Sub-Canvas] (背景・装飾用 / *GraphicRaycaster削除*)
  │     └── Image (Raycast Target: Off)
  │
  ├── [Dynamic Sub-Canvas] (演出・動的HUD用 / *GraphicRaycaster削除*)
  │     └── HP Bar, Damage Popups (Raycast Target: Off)
  │
  └── [Interactive Sub-Canvas] (操作用 / *GraphicRaycasterをアタッチ*)
        └── Button, ScrollRect (最小限のRaycast TargetのみOn)
  

図:GraphicRaycasterの役割に応じたCanvas分割とコンポーネント構成

このように分割することで、レイキャスト処理は Interactive Canvas 配下のわずかな要素だけを走査すればよくなるため、走査範囲が劇的に縮小し、GraphicRaycaster.Raycast の処理コストをほぼゼロに近づけることができます。

5. 解決策③:EventSystemの不要なモジュール調整

EventSystem自体の設定も見直す余地があります。デフォルトで作成される EventSystem オブジェクトには、PC用の入力判定(StandaloneInputModule 等)と合わせて、3D空間へのレイキャスト(PhysicsRaycasterPhysics2DRaycaster)が動作している場合があります。

もし3D空間のオブジェクト(3Dのキャラクターや箱など)をUIのポインタシステムでクリックする必要がない場合、カメラに Physics Raycaster がアタッチされていないか確認してください。これがアタッチされていると、UIの裏側にある3Dコライダーに対しても毎フレーム物理レイキャストが走り、物理演算側のボトルネックを引き起こします。

6. まとめ:uGUIパフォーマンス最適化チェックリスト

UIのCPU負荷を削減し、ゲームを滑らかに動作させるためのチェックリストです。プロジェクトのリリース前に必ず確認しましょう。

最適化項目 推奨設定・対策 得られる効果
静的グラフィックス 背景や枠線、装飾テキストの Raycast Target をすべてオフにする。 GraphicRaycaster.Raycast ループ内の走査数を削減し、CPU負荷を直接低減。
Canvasの分割 動的要素と静的要素でCanvasを分割し、不要な親から GraphicRaycaster を削除する。 不要なCanvas Rebuildを防止すると同時に、レイキャストの検索ノイズを排除。
不要なRaycasterの排除 3Dクリックが不要ならカメラから PhysicsRaycaster を完全に削除。 物理演算バッファ走査の完全スキップによるCPU時間節約。
Pixel Perfect スクロールビューやアニメーションする動的Canvasでの Pixel Perfect をオフにする。 毎フレームのピクセルアライメント計算によるCPU負荷を低減。

UIのレイキャスト最適化は、特にオブジェクト数が増えやすい大型ゲームや、リソースが制限されているモバイル/VRプラットフォームにおいて、非常に効果の高いチューニングポイントです。デフォルトの「Raycast Target: On」の罠を意識し、エディタツールやCanvas分割を活用して、快適なUI体験を実現しましょう。