在雨松MOMO的微博上发现可以传入lua文件路径和行数,在sublime中直接定位
由于最近一直在写lua, 也苦于开发工具的匮乏,所以就捣鼓了一下:
下面可以看到lua代码输出的内容,双击这一条log,就根据我的配置打开sublime跳转到对应的文件以及行:
使用方法:将下面的C#代码放到工程Editor目录下面,然后在菜单栏Tools下面有两个按钮,分别配置sublime可执行文件的路径和lua工程的根目录。
另外需要注意的是,lua层的输出有格式规范,从上面的示例图片中可以考到,lua文件的相对路径信息在和中间,行号也在和之间。在lua层我是这么做的:
在lua执行的最开始,加入这么一段代码以替换全局的print函数,然后由外部调用该函数,会得到调用处的文件信息和行号,输出到console中,之后就会根据这些信息来定位到相应的lua文件和行。
local tempPrint = print
function print(...)
local tempLog = ""
for _, singleLog in ipairs({...}) do
tempLog = tempLog..singleLog
end
tempPrint(tempLog, "" , debug.getinfo(2).short_src ,"" ,debug.getinfo(2).currentline ,"")
end
下面是C#代码:
// http://blog.csdn.net/rickshaozhiheng
// OpenLuaHelper.cs
// Created by zhiheng.shao
// Copyright 2017年 zhiheng.shao. All rights reserved.
//
// Description
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
public class OpenLuaHelper : Editor
{
private const string EXTERNAL_EDITOR_PATH_KEY = "mTv8";
private const string LUA_PROJECT_ROOT_FOLDER_PATH_KEY = "obUd";
//[UnityEditor.Callbacks.OnOpenAssetAttribute(0)]
//static bool OnOpenAsset(int instanceID, int line)
//{
// string name = EditorUtility.InstanceIDToObject(instanceID).name;
// Debug.Log("Open Asset Step1,asset name=>" + name);
// return true;
//}
[UnityEditor.Callbacks.OnOpenAssetAttribute(2)]
public static bool OnOpenAsset2(int instanceID, int line)
{
string logText = GetLogText();
if (string.IsNullOrEmpty(logText))
return false;
// get filePath
Regex regex = new Regex(@".*<\/filePath>" );
Match match = regex.Match(logText);
string filePath = match.Groups[0].Value.Trim();
int length = filePath.Length - 10 - 12;
filePath = filePath.Substring(10, length);
filePath = filePath.Replace(".", "/");
string luaFolderRoot = EditorUserSettings.GetConfigValue(LUA_PROJECT_ROOT_FOLDER_PATH_KEY);
if (string.IsNullOrEmpty(luaFolderRoot))
{
SetLuaProjectRoot();
luaFolderRoot = EditorUserSettings.GetConfigValue(LUA_PROJECT_ROOT_FOLDER_PATH_KEY);
}
filePath = luaFolderRoot.Trim() + "/" + filePath.Trim() + ".lua";
// get line number
Regex lineRegex = new Regex(@".*<\/line>" );
match = lineRegex.Match(logText);
string luaLineString = match.Groups[0].Value;
luaLineString.Trim();
length = luaLineString.Length - 6 -11;
luaLineString = luaLineString.Substring(10, length);
int luaLine = int.Parse(luaLineString.Trim());
return OpenFileAtLineExternal(filePath, luaLine);
}
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;
proc.StartInfo.Arguments = string.Format("{0}:{1}:0", fileName, line);
proc.Start();
}
[MenuItem("Tools/SetExternalEditorPath")]
static void SetExternalEditorPath()
{
string path = EditorUserSettings.GetConfigValue(EXTERNAL_EDITOR_PATH_KEY);
path = EditorUtility.OpenFilePanel(
"SetExternalEditorPath",
path,
"exe");
if (path != "")
{
EditorUserSettings.SetConfigValue(EXTERNAL_EDITOR_PATH_KEY, path);
Debug.Log("Set Editor Path: " + path);
}
}
[MenuItem("Tools/SetLuaProjectRoot")]
static void SetLuaProjectRoot()
{
string path = EditorUserSettings.GetConfigValue(LUA_PROJECT_ROOT_FOLDER_PATH_KEY);
path = EditorUtility.OpenFolderPanel(
"SetLuaProjectRoot",
path,
"");
if (path != "")
{
EditorUserSettings.SetConfigValue(LUA_PROJECT_ROOT_FOLDER_PATH_KEY, path);
Debug.Log("Set Editor Path: " + path);
}
}
static string GetLogText()
{
// 找到UnityEditor.EditorWindow的assembly
var assembly_unity_editor = Assembly.GetAssembly(typeof(UnityEditor.EditorWindow));
if (assembly_unity_editor == null) return null;
// 找到类UnityEditor.ConsoleWindow
var type_console_window = assembly_unity_editor.GetType("UnityEditor.ConsoleWindow");
if (type_console_window == null) return null;
// 找到UnityEditor.ConsoleWindow中的成员ms_ConsoleWindow
var field_console_window = type_console_window.GetField("ms_ConsoleWindow", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
if (field_console_window == null) return null;
// 获取ms_ConsoleWindow的值
var instance_console_window = field_console_window.GetValue(null);
if (instance_console_window == null) return null;
// 如果console窗口时焦点窗口的话,获取stacktrace
if ((object)UnityEditor.EditorWindow.focusedWindow == instance_console_window)
{
// 通过assembly获取类ListViewState
var type_list_view_state = assembly_unity_editor.GetType("UnityEditor.ListViewState");
if (type_list_view_state == null) return null;
// 找到类UnityEditor.ConsoleWindow中的成员m_ListView
var field_list_view = type_console_window.GetField("m_ListView", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (field_list_view == null) return null;
// 获取m_ListView的值
var value_list_view = field_list_view.GetValue(instance_console_window);
if (value_list_view == null) return null;
// 下面是stacktrace中一些可能有用的数据、函数和使用方法,这里就不一一说明了,我们这里暂时还用不到
/*
var field_row = type_list_view_state.GetField("row", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
if (field_row == null) return null;
var field_total_rows = type_list_view_state.GetField("totalRows", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
if (field_total_rows == null) return null;
var type_log_entries = assembly_unity_editor.GetType("UnityEditorInternal.LogEntries");
if (type_log_entries == null) return null;
var method_get_entry = type_log_entries.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public);
if (method_get_entry == null) return null;
var type_log_entry = assembly_unity_editor.GetType("UnityEditorInternal.LogEntry");
if (type_log_entry == null) return null;
var field_instance_id = type_log_entry.GetField("instanceID", BindingFlags.Instance | BindingFlags.Public);
if (field_instance_id == null) return null;
var field_line = type_log_entry.GetField("line", BindingFlags.Instance | BindingFlags.Public);
if (field_line == null) return null;
var field_condition = type_log_entry.GetField("condition", BindingFlags.Instance | BindingFlags.Public);
if (field_condition == null) return null;
object instance_log_entry = Activator.CreateInstance(type_log_entry);
int value_row = (int)field_row.GetValue(value_list_view);
int value_total_rows = (int)field_total_rows.GetValue(value_list_view);
int log_by_this_count = 0;
for (int i = value_total_rows – 1; i > value_row; i–) {
method_get_entry.Invoke(null, new object[] { i, instance_log_entry });
string value_condition = field_condition.GetValue(instance_log_entry) as string;
if (value_condition.Contains("[SDebug]")) {
log_by_this_count++;
}
}
*/
// 找到类UnityEditor.ConsoleWindow中的成员m_ActiveText
var field_active_text = type_console_window.GetField("m_ActiveText", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (field_active_text == null) return null;
// 获得m_ActiveText的值,就是我们需要的stacktrace
string value_active_text = field_active_text.GetValue(instance_console_window).ToString();
return value_active_text;
}
return null;
}
}
参考:
1. http://weibo.com/2332920021/ExWUWBjrU?filter=hot&root_comment_id=0&type=comment#_rnd1488980161004
2. http://www.wy182000.com/2017/02/09/unity-%E8%87%AA%E5%AE%9A%E4%B9%89log%E7%B1%BB%E5%9C%A8%E7%BC%96%E8%BE%91%E5%99%A8%E4%B8%AD%E4%BB%A3%E7%A0%81%E5%AE%9A%E4%BD%8D%EF%BC%88unityeditor-callbacks-onopenassetattribute%EF%BC%89/