unity console日志双击响应事件扩展
1 对于项目中一些比较长的日志,比如前后端交互协议具体数据等,这些日志内容可能会比较长,在unity控制面板上查看不是十分方便,我们可以对双击事件进行扩展,将日志保存到一个文本中,然后用系统默认的文本查看工具查看这个日志
2 项目中如果用到了lua,当我们在控制台输出lua文件的时候,能不能双击日志用我们的代码编辑器软件打开日志输出位置
console日志双击然后用记事本打开显示
我们先用 OnOpenAsset 特性,重写双击回调方法,这个方法返回false,会继续执行系统默认日志双击打开方法,返回true则会中断。
我们想要的效果是用记事本打开这个特殊日志,但是不需要跳转到对应的代码输出位置
首先我们先在lua里面把日志输出方法重写一下,在日志输出的时候,添加一个标签 “”,当我们检测到双击的日志信息包含这个标签,则用记事本打开这一段日志(我们也可以把一些特殊的日志先在代码中缓存,在这个时候调用缓存读取那些特殊的日志进行输出显示)
function printLongLog(fmt, ...)local logText = string.format(fmt, ...)local debugInfo = debug.getinfo(2)local str = string.format("%s\n<open in file>", logText, debugInfo.short_src, debugInfo.currentline)local logMessage = concatStrs(str, debug.traceback("", 2))UnityEngine.Debug.Log(logMessage)
end
然后再C#代码中监听这一段日志
[OnOpenAsset(0)]public static bool OnAsset(int instanceID, int line){string assetPath = AssetDatabase.GetAssetPath(instanceID);string fileExtension = Path.GetExtension(assetPath);string stackTrace = GetStackTrace();if (string.IsNullOrEmpty(stackTrace)){return false;}if (stackTrace.Contains("<open in file>")) //对于一些比较长的日志,添加标签<open in file>,双击的时候再文本中打开{openLog(stackTrace);return true;}return false;}
获取console里面的日志
private static string GetStackTrace(){var consoleWindowType = typeof(EditorWindow).Assembly.GetType("UnityEditor.ConsoleWindow");var fieldInfo = consoleWindowType.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic);var consoleWindowInstance = fieldInfo.GetValue(null);if (null != consoleWindowInstance && (object)EditorWindow.focusedWindow == consoleWindowInstance){fieldInfo = consoleWindowType.GetField("m_ActiveText", BindingFlags.Instance | BindingFlags.NonPublic);string activeText = fieldInfo.GetValue(consoleWindowInstance).ToString();return activeText;}return "";}
用记事本打开日志文件,我们先把日志信息保存在本地,然后调用系统方法打开这个文件
这里用到了一个方法 System.Diagnostics.Process.Start(fileName),参数是文件的完整路径,调用系统默认方式打开改文件
private static void openLog(string stackTrace){string fileName = GetFileSaveName();string saveDirPath = Application.dataPath + "/../Debug";if (!Directory.Exists(saveDirPath)){Directory.CreateDirectory(saveDirPath);}string savePath = saveDirPath + "/" + fileName;File.WriteAllText(savePath, stackTrace);System.Diagnostics.Process.Start(savePath);}private static string GetFileSaveName(){DateTime currentTime = DateTime.Now;string customFormat = currentTime.ToString("yyyy_MM_dd_HH_mm_ss");string fileName = $"debug_{customFormat}.txt";return fileName;}
双击打开lua日志然后用lua编辑器定位到日志输出位置
对于lua输出的日志,我们为了便于打开可以用类似上面的方法重新封装一个lua日志输出方法,然后通关特殊标签的进行确定lua文件的名称和对应的行号。也可以进行拆分lua堆栈数据取到lua代码名称和对应堆栈。
添加标签方式的lua日志输出
function print(fmt, ...)local logText = string.format(fmt, ...)local debugInfo = debug.getinfo(2)local str = string.format("%s\n<filePath>%s</filePath><line>%s</line>", logText, debugInfo.short_src, debugInfo.currentline)local logMessage = concatStrs(str, debug.traceback("", 2))UnityEngine.Debug.Log(logMessage)
end
获取文件名称和行号
static bool OpenLua(string logText){//获取lua文件路径Regex regex = new Regex(@"<filePath>.*<\/filePath>");Match match = regex.Match(logText);if (!match.Success){return false;}string filePath = match.Groups[0].Value.Trim();int length = filePath.Length - 10 - 11; //去掉开头和结尾的字符串 <filePath> </filePath>filePath = filePath.Substring(10, length);filePath = filePath.Replace(".", "/");if (!filePath.EndsWith(".lua")){filePath = filePath + ".lua";}//获取日志行号Regex lineRegex = new Regex(@"<line>.*<\/line>");match = lineRegex.Match(logText);if (!match.Success){return false;}string luaLineString = match.Groups[0].Value;luaLineString.Trim();length = luaLineString.Length - 6 - 7;luaLineString = luaLineString.Substring(6, length);int luaLine = int.Parse(luaLineString.Trim());return OpenFileAtLineExternal(filePath, luaLine);}
对于系统默认输出日志,我们通关拆分字符串方式获取
static bool OpenLuaDefault(string logText){int index = logText.IndexOf("stack traceback:");string temp = logText.Substring(index, logText.Length - index);string[] arr = temp.Split(':');if (arr.Length < 3){return false;}string filePath = arr[1].Trim();int luaLine = int.Parse(arr[2]);filePath = filePath + ".lua";return OpenFileAtLineExternal(filePath, luaLine);}
有了文件名和行号,我们就可以通关调用VScode或者Idea编辑器软件,打开对应的文件并定位到指定的行
static void OpenFileWith(string fileName, int line){string editorPath = EditorUserSettings.GetConfigValue(EXTERNAL_EDITOR_PATH_KEY);System.Diagnostics.Process proc = new System.Diagnostics.Process();proc.StartInfo.FileName = editorPath;string projectRootPath = EditorUserSettings.GetConfigValue(LUA_PROJECT_ROOT_FOLDER_PATH_KEY);if (string.IsNullOrEmpty(projectRootPath)){SetLuaProjectRoot();return;}string procArgument = "";if (editorPath.IndexOf("idea") != -1) //idea{procArgument = string.Format("{0} --line {1} {2}", projectRootPath, line, fileName);}else if (editorPath.IndexOf("Code.exe") != -1) // VSCode{string filePath = Path.Combine(projectRootPath, fileName);procArgument = string.Format("-g {0}:{1}:0", filePath, line);}else{procArgument = string.Format("{0}:{1}:0", fileName, line);}proc.StartInfo.Arguments = procArgument;proc.Start(); }
完整代码
lua 日志方法
function print(fmt, ...)local logText = string.format(fmt, ...)local debugInfo = debug.getinfo(2)local str = string.format("%s\n<filePath>%s</filePath><line>%s</line>", logText, debugInfo.short_src, debugInfo.currentline)local logMessage = concatStrs(str, debug.traceback("", 2))UnityEngine.Debug.Log(logMessage)
endfunction printLongLog(fmt, ...)local logText = string.format(fmt, ...)local debugInfo = debug.getinfo(2)local str = string.format("%s\n<open in file>", logText, debugInfo.short_src, debugInfo.currentline)local logMessage = concatStrs(str, debug.traceback("", 2))UnityEngine.Debug.Log(logMessage)
end
C#方法
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;public class ConsoleTools : MonoBehaviour
{public const string EXTERNAL_EDITOR_PATH_KEY = "mTv8";public const string LUA_PROJECT_ROOT_FOLDER_PATH_KEY = "obUd";[OnOpenAsset(0)]public static bool OnAsset(int instanceID, int line){string assetPath = AssetDatabase.GetAssetPath(instanceID);string fileExtension = Path.GetExtension(assetPath);// string fileName = Path.GetFileNameWithoutExtension(assetPath);if(fileExtension != ".cs" && fileExtension != ".lua"){return false; }string stackTrace = GetStackTrace();if (string.IsNullOrEmpty(stackTrace)){return false;}if (stackTrace.Contains("<open in file>")) //对于一些比较长的日志,添加标签<open in file>,双击的时候再文本中打开{openLog(stackTrace);return true;}if (stackTrace.Contains("stack traceback:")) //lua输出堆栈信息,会自带 “"stack traceback:” 这描述,这里用来筛选lua日志{bool isOpenSuccess = false;if (stackTrace.Contains("<filePath>")){isOpenSuccess = OpenLua(stackTrace);}else{isOpenSuccess = OpenLuaDefault(stackTrace);}return isOpenSuccess;}return false;}/// <summary>/// 通关日志堆栈获取日志信息/// </summary>/// <returns></returns>private static string GetStackTrace(){var consoleWindowType = typeof(EditorWindow).Assembly.GetType("UnityEditor.ConsoleWindow");var fieldInfo = consoleWindowType.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic);var consoleWindowInstance = fieldInfo.GetValue(null);if (null != consoleWindowInstance && (object)EditorWindow.focusedWindow == consoleWindowInstance){fieldInfo = consoleWindowType.GetField("m_ActiveText", BindingFlags.Instance | BindingFlags.NonPublic);string activeText = fieldInfo.GetValue(consoleWindowInstance).ToString();return activeText;}return "";}/// <summary>/// 以文件个格式打开日志/// </summary>/// <param name="stackTrace"></param>private static void openLog(string stackTrace){string fileName = GetFileSaveName();string saveDirPath = Application.dataPath + "/../Debug";if (!Directory.Exists(saveDirPath)){Directory.CreateDirectory(saveDirPath);}string savePath = saveDirPath + "/" + fileName;File.WriteAllText(savePath, stackTrace);System.Diagnostics.Process.Start(savePath);}private static string GetFileSaveName(){DateTime currentTime = DateTime.Now;string customFormat = currentTime.ToString("yyyy_MM_dd_HH_mm_ss");string fileName = $"debug_{customFormat}.txt";return fileName;}static void SetExternalEditorPath(){string path = EditorUserSettings.GetConfigValue(EXTERNAL_EDITOR_PATH_KEY);path = EditorUtility.OpenFilePanel("设置lua文件默认打开的编辑器软件路径(选择exe文件)",path,"exe");if (path != ""){EditorUserSettings.SetConfigValue(EXTERNAL_EDITOR_PATH_KEY, path);Debug.Log("Set Editor Path: " + path);}} static void SetLuaProjectRoot(){string path = EditorUserSettings.GetConfigValue(LUA_PROJECT_ROOT_FOLDER_PATH_KEY);path = EditorUtility.OpenFolderPanel("设置lua项目根目录位置",path,"");if (path != ""){EditorUserSettings.SetConfigValue(LUA_PROJECT_ROOT_FOLDER_PATH_KEY, path);Debug.Log("Set Editor Path: " + path);}}static bool OpenLua(string logText){//获取lua文件路径Regex regex = new Regex(@"<filePath>.*<\/filePath>");Match match = regex.Match(logText);if (!match.Success){return false;}string filePath = match.Groups[0].Value.Trim();int length = filePath.Length - 10 - 11; //去掉开头和结尾的字符串 <filePath> </filePath>filePath = filePath.Substring(10, length);filePath = filePath.Replace(".", "/");if (!filePath.EndsWith(".lua")){filePath = filePath + ".lua";}//获取日志行号Regex lineRegex = new Regex(@"<line>.*<\/line>");match = lineRegex.Match(logText);if (!match.Success){return false;}string luaLineString = match.Groups[0].Value;luaLineString.Trim();length = luaLineString.Length - 6 - 7;luaLineString = luaLineString.Substring(6, length);int luaLine = int.Parse(luaLineString.Trim());return OpenFileAtLineExternal(filePath, luaLine);}/// <summary>/// 打开没有标签的日志对应的脚本/// </summary>/// <param name="logText"></param>/// <returns></returns>static bool OpenLuaDefault(string logText){int index = logText.IndexOf("stack traceback:");string temp = logText.Substring(index, logText.Length - index);string[] arr = temp.Split(':');if (arr.Length < 3){return false;}string filePath = arr[1].Trim();int luaLine = int.Parse(arr[2]);filePath = filePath + ".lua";return OpenFileAtLineExternal(filePath, luaLine);}/// <summary>/// 打开指定的文件/// </summary>/// <param name="fileName">文件名</param>/// <param name="line">行号</param>/// <returns></returns>static bool OpenFileAtLineExternal(string fileName, int line){string editorPath = EditorUserSettings.GetConfigValue(EXTERNAL_EDITOR_PATH_KEY);if (string.IsNullOrEmpty(editorPath) || !File.Exists(editorPath)){ // 没有path就弹出面板设置SetExternalEditorPath();}OpenFileWith(fileName, line);return true;}static void OpenFileWith(string fileName, int line){string editorPath = EditorUserSettings.GetConfigValue(EXTERNAL_EDITOR_PATH_KEY);System.Diagnostics.Process proc = new System.Diagnostics.Process();proc.StartInfo.FileName = editorPath;string projectRootPath = EditorUserSettings.GetConfigValue(LUA_PROJECT_ROOT_FOLDER_PATH_KEY);if (string.IsNullOrEmpty(projectRootPath)){SetLuaProjectRoot();return;}string procArgument = "";if (editorPath.IndexOf("idea") != -1) //idea{procArgument = string.Format("{0} --line {1} {2}", projectRootPath, line, fileName);}else if (editorPath.IndexOf("Code.exe") != -1) // VSCode{string filePath = Path.Combine(projectRootPath, fileName);procArgument = string.Format("-g {0}:{1}:0", filePath, line);}else{procArgument = string.Format("{0}:{1}:0", fileName, line);}proc.StartInfo.Arguments = procArgument;proc.Start(); }}
相关文章:

unity console日志双击响应事件扩展
1 对于项目中一些比较长的日志,比如前后端交互协议具体数据等,这些日志内容可能会比较长,在unity控制面板上查看不是十分方便,我们可以对双击事件进行扩展,将日志保存到一个文本中,然后用系统默认的文本查看…...
维度建模维度表技术基础解析(以电商场景为例)
维度建模维度表技术基础解析(以电商场景为例) 维度表是维度建模的核心组成部分,其设计直接影响数据仓库的查询效率、分析灵活性和业务价值。本文将从维度表的定义、结构、设计方法及典型技术要点展开,结合电商场景案例,深入解析其技术基础。 1. 维度表的定义与作用 定义…...

Leetcode 264-丑数/LCR 168/剑指 Offer 49
题目描述 我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。 示例: 说明: 1 是丑数。 n 不超过1690。 题解 动态规划法 根据题意,每个丑数都可以由其他较小的丑数通过乘以 2 或 3 或 5 得到…...
阿里云MaxCompute面试题汇总及参考答案
目录 简述 MaxCompute 的核心功能及适用场景,与传统数据仓库的区别 解释 MaxCompute 分层架构设计原则,与传统数仓分层有何异同 MaxCompute 的存储架构如何实现高可用与扩展性 解析伏羲(Fuxi)分布式调度系统工作原理 盘古(Pangu)分布式存储系统数据分片策略 计算与存…...
笔记:Directory.Build.targets和Directory.Build.props的区别
一、目的:分享Directory.Build.targets和Directory.Build.props的区别 Directory.Build.targets 和 Directory.Build.props 是 MSBuild 的两个功能,用于在特定目录及其子目录中的所有项目中应用共享的构建设置。它们的主要区别在于应用的时机和用途。 二…...
istio入门到精通-2
上部分讲到了hosts[*] 匹配所有的微服务,这部分细化一下 在 Istio 的 VirtualService 配置中,hosts 字段用于指定该虚拟服务适用的 目标主机或域名。如果使用具体的域名(如 example.com),则只有请求的主机 域名与 exa…...

第5章:vuex
第5章:vuex 1 求和案例 纯vue版2 vuex工作原理图3 vuex案例3.1 搭建vuex环境错误写法正确写法 3.2 求和案例vuex版细节分析源代码 4 getters配置项4.1 细节4.2 源代码 5 mapState与mapGetters5.1 总结5.2 细节分析5.3 源代码 6 mapActions与mapMutations6.1 总结6.2…...
[Python入门学习记录(小甲鱼)]第5章 列表 元组 字符串
第5章 列表 元组 字符串 5.1 列表 一个类似数组的东西 5.1.1 创建列表 一个中括号[ ] 把数据包起来就是创建了 number [1,2,3,4,5] print(type(number)) #返回 list 类型 for each in number:print(each) #输出 1 2 3 4 5#列表里不要求都是一个数据类型 mix [213,"…...

Docker 学习(四)——Dockerfile 创建镜像
Dockerfile是一个文本格式的配置文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。有了Dockerfile,当我们需要定制自己额外的需求时,只需在D…...

Java多线程与高并发专题——为什么 Map 桶中超过 8 个才转为红黑树?
引入 JDK 1.8 的 HashMap 和 ConcurrentHashMap 都有这样一个特点:最开始的 Map 是空的,因为里面没有任何元素,往里放元素时会计算 hash 值,计算之后,第 1 个 value 会首先占用一个桶(也称为槽点ÿ…...
LeetCode hot 100—二叉树的中序遍历
题目 给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。 示例 示例 1: 输入:root [1,null,2,3] 输出:[1,3,2]示例 2: 输入:root [] 输出:[]示例 3: 输入:root […...
代码随想录算法训练营第35天 | 01背包问题二维、01背包问题一维、416. 分割等和子集
一、01背包问题二维 二维数组,一维为物品,二维为背包重量 import java.util.Scanner;public class Main{public static void main(String[] args){Scanner scanner new Scanner(System.in);int n scanner.nextInt();int bag scanner.nextInt();int[…...

与中国联通技术共建:通过obdiag分析OceanBase DDL中的报错场景
中国联通软件研究院(简称联通软研院)在全面评估与广泛调研后,在 2021年底决定采用OceanBase 作为基础,自研分布式数据库产品CUDB(即China Unicom Database,中国联通数据库)。目前,该…...

IDEA 接入 Deepseek
在本篇文章中,我们将详细介绍如何在 JetBrains IDEA 中使用 Continue 插件接入 DeepSeek,让你的 AI 编程助手更智能,提高开发效率。 一、前置准备 在开始之前,请确保你已经具备以下条件: 安装了 JetBrains IDEA&…...
斗地主小游戏
<!DOCTYPE html> <html><head><meta charset="utf-8"><title>斗地主</title><style>.game-container {width: 1000px;height: 700px;margin: 0 auto;position: relative;background: #35654d;border-radius: 10px;padding…...

如何改变怂怂懦弱的气质(2)
你是否曾经因为害怕失败而逃避选择?是否因为不敢拒绝别人而让自己陷入困境?是否因为过于友善而被人轻视?如果你也曾为这些问题困扰,那么今天的博客就是为你准备的。我们将从行动、拒绝、自我认知、实力提升等多个角度,…...

C# OnnxRuntime部署DAMO-YOLO人头检测
目录 说明 效果 模型信息 项目 代码 下载 参考 说明 效果 模型信息 Model Properties ------------------------- --------------------------------------------------------------- Inputs ------------------------- name:input tensor:Floa…...

基于GeoTools的GIS专题图自适应边界及高宽等比例生成实践
目录 前言 一、原来的生成方案问题 1、无法自动读取数据的Bounds 2、专题图高宽比例不协调 二、专题图生成优化 1、直接读取矢量数据的Bounds 2、专题图成果抗锯齿 3、专题成果高宽比例自动调节 三、总结 前言 在当今数字化浪潮中,地理信息系统(…...

各种DCC软件使用Datasmith导入UE教程
3Dmax: 先安装插件 https://www.unrealengine.com/zh-CN/datasmith/plugins 左上角导出即可 虚幻中勾选3个插件,重启引擎 左上角选择文件导入即可 Blender导入Datasmith进UE 需要两个插件, 文章最下方链接进去下载安装即可 一样的,直接导出,然后UE导入即可 C4D 直接保存成…...

尚硅谷爬虫note15
一、当当网 1. 保存数据 数据交给pipelines保存 items中的类名: DemoNddwItem class DemoNddwItem(scrapy.Item): 变量名 类名() book DemoNddwItem(src src, name name, price price)导入: from 项目名.items import 类…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...