作为一款轻量级开源的Unity程序框架,QFramework结合了作者凉鞋多年的开发经验,是比较值得想要学习框架的初学者窥探一二的对象,我就尝试结合凉鞋大大给出的文档和ai,解析一下其背后的代码逻辑,以作提升自己的一次试炼
声明:本系列不做任何商业用途,权当学习交流,而且我是个菜鸡算法能力和代码能力都不如常人,如有不足还请斧正
03. CodeGenKit 脚本生成 — QFramework 1.0.60 文档
讲的是这个东西
0.总览与疑问
我有一个疑问就是,这部分大概有三百多行了
因此我不是很清楚凉鞋大大写的该框架大约一千多行代码是什么意思
然后官网文档也没看到这部分(可能是我没找到?)因此十分不解,是不是因为这个并不算做框架主体内容,就是边角料部分呢??
/****************************************************************************
* Copyright (c) 2016 - 2023 liangxiegame UNDER MIT License
*
* https://qframework.cn
* https://github.com/liangxiegame/QFramework
* https://gitee.com/liangxiegame/QFramework
*
****************************************************************************/
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
namespace QFramework
{
using System;
using UnityEngine;
#if UNITY_EDITOR
[ClassAPI("02.LogKit", "LogKit", 4)]
[APIDescriptionCN("简单的日志工具")]
[APIDescriptionEN("Simple Log ToolKit")]
#endif
public class LogKit
{
#if UNITY_EDITOR
// v1 No.157
[MethodAPI]
[APIDescriptionCN("Debug.Log & Debug.LogFormat")]
[APIDescriptionEN("Debug.Log & Debug.LogFormat")]
[APIExampleCode(@"
LogKit.I(""Hello LogKit"");
// Hello LogKit
LogKit.I(""Hello LogKit {0}{1}"",1,2);
// Hello LogKit 12
""Hello LogKit FluentAPI"".LogInfo();
// Hello LogKit FluentAPI
")]
#endif
public static void I(object msg, params object[] args)
{
if (mLogLevel < LogLevel.Normal)
{
return;
}
if (args == null || args.Length == 0)
{
Debug.Log(msg);
}
else
{
Debug.LogFormat(msg.ToString(), args);
}
}
#if UNITY_EDITOR
// v1 No.158
[MethodAPI]
[APIDescriptionCN("Debug.LogWarning & Debug.LogWaringFormat")]
[APIDescriptionEN("Debug.LogWarning & Debug.LogWarningFormat")]
[APIExampleCode(@"
LogKit.E(""Hello LogKit"");
// Hello LogKit
LogKit.E(""Hello LogKit {0}{1}"",1,2);
// Hello LogKit 12
""Hello LogKit FluentAPI"".LogError();
// Hello LogKit FluentAPI
")]
#endif
public static void W(object msg, params object[] args)
{
if (mLogLevel < LogLevel.Warning)
{
return;
}
if (args == null || args.Length == 0)
{
Debug.LogWarning(msg);
}
else
{
Debug.LogWarningFormat(msg.ToString(), args);
}
}
#if UNITY_EDITOR
// v1 No.159
[MethodAPI]
[APIDescriptionCN("Debug.LogError & Debug.LogErrorFormat")]
[APIDescriptionEN("Debug.LogError & Debug.LogErrorFormat")]
[APIExampleCode(@"
LogKit.E(""Hello LogKit"");
// Hello LogKit
LogKit.E(""Hello LogKit {0}{1}"",1,2);
// Hello LogKit 12
""Hello LogKit FluentAPI"".LogError();
// Hello LogKit FluentAPI
")]
#endif
public static void E(object msg, params object[] args)
{
if (mLogLevel < LogLevel.Error)
{
return;
}
if (args == null || args.Length == 0)
{
Debug.LogError(msg);
}
else
{
Debug.LogError(string.Format(msg.ToString(), args));
}
}
#if UNITY_EDITOR
// v1 No.160
[MethodAPI]
[APIDescriptionCN("Debug.LogException")]
[APIDescriptionEN("Debug.LogException")]
[APIExampleCode(@"
LogKit.E(""Hello LogKit"");
// Hello LogKit
LogKit.E(""Hello LogKit {0}{1}"",1,2);
// Hello LogKit 12
""Hello LogKit FluentAPI"".LogError();
// Hello LogKit FluentAPI
")]
#endif
public static void E(Exception e)
{
if (mLogLevel < LogLevel.Exception)
{
return;
}
Debug.LogException(e);
}
#if UNITY_EDITOR
// v1 No.161
[MethodAPI]
[APIDescriptionCN("获得 StringBuilder 用来拼接日志")]
[APIDescriptionEN("get stringBuilder for generate log string")]
[APIExampleCode(@"
LogKit.Builder()
.Append(""Hello"")
.Append("" LogKit"")
.ToString()
.LogInfo();
// Hello LogKit
")]
#endif
public static StringBuilder Builder()
{
return new StringBuilder();
}
public enum LogLevel
{
None = 0,
Exception = 1,
Error = 2,
Warning = 3,
Normal = 4,
Max = 5,
}
private static LogLevel mLogLevel = LogLevel.Normal;
#if UNITY_EDITOR
// v1 No.162
[PropertyAPI]
[APIDescriptionCN("日志等级设置")]
[APIDescriptionEN("log level")]
[APIExampleCode(@"
LogKit.Level = LogKit.LogLevel.None;
LogKit.I(""LogKit""); // no output
LogKit.Level = LogKit.LogLevel.Exception;
LogKit.Level = LogKit.LogLevel.Error;
LogKit.Level = LogKit.LogLevel.Warning;
LogKit.Level = LogKit.LogLevel.Normal;
LogKit.I(""LogKit"");
// LogKit
LogKit.Level = LogKit.LogLevel.Max;
")]
#endif
public static LogLevel Level
{
get => mLogLevel;
set => mLogLevel = value;
}
}
public static class LogKitExtension
{
public static StringBuilder GreenColor(this StringBuilder self, Action<StringBuilder> childContent)
{
self.Append("<color=green>");
childContent?.Invoke(self);
self.Append("</color>");
return self;
}
public static void LogInfo<T>(this T selfMsg)
{
LogKit.I(selfMsg);
}
public static void LogWarning<T>(this T selfMsg)
{
LogKit.W(selfMsg);
}
public static void LogError<T>(this T selfMsg)
{
LogKit.E(selfMsg);
}
public static void LogException(this Exception selfExp)
{
LogKit.E(selfExp);
}
}
#if UNITY_EDITOR
// * 参考: https://zhuanlan.zhihu.com/p/92291084
// 感谢 https://github.com/clksaaa 提供的 issue 反馈 和 解决方案
public static class OpenAssetLogLine
{
private static bool m_hasForceMono = false;
// 处理asset打开的callback函数
[UnityEditor.Callbacks.OnOpenAssetAttribute(-1)]
static bool OnOpenAsset(int instance, int line)
{
if (m_hasForceMono) return false;
// 自定义函数,用来获取log中的stacktrace,定义在后面。
string stack_trace = GetStackTrace();
// 通过stacktrace来定位是否是自定义的log,log中有LogKit/LogKit.cs,很好识别
if (!string.IsNullOrEmpty(stack_trace) && stack_trace.Contains("LogKit/LogKit.cs"))
{
// 正则匹配at xxx,在第几行
Match matches = Regex.Match(stack_trace, @"\(at (.+)\)", RegexOptions.IgnoreCase);
string pathline = "";
while (matches.Success)
{
pathline = matches.Groups[1].Value;
// 找到不是我们自定义log文件的那行,重新整理文件路径,手动打开
if (!pathline.Contains("LogKit/LogKit.cs") && !string.IsNullOrEmpty(pathline))
{
int split_index = pathline.LastIndexOf(":");
string path = pathline.Substring(0, split_index);
line = Convert.ToInt32(pathline.Substring(split_index + 1));
m_hasForceMono = true;
//方式一
AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path), line);
m_hasForceMono = false;
//方式二
//string fullpath = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Assets"));
// fullpath = fullpath + path;
// UnityEditorInternal.InternalEditorUtility.OpenFileAtLineExternal(fullpath.Replace('/', '\\'), line);
return true;
}
matches = matches.NextMatch();
}
return true;
}
return false;
}
static string GetStackTrace()
{
// 找到类UnityEditor.ConsoleWindow
var type_console_window = typeof(EditorWindow).Assembly.GetType("UnityEditor.ConsoleWindow");
// 找到UnityEditor.ConsoleWindow中的成员ms_ConsoleWindow
var filedInfo =
type_console_window.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic);
// 获取ms_ConsoleWindow的值
var ConsoleWindowInstance = filedInfo.GetValue(null);
if (ConsoleWindowInstance != null)
{
if ((object)EditorWindow.focusedWindow == ConsoleWindowInstance)
{
// 找到类UnityEditor.ConsoleWindow中的成员m_ActiveText
filedInfo = type_console_window.GetField("m_ActiveText",
BindingFlags.Instance | BindingFlags.NonPublic);
string activeText = filedInfo.GetValue(ConsoleWindowInstance).ToString();
return activeText;
}
}
return null;
}
}
#endif
}
1.LogLevel 枚举 前提
在这之前应该认识一个枚举用来控制日志输出等级,不知道为什么凉鞋大大把这个枚举写到脚本中间去了
public enum LogLevel
{
None = 0,
Exception = 1,
Error = 2,
Warning = 3,
Normal = 4,
Max = 5,
}
private static LogLevel mLogLevel = LogLevel.Normal;
其属性
public static LogLevel Level
{
get => mLogLevel;
set => mLogLevel = value;
}
2.LogKit 类 日志核心
LogKit 类是日志工具的核心类,提供了多种日志记录方法和日志等级设置
给出了几个方法,I=Log,E=Error,W=Warning
以I举例,形参msg:信息本体,args:可变长参数信息
public static void I(object msg, params object[] args)
{
//判断枚举类型
if (mLogLevel < LogLevel.Normal)
{
return;
}
if (args == null || args.Length == 0)
{
Debug.Log(msg);
}
else
{
Debug.LogFormat(msg.ToString(), args);
}
}
经过日志等级判断后通过UnityEngine下的LogFromat输出
LogKit.I("Hello LogKit{0},{1}",123456,789);
另外Error还需要这个东西,用来捕获异常的
try
{
// 可能会引发异常的代码
int[] arr = null;
int value = arr[0];
}
catch (Exception e)
{
LogKit.E(e);
}
System下的Exception 是 C# 中表示异常的基类,常见的异常有
NullReferenceException
(当尝试访问空引用的成员时)、ArgumentException
(当传递给方法的参数不合法时)、FileNotFoundException
(当尝试访问不存在的文件时)等
3.LogKitExtension类 封装拓展
这里对StringBuilder 类进行了拓展
就是把传进来的字符串变绿或者其他什么的
public static StringBuilder Builder()
{
return new StringBuilder();
}
public static StringBuilder GreenColor(this StringBuilder self, Action<StringBuilder> childContent)
{
self.Append("<color=green>");
childContent?.Invoke(self);
self.Append("</color>");
return self;
}
然后就是对LogKit里面的方法的再封装,
public static void LogInfo<T>(this T selfMsg)
{
LogKit.I(selfMsg);
}
public static void LogWarning<T>(this T selfMsg)
{
LogKit.W(selfMsg);
}
public static void LogError<T>(this T selfMsg)
{
LogKit.E(selfMsg);
}
public static void LogException(this Exception selfExp)
{
LogKit.E(selfExp);
}
这里是比较巧妙的:
1.是因为编译器自动将string传了进去 也就是泛型的隐式转换
2.this在这里是扩展方法关键词,允许你向现有类型添加新的方法,而无需修改该类型的原始代码,也就说给string添加了拓展方法,因此一个字符串可以直接点出该方法
"Hellow Qf".LogInfo( );
4.OpenAssetLogLine类 定位错误
起到一个定位错误行的作用,是一种对原来unity定位错误行方法的再封装,比如
但是这部分信息量太大了,System和UnityEngine的方法没有细读过,因此这部分代码读起来十分费劲,让ai办了
using UnityEngine;
using UnityEditor;
using System.Text.RegularExpressions;
using System.Reflection;
public static class OpenAssetLogLine
{
// 标志,用于防止多次执行打开文件的操作
private static bool m_hasForceMono = false;
// 处理 asset 打开的 callback 函数
[UnityEditor.Callbacks.OnOpenAssetAttribute(-1)]
static bool OnOpenAsset(int instance, int line)
{
// 检查是否已经在处理打开文件操作,如果是则返回 false,避免重复操作
if (m_hasForceMono) return false;
// 自定义函数,用来获取 log 中的 stacktrace,定义在后面
string stack_trace = GetStackTrace();
// 通过 stacktrace 来定位是否是自定义的 log,log 中有 LogKit/LogKit.cs,很好识别
if (!string.IsNullOrEmpty(stack_trace) && stack_trace.Contains("LogKit/LogKit.cs"))
{
// 正则匹配 at xxx,在第几行
Match matches = Regex.Match(stack_trace, @"\(at (.+)\)", RegexOptions.IgnoreCase);
string pathline = "";
while (matches.Success)
{
// 获取匹配的组的值
pathline = matches.Groups[1].Value;
// 找到不是我们自定义 log 文件的那行,重新整理文件路径,手动打开
if (!pathline.Contains("LogKit/LogKit.cs") &&!string.IsNullOrEmpty(pathline))
{
// 找到路径和行号的分隔符的位置
int split_index = pathline.LastIndexOf(":");
// 获取文件路径
string path = pathline.Substring(0, split_index);
// 获取行号并转换为整数
line = Convert.ToInt32(pathline.Substring(split_index + 1));
m_hasForceMono = true;
// 方式一:使用 AssetDatabase 打开指定路径和行号的资产文件
AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path), line);
m_hasForceMono = false;
// 方式二:
// string fullpath = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Assets"));
// fullpath = fullpath + path;
// UnityEditorInternal.InternalEditorUtility.OpenFileAtLineExternal(fullpath.Replace('/', '\\'), line);
return true;
}
// 继续匹配下一个结果
matches = matches.NextMatch();
}
return true;
}
return false;
}
// 获取堆栈跟踪信息
static string GetStackTrace()
{
// 找到类 UnityEditor.ConsoleWindow
var type_console_window = typeof(EditorWindow).Assembly.GetType("UnityEditor.ConsoleWindow");
// 找到 UnityEditor.ConsoleWindow 中的成员 ms_ConsoleWindow
var filedInfo =
type_console_window.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic);
// 获取 ms_ConsoleWindow 的值
var ConsoleWindowInstance = filedInfo.GetValue(null);
if (ConsoleWindowInstance!= null)
{
// 检查当前聚焦的窗口是否是控制台窗口
if ((object)EditorWindow.focusedWindow == ConsoleWindowInstance)
{
// 找到类 UnityEditor.ConsoleWindow 中的成员 m_ActiveText
filedInfo = type_console_window.GetField("m_ActiveText",
BindingFlags.Instance | BindingFlags.NonPublic);
// 获取 m_ActiveText 的值,该值包含堆栈跟踪信息
string activeText = filedInfo.GetValue(ConsoleWindowInstance).ToString();
return activeText;
}
}
return null;
}
}