1.【十一】重新起步
2.Text Mesh Pro图文混排如何对任何都能实现
3.hybridclr源代码解析
4.Lua 脚本静态分析的源码思路与实现
【十一】重新起步
哈,没想到吧,源码本专栏还有再次更新的源码一天。
其实是源码看到这个之后,决定把热更新也整合进去。源码既然要整合热更新,源码精灵q源码那现在的源码框架不得不重新写一次,因为代码最终要编译成Dll的源码。
这次重写的源码话,我做出了如下选择:
脚本语言:xLua
Unity版本:Unity .3.5 (VS)
文件夹设置:游戏工程的源码建立
没啥好说的,中规中矩建一个3D工程。源码随意新建一个脚本,源码用VS打开,源码在“解决方案管理器”视图找到Unity相关的源码引用,其属性为:
把路径里用到的源码东西打包复制到Dll工程的3rd目录下,供Dll工程引用。然后再找到pdb2mdb.exe和mono.exe:
如果电脑里有多个Unity版本,则可能会有多个该程序,选择对应版本的即可,等下要用的是路径,pdb2mdb.exe是DLL的调试符号转换器,不生成mdb就没法调试DLL中的代码。
类库工程建立
依旧是中规中矩创建一个类库的工程。先添加刚才准备好的Unity相关程序集引用,然后打开项目属性,设置生成后事件:
此处就使用了mono和pdb2mdb的路径,生成之后复制到游戏工程中。这里随便写了一个测试类,在游戏工程中可见,Dll中已经有这个类了。
打上断点,网件源码挂上调试。然后运行游戏工程,好的,成功断住:
接入xLua
将Plugin放进游戏工程中,将Src下的源码分别放进Editor目录和DLL工程中,测试一下:
没毛病,成功启动Lua虚拟机。接下来就是要配置xLua,选择一些可能要在Lua端使用的C#内容,按照xLua文档所示,直接整个静态类静态List,把要用的东西码入。
这里我根据个人的判断添加了如下内容:
然后调整一下配置生成器的路径:
然后执行一下生成命令,更新Dll工程,然后编译一下。是时候测试了。
也确实打印出来了“hello”字样。
API定义生成
写这种脚本语言,没有个编辑和调试插件其实是挺蛋疼的。这里推荐luaide,直接在vscode插件里找就可以,收费也比较便宜。
不过暂时还没有用它的打算,而是先接入它的api定义生成,这样编写Lua的时候可以相对直观的看到C#中一些api和数据的写法。
放进工程目录之后,修改导出路径,然后注释掉LuaIdeApi.cs里的菜单标签和自动生成;在xlua的生成函数末尾添加LuaIdeApi的生成即可。
这样,每次生成xlua内容的下单社区源码时候就会自动把api定义也更新,可以说是非常完美。
VS Code准备
用VS Code打开了Lua文件夹之后,会发现Unity生成的.meta文件也被计入了其中,因此我们要设定过滤,保证VS Code开发环境的清爽:
另外我比较习惯折叠代码,这点VSCode的Lua样式还没有,手动打开配置一下:
加载Lua
在编辑器下的开发应当越快越好,越高效越好,所以加载的时候,就有必要设计一种编辑器下的加载模式,跟使用AssetBundle或者其他自定义数据存储形式的生产环境不同,该模式应当做到修改Prefab\Lua等资产之后,无需打包即可立刻启动。
因为是为了测试xLua的加载,所以一切从简:
其中LoadContext是一个发起加载的上下文,该结构我暂时还没想好填充什么,不过也无关紧要,对于现在的测试来说,只要保证LoadLua方法可用就行。
这里使用System.IO.File而不是用AssetDatabase的原因是,“*.lua”在Unity中会被认为是DefaultAsset,无法被当做TextAsset处理。
在测试用的MonoBehavior类中用require('Game/Game'),打印成功。
Lua的class实现
才疏学浅,自己写的果然又长又臭,这里使用了quick-cocos2d-x的实现,但是因为我并没用到什么native C++的东西,所以大笔一挥,只保留了基本的酷qpro 源码Lua Object的内容:
好,我们来稍加测试一下:
表格的使用
将Excel表格生成成如下形式,代码可参照之前的内容:
使用一个_Data.lua来封装所有对数据表的查询操作:
测试,然后通过:
Text Mesh Pro图文混排如何对任何都能实现
1)Text Mesh Pro图文混排如何对任何都能实现
2)PlayerSettings.WebGL.emscriptenArgs设置无效的问题
3)Prefab对DLL中脚本的引用丢失
4)如何在第三人称蓝图模板中获得当前相机SpringArm的Target Arm Length
这是第篇UWA技术知识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大家更全面地掌握和学习。
Q:目前Text Mesh Pro图文混排需要把打入图集并放入TMP的Resources文件夹中。项目中想在任务UI面板展示很多装备或物品的icon,这些icon都是打好了的图集,很多别的系统也要用,不可能都放入TMP的这个资源文件夹里,但这样就无法进行图文混排,请问有什么解决办法吗?
A1:把TMP里面所有的Resources.Load接口,都替换成项目自身的Bundle加载接口,应该就可以解决了。
A2:可以注册以下回调:TextMeshProUGUI.OnFontAssetRequest,这样就可以自定义一个加载方法。 如果加载成功了,TMP就不会Resources.Load。
Q:游戏里用了C++的第三方库集成到xLua里面,端版是用CMake构建,可以配置宏、Include路径等,很方便。
我现在用Unity .3发布的WebGL版本,xLua是把所有Lua代码放在了“工程目录/WebGLPlugins”下面,在引擎里去#include这里的C代码,也没问题。
但是日历网页源码自己的C/C++代码比较多,也不能全部扔在同一个目录下,如果加了目录,就有跨目录Include的问题(主要是lua.h,lauxlib.h)。我从PlayerSettings.WebGL.emscriptenArgs = "-Imy_file_folder_path";这样设置搜索路径,但是没用;然后定义宏-Dxx也没用,不知道为什么。(参数中没有自己定义的路径和宏,都是Unity自己的,虽然ProjectSettings.asset已经生效了。)请问有解决方案吗?
A:在Unity .2之后更新Emscripten,emscriptenArgs属性失效了,可以把Lua代码和C/C++代码都放到一个层级,然后改引用。 还有一个方式:找到你Unity安装目录\PlaybackEngines\WebGLSupport\BuildTools\Emscripten\emscripten\emcc2.py,如果没有emcc2.py就找emcc.py,在里面找到get_clang_command方法,这个方法就是拼接执行编译C/C++代码语句的,在[src_file]后面增加+ ['-IAssets/XXX/XXX'],注意-I和Assets之间没空格,大概这样子:
注意py文件缩进问题,多个文件夹就添加多个“-I”,我猜因为它自己有“-I.”参数,所以可以使用相对目录(对clang++编译不太熟,我在Windows的Unity .3.f1上使用可行。) compile_args变量是记录传进来的参数的,有精力也可以找找调用emcc2.py的地方传进来更合适,我这边没找到。 其它需要改C/C++编译参数的地方我也在这里改了,如果有其它更简便有效的方法,欢迎分享。
Q:开发时,不希望策划看到源码,于是将源码替换成DLL,但是这样挂在Prefab上的脚本引用会丢失,请问怎么解决?
A1:是原有的预制挂了CS,后面想打成DLL重新挂?如果是这样,遍历预制,去批量修改GUID和FileID。可参考文章 《Unity将C#脚本转换为DLL,Prefab等文件不丢失引用的方法(转)》,文章里面用了DLLSwitcher插件,也可以自己理解Prefab GUID fileID和DLL,CS文件对应的关系就行了。
A2:代码要分成底层和逻辑层。底层用DLL+Obfuscator,逻辑层就不能了,因为如果也加密,开发验收查bug都会费劲。 如果要防止策划带走代码,按照我说的底层DLL就行,即使他带出去,只有逻辑层的开发,后面也会一大堆问题,除非自己写底层,就要调试,时间还不如自己重新写,这样的博弈论一旦开始,这个代码库重要性就不高了。 同时也要防止策划带走服务端代码,做到服务端拿不到客户端,客户端拿不到服务端,这样即使有一方代码,也是无用代码。
Q:使用SpringArm组件会让相机在发生碰撞时拉近,但是为什么获得到的Target Arm Length值却一直不变?
A:在代码中,Target Arm Length永远是不变的,但是它下面有RelativeSocket,在蓝图中可以使用GetSocketTransform来获取,并且使用RTS_Component参数,取Location然后用VectorLength来计算:
还可以用SpringArm与Camera的位置相减来计算,分别GetWorldLocation再Sub和VectorLength:
可以发现两种方式得到的数据似乎有些小区别,这是因为该案例中还有一个相机跟随鼠标位置移动的功能,它会对Camera进行SetRelativeLocation,偏移其Y和Z值。方式一是从SpringArm中取,与Camera无关,所以不受偏移量影响;方式二用到了Camera的RelativeLocation,因此将偏移量也算了进去。 另外还有一种方式,无法计算距离,而是直接检测相机是否与世界发生了碰撞,发生碰撞时就会进行缩放。
hybridclr源代码解析
基于lua的unity热更新解决方案
使用lua5.3.5,可以通过VS进行调试,lparser.c负责解释lua源代码,LClosure *luaY_parser函数是解释lua源码的入口。llex.c中的llex函数负责词法分析,而lparser.c中的statement函数进行语法分析。lvm.c则用于执行lua代码。观察到lua需要第三方插件以查看性能,其基于寄存器的虚拟机性能优于ilruntime,但与unity交互成本高,依赖于lua的堆栈交互。
流行解决方案如XLua和ToLua,XLua在处理如Vector3等结构体时,避免了不必要的拆箱和装箱操作,ToLua则直接在lua代码中实现了与C#类似的Vector3数据结构。
基于ilruntime的unity热更新解决方案
ilruntime的下载地址为github.com/Ourpalm/ILRuntimeU3D。它提供了unity示例工程,其中ImageReader.cs负责加载dll,而ilruntime使用Mono.Cecil来读取dll的PE信息。从2.0版本开始,ilruntime引入了寄存器模式以解决数值计算效率问题,分为按需JIT(ILRuntimeJITFlags.JITOnDemand)和立即JIT(ILRuntimeJITFlags.JITImmediately)两种模式。ILIntepreter.cs用于执行il代码,非寄存器模式下,Execute函数负责执行代码,而寄存器模式下的ExecuteR函数实现相同功能。然而,所有解决方案的虚拟机与il2cpp相互独立,导致元数据不相通,影响了与unity类的集成,需要额外封装和跨域访问处理。ilruntime支持大部分C#语法,但使用时需注意避免一些陷阱。
基于hybridclr的unity热更新解决方案
hybridclr提供了unity示例工程,官方博客地址为hybridclr.doc.code-philosophy.com...,使用手册可参考介绍 | HybridCLR。建议在vs和unity.3.0f1环境下调试PC工程。加载dll的两个主要入口在于.metadataModule.cpp中的LoadMetadataForAOTAssembly函数和RawImage.cpp读取原始信息,随后Image.cpp解析dll信息并翻译成il2cpp类型,AOTHomologousImage.cpp和ConsistentAOTHomologousImage.cpp分别用于封装加载过程,确保一致性或超集程序集的灵活管理。Assembly.cpp的Il2CppAssembly* Create函数解析PE头、CLR头和元数据以得到镜像信息,随后初始化metadata和interpreter模块以提供快速访问和执行速度。
hybridclr的优势在于直接使用il2cpp的内存对象,避免跨域问题;利用C#语言特性进行开发;并能够使用unity自带的profiler工具查看性能。
Lua 脚本静态分析的思路与实现
作者:levizhao,腾讯 IEG 客户端开发工程师
在游戏开发过程中,lua 脚本的广泛应用带来了一定挑战。由于其语法检查主要在运行时进行,且作为胶水语言,全局变量的注入和修改可能导致编辑器难以提前发现错误,影响开发效率。本文探讨了如何通过静态分析来改善这一状况。
xlua,作为热门的lua解决方案,它支持Unity等C#环境的扩展,通过代码生成和反射实现与C#的交互,对代码的侵入性相对较小。在Unity项目中,运营内容的热更新通常依赖xlua进行开发,但其局限性在遇到lua脚本错误时显得明显。
目标是通过静态分析在编译阶段检查lua代码的符号语义,避免运行时的错误。这种思路类似于动态链接,但在脚本执行前进行模拟,关注符号的有效性,遇到无法解析的符号,视为“链接失败”。这一方法并非局限于Unity,而是适用于其他lua绑定的静态分析。
实现这一目标需要对lua源码进行预编译,包括词法分析、语法解析、解释器和编译器等。lua标准库函数需要导出,同时处理外部注入的符号,通过懒加载机制减少开销。lua虚拟机的指令解析是关键环节,涉及多种指令操作。
尽管静态分析解决了部分问题,但混合编程的局限性仍存在。混合编程初衷是利用不同语言的优势,但目前实践中存在效率和简洁性的妥协。为了更优雅地解决这些问题,我们需要深入考虑游戏引擎、编译器和代码编辑器等基础设施的改进,以打破单纯的插件式设计。