Unity3D 画面上に表示出来るログ機能（レベル付き）改修版


[前書いた投稿](http://qiita.com/dk4kd/items/0b9d0edccb31048455f3)は、改めて見ているとコメントぐちゃぐちゃだったり
その後使用感がイマイチで変えたところもあるので、改めて別記事として投稿しておきます。

基本的に、エディタ実行時はなるべくエディタ機能として動くようにしています。

# １．変更点

1. ソース内コメントの修正
2. MonoBehaviour からの継承ではなく static クラスへ（何でライフサイクル無いのにBehaviourにしてた？）
3. エディタスクリプトから使用する前提の機能の追加（エディタ用描画。全ログをクリップボードにコピー）
4. エディタ実行時はゲーム画面側へ出力しないように
5. ログクリアボタンの追加

# ２．コード（ログクラス）

```csharp:DkLog.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text;

#if UNITY_EDITOR
using UnityEditor;
#endif

/// <summary>
/// 画面表示可能なデバッグログ
/// </summary>
/// <remarks>
/// [機能]
/// ・レベル分けログ
/// ・ログの画面表示＋色分け表示
/// ・ボタンで表示ログレベルフィルタリング
/// ・コンソールに出さないように呼び出すことも可能（コンソールログは重いので・・・）
/// </remarks>
/// <example>
/// // 各レベルのログ出力（コンソールにも同時に出力）
/// DkLog.V("verbose log");
/// DkLog.D("debug log");
/// DkLog.I("information log");
/// DkLog.W("warning log");
/// DkLog.E("error log");
/// 
/// // 画面にのみ出力
/// DkLog.V("verbose log (not console)", false);
/// DkLog.D("debug log (not console)", false);
/// 
/// // 画面上にログウィンドウを表示（※ OnGUI()内での呼び出しにのみ対応）
/// DkLog.DrawLogWindow(new Rect(0, Screen.Height * 0.5f, Screen.Width * 1.0f, Screen.Height * 0.5f));
/// </example>


public static class DkLog
{
    #region ///////////// タイプ /////////////
    public enum Level
    {
        Verbose,
        Debug,
        Information,
        Warning,
        Error,

        Max
    }

    private struct LogData
    {
        public Level Level { get; set; }
        public string Message { get; set; }
        public System.DateTime TimeStamp { get; set; }
    }
    #endregion

    #region ///////////// 定数 /////////////
    /// 表示可能ログ最大数
    private static int LOG_MAX = 512;

    /// 各レベルのログ色
    private static Color[] LOG_COLOR =
    {
        Color.gray,
        Color.white,
        Color.cyan,
        Color.yellow,
        Color.red
    };
    #endregion

    #region ///////////// メンバ /////////////
    private static Level _logLevel = Level.Debug;                           /// 表示対象ログレベル
    private static Queue<LogData> _logQue = new Queue<LogData>(LOG_MAX);    /// ログキュー
    private static Vector2 _scrollPosition = Vector2.zero;                  /// スクロールビュー位置
    private static bool _isNeedScrollReset = false;                         /// スクロール位置リセットフラグ
    #endregion

    #region ///////////// ログ処理 /////////////
    public static void V(string message, bool isConsole = true) { _Push(Level.Verbose, message, isConsole); }
    public static void D(string message, bool isConsole = true) { _Push(Level.Debug, message, isConsole); }
    public static void I(string message, bool isConsole = true) { _Push(Level.Information, message, isConsole); }
    public static void W(string message, bool isConsole = true) { _Push(Level.Warning, message, isConsole); }
    public static void E(string message, bool isConsole = true) { _Push(Level.Error, message, isConsole); }

    /// <summary>
    /// ログを画面上に表示する
    /// OnGUI() 内で呼び出して下さい
    /// </summary>
    /// <param name="drawArea">描画対象領域（左上が0,0）</param>
    /// <param name="isEditor">エディタスクリプトから使用する場合trueにする</param>
    public static void DrawLogWindow(Rect drawArea, bool isEditor = false)
    {
        // エディタ実行中はエディタ側のウィンドウに任せる
        if (Application.isEditor && !isEditor)
        {
            return;
        }

        // 下地
        GUI.Box(drawArea, "");

        GUILayout.BeginArea(drawArea);
        {
            GUILayout.BeginHorizontal();

            /////////////
            // ログレベル切り替えボタン
            if (GUILayout.Button(_logLevel.ToString()))
            {
                _logLevel = (Level)((int)(_logLevel + 1) % (int)Level.Max);
                _isNeedScrollReset = true;
            }

            /////////////
            // クリップボードにコピー
#if UNITY_EDITOR
            if (GUILayout.Button("Copy All"))
            {
                EditorGUIUtility.systemCopyBuffer = _GetLogString(_logLevel);
            }
#endif

            GUILayout.EndHorizontal();

            /////////////
            // ログスクロールビュー
            // @todo    メッセージの数だけLabelなりを作る。重い
            //          ただしTextAreaにすると色分けできない
            _scrollPosition = GUILayout.BeginScrollView(_scrollPosition, true, true);

            int num = 0;
            GUIStyle style = new GUIStyle();
            foreach (LogData d in _logQue)
            {
                // ログレベルによって弾く
                if (d.Level >= _logLevel)
                {
                    // 0始まりのログ行数をプレフィックスとして付加しています
                    // 時刻にした方がいいかも
                    style.normal.textColor = LOG_COLOR[(int)d.Level];
                    // エディターウィンドウ用の描画
                    if (isEditor)
                    {
#if UNITY_EDITOR
                        EditorGUILayout.TextArea(d.TimeStamp.ToString(@"MM/dd HH:mm:ss | ") + d.Message, style);
#endif
                    }
                    else
                    {
                        //GUILayout.Label(num.ToString() + " " + d.Message, style);
                        GUILayout.Label(d.Message, style);
                    }
                    ++num;
                }
            }

            // ログ表示位置を先頭にリセットする
            if (_isNeedScrollReset)
            {
                _isNeedScrollReset = false;
                _scrollPosition.y = (num > 0) ? (num - 1) * 20 : 0;
            }

            GUILayout.EndScrollView();

            /////////////
            // クリアボタン
            if (GUILayout.Button("Clear"))
            {
                _logQue.Clear();
            }
        }
        GUILayout.EndArea();

    }

    /// <summary>
    /// ログをキューに詰める
    /// </summary>
    /// <param name="level"></param>
    /// <param name="message"></param>
    /// <param name="isConsole"></param>
    private static void _Push( Level level, string message, bool isConsole )
    {
        /////////////
        // キューが一杯だったら後ろを削除
        if (_logQue.Count >= LOG_MAX)
        {
            _logQue.Dequeue();
        }

        /////////////
        // キューに詰める
        LogData data = new LogData()
        {
            Level = level,
            Message = message,
            TimeStamp = System.DateTime.Now
        };
        _logQue.Enqueue(data);

        // ログ位置を調整する
        // @todo 出る度に勝手に動く。要らないかも？
        _isNeedScrollReset = true;

        /////////////
        // コンソール出力
        if (isConsole)
        {
            switch (level)
            {
                case Level.Warning: Debug.LogWarning(message); break;
                case Level.Error: Debug.LogError(message); break;
                default: Debug.Log(message); break;
            }
        }
    }
    #endregion

    #region ///////////// 内部処理 /////////////
    /// <summary>
    /// ログを1文字列としてまとめて取得。クリップボードコピー用の処理
    /// </summary>
    private static string _GetLogString(Level minLevel)
    {
        StringBuilder builder = new StringBuilder();

        foreach (LogData d in _logQue)
        {
            // ログレベルによって弾く
            if (d.Level >= minLevel)
            {
                builder.AppendLine(d.TimeStamp.ToString("MM/dd HH:mm:ss \t ") + System.Enum.GetName(typeof(Level), d.Level).Substring(0, 1) + "\t" + d.Message);
            }
        }

        return builder.ToString();
    }
    #endregion
}
```


# ３．コード（エディタスクリプト）

```csharp:DkLogEditor.cs
using UnityEngine;
using UnityEditor;
using System.Collections;

/// <summary>
/// DkLog に対応するエディタ処理
/// </summary>

public class DkLogEditor : EditorWindow
{
    /// <summary>
    /// ログウィンドウを開く
    /// </summary>
    [MenuItem("DkTools/Log")]
    public static void Open()
    {
        DkLogEditor window = EditorWindow.GetWindow<DkLogEditor>();
        window.Initialize();
        window.ShowUtility();
    }

    /// <summary>
    /// 初期化
    /// </summary>
    public void Initialize()
    {
        title = "DkLog";
    }

    /// <summary>
    /// 更新
    /// </summary>
    void Update()
    {
        Repaint();
    }

    /// <summary>
    /// ウィンドウ内描画
    /// </summary>
    void OnGUI()
    {
        BeginWindows();
        DkLog.DrawLogWindow(new Rect(0, 0, position.width, position.height), true);
        EndWindows();
    }
}
```

# ４．実行結果

ある処理をエディタ上で実行した場合

![qiita_20140722_0.png](https://qiita-image-store.s3.amazonaws.com/0/39914/090ed334-2fb4-f048-8352-9db225cfe7a0.png)

PCビルドして上記と同じ処理を実行した場合

![qiita_20140722_1.png](https://qiita-image-store.s3.amazonaws.com/0/39914/e562cbac-19fe-aa29-da62-9157c454ed42.png)


なお、エディタ実行時のCopy Allボタンは
Excelに張り付ける前提のタブ区切りのテキストとしてコピーします。


# ５．問題点

ログ描画が重い・・・