Unity日志工具——封装,跳转
By D.S.Qiu
尊重他人的劳动,支持原创,转载请注明出处:http://dsqiu.iteye.com
好久没有写博客分享了,主要有三个原因:1.iteye博客不支持公式等高级特性的支持(不知道iteye的产品经理是怎么想的),就一直想自己搭建一个类似stackedit.io编辑器的博客站点,一直没有憋出来就一直没继续写了;2.自己想做的事情太多了(比如像写一个Visual Studio MFC的那种界面一样的Unity UGUI编辑工具,写博客花了太多时间了,可是还是没有憋出来(忧伤啊);3.之前写的大部分就没有什么含量,当然是作为自己学习的一个途径吧,所以还是需要大量的积累先!
应该所有的团队都会自己封装日志工具,除非引擎已经集成了,在Unity也不例外,当时之前的同事封装了一个有一个很大不爽的地方是:从Unity ConsoleWindow 双击日志跳转到代码总是跳转到封装类中的函数,而不能直接跳转到调用封装类被调用的地方。
切好在准备新项目,我把原来不够优良的地方都进行了改进直至尽可能的完美。之前一直就知道1.利用反射可以获取Unity的private FieldInfo和 MethodInfo 可以做很多事情,2.可以利用Unity提供的api调整到指定的代码中去,3.Unity提供跳转回调的机制。算是理论只是具备了,今天来公司就把这个给写出来了,当然还对LogLevel和StackFrame信息进行了优化(之前的有点丑,是13年一个前前同事写的)。
其实是很简单的,直接说下思路吧(Unity5.3):
1.记录通过封装日志工具的函数调用栈信息 StackFrame。
2.添加UnityEditor.Callbacks.OnOpenAssetAttribute(0)的回调方法,处理从ConsoleWindow双击跳转
3.利用反射获取ConsoleWindow 的 ListeViewState 的 row(当前双击的行)和总行数
4.利用3得到行数反射获取LogEntry信息进行匹配获得对应StackFrame
5.调用AssetDatabase.OpenAsset()即可。
更新到Unity5.3发现,他提供Logger这个类,本来还以为可以实现这些功能,不过简单测试下来发现是不行的,我就还不清楚Unity构造一个Logger类是干嘛的,搞得我把下面的类名改成LoggerUtility。
贴下完整的代码:
/* * File: Assets/Scripts/Game/Utility/LoggerUtility.cs * Project: **** * Company: Lucky * Code Porter: D.S.Qiu * Create Date: 10/9/2015 10:11:53 PM */ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; #if UNITY_EDITOR using System.Reflection; using UnityEditor; using UnityEditor.Callbacks; #endif using UnityEngine; using Debug = UnityEngine.Debug; namespace Utility { public class LogUtility { public enum LogLevel : byte { None = 0, Exception = 1, Error = 2, Warning = 3, Info = 4, } public static LogLevel logLevel = LogLevel.Info; public static string infoColor = "#909090"; public static string warningColor = "orange"; public static string errorColor = "red"; public static void LogBreak(object message, UnityEngine.Object sender = null) { LogInfo(message, sender); Debug.Break(); } public static void LogFormat(string format, UnityEngine.Object sender, params object[] message) { if (logLevel >= LogLevel.Info) LogLevelFormat(LogLevel.Info, string.Format(format, message), sender); } public static void LogFormat(string format, params object[] message) { if (logLevel >= LogLevel.Info) LogLevelFormat(LogLevel.Info, string.Format(format, message), null); } public static void LogInfo(object message, UnityEngine.Object sender = null) { if(logLevel >= LogLevel.Info) LogLevelFormat(LogLevel.Info,message,sender); } public static void LogWarning(object message, UnityEngine.Object sender = null) { if (logLevel >= LogLevel.Warning) LogLevelFormat(LogLevel.Warning, message, sender); } public static void LogError(object message, UnityEngine.Object sender = null) { if (logLevel >= LogLevel.Error) { LogLevelFormat(LogLevel.Error, message, sender); } } public static void LogException(Exception exption, UnityEngine.Object sender = null) { if (logLevel >= LogLevel.Exception) { LogLevelFormat(LogLevel.Exception, exption, sender); } } private static void LogLevelFormat(LogLevel level, object message, UnityEngine.Object sender) { string levelFormat = level.ToString().ToUpper(); StackTrace stackTrace = new StackTrace(true); var stackFrame = stackTrace.GetFrame(2); #if UNITY_EDITOR s_LogStackFrameList.Add(stackFrame); #endif string stackMessageFormat = Path.GetFileName(stackFrame.GetFileName()) + ":" + stackFrame.GetMethod().Name + "():at line " + stackFrame.GetFileLineNumber(); string timeFormat = "Frame:" + Time.frameCount + "," + DateTime.Now.Millisecond + "ms"; string objectName = string.Empty; string colorFormat = infoColor; if (level == LogLevel.Warning) colorFormat = warningColor; else if (level == LogLevel.Error) colorFormat = errorColor; StringBuilder sb = new StringBuilder(); sb.AppendFormat("<color={3}>[{0}][{4}][{1}]{2}</color>", levelFormat, timeFormat, message, colorFormat, stackMessageFormat); Debug.Log(sb,sender); } #if UNITY_EDITOR private static int s_InstanceID; private static int s_Line = 104; private static List<StackFrame> s_LogStackFrameList = new List<StackFrame>(); //ConsoleWindow private static object s_ConsoleWindow; private static object s_LogListView; private static FieldInfo s_LogListViewTotalRows; private static FieldInfo s_LogListViewCurrentRow; //LogEntry private static MethodInfo s_LogEntriesGetEntry; private static object s_LogEntry; //instanceId 非UnityEngine.Object的运行时 InstanceID 为零所以只能用 LogEntry.Condition 判断 private static FieldInfo s_LogEntryInstanceId; private static FieldInfo s_LogEntryLine; private static FieldInfo s_LogEntryCondition; static LogUtility() { s_InstanceID = AssetDatabase.LoadAssetAtPath<MonoScript>("Assets/Scripts/Game/Utility/LoggerUtility.cs").GetInstanceID(); s_LogStackFrameList.Clear(); GetConsoleWindowListView(); } private static void GetConsoleWindowListView() { if (s_LogListView == null) { Assembly unityEditorAssembly = Assembly.GetAssembly(typeof(EditorWindow)); Type consoleWindowType = unityEditorAssembly.GetType("UnityEditor.ConsoleWindow"); FieldInfo fieldInfo = consoleWindowType.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic); s_ConsoleWindow = fieldInfo.GetValue(null); FieldInfo listViewFieldInfo = consoleWindowType.GetField("m_ListView", BindingFlags.Instance | BindingFlags.NonPublic); s_LogListView = listViewFieldInfo.GetValue(s_ConsoleWindow); s_LogListViewTotalRows = listViewFieldInfo.FieldType.GetField("totalRows", BindingFlags.Instance | BindingFlags.Public); s_LogListViewCurrentRow = listViewFieldInfo.FieldType.GetField("row", BindingFlags.Instance | BindingFlags.Public); //LogEntries Type logEntriesType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntries"); s_LogEntriesGetEntry = logEntriesType.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public); Type logEntryType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntry"); s_LogEntry = Activator.CreateInstance(logEntryType); s_LogEntryInstanceId = logEntryType.GetField("instanceID", BindingFlags.Instance | BindingFlags.Public); s_LogEntryLine = logEntryType.GetField("line", BindingFlags.Instance | BindingFlags.Public); s_LogEntryCondition = logEntryType.GetField("condition", BindingFlags.Instance | BindingFlags.Public); } } private static StackFrame GetListViewRowCount() { GetConsoleWindowListView(); if (s_LogListView == null) return null; else { int totalRows = (int)s_LogListViewTotalRows.GetValue(s_LogListView); int row = (int)s_LogListViewCurrentRow.GetValue(s_LogListView); int logByThisClassCount = 0; for (int i = totalRows - 1; i >= row; i--) { s_LogEntriesGetEntry.Invoke(null, new object[] { i, s_LogEntry }); string condition = s_LogEntryCondition.GetValue(s_LogEntry) as string; //判断是否是由LoggerUtility打印的日志 if (condition.Contains("][") && condition.Contains("Frame")) logByThisClassCount++; } //同步日志列表,ConsoleWindow 点击Clear 会清理 while (s_LogStackFrameList.Count > totalRows) s_LogStackFrameList.RemoveAt(0); if (s_LogStackFrameList.Count >= logByThisClassCount) return s_LogStackFrameList[s_LogStackFrameList.Count - logByThisClassCount]; return null; } } [UnityEditor.Callbacks.OnOpenAssetAttribute(0)] public static bool OnOpenAsset(int instanceID, int line) { if (instanceID == s_InstanceID && s_Line == line) { var stackFrame = GetListViewRowCount(); if (stackFrame != null) { string fileName = stackFrame.GetFileName(); string fileAssetPath = fileName.Substring(fileName.IndexOf("Assets")); AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath<MonoScript>(fileAssetPath), stackFrame.GetFileLineNumber()); return true; } } return false; } #endif } }
小结:
其实都没有什么小结的,多说几句:对于这个日志工具我还会进一步增加两个优化:远程日志和通过字符串反射查询运行时的值(前端调试还是没有后端的来的方便,打断点太低效了)。雨松MOMO最近分享了很多Editor的小trick,可以去他的博客和微博上找下,这里分享一个他反编译Unity5.3的Bitbucket代码 ,不过还不够完美,反编译的看不到private的 FieldInfo 和 MethdInfo ,这个也很有用。
欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德!
如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。
转载请在文首注明出处:http://dsqiu.iteye.com/blog/2263664
更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)
相关推荐
今天给大家提供一个自动去掉图片mipmap勾选的小工具。对于UI贴图来说,我们不必应用mipmap,因为一般的UI都是“平铺”在正交摄像机视口的,和摄像机木有距离这一概念,所以我们大可以把UI贴图的mipmap选项去掉,以...
本教程将围绕"Unity编辑器——日期选择窗口插件"这一主题展开,详细讲解如何创建一个在编辑器内部弹出的日历选择窗口,以方便用户在制作游戏或应用时选取日期。这涉及到Unity的EditorWindow类、GUI系统以及可能的...
1. unity.udbx数据源里存储了...4.“TextScripts”文件夹里是SuperMap 3D SDKs_Unity插件开发——连接数据服务进行SQL查询并过滤显示的实现过程。将它复制粘贴到您的unity项目的Assets目录下。
"Unity3D模型——真名法典zeph全套动作加骨骼" 是一个针对Unity3D引擎的资源包,它包含了用于游戏或交互式项目的模型、动作和骨骼系统。这个资源包可能特别适用于那些需要丰富角色动画和动态效果的游戏。 首先,...
自己用Unity5.2.3写的一个小游戏——拼图,内含所有的代码及场景文件
Unity高亮插件,高亮物体,可调颜色,闪烁类型,延迟闪烁等参数
Unity插件——Best HTTP 封装好的网络插件,节省自己写http的时间
用法可参见http://blog.csdn.net/candycat1992/article/details/10940245
在Unity3D开发中,有时候我们需要在游戏或者应用中实现网页的跳转功能,这在Web Player版本的项目中尤其常见。然而,Unity3D在发布为Web格式后,网页跳转的方式与常规的浏览器行为有所不同,可能会导致一些问题。...
Unity日志查看器是一款专为开发者设计的工具,旨在帮助他们在Android或iOS设备上便捷地查看Unity引擎生成的日志信息。Unity作为一个强大的跨平台游戏开发框架,其在运行时会生成大量的日志数据,这对于调试、性能...
总之,AssetStudio作为Unity解包工具,为开发者提供了深入探索和管理Unity项目资源的能力,是Unity开发过程中一个实用的辅助工具。正确使用它可以提高开发效率,同时也能帮助开发者更好地理解和优化他们的项目。
Unity3D——射击游戏
原生的Unity UnityEngine.Debug类提供了丰富的调试工具,如Log、LogError、LogWarning等方法,用于输出信息到控制台。然而,在发布游戏时,这些调试信息通常需要关闭,以减少性能损耗和避免泄露敏感信息。本项目就是...
版本 2.4.4(当前版本) 发布时间:2021年6月3日 2.4.4 [Common]Updated to OpenCV4.5.2. [Common]Added VideoCaptureCameraInputExample and BackgroundSubtractorComparisonExample. [Common]Updated ...
一共有两份,这是第一份。 Unity3D——贴花.part1 Unity3D——贴花.part1
Unity日志输出插件,在手机和pc上均有用,能很清晰的看到日志打印,非常好用。...使用这个工具,你可以很容易地在游戏内部检查你的编辑器控制台日志!在移动端同样有效! 支持unity2017.4.29 or 更高版本
Unity3D是一款强大的跨平台游戏开发工具,被广泛应用于创建2D、3D游戏以及交互式体验内容。在这个“unity素材——可以直接用”的资源包中,包含了一个名为"Chapter4_AngryBotsAssets"的文件,这通常代表了一次Unity...
AssetStudio 是一款强大的Unity资源分析和解包工具,主要用于帮助开发者和逆向工程爱好者解析Unity游戏或应用中的资源文件。这个工具能够提取出各种类型的资产,包括3D模型、纹理、音频、脚本、动画等,对于游戏开发...