现在使用Unity开发,有许多Lua插件供选择:ulua,slua,xlua等等。开发使用Lua的目的是为了热更新,换句话说就是将游戏中可变的资源和逻辑一起进行更新。Lua作为一种脚本非常适合做这种事情,但是使用Lua并不像表面这么简单,这中间会涉及到虚拟机技术以及Lua和Unity之间的互调,内存的释放等等很多坑。这些问题如果搞不清楚就会出现游戏占用大量内存而不知所措,因为Lua并不适合于调试,所以遇到问题解决起来非常麻烦,本篇文章就从这几个技术点给读者介绍一下:
**

虚拟机

**
我们知道Lua是运行在虚拟机上的,虚拟机通过字面意思知道它是一种虚拟的设备,在对应虚拟机上运行,需要对字节码进行取指令,译码,执行,结果回写等操作,这些步骤和真实物理机器上的概念都很相似。相对应的二进制指令是在物理机器上运行,物理机器从内存中取指令,通过总线传输到CPU,然后译码、执行、结果存储。虚拟机分为基于栈的和基于寄存器的,Lua是基于寄存器的,而我们的Unity使用的.Net是基于栈的。本篇博客关于虚拟机讲解的很详细:
我们的Lua脚本在运行时也是基于虚拟机的,大家知道寄存器运行速度还是蛮快的,另外它的数据不需要来回传递,因为这些寄存器也是存储在内存上的。

**

Unity与Lua交互注意事项

**
很多开发者在使用Lua开发时,只知道逻辑的编写,并不关注其他事情,这样写的代码会出现内存泄露,举个例子:我们知道Unity释放内存使用的是GC,Lua使用的也是GC。这样就会导致Mono对象、Lua对象、Unity对象三者的释放流程难以把控,导致资源无法及时释放。有时我们找到了Lua的问题所在,但是如何优化也是一个问题等等。下面通过案例的方式给读者介绍:
我们在游戏开发时,通常的做法是使用C#封装接口,然后Lua去调用,当然Lua封装的接口,C#也可以调用,因为二者是互通的。我们只知道调用API,并不知道两个脚本交互的时候细节上的损耗到底有多大。比如下面代码:

transform.position = new Vector3(0,0,0);
 transform.name = “Role”;


在C#里面是非常寻常的,也没啥消耗,但是Lua就不同了,Lua会因为字符串传递的消耗,因为C#与Lua在字符串这块的定义是不同的,另外,Lua会把Vector3当成table或者userdata处理,这样会导致问题的出现。所以,一定要避免在C#和Lua进行字符串频繁的传递,比如UI中的一些控件显示,不要用字符串传递,直接在C#里面组织好,然后通过LuaTable注册给UI操作table。
前面还涉及到Vector3的位移计算,这个也要避免在Lua中使用,因为C#是栈对象,Lua是堆对象,我们可以通过三个数字进行传递,避免Lua频繁的GC。
另外,transform.gameObject在Lua中使用有着比较严重的虚拟机交互过程,所以要尽量减少_index元运算,多用缓存把C#的一些对象保存起来。比如ObjectManager.GetInstance()在C#效率影响不大,在Lua这边整个全局变量保存起来。再介绍Lua与Unity之间的交互:
**

Lua与Unity的内存释放

**

我们在编程时Lua不仅要调用C#的接口,Lua也需要把对象传递给C#这边,在C#这边只是一个引用,内存占用小,但是Lua可能是一个UI界面的table或者函数调用,Lua的内存会增大,同时Lua的UI类还引用了不少C#的UI控件。换句话说,如果销毁一个UI界面,在C#这边不应该把对应的table以及注册到C#的回调函数注销掉,这样会导致Lua与C#相互持有内存的死锁状态。

比如,在项目开发中正常的UI打开如下所示:

lua获取当前运行脚本路径 lua脚本怎么读取游戏内存_Lua优化


粗线表示持有,细线表示应用,我们在释放资源时,首先调用C#的Destroy方法进行资源释放,同时确保Lua虚拟机没有ui table的引用,在Destroy函数中将ui的table置空。但是也有可能ui table是个全局变量或者其他ui持有,这样整个Lua和C#对象都将无法释放。我们继续思考如何释放掉C#对这个ui table的引用,有两种方法:方法一直接调用dispose方法;方法二,等待C#的GC,在LuaTable这个类的析构函数中对引用进行释放,因为资源是立即销毁的,推荐使用Dispose方法。如果C#的引用也为空,内存状态如下所示:

lua获取当前运行脚本路径 lua脚本怎么读取游戏内存_Lua优化_02


最后调用C#的GC方法,对象就被最终释放干净了。下面总结一下流程:

第一步:在C#中调用Destroy或者Unload方法,释放掉Unity的资源内存,这个大家都了解,经常使用。

第二步:lua代码对应的操作是将所有对应的table引用置空,当然没有引用是最好的,这一步就可以忽略了。

第三步:在C#这边使用Dispose掉table以及各种Lua的回调函数。

第四步:释放Lua的内存使用GC方法,通过_gc元方法释放掉C#的对象池中的对象引用,到第四步我们就完成了lua对象的释放工作。

第五步:将C#的所有对象的引用置空。

第六步:调用C#的GC 释放C#对象,这一步是将C#的对象释放完。

最后,如何检测lua的函数消耗和内存消耗,因为在Unity中提供了Profiler可以查看C#的GC消耗,但是Lua比较麻烦,有网友开发了一个类似Profiler用于lua检测工具推荐给大家:LuaProfiler-For-Unity