本文最后更新于 2024年9月8日 凌晨
前言:
适用范围:Unity 中需要查找所有预制体里面的某一个脚本的属性值,或者Lua脚本里面的某一个属性值
本文介绍如何查找预制体和Lua脚本里面调用的本地化id
下面首先介绍改插件的功能以及使用方法,然后对该插件的原理进行说明
使用说明
本插件能够查找到预制体和lua脚本里面那些地方调用了指定的本地化id。操作步骤:首先在LangID输入框中输入要查找的id(使用分号间隔),也可以直接从csv文件导入id。点击【查找】按钮,就可以找到对应的id被哪些预制体和lua代码调用了。
【打开】按钮会调用默认编辑器打开对应的文件
【更新Lua代码索引】按钮会运行python程序生成lua代码的索引文件
【设置】按钮用于设置本地化函数名
插件原理说明
首先需要确保工程已经安装了Odin Inspector插件,否则无法运行本插件。
因为是要查找指定的本地化id,那么可以按照功能模块将代码分为两部分——查找预制体和查找Lua代码。
查找Lua代码中的本地化id
假设获取本地化id的函数名为lang.Get。
那么,是不是使用正则匹配lang.Get这个字符串,然后就可以获取调用的id了呢?实际项目中情况可能更复杂。
通过总结实际项目中代码的编写格式,可以将函数调用分为一下几种情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| function test() print("function test") lang.Get(10000) lang.Get(10000 + 10) local id = 20 lang.Get(id) lang.Get(10000 + id) lang.Get(id + 10000) local LANG_ENUM = { id1 = 10000, id2 = 10001, id3 = 10002, } lang.Get(LANG_ENUM.id1) lang.Get(LANG_ENUM.id2) lang.Get(LANG_ENUM.id3) local LANG_ARRAY = {20000, 20001, 20002} lang.Get(LANG_ARRAY[1]) lang.Get(LANG_ARRAY[2]) end
|
面对这些情况,正则就不够用了。为了实现对以上情况的查找,所以lua代码部分的查找工作使用语法树来完成,这里使用基于python的luaparser(也有基于JS的)。luaparser的官方文档:https://pypi.org/project/luaparser/
这里使用python是为了方便将python程序打包,因为大部分人的电脑中没有python环境,而且我们不可能在插件中内嵌一个python环境,所有最后需要将python代码打包成exe程序。
上面是整套python代码的执行流程,简单来说就是首先找出差异文件,然后生成lua代码的语法树,然后找到所有的本地化id,最后将查找的结果进行序列化并以JSON格式保存在本地。C#层会读取该JSON文件,从而查找对应id。
P.S. 原本没有差异文件对比的,后来实际运行发现速度太慢了,构建2000个lua文件的索引大概需要8分钟左右,完全接受不了。后面加入差异化文件对比之后每次构建时间缩短到了1分钟以内,速度大大提高。
下面对python代码进行简单介绍
ast.walk(tree):获取树结构进行遍历
isinstance(node, astnodes.Call) :判断该节点是否是函数调用(.调用lua的函数)
isinstance(node, astnodes.Invoke) :判断该节点是否是函数调用(:调用lua的函数)
isinstance(node.func, astnodes.Name):判断是否是lua的命名表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| def FindFuncNude(tree, funcName = "lang.Get"): funcList = [] funcInfo = funcName.split('.') for node in ast.walk(tree): if isinstance(node, astnodes.Call) or isinstance(node, astnodes.Invoke): if isinstance(node.func, astnodes.Index): if isinstance(node.func.value, astnodes.Name) and funcInfo[0] == node.func.value.id: if isinstance(node.func.idx, astnodes.Name) and funcInfo[1] == node.func.idx.id: funcList.append(node) print("lang.Get line->", node.line) elif isinstance(node.func, astnodes.Name): if node.func.id == funcName : funcList.append(node) print("lang.Get->", node.line) return funcList
def GetTableKeyValuePairs(tableNode): ''' :param tableNode: table 节点 :return: key:变量名,value:变量的节点 ''' keyValueList = {} if isinstance(tableNode, astnodes.Table): for pair in tableNode.fields : if isinstance(pair.key, astnodes.Name) and isinstance(pair.value, astnodes.Number) : keyValueList[pair.key.id] = pair.value elif isinstance(pair.key, astnodes.Number) and isinstance(pair.value, astnodes.Number) : keyValueList[str(pair.key.n)] = pair.value return keyValueList
def FindAllVariable(tree): ''' :param tree: ast树 :return: key:变量名,value:{value:变量值,node: 节点} ''' varList = {} for node in ast.walk(tree): if isinstance(node, astnodes.LocalAssign): if len(node.targets) > 0 and len(node.values) > 0 : tempVar = node.targets[0] tempValue = node.values[0] if isinstance(tempVar, astnodes.Name) and isinstance(tempValue, astnodes.Number): varList[tempVar.id] = {"value" : tempValue.n, "node": tempValue} elif isinstance(tempVar, astnodes.Name) and isinstance(tempValue, astnodes.Table): keyValueList = GetTableKeyValuePairs(tempValue) for key in keyValueList : varList[tempVar.id + "." + key] = {"value" : keyValueList[key].n, "node": keyValueList[key]} print("---------------------------------") for key in varList : print(key,"=", varList[key]["value"]) print("---------------------------------") return varList
|
至此,C#端只需要读取python端生成的json文件就可以获取lua的索引信息了,C#端的代码不做信息介绍。完整代码附在末尾的链接里面。
查找预制体中的本地化id
下面介绍如何查找预制体中的本地化id。首先获取所有预制体的路径,然后依次遍历这些路径,使用AssetDatabase.LoadAssetAtPath实例化预制,然后搜索对应的本地化脚本组件即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public static Dictionary<int ,Dictionary<string, List<string>>> Finder(string prefabPath, HashSet<int> langIDSet) { List<string> allPrefabDir = Util.GetAllPrefabDir(prefabPath); Dictionary<int ,Dictionary<string, List<string>>> resDic = new Dictionary<int ,Dictionary<string, List<string>>>(); foreach (var dir in allPrefabDir) { GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(dir); if (prefab != null) { LocalizationText[] localizeTextList = prefab.GetComponentsInChildren<LocalizationText>(true); foreach (var localizeText in localizeTextList) { int compLangID = localizeText.TextKey; if (langIDSet.Contains(compLangID)) { if (!resDic.ContainsKey(compLangID)) { resDic[compLangID] = new Dictionary<string, List<string>>(); }
if (!resDic[compLangID].ContainsKey(dir)) { resDic[compLangID][dir] = new List<string>(); } resDic[compLangID][dir].Add(Util.GetRoute(localizeText.transform)); } } } }
return resDic; }
|
代码自取:
langIDFinder 文件为unity插件的代码,其中的python代码位于main.py中
https://github.com/momohola/Localization-ID-Finder-by-luaparser