記事の目次

    今回は、コンテンツ開発の過程では欠かせないデバッグと、設定ファイルの記述法について紹介します。

    TEXT_高田稔則 / Toshinori Takata(Codelight
    EDIT_小村仁美 / Hitomi Komura(CGWORLD)

    こんにちは、高田です。今回は主にデバッグやコンテンツの設定の話をします。プログラムの制作過程においては、期待通りに動かない場合が必ず出てきます。プログラムのミス、インスペクタでの値の設定ミス、勘違いなどなどその理由は様々ですが、それをできるだけ早く見つけ、修正することが重要です。

    今回は、下記の4つのトピックに分けて解説していきます。
    1.printfデバッグ:Debug.Log()
    2.デバッガーを使うデバッグ
    3.Debugを使った視覚的なデバッグ:Debug.DrawRay()
    4.外部設定ファイル

    <1>printfデバッグ:Debug.Log()

    デバッグの目的で最も多いのは、あるタイミングで変数がどんな値になっているかを確認したい場合だと思います。簡単なのはDebug.Log()を使う方法で、スクリプト内の値を確認したい場所に以下のように書きます。

    Debug.Log(message); //messageは出力したい文字列
    

    この方法は古くから使われていて、「printfデバッグ」とも呼ばれます。名前の由来はC言語で値を表示するときに次のように書くことからです。ほぼどんなときにも使える万能な方法です。

    printf("%s\n", message);
    

    以下のようにスクリプトに書くと、Updateが呼び出されるたびにそのときのフレーム番号がEditorのConsoleに表示されます。

    void Update()
    {
        Debug.Log(Time.frameCount); 
    }
    

    このスクリプトを、以下のようにオプション指定なしでビルドして実行してみます。

    Unityは、実行するとDebug.Log()などの出力をテキストファイルにログとして書き出してくれます。適当に実行を止めて確認してみましょう。

    デフォルトでは、Unityのログは下記のディレクトリに出力されます。
    C:¥Users¥[ユーザ名]¥AppData¥LocalLow¥[Company Name]¥[Product Name]

    AppDataフォルダはデフォルトではエクスプローラに表示されないため、エクスプローラの[表示オプション→隠しファイル]のチェックを入れてください。同時に[ファイル名拡張子]のチェックも入れておくことをオススメします。

    [Company Name]と[Product Name]はUnityエディタのProject Settingsで指定したパラメータです。


    Unity 2019では、「Player.log」という名前でログが保存されます。一世代前のログは「Player-prev.log」として残ります。Unity 2018の場合は「output_log.txt」という名前で保存されます。Player.logの内容は、次のようになっていると思います。


    "1", "2", "3"と表示されている部分が先ほど書いたUpdate()のUnity.Log()で表示された部分です。

    次に、ビルドオプションに「Development Build」を指定して実行してみます。

    Prev.logの内容は以下のように変化します。


    ビルドオプションなしのログと比べてみると、"1"を表示したDebug.log()が書かれているファイル名と行番号も表示してくれるようになっています。DebugSample.csの13行目で"1"を表示したことがわかります。

    次に、意図的にエラーを起こしてみます。Development Buildを指定して実行すると、エラーが発生した場合、Development Consoleにエラーメッセージが表示されるようになります。このメッセージは前述したPlayer.logに残るので、ランニングテスト中はDevelopment Buildを指定しておくと良いでしょう。


    必要な部分にDebug.Log()を書くだけでかなりの情報を収集することができるのですが、この方法ではDebug.Log()を追加するごとにビルドをし直す必要があります。ここでデバッガーを使うと、もう少し手軽に値を確認することができます。

    <2>デバッガーを使うデバッグ

    Unityでは、インストール時にVisual Studio用のデバッグプラグインがインストールされるため、Unityエディタからスクリプトをダブルクリックして開くだけで、Visual Studioの強力なデバッグ機能を利用できるようになっています。Visual Studioの「Unityにアタッチ」を押すとUnityエディタに接続され、デバッガーが利用可能になります。


    スクリプトの行番号の左側をクリックすると赤い丸が表示されます。これは「ブレークポイント」と呼ばれ、スクリプトの実行を止める目印になります。


    UnityでPlayするとブレークポイントで実行が止まります。ステップオーバー(F10)を押すと、1行ずつスクリプトが実行され、それぞれの変数の内容を確認することができます。


    デバッガーはビルドしたプログラムに対しても有効です。ビルドしたプログラムの動作確認を行うときにUnityエディタで実行すると不具合が再現できない、しにくい場合があります。そのようなときは、UnityエディタのConsoleの出力にビルドしたプログラムのログを表示させることができます。

    ここで、例としてボタンを押すとDebug.Log()に"CLICK"と表示されるプログラムを用意しました。Editor上ではCLICKと表示されますが、ビルドすると動いているか判断できません。ConsoleのEditorから表示するプログラムの対象を変更することができます。


    [Script Debugging]にもチェックを入れると、デバッガーでブレークポイントを設定することができるようになります。

    実行してデバッガーを接続してみましょう。Visual StudioでUnity Debuggerにアタッチします。

    [WindowsPlayer]と表示されている方を選んでOKを押します。これでUnityPlayerと接続します。


    ブレークポイントを設定すると、そこできちんと止まってくれます。


    次ページ:
    <3>Debugを使った視覚的なデバッグ:Debug.DrawRay()

    [[SplitPage]]

    <3>Debugを使った視覚的なデバッグ:Debug.DrawRay()

    Unityでは、ベクトルの向きやオブジェクトの位置を確認したいことが良くありますが、Debug.Log()でベクトルの要素を数字で表示しても直感的にはなかなか理解できません。そのようなときにベクトルを視覚的に表示させることができます。基本的には次の3つの方法があります。

    ・Gizmosを使う方法
    ・Debugクラスの3D表示機能を使う方法
    ・GLを使う方法

    下記のGif画像において、青線はGizmos.DrawRay()、緑線はDebug.DrawRay()、赤線はGLで描画を行なっています。左が、Sceneビュー、右がGameビューの表示です。


    これを見るとまったく同じように見えるのですが、Gizmos、Debug、OpenGLでそれぞれ表示できるタイミングが決まっています。

    ●Gizmos

    Gizmos.DrawRay()は、Editor画面上でGizmosが有効になっていると表示されます。Gizmosはライトやカメラなどのアイコンと同じ扱いです。

    Unityエディタで常に表示されているため、使いやすい方法です。以下のスクリプトでオブジェクト座標の前方向(forward)に長さ5の線分を青色で表示させることができます。

    void OnDrawGizmos()
    {
        Gizmos.color = Color.blue;
        Gizmos.DrawRay(transform.localPosition, transform.forward * 5);
    }
    

    ●Debug

    Debug.DrawRay()はGizmosが有効になっているときに、Playすると表示されます。レイキャストの方向など、エディタで常に表示させる必要のない情報を確認するときに便利です。コードは以下のようになります。オブジェクト座標の上方向(up)に長さ5の線分を緑色で描いています。

    Debug.DrawRay(transform.localPosition, transform.up * 5, Color.green)
    

    ●GL

    GLはGizmosが無効でもPlayすると表示されます。使いどころはDebug.DrawRay()と同じでコード量も多く面倒ですが、UnityPlayerで表示できるのが最大の利点です。こんな感じのコードになります。

    // ライン用のマテリアルを作成
    static Material lineMaterial;
    static void CreateLineMaterial()
    {
        if (!lineMaterial)
        {
            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);
        }
    }
    
    // GLを使ってラインを描く
    public void OnRenderObject()
    {
        var o = transform.position;
        var p = o + transform.up;
    
        CreateLineMaterial();
        lineMaterial.SetPass(0);
    
        GL.PushMatrix();
        GL.Color(new Color(1, 0, 0));
        GL.Begin(GL.LINES);
        GL.Vertex3(o.x, o.y, o.z);
        GL.Vertex3(p.x, p.y, p.z);
        GL.End();
    
        GL.PopMatrix();
    }
    


    <4>外部設定ファイル

    ここでデバッグとは直接関係がない話題になりますが、展示コンテンツは、その展示場所の運営スタッフにパラメータを編集してもらう余地を残すことが多くあります。Unityで直接扱えるのはJSON形式ですが、展示用コンテンツでよく使われるのは昔ながらのiniファイル形式です。iniファイルを使う利点は理解が簡単なことと、使い慣れたメモ帳で編集できることに尽きます。タイムアウトの秒数や画像ファイルのパスなどの情報を保持するためにJSONを使うと、コンピュータの操作に詳しい人が少ない運営スタッフに説明する手間が大きくなってしまいます。

    例えば、以下のようなJSONの記述があるとします。待機画面に戻る秒数を90秒、センサの最大値、最小値をそれぞれ1と100にしてあります。

    {
      "Timeout": 90,
      "Sensor" : [1, 100]
    }
    

    このようなシンプルな記述でも、JSONを知らない人に説明するのは少し大変です。90の次のカンマを消してしまってデータが読み込まれなくなっても原因が分からず、動かなくなってしまうといったことがあるからです。さらにJSONはコメントアウトするのが面倒です。

    iniであれば以下のように書けます。プログラムに慣れていない人でも簡単に理解ができると思います。

    iniファイルでの記述例
    ; タイムアウト秒数
    Timeout=90
    ; センサーの最大、最小値
    Sensor_min=1
    Sensor_max=100
    

    ただ、iniは構造化することができずパラメータの記述が冗長になりがちなので、筆者は「TOML」という言語を使うようにしています。

    TOMLでの記述例
    # タイムアウト秒数
    Timeout = 90
    # センサーの最大、最小値
    Sensor = [1, 100]
    

    基本的にはiniのように書け、JSONのように配列を使えるのが利点です。UnityでTOMLを使うためには、ここの実装をNuGetでインストールするのが簡単だと思います。Visual StudioからPackage Manager Consoleを開きます。


    Consoleが開いたら、

    Install-Package Nett -Version 0.13.0
    

    と入力してインストールします。


    ファイルはプロジェクトのPackageフォルダにインストールされますが、このままだとUnityエディタは認識してくれません。インストールされた、Packages¥Nett.0.13.0¥net40フォルダの名前をtomlに変更し、Assetフォルダ以下にコピーします。

    前述のTOMLファイルを読み込むには、次のようにします。前述のTOMLをconfig.tomlとして保存します。config.tomlはAssetsと同じ階層にある前提です。これでTimeoutには90が、Sensorには配列として1と100が読み込まれます。

    public class Param
    {
        public int Timeout { get; set; }
        public int[] Sensor { get; set; }
    }
    
    public class TomlSample: MonoBehaviour
    {
       void Start()
        {
            var toml_file = string.Format("{0}/../config.toml", Application.dataPath);
            var p = Nett.Toml.ReadFile(toml_file);
        }
    }
    

    このように、できるだけ設定を外部で変更できるようにしておくとメンテナンス性が上がり、バグが入る可能性を減らすこともできます。



    Profile.

    高田稔則/Toshinori Takata(Codelight)
    Codelight株式会社 代表取締役・インタラクションエンジニア
    フリーランス、株式会社TBSテレビ等で映画CG制作、株式会社ソニー・コンピュータエンタテインメント(現 ソニー・インタラクティブエンタテインメント)でPS4のOSD開発などを経て2006年にCodelight株式会社を設立。インタラクティブコンテンツの制作を中核として、製造業向けのプロトタイプ開発なども行う
    www.codelight.co.jp