当前位置: 首页 > news >正文

【第四章自定义编辑器窗口_Game窗口中的GUI_运行时控制台窗口(10/12)】

4.3.1 运行时控制台窗口

书上了是讲一个实现了一个简易控制台小案例,这个控制台可以运行在编辑器和非编辑器下,个人感觉新东西不多,基本都是前面涉及过的东西,就是日志获取方面的知识。这个只能在非编辑器使用,当你在编辑器模式下,点击挂载该脚本的物体,会疯狂报错,原因就是Unity 的 IMGUI(OnGUI) 在 Inspector 选中物体时也会执行,而例子代码在 OnGUI() 里 没有检查是否在 Game 视图,导致 Editor 环境下布局错乱。有一些方法似乎可以避免,有兴趣的可以研究怎么避免。

// 当Unity引擎产生任何日志(包括Debug.Log/LogWarning/LogError等)时
// 将自动触发OnLogMessageReceived方法
// 参数说明:
//   condition: 日志消息内容
//   stackTrace: 调用堆栈信息
//   type: 日志类型(Log/Warning/Error等)
Application.logMessageReceived += OnLogMessageReceived;

先看效果
在这里插入图片描述
代码

using System;
using UnityEngine;
using System.Collections.Generic;/// <summary>
/// 控制台GUI窗口组件
/// </summary>
public class ConsoleGUIWindow : MonoBehaviour
{[SerializeField]private WorkingType workingType = WorkingType.ALWAYS_OPEN; // 工作模式// 窗口尺寸定义private Rect expandRect;   // 展开状态窗口private Rect retractRect;  // 收起状态窗口private Rect dragableRect; // 可拖拽区域private bool isExpand;      // 当前是否展开private int fps;            // 当前帧率private float lastShowFPSTime; // 上次更新FPS的时间private Color fpsColor = Color.white; // FPS显示颜色// 日志存储private readonly List<ConsoleItem> logs = new List<ConsoleItem>();// 滚动视图位置private Vector2 listScroll;   // 日志列表滚动条private Vector2 detailScroll; // 详情滚动条// 日志计数private int infoCount;  // 普通日志数量private int warnCount;  // 警告日志数量private int errorCount; // 错误日志数量// 显示过滤开关[SerializeField] private bool showInfo = true;  // 显示普通日志[SerializeField] private bool showWarn = true;  // 显示警告日志[SerializeField] private bool showError = true; // 显示错误日志private ConsoleItem currentSelected; // 当前选中的日志项[SerializeField] private bool showTime = true; // 是否显示时间[SerializeField] private int maxCacheCount = 100; // 最大缓存日志数量private string searchContent; // 搜索关键词void Awake(){// 注册全局日志回调处理器// 当Unity引擎产生任何日志(包括Debug.Log/LogWarning/LogError等)时// 将自动触发OnLogMessageReceived方法// 参数说明://   condition: 日志消息内容//   stackTrace: 调用堆栈信息//   type: 日志类型(Log/Warning/Error等)Application.logMessageReceived += OnLogMessageReceived;}void Start(){// 根据工作模式初始化switch (workingType){case WorkingType.ALWAYS_OPEN:enabled = true;break;case WorkingType.ONLY_OPEN_WHEN_DEVELOPMENT_BUILD:enabled = Debug.isDebugBuild; // 仅在开发构建启用break;case WorkingType.ONLY_OPEN_IN_EDITOR:enabled = Application.isEditor; // 仅在编辑器启用break;case WorkingType.ALWAYS_CLOSE:enabled = false;break;}// 初始化窗口尺寸expandRect = new Rect(Screen.width * .7f, 0f,Screen.width * .3f, Screen.height * .5f);retractRect = new Rect(Screen.width - 100f, 0f, 100f, 60f);dragableRect = new Rect(0, 0, Screen.width * .3f, 20f);}void OnDestroy(){// 注销日志回调Application.logMessageReceived -= OnLogMessageReceived;}/// <summary>/// 日志接收处理/// </summary>/// <param name="condition"></param>/// <param name="stackTrace"></param>/// <param name="logType"></param>void OnLogMessageReceived(string condition, string stackTrace, LogType logType){if (!enabled)return;var item = new ConsoleItem(DateTime.Now, logType, condition, stackTrace);logs.Add(item);// 精确统计日志类型switch (logType){case LogType.Log:infoCount++;break;case LogType.Warning:warnCount++;break;case LogType.Error:case LogType.Assert:case LogType.Exception:errorCount++;break;}// 清理旧日志if (logs.Count > maxCacheCount){var removed = logs[0];switch (removed.type){case LogType.Log:infoCount--;break;case LogType.Warning:warnCount--;break;case LogType.Error:case LogType.Assert:case LogType.Exception:errorCount--;break;}if (currentSelected == removed)currentSelected = null;logs.RemoveAt(0);}}void OnGUI(){// 只在启用时渲染GUIif (!enabled)return;// 根据展开状态渲染不同窗口if (isExpand){expandRect = GUI.Window(0, expandRect, OnExpandGUI, "Console");//限制范围expandRect.x = Mathf.Clamp(expandRect.x, 0, Screen.width * .7f);expandRect.y = Mathf.Clamp(expandRect.y, 0, Screen.height * .5f);dragableRect = new Rect(0, 0, Screen.width * .3f, 20f);}else{retractRect = GUI.Window(0, retractRect, OnRetractGUI, "Console");//限制范围retractRect.x = Mathf.Clamp(retractRect.x, 0, Screen.width - 100f);retractRect.y = Mathf.Clamp(retractRect.y, 0, Screen.height - 60f);dragableRect = new Rect(0, 0, 100f, 20f);}// FPS计算(每秒更新)if (Time.realtimeSinceStartup - lastShowFPSTime >= 1){fps = Mathf.RoundToInt(1f / Time.unscaledDeltaTime);lastShowFPSTime = Time.realtimeSinceStartup;// 根据错误状态设置颜色fpsColor = errorCount > 0 ? Color.red: warnCount > 0 ? Color.yellow : Color.white;}// 调试按钮:强制添加测试日志if (GUI.Button(new Rect(10, 10, 150, 30), "Add Test Log")){Debug.Log("Test console message");Debug.LogWarning("Test warning message");Debug.LogError("Test error message");}}// 收起状态GUIvoid OnRetractGUI(int windowId){//Unity中,窗口的绘制和事件处理(包括拖拽)都是在回调函数中进行的。GUI.DragWindow(dragableRect); // 可拖拽区域GUI.contentColor = fpsColor;  //设置文本颜色,用来绘制// FPS按钮(点击展开窗口)if (GUILayout.Button($"FPS:{fps}", GUILayout.Height(30f)))isExpand = true;GUI.contentColor = Color.white; // 重置颜色,避免后续文本渲染都是这个颜色}/// <summary>/// 展开状态GUI/// </summary>/// <param name="windowId"></param>void OnExpandGUI(int windowId){GUI.DragWindow(dragableRect);GUI.contentColor = fpsColor;// FPS按钮(点击收起窗口)if (GUILayout.Button($"FPS:{fps}", GUILayout.Height(20f)))isExpand = false;// 渲染各区域OnTopGUI();OnListGUI();OnDetailGUI();}/// <summary>/// 顶部控制区/// </summary>void OnTopGUI(){GUILayout.BeginHorizontal();// 清空按钮if (GUILayout.Button("Clear", GUILayout.Width(50f))){logs.Clear();infoCount = warnCount = errorCount = 0;currentSelected = null;}// 时间显示开关showTime = GUILayout.Toggle(showTime, "ShowTime", GUILayout.Width(80f));// 搜索框searchContent = GUILayout.TextField(searchContent, GUILayout.ExpandWidth(true));// 日志类型过滤开关GUI.contentColor = showInfo ? Color.white : Color.grey;showInfo = GUILayout.Toggle(showInfo, $"Info [{infoCount}]", GUILayout.Width(60f));GUI.contentColor = showWarn ? Color.white : Color.grey;showWarn = GUILayout.Toggle(showWarn, $"Warn [{warnCount}]", GUILayout.Width(65f));GUI.contentColor = showError ? Color.white : Color.grey;showError = GUILayout.Toggle(showError, $"Error [{errorCount}]", GUILayout.Width(65f));// 添加重置过滤按钮,重置所有过滤和搜索内容,显示出所有日志if (GUILayout.Button("Reset", GUILayout.Width(50f))){showInfo = showWarn = showError = true;searchContent = "";}GUI.contentColor = Color.white;GUILayout.EndHorizontal();}/// <summary>/// 日志列表区/// </summary>void OnListGUI(){// 修复:使用最小高度确保可见GUILayout.BeginVertical("Box", GUILayout.MinHeight(100));listScroll = GUILayout.BeginScrollView(listScroll);// 显示日志计数调试信息GUILayout.Label($"Total logs: {logs.Count} | Visible: {infoCount + warnCount + errorCount}");// 倒序显示最新日志for (int i = logs.Count - 1; i >= 0; i--){var temp = logs[i];// 关键词过滤if (!string.IsNullOrEmpty(searchContent) &&!temp.message.ToLower().Contains(searchContent.ToLower()))continue;// 类型过滤bool show = false;switch (temp.type){case LogType.Log when showInfo:show = true;break;case LogType.Warning when showWarn:show = true;GUI.contentColor = Color.yellow;break;case LogType.Error:case LogType.Assert:case LogType.Exception when showError:show = true;GUI.contentColor = Color.red;break;}// 渲染可点击的日志项if (show){if (GUILayout.Toggle(currentSelected == temp,showTime ? temp.brief : temp.message))currentSelected = temp;}GUI.contentColor = Color.white;}// 添加提示信息if (logs.Count == 0){GUILayout.Label("No logs available");}else if (infoCount + warnCount + errorCount == 0){GUILayout.Label("All logs filtered out");}GUILayout.EndScrollView();GUILayout.EndVertical();}/// <summary>/// 日志详情区/// </summary>void OnDetailGUI(){GUILayout.BeginVertical("Box", GUILayout.ExpandHeight(true));detailScroll = GUILayout.BeginScrollView(detailScroll);// 显示选中日志的完整信息if (currentSelected != null){GUILayout.Label(currentSelected.detail);}else{GUILayout.Label("Select a log to view details");}GUILayout.EndScrollView();GUILayout.FlexibleSpace();// 复制按钮(仅在选中日志时可用)GUI.enabled = currentSelected != null;if (GUILayout.Button("Copy", GUILayout.Height(20f))){GUIUtility.systemCopyBuffer = currentSelected?.detail ?? "";}GUI.enabled = true;GUILayout.EndVertical();}
}// 工作模式枚举
public enum WorkingType
{ALWAYS_OPEN,                       // 始终开启ONLY_OPEN_WHEN_DEVELOPMENT_BUILD,  // 仅开发构建开启ONLY_OPEN_IN_EDITOR,               // 仅编辑器开启ALWAYS_CLOSE                       // 始终关闭
}// 日志项数据结构
public class ConsoleItem
{public LogType type;        // 日志类型public DateTime time;       // 记录时间public string message;      // 日志内容public string stackTrace;   // 调用堆栈public string brief;        // 简略信息(含时间)public string detail;       // 完整信息(含堆栈)public ConsoleItem(DateTime time, LogType type, string message, string stackTrace){this.type = type;this.time = time;this.message = message;this.stackTrace = stackTrace;// 修复时间格式brief = $"[{time:HH:mm:ss}] {message}";detail = $"[{time:HH:mm:ss.fff}] {message}\n{stackTrace}";}
}
http://www.dtcms.com/a/308462.html

相关文章:

  • 深度解析领域特定语言(DSL)第七章:语法分析器组合子 - 用乐高思维构建解析器
  • go2sky的封装及使用
  • LeetCode 刷题【23. 合并 K 个升序链表】
  • Android屏幕适配:从dp到px的转换与今日头条适配方案详解
  • 嵌入式第十六课!!!结构体与共用体
  • 安卓 Activity 四种启动模式(Launch Mode)的核心知识点整理
  • Linux 进程调度管理
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘plotly’问题
  • SAM附录详解
  • 乱删文件,电脑不能开机,怎么办
  • 电子电路原理学习笔记---第5章特殊用途二极管---第1天
  • XSS跨站脚本攻击详解
  • 从0到1学PHP(九):PHP 会话管理:跟踪用户状态
  • opencv解迷宫
  • Nuitka:将源码编译为 `.pyd`
  • vue+elementui实现问卷调查配置可单选、多选、解答
  • vector的增删改查模拟实现(简单版)【C++】
  • 【ProtoBuf】ProtoBuf安装
  • 力扣面试150(45/150)
  • 【C语言】深度剖析指针(三):回调机制、通用排序与数组指针逻辑
  • esp32s3 + ov2640,给摄像头加上拍照功能,存储到sd卡
  • 109㎡中古风家装:北京业之峰在朝阳区绘就温馨画卷
  • 【实际项目1.2-西门子PLC的报警监控思路】
  • Java多线程详解(1)
  • C#反射的概念与实战
  • [2025CVPR-小样本方向]ImagineFSL:基于VLM的少样本学习的想象基集上的自监督预训练很重要
  • 三方支付详解
  • SQL 中 WHERE 与 HAVING 的用法详解:分组聚合场景下的混用指南
  • 大数据平台数仓数湖hive之拉链表高效实现
  • 深度学习入门:用pytorch跑通GitHub的UNET-ZOO项目