Unityでゲームを開発している際、キャラクターの索敵範囲やAIの巡回ルート、物理的な当たり判定などを視覚的に確認するためにGizmos.DrawWireSphereGizmos.DrawLineといったギズモ描画機能は欠かせません。しかし、「Sceneビューでは綺麗にギズモのラインが見えているのに、ゲームを再生してGameビューで見ると何も表示されない」「PCやスマートフォン向けにビルドした実機で、デバッグ用の範囲線が一切映らない」というトラブルに遭遇したことはないでしょうか。これはバグではなく、Unityの描画システムの厳密な仕様によるものです。本記事では、この現象が発生する内部的な仕組みと、初心者向けの分かりやすい例え話、そしてGameビューやビルド後の実機でも正しくデバッグ用のラインを描画するための具体的な実装手法をステップバイステップで徹底解説します。

1. 例え話で理解する:『アトリエの補助線』と『劇場の観客席』

このSceneビューとGameビュー/実機における描画のギャップを理解するために、「絵画のアトリエ(アトリエ)」と「一般公開される美術館(展示室)」を例えに考えてみましょう。

あなたが絵の描き方を教える講師、あるいは制作者だとします。絵の具でキャンバスに下絵を描くとき、キャンバスの周りに「黄金比を示す木枠のガイド」を取り付けたり、パース(遠近法)を狂わせないために「赤い水糸」をアトリエ内に何本も張り巡らせて作業(デバッグ)します。この作業用ガイドや糸は、絵を正確に仕上げるために不可欠なものです。これが、Unityでいう「SceneビューにおけるGizmos(ギズモ)」です。

しかし、完成した絵を展示室で一般のお客さん(プレイヤー)に見せる際、アトリエで使っていた木枠やパース用の赤い糸をそのまま展示室に持ち込むでしょうか。当然、見栄えが悪くなるため、これらはすべて片付けられます。展示室では純粋な「完成された絵画」だけが飾られます。これが「Gameビューおよびビルドされた実機」の状態です。

Unityエディタは、プレイヤーがゲームを遊ぶ画面(Gameビュー)や、最終成果物(実機ビルド)に対して、開発者用の作業用ガイドであるGizmosを「勝手に親切心で片付けてくれている」のです。ですから、ゲーム画面や実機でガイドを見せたい場合は、「片付けられない特殊な線(ゲーム内のオブジェクトとしての描画)」を意図的に描く必要があります。

2. 根本的な原因:Gizmosクラスの動作スコープ

技術的に言うと、GizmosクラスおよびHandlesクラスはUnityエディタ専用のアセンブリ(UnityEditor)に密接に紐付いた機能です。これらが描画されるのは、エディタが内部的に管理しているSceneビュー(およびGameビューのエディタ限定デバッグ表示)のみです。Unityのビルドプロセスにおいて、OnDrawGizmos()OnDrawGizmosSelected()といったライフサイクルイベントは、コンパイル時にバイナリからストリップ(削除)されます。これにより、ゲーム実機での実行速度を向上させ、不要な描画負荷を削減しています。そのため、製品コードにGizmosの処理が残っていても、実機では完全に無視され、何も表示されない状態になります。

3. 実務で役立つ4つの解決アプローチ

デバッグの文脈や実行環境(エディタ内か実機ビルドか)に応じて、最適な解決方法を選択しましょう。

アプローチ 表示可能な環境 メリットと推奨されるユースケース
A. GameビューのGizmosトグル エディタ内のGameビューのみ コードの変更不要。エディタ内でゲームをプレイしながら手軽に索敵範囲などを確認したい場合。
B. Debug.DrawLine の活用 エディタ内のScene/Game両方 1フレーム限りのラインを手軽に描画可能。レイキャスト(光線)の衝突確認に最適。
C. GLクラスによるダイレクト描画 エディタ内 + 実機ビルドすべて 実機でも動作。コンポーネントを汚さず、高速なスクリプト記述で大量のデバッグ線を描画可能。
D. LineRenderer の使用 エディタ内 + 実機ビルドすべて 物理的なGameObjectとして存在するため、一番安全で標準的。デザインの調整も容易。

解決アプローチA:Gameビューの「Gizmos」ボタンをONにする(エディタ内限定)

エディタ内で再生モード中にGameビューでギズモを確認したいだけであれば、最も簡単な方法です。Gameビューウィンドウの右上にある「Gizmos」と書かれたトグルボタンをクリックしてONにします。これにより、Sceneビューと同じギズモ線がGameビューにもオーバーレイ表示されます。ただし、これはエディタのデバッグ機能であるため、実機ビルドには絶対に反映されないことに留意してください。

解決アプローチB:Debug.DrawLine / Debug.DrawRay を使用する

Debug.DrawLineDebug.DrawRay を用いると、スクリプトから任意の座標間にラインを描画できます。これらはエディタ内の再生時、Gameビューにも自動的に描画されます(Gameビューの「Gizmos」ボタンがONである必要があります)。

using UnityEngine;

public class RaycastDebugger : MonoBehaviour
{
    void Update()
    {
        Vector3 forward = transform.TransformDirection(Vector3.forward) * 10f;
        
        // 第1引数:開始位置、第2引数:終了位置、第3引数:色、第4引数:表示持続時間(秒)
        Debug.DrawLine(transform.position, transform.position + forward, Color.red);
    }
}

解決アプローチC:GLクラスによる実機対応の低レベル描画(推奨)

実機ビルドでも、コンポーネントを追加せずにプログラマブルにデバッグ用の線やワイヤーフレームを描画したい場合は、グラフィックスデバイスに直接描画命令を送信するGLクラスを使用するのが最も効果的です。カメラの描画直後に呼び出すライフサイクルメソッド OnPostRender または OnRenderObject 内で記述します。

using UnityEngine;

public class RuntimeLineDrawer : MonoBehaviour
{
    public Color lineColor = Color.green;
    private Material lineMaterial;

    void CreateLineMaterial()
    {
        if (lineMaterial == null)
        {
            // 最もシンプルなシェーダー(頂点カラーを描画するもの)を動的生成
            Shader shader = Shader.Find("Hidden/Internal-Colored");
            lineMaterial = new Material(shader);
            lineMaterial.hideFlags = HideFlags.HideAndDontSave;
            
            // レンダリングステートの設定(深度テストの設定など)
            lineMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
            lineMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
            lineMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
            lineMaterial.SetInt("_ZWrite", 0); // 深度の書き込みをオフに
        }
    }

    // すべてのオブジェクトのレンダリングが完了した後に呼ばれる
    void OnRenderObject()
    {
        CreateLineMaterial();
        lineMaterial.SetPass(0);

        GL.PushMatrix();
        // ワールド座標系で直接描画するための行列設定
        GL.MultMatrix(transform.localToWorldMatrix);

        GL.Begin(GL.LINES); // 線を描画するモードを開始
        GL.Color(lineColor);

        // 原点から前方(Z+)へ10メートルの線を描く
        GL.Vertex3(0, 0, 0);
        GL.Vertex3(0, 0, 10f);

        GL.End();
        GL.PopMatrix();
    }

    void OnDestroy()
    {
        if (lineMaterial != null)
        {
            DestroyImmediate(lineMaterial);
        }
    }
}
注意:URP環境でのGLクラスについて
ユニバーサルレンダーパイプライン(URP)では、OnRenderObjectの実行タイミングが標準パイプラインと異なるため、正しく描画されない場合があります。URP環境下で実機デバッグラインを安全に実装するには、後述の LineRenderer を使用するか、カスタム Render Feature を作成して描画パスを統合するアプローチが確実です。

解決アプローチD:LineRenderer コンポーネントを使用する(最も確実)

実機ビルドにおいて、描画エンジン(URP/HDRP)やプラットフォームを問わず最も堅牢に動作するのが、Unityの標準コンポーネントであるLineRendererを使用する方法です。プログラムから頂点の数と座標を動的に更新することで、滑らかな曲線や任意の軌跡を描画できます。

using UnityEngine;

[RequireComponent(typeof(LineRenderer))]
public class CircleRangeVisualizer : MonoBehaviour
{
    public float radius = 5.0f;       // 円の半径
    public int segments = 50;         // 円を構成する線分の分割数
    private LineRenderer lineRenderer;

    void Start()
    {\
        lineRenderer = GetComponent<LineRenderer>();
        lineRenderer.useWorldSpace = false; // ローカル座標系で描画
        DrawCircle();
    }

    void DrawCircle()
    {
        lineRenderer.positionCount = segments + 1;
        float angle = 0f;

        for (int i = 0; i <= segments; i++)
        {
            float x = Mathf.Sin(Mathf.Deg2Rad * angle) * radius;
            float z = Mathf.Cos(Mathf.Deg2Rad * angle) * radius;

            lineRenderer.SetPosition(i, new Vector3(x, 0, z));
            angle += (360f / segments);
        }
    }

    // インスペクター上で半径が変更された際に動的にプレビューを更新
    void OnValidate()
    {
        if (lineRenderer == null) lineRenderer = GetComponent<LineRenderer>();
        DrawCircle();
    }
}

4. 実務で役立つデバッグ用オブジェクト管理チェックリスト

開発が進むにつれて、デバッグ用のコードやコンポーネントが残ったまま製品をリリースしてしまう事故が多発します。以下の対策をワークフローに組み込んでください。

  • Conditional属性の付与: デバッグ用の描画処理を行うメソッドには、[System.Diagnostics.Conditional("UNITY_EDITOR")] 属性を付与することで、製品ビルド時にそのメソッドの呼び出しコード自体をコンパイル結果から自動削除できます。
  • シリアル化コードの分離: インスペクター拡張用のクラスは必ずEditorフォルダ内に配置し、アセンブリを分離します。
  • Debugコンポーネントのクリーンアップスクリプト: ビルド前処理(IPreprocessBuildWithReportを継承したクラス)を作成し、シーン内の「Debug」タグが付いた可視化用ゲームオブジェクトを自動的に破棄するシステムを導入します。