うら干物書き

ゲームを作っています。

【Unity】ゲーム画面にDebug.Logを出したい!

 Debug.Log()をスクリプト上に仕込んでおけば、デバック作業が楽になりますよね。
 Debug.Log()で吐き出されるログは、UnityEditor上ではConsoleウィンドウから見れますし、iOSならXcode経由で、Androidならlogcatで、と見る方法はいろいろあります。
 
 ただ僕はゲーム画面でもログが見たい!
 今作っているPhotonを使ったマルチプレイのゲームなんかは、各実機のゲーム画面にログが表示されないとデバッグがしにくいのです。

 というわけで、今回はゲーム画面にログを出して見たいと思います。
f:id:urahimono:20170425162636p:plain


この記事にはUnity5.6.0f3を使用しています。

 さて、どうやってログをゲーム画面に表示させましょうか。
 uGUITextを使えば、ゲーム画面上に文字列を出すことは出来ます。

 ではDebug.Log()で出力する文字列をどのように取得すればいいのでしょうか。
 スクリプト上でDebug.Log()を呼んでいる前後で、Debug.Log()で渡している文字列と同じものをuGUITextに渡してあげればいいのですが、それはさすがに面倒くさいです。
 さて、Unity側に何か便利な関数やらはないものでしょうか。

Application.logMessageReceivedを使ってログをキャッチ!

 Application.logMessageReceivedというイベントハンドラーがありました。
 このイベントにコールバック関数を登録しておけば、ログを出力した際に呼び出されるという優れものです。

docs.unity3d.com

 Application.logMessageReceivedに指定できるコールバックの関数は、引数3個で返り値なし。
 渡される引数は以下の通りです。

第1引数:string型 ログの文字
第2引数:string型 スタックトレース
第3引数:LogType型 ログのタイプ

 さっそく使ってみましょう。

LogMenu.cs

using UnityEngine;
using UnityEngine.UI;

public class LogMenu : MonoBehaviour
{
    [SerializeField]
    private Text    m_textUI    = null;

    private void Awake()
    {
        Application.logMessageReceived  += OnLogMessage;
    }

    private void OnDestroy()
    {
        Application.logMessageReceived  += OnLogMessage;
    }

    private void OnLogMessage( string i_logText, string i_stackTrace, LogType i_type )
    {
        if( string.IsNullOrEmpty( i_logText ) )
        {
            return;
        }

        m_textUI.text   += i_logText;
    }

} // class LogMenu

 以下のテストコンポーネントを使って、ログが画面に映るかテストしてみます。

using UnityEngine;
using System.Collections;

public class TestComponent : MonoBehaviour
{
    private IEnumerator Start()
    {
        while( true )
        {
            Debug.LogFormat( "Time:{0}", System.DateTime.Now );
            yield return new WaitForSeconds( 1.0f );
        }
    }

} // class TestComponent

f:id:urahimono:20170425162440g:plain

 ログがゲーム画面に表示されました。
 ただ……、改行し忘れてますね。
 ログがごしゃごしゃになってしまっている。
 uGUITextに文字列を渡す際に、改行コードも一緒に渡してあげましょう。

LogMenu.cs

private void OnLogMessage( string i_logText, string i_stackTrace, LogType i_type )
{
    if( string.IsNullOrEmpty( i_logText ) )
    {
        return;
    }

    m_textUI.text   += i_logText + System.Environment.NewLine;
}

f:id:urahimono:20170425162513g:plain

スクロールビューをつけることでログが多い日も安心!

 さて、ログが出たのはいいんですが、このままじゃすぐにログが画面から溢れてしまいます。
 これはuGUI側で解決しましょうか。
 たしかスクロールビューという便利なものがあったような気がしますよ。

 UIメニューからScrollViewを選択して作成しましょう。

f:id:urahimono:20170425162546p:plain

 そして、ContentGameObjectに先ほど使っていたTextのUIとContentSizeFitterを加えてっと。

f:id:urahimono:20170425162558p:plain
f:id:urahimono:20170425162606g:plain

 3分クッキング感覚で、スクロールが出来ました。

もっとログをカラフルに!

 ログにもいろいろなタイプがあります。
 Debug.LogWarning()が呼ばれていれば、何かしら不味いことが起きているんでしょうし、
 Debug.LogError()が呼ばれた日には、非常事態な状況に違いないわけです。

 それらが通常のログと同じ感じに表示されていたんでは、重要なログを見逃してしまいます。
 というわけで、ログに色を付けてみましょう。

 Application.logMessageReceivedに登録したコールバックの引数にLogTypeが渡されているはずです。
 このLogTypeで色分けしていきましょう。
 
docs.unity3d.com

LogMenu.cs

private void OnLogMessage( string i_logText, string i_stackTrace, LogType i_type )
{
    if( string.IsNullOrEmpty( i_logText ) )
    {
        return;
    }

    switch( i_type )
    {
        case LogType.Error:
        case LogType.Assert:
        case LogType.Exception:
            i_logText = string.Format( "<color=red>{0}</color>", i_logText );
            break;
        case LogType.Warning:
            i_logText = string.Format( "<color=yellow>{0}</color>", i_logText );
            break;
        default:
            break;
    }

    m_textUI.text   += i_logText + System.Environment.NewLine;
}

TestComponent.cs

private IEnumerator Start()
{
    while( true )
    {
        switch( Random.Range( 0, 3 ) )
        {
            case 0:
                Debug.LogFormat( "Time:{0}", System.DateTime.Now );
                break;
            case 1:
                Debug.LogWarningFormat( "Time:{0}", System.DateTime.Now );
                break;
            case 2:
                Debug.LogErrorFormat( "Time:{0}", System.DateTime.Now );
                break;

            default:
                break;

        }
        yield return new WaitForSeconds( 0.5f );
    }
}

f:id:urahimono:20170425162636p:plain

 カラフル!
 いいですねー、赤い文字が事態の深刻さをあおってくれます。

ことのついでにスタックトレースも使っちゃおう!

 大体望んでいた感じに作れたのですが、折角引数としてスタックトレースの文字列も渡されているのでこれも使ってみましょう。

 毎回スタックトレースまで表示していたらログが溢れかえるので、Assertなど非常事態なときだけスタックトレースを表示するようにしましょう。

private void OnLogMessage( string i_logText, string i_stackTrace, LogType i_type )
{
    if( string.IsNullOrEmpty( i_logText ) )
    {
        return;
    }

    if( !string.IsNullOrEmpty( i_stackTrace ) )
    {
        switch( i_type )
        {
            case LogType.Error:
            case LogType.Assert:
            case LogType.Exception:
                i_logText   += System.Environment.NewLine + i_stackTrace;
                break;
            default:
                break;
        }
    }

    switch( i_type )
    {
        case LogType.Error:
        case LogType.Assert:
        case LogType.Exception:
            i_logText = string.Format( "<color=red>{0}</color>", i_logText );
            break;
        case LogType.Warning:
            i_logText = string.Format( "<color=yellow>{0}</color>", i_logText );
            break;
        default:
            break;
    }

    m_textUI.text   += i_logText + System.Environment.NewLine;

}

f:id:urahimono:20170425162650p:plain

 エラーが発生した際はスタックトレースで、どのようにしてエラーが発生したのかがわかるので、よりデバッグがしやすくなりましたね。

出来上がり!

 このデバッグログ出力をデバッグメニューなどに仕込めば、どんなときでもログが確認できますね。
 更に改良を加えれば、ログタイプごとで表示するメニューをかえたり、文字列を検索することも出来るようになるかもしれません。

 しかし、スクリプトを見返してみると、最後のスタックトレースの件はswitch文が連続していて美しくない……。
 あとで直そうっと。