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 类…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...