徳島ゲーム開発ごっこ 技術ブログ

ゲームを作るために役に立ったり立たなかったりする技術を学んでいきます!

【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文が連続していて美しくない……。
 あとで直そうっと。