(2025)Unity小工具:非编辑器环境实时输出Log日志到屏幕上

在软件打包后要想查看Unity的日志非常麻烦,所以想在游戏内直接按键呼出日志(之所以不叫控制台是因为此窗口并没有写Console.ReadLine功能)。参考StackOverflow上外国人写的脚本,更新到最新的语法并解决了2020版本以后部分语法的Obsolete问题,并新增日志筛选和窗口大小自适应功能。

效果如下:

默认按键我设置为 ~ 键,在Esc下方,致敬CSGO控制台。

功能介绍:

下方三个show Toggle分别是筛选普通、Warning和Error日志;

Collapse是将相同内容合并成一条,在你的程序输出大量无意义内容时效果很好;

Clear按钮是清除所有日志,但清除的只是UGUI上的,此路径下Unity Runtime输出的 默认日志还在:C:\Users\username\AppData\LocalLow\CompanyName\ProductName\Player.log

 窗口可以直接用鼠标拖动,故没有在Inspector里写窗口位置。默认位置是距屏幕左上角20x20像素

 脚本外部选项:

 

按键可自行更改,最大日志数量只有在勾选了 限制日志记录数量 才会生效。

建议自己封装一个Debug类

日志窗口大小默认是屏幕的一半大小,在编辑器修改这个数值后需要刷新一下才会生效。

具体代码如下: 

using System.Collections.Generic;
using UnityEngine;


public class RuntimeLog : MonoBehaviour
{
    [Header("打开日志的按键")]
    [SerializeField] private KeyCode toggleKey = KeyCode.BackQuote;   // 打开日志的按键

    [Header("是否限制日志记录数量")]
    [SerializeField] private bool restrictLogCount = false;           // 是否限制日志记录数量

    [Header("最大日志数量")]
    [SerializeField] private int maxLogs = 10000;                    // 最大日志数量

    [Header("日志显示筛选控制")]
    [SerializeField] private bool showLog = true;      // 控制是否显示普通日志
    [SerializeField] private bool showWarning = false;  // 控制是否显示警告日志
    [SerializeField] private bool showError = true;    // 控制是否显示错误日志

    [Header("日志窗口大小")]
    [SerializeField] private float windowWidth = Screen.width / 2f;
    [SerializeField] private float windowHight = Screen.height / 2f;

    private readonly List<Log> logs = new List<Log>();
    private Vector2 scrollPosition;
    private bool visible;
    private bool collapse;

    private static readonly Dictionary<LogType, Color> logTypeColors = new Dictionary<LogType, Color>
    {
        { LogType.Assert, Color.white },
        { LogType.Error, Color.red },
        { LogType.Exception, Color.red },
        { LogType.Log, Color.white },
        { LogType.Warning, Color.yellow },
    };

    private const string windowTitle = "日志";
    private const int margin = 20;
    private static readonly GUIContent clearLabel = new GUIContent("Clear", "清除日志内容");
    private static readonly GUIContent collapseLabel = new GUIContent("Collapse", "隐藏重复的日志");
    private static readonly GUIContent showLogLabel = new GUIContent("showLog", "控制是否显示普通日志");      // 控制是否显示普通日志
    private static readonly GUIContent showWarningLabel = new GUIContent("showWarning", "控制是否显示警告日志");  // 控制是否显示警告日志
    private static readonly GUIContent showErrorLabel = new GUIContent("showError", "控制是否显示错误日志");    // 控制是否显示错误日志

    private readonly Rect titleBarRect = new Rect(0, 0, 10000, 20);
    private Rect windowRect = new Rect();

    private void OnEnable()
    {
        windowRect = new Rect(margin, margin, windowWidth, windowHight);

        Application.logMessageReceived += HandleLog;
    }

    private void OnDisable()
    {
        Application.logMessageReceived -= HandleLog; // 注销回调
    }

    private void Update()
    {
        if (Input.GetKeyDown(toggleKey))
        {
            visible = !visible;
        }
    }

    private void OnGUI()
    {
        if (!visible) return;

        windowRect = GUILayout.Window(1, windowRect, DrawConsoleWindow, windowTitle);
    }

    private void DrawConsoleWindow(int windowID)
    {
        DrawLogsList();
        DrawToolbar();
        GUI.DragWindow(titleBarRect);
    }

    private void DrawLogsList()
    {
        scrollPosition = GUILayout.BeginScrollView(scrollPosition);

        for (var i = 0; i < logs.Count; i++)
        {
            var log = logs[i];

            // 根据用户设置的显示控制来过滤日志
            if ((log.type == LogType.Log && !showLog) ||
                (log.type == LogType.Warning && !showWarning) ||
                (log.type == LogType.Error && !showError))
            {
                continue; // 如果该类型的日志被禁用,则跳过
            }

            if (collapse && i > 0 && log.message == logs[i - 1].message)
            {
                continue; // 如果开启了折叠功能且日志消息与上一条相同,跳过
            }

            GUI.contentColor = logTypeColors[log.type];
            GUILayout.Label(log.message);
        }

        GUILayout.EndScrollView();
        GUI.contentColor = Color.white;
    }

    private void DrawToolbar()
    {
        GUILayout.BeginHorizontal();

        if (GUILayout.Button(clearLabel))
        {
            logs.Clear(); // 清空日志
        }

        showLog = GUILayout.Toggle(showLog, showLogLabel, GUILayout.ExpandWidth(false));
        showWarning = GUILayout.Toggle(showWarning, showWarningLabel, GUILayout.ExpandWidth(false));
        showError = GUILayout.Toggle(showError, showErrorLabel, GUILayout.ExpandWidth(false));
        collapse = GUILayout.Toggle(collapse, collapseLabel, GUILayout.ExpandWidth(false));

        GUILayout.EndHorizontal();
    }

    private void HandleLog(string message, string stackTrace, LogType type)
    {
        logs.Add(new Log
        {
            message = message,
            stackTrace = stackTrace,
            type = type
        });

        TrimExcessLogs();
    }

    private void TrimExcessLogs()
    {
        if (!restrictLogCount) return;

        var excessLogs = logs.Count - maxLogs;
        if (excessLogs > 0)
        {
            logs.RemoveRange(0, excessLogs); // 删除超过最大日志数量的日志
        }
    }

    // 用于存储日志的结构体
    private struct Log
    {
        public string message;
        public string stackTrace;
        public LogType type;
    }
}

 此脚本只要挂载到任意物体上即可,场景里不需要有Canvas也可以显示。