不需要反射,不需要了解 Unity 源码,只需要一点点🤏额外的技巧即可~
<aside>
<aside> 🌟
Achieving a mental-leap request needs a mental-leap solution.
</aside>
</aside>
这是开发过程中面临的一个很现实的需求,对于大量的 lua 代码来说,怎样可以将 lua 打印到控制台的日志,能快速准确的定位到代码行,可以很大的提高我们对日志定位的效率,从而进一步提高 debug 的效率。
我们知道双击控制台日志会跳转到打印此行日志对应的 C# 代码行,而单击日志会跳转到日志对应的上下文(如果有的话)。 lua 侧打印的日志,实际还是会回到 C# 调用 Unity 的日志接口,这时双击日志也仅仅会跳转到 C# 的打印语句(包括主观打印的日志或报错导致的日志)。那么除了单击和双击这两种已经存在的方式,还有第三种方式吗?有!
Unity 日志中可以添加超链接,效果即蓝色文字的部分:点击超链接的效果和双击日志是相同的。

所以基本思路就是通过向 Lua 日志堆栈信息中添加超链接的方式,将超链接的目标指向 lua 代码。拆解一下这个方案,分成如下几个步骤:
这一步其实就是 xLua 中通过添加 CustomLuaLoader 函数并修改其中的文件路径为真实完整的文件路径(相对路径即可),这样无论是在调试还是日志的堆栈信息中都能显示正确的文件路径。
private static LuaEnv _luaEnv;
private void InitEnv()
{
_luaEnv = new LuaEnv();
_luaEnv.AddLoader(CustomLuaLoader);
}
// LoadScriptContent 方法为读取文件的函数;
// 由于仅编辑器使用,所以添加 UNITY_EDITOR 判断;
byte[] CustomLuaLoader(ref string fileName)
{
#if UNITY_EDITOR
//for debug purpose
byte[] bytes = LoadScriptContent(fileName);
fileName = $"{fileName.Replace(".", "/")}.lua";
return bytes;
#else
return LoadScriptContent(fileName);
#endif
}
这个方法本质上是 lua 代码中的 require 的实现,fileName 为 require 的参数。需要注意的是,lua 代码中使用 require 时,文件相对路径是正确的。因此将 . 替换为 / 并添加 .lua 后缀,即完整的相对路径了。
在 lua 打印日志时,需要向 C# 传递堆栈信息中包含了上面的文件路径。HTML 的超链接的格式如下:
<a href="filePath" line="lineNo"></a>
其中两个参数代表了点击链接后跳转的 C# 脚本和行数。
如何获得文件路径,需要我们对日志里的堆栈信息字符串格式进行一些处理,并提取出行数和文件路径。需要注意,堆栈信息中可能包含多行文件调用,取决于设置的堆栈深度。
public static string ParseLuaTraceback(string traceback)
{
if (string.IsNullOrEmpty(traceback))
{
return string.Empty;
}
StringBuilder sb = new StringBuilder();
foreach (string line in traceback.Split('\\r', '\\n'))
{
if (line.Contains("stack traceback"))
{
sb.AppendLine(line);
continue;
}
if (line.Contains("util/log.lua"))
continue;
string[] content = line.Split(':');
if (content.Length <= 1)
{
sb.AppendLine(line);
continue;
}
string fileName = content[0].Trim();
if (fileName.Contains("[") && fileName.Contains("]"))
{
string[] str = fileName.Split('\\"');
if (str.Length != 3)
{
sb.AppendLine(fileName);
continue;
}
fileName = str[1];
}
string filePath = $"{Application.dataPath}/../../Resources/Lua/{fileName}";
int lineNumber = content.Length > 2 ? int.Parse(content[1]) : 0;
string info = content.Length > 2 ? content[2].Trim() : content[1].Trim();
int guid = RegisterLogMessage($"{filePath}|{lineNumber}");
sb.AppendLine($"<a href=\\"Assets/Editor/Utils/AssetOpenHandler.cs\\" line=\\"{guid}\\">[{fileName}:{lineNumber}]</a> {info}");
}
return sb.ToString();
}
Unity 的代码跳转到接口的回调是 UnityEditor.Callbacks.OnOpenAssetAttribute 标签的函数,其中两个参数一个为文件的 InstanceId,一个是行数,而文件的 InstanceId 指向的是跳转的 C# 脚本,只有一个行数参数,怎么传递 lua 的文件路径和行数呢?我们可以是先将其进行缓存,并分配一个 guid,将 guid 通过行数传递给 C# 回调。