lua语言算是游戏行业中就热门的脚本语言了,所有从事游戏行业的程序员或多或少都有接触这门语言。但是lua这门语言又是争议性最大的语言,有的人对它恨之入骨,有的人把它奉上神坛,这究竟是什么原因导致对lua的评价形成了两个极端,而我们又应该如何去规避使用lua脚本语言将遇到的问题。
为什么有人把lua奉上神坛?
其实大量使用lua脚本的一般指的是 网络游戏行业,这个行业有个特点,就是运营商必须不停的提供新的玩法,以及对bug处理反应要非常迅速。
传统的编译语言就无法胜任这部分的内容,而脚本语言因为是通过文本解析来控制一些静态语言提供好的接口,以至于脚本代码的实质非常像策划的配置表。所以用了脚本语言之后运营商就可以快速对用户需求以及BUG进行处理迭代,从而保持自己的用户粘性,最后保证自己的稳定收益。而lua语言也因为行业内使用比较广泛,国内大神非常多,成功的项目数不胜数,从而使自己成为脚本语言的首选。
lua脚本语言的使用与引擎一点关系都没有,端游、cocos、unity、虚幻4都可以使用lua,很明显好好掌握这一门技能,无论游戏行业如何变化,lua都可以有用武之地。
为什么有人对lua语言恨之入骨?
在全行业都在研究lua语言的时候有一部分人并对lua仅仅停留在使用它进行热更新的层面,同时lua早期的研究BUG比较多,坑也不少。而程序员有个通病,如果被坑的很惨,就会对这个方案失去耐性,要么放弃lua要么做完一个项目后不再使用。
弱语言:lua代码作为弱类型的脚本语言代码,所以很难提供一个非常良好的代码编辑器。而且代码检测并不完善,成员变量不存在必须跑到对应的代码才能爆出来,所以BUG不断。
重构代价过高:很多人认为lua语言是个很容易上手的语言,又非常自由的语言。程序员如果非常有设计能力的话,可以写出来各种花样的代码,按照行业大佬的话:我只看得懂我写的lua代码。虽然放飞自我是一件好事,但是游戏项目毕竟是一个团体项目,好的代码往往以每个人能够快速理解做衡量。但是一旦某些团队不遵守这个原则对lua代码进行滥用的时候,往往会导致项目的人员不停更换,而后来者在重构lua代码的时候代价非常高,很容易导致项目被温水煮青蛙煮成青蛙汤了。
性能优化难度大:目前行业主流游戏仍然是手机游戏,而移动端的设备往往对性能要求非常高,而前几年市场上并没有什么良好的性能优化工具对lua代码进行检查。比如一个MMORPG对UI打开速度要求非常高,而UI的主要逻辑又写在lua这边,一旦界面打开卡顿,要进行优化无疑是一个非常痛苦的过程。如果没有什么好的解决方案很容易把项目给做死掉。
所以对于很多团队来说,在项目中大规模使用lua代码很有可能使项目处于一个难以把控的状态,总结下:用了lua后游戏可能因为弱语言不好重构调试、没有好的性能优化器的代价,导致游戏压根做不出来,这时候谈运营能带来多少好处的时候就没有太多意义了。
如何针对Lua脚本语言的痛点
最近几年lua编辑器、调试器、优化器的发展也是突飞猛进,然后加上lua脚本语言这种动态语言的优势,习惯了之后可以说使用lua进行项目开发比静态语言反而要快、优化上也更有优势。
下面介绍如何解决弱语言、调试器、性能优化器的问题
EmmyLua代码编辑器(idea插件,vscode据说也在开发)
https://github.com/EmmyLua/IntelliJ-EmmyLuagithub.com https://www.cnblogs.com/sanyejun/p/9673198.htmlwww.cnblogs.comqq群:1群29850775(已满),2群:529914962(已满),3群:805629309
这是一个多写注释就可以把代码从弱语言变成强语言的编辑器
主要掌握比较重要的doc注释语法:
对于C#的对象你可以声明为:
---@class Test
---@field public Size number
---@field public HashValue uint64
---@field public HashTime uint64
---@field public IsLoaded boolean
---@field public FileName string
然后任何一个地方你只要使用type的关键字将对于变量声明为Test,然后就能得到关键字提示
---@type Test
local t
如果一个函数返回为Test你可以声明为,在其他地方使用的时候就有点像C#的var语法
---@return Test
function getTest()
end
在项目的实际使用中要在C#函数导出、UI导出、配置表解析、网络协议、枚举定义上做好自动化的生成代码提示的工作。
其中C#函数导出你可以使用Emmylua群里面提供的做法进行
项目中的自动化生成范例:
UI导出
---@class LoginPanel
---@field PanelRef MoonClient.MLuaUIPanel
---@field txtVersion UnityEngine.UI.Text
---@field TxtAgreement UnityEngine.UI.Text
---@field Button_Visitor UnityEngine.UI.Button
---@return LoginPanel
---@param ctrl UIBase
function UIBind.BindLoginPanel(ctrl)
return BindMLuaPanel(ctrl)
end
代码并不复杂,就是在UI的prefab上设置好要导出的组件,然后生成对应的提示API,最后把代码独立放在一个 lua文件中,在UI界面逻辑中调用一下Bind方法即可。
配置表导出:
每个配置表都生成一个对应的函数,然后使用return的doc语法进行配置生成,这样你的代码用起来就很爽了
---@class TaskConfig
---@field id number
---@field taskType number
---@field taskName string
---@field param1 number
---@field param2 number
---@field param3 number
---@field strParam1 string
---@field strParam2 string
---@return TaskConfig
---@param id number
function ConfigManager.GetTaskConfig(id)
return taskConfigTable[id]
end
网络协议导出:
-- 发送
---@return usefirststring
---@param msgName string
function GetProtoBufSendTable(msgName)
end
-- 接收
---@return usefirststring
function ParseProtoBufToTable(msgName, datas)
end
这里我魔改了emmylua,使emmylua拥有了将第一个为字符串参数的类型解析为返回类型,魔改的代码可以去emmylua群里面,乐隐林上传的第三方emmylua进行试用。
使用emmylua插件的时候,一定要保证每个变量都能通过emmylua插件猜测到相应类型。多多使用type、param的代码注释。最后你会发现emmy通过注释把lua代码变成一个强类型代码。
emmylua插件如何进行调试:
emmylua官方插件因为idea插件市场的原因被强行拿掉了代码注入功能,目前使用require的方式加载lua的dll,然后进行远程调试。
教程:
![057e2140631b109690d40e86c86a1a74.png](https://i-blog.csdnimg.cn/blog_migrate/45fccf8bc03da9600e7c4a43d547ca0e.png)
![87a97c213206cee9db0f9c2ccbde36ec.png](https://i-blog.csdnimg.cn/blog_migrate/e598c49280ec947ecaf02d86f6c8e2c7.png)
游戏开始的时候运行对应的代码:
![84c6c9f9a893abb7c55c01d4722ca903.png](https://i-blog.csdnimg.cn/blog_migrate/d453a373c24ec80b67f3782ae2e69624.png)
然后在idea编辑器这边打开
![4d0cbe50ea4ff434d06413b971ae7d21.png](https://i-blog.csdnimg.cn/blog_migrate/8154e52ff50ac24ad0569b06978f1a12.png)
打断点进游戏看效果。
当然你嫌弃麻烦可以,直接用我的第三方EmmyLua插件,直接Attach to Process,选择Unity即可。
![a6b1af94ada12657ec8c7252247672aa.png](https://i-blog.csdnimg.cn/blog_migrate/7de7ed29e6cd02a515a64aac7adc169b.png)
![8ec6c1f66385fceb98f78309cc1654a6.png](https://i-blog.csdnimg.cn/blog_migrate/e70cbd712f8816cbe12cda46cfb4e30c.png)
你会发现用起来比C#的调试器还方便一些。
Lua性能优化器:
https://github.com/ElPsyCongree/LuaProfiler-For-Unitygithub.com本人写的给Unity编辑器下使用的Lua 性能分析工具,只要你版本在Unity5.6以上都可以很方便的进行使用。
这是我在项目中得到的一些使用经验
使用此工具你将获得:
- 整体优化上可以获取函数的代码的调用信息,并针对进行排序,从而找出性能优化的切入点。
- 如果UI逻辑写在LUA,你可以通过此工具快速定位打开UI的卡顿点
- 快速定位update函数的gc产生情况,并针对这些函数做优化
- 查找lua函数的泄漏情况
- Record对每一次函数进行精细的分析,并且采样一段时间的样本进行综合分析 使用方法:
使用方法:
在Windows标签下,点开LuaProfilerWindow这个选项
![bce8e914ff212a44b1930d18d3071909.png](https://i-blog.csdnimg.cn/blog_migrate/30194cf455c4e725c786fbf788a10624.png)
在窗口下打开DeepLua
![a93dc5b859bc994303d42216560735b5.png](https://i-blog.csdnimg.cn/blog_migrate/cf42291d7191190216ea6227ae3e1ec3.png)
最后进入游戏,就能获取到对应Lua函数的执行信息.
1、对Lua函数进行整体分析:
在代码框中输入 [lua]:,并点击右上角的merge按钮,最后点击对应的标签,进行排序就可以对Lua性能进行一个整体的分析,快速定位一些性能点。
![8e481ee4609f631e8116e9b0a605aa99.png](https://i-blog.csdnimg.cn/blog_migrate/2ea6c15f404a5e315b093aebfc1f1d4a.jpeg)
2、优化UI中的逻辑卡点
打开UI前,将数据Clear一下,打开UI,暂停游戏,按照currentTime进行排序。
![5f2a3ebad4bed53cbe93d9792c0ad903.png](https://i-blog.csdnimg.cn/blog_migrate/5add85146cc496bb90a526d2d8344363.jpeg)
3、快速定位函数的gc情况
根据TotalLuaMemory和LuaGC进行分析,直接眼睛看哪里的lua内存涨得飞快,就能很快得到要优化的函数了
![fcaafcbc9ed4c16a38805ca16bebc325.png](https://i-blog.csdnimg.cn/blog_migrate/47d5ace7cd0a3e400df530970047673e.jpeg)
4、查找一些函数泄漏
分三步走
![002d9da5c5ec2dac47f6ecad09d822a7.png](https://i-blog.csdnimg.cn/blog_migrate/4880d67cc995aae8afdcf98b807cf5f2.png)
1、在打开UI前进行MarkStaticRecord
2、打开UI后MarkLuaRecord
3、关闭UI后DiffRecord
查看Destroy Null Values 可以查看一些Unity这边已经销毁,但是Lua仍然持有的对象:
![b6fa15a24907b43aa4cfeeadbfad7c44.png](https://i-blog.csdnimg.cn/blog_migrate/c813109aac2a2d8f0eff24d6aa7d8d5d.jpeg)
查看remain 的values就能得到一些泄漏名单
![c65d0d00d00769f76699736bd6c23be9.png](https://i-blog.csdnimg.cn/blog_migrate/c7f62adafb4d0f1ec15c5d71b7b9b1bc.png)
![341bde3d962d38fdcbbd70eb26c825d6.png](https://i-blog.csdnimg.cn/blog_migrate/1c3a738ef58d4ca63fd6ad0f1735f84d.jpeg)
总结:查找Lua泄漏大部分情况都是 委托泄漏,建议在销毁对象的时候把函数委托注销掉
5、Record对每一次函数进行精细的分析,并且采样一段时间的样本进行综合分析
主要目的是确定一个函数暴涨或者帧率下降点之中,lua函数干了些什么
使用方法:
1、点开DeepLua以及Record按钮,在想采样的地方点击StartRecord
2、选取一个内存暴涨的区间(先点击鼠标差不多选到一个位置,然后按键盘←→键)
![dd9086614b5c1939ac92765a2ab4376c.png](https://i-blog.csdnimg.cn/blog_migrate/d161123c6106929a5f2276e94f6f4e35.jpeg)
然后你差不多就知道改如何优化这个区间了
优化帧率突然下降的点同理:
![a4f02c3eee9ad740e58221ae17508435.png](https://i-blog.csdnimg.cn/blog_migrate/8ea90d24c9681da2b6b09a93e32e67c6.jpeg)
最后谈一下使用Lua语言为什么会使项目的开发变得更快,优化更好做:
开发变得更快
使用C#代码的时候每一次修改代码,都要关闭游戏,然后编译一下代码,最后重进游戏,这个代码还是蛮高的。使用了Lua之后你至少不用重新编译代码,相对于C++作为代码开发的项目速度是精进了一层,相对C#来说也提升不少。
然后Lua代码甚至可以热重载,你不退出游戏,直接改了代码,逻辑就变了,我个人写了一个基于lua5.3的一个demo
https://github.com/ElPsyCongree/luareload-unitygithub.com![b6506d4bcf81b58edbf17b782a5096df.gif](https://i-blog.csdnimg.cn/blog_migrate/346b39e65e65a3a723a4363a9a60a1c9.gif)
优化更加好做:
在使用静态语言的时候,服务器协议的序列化以及配置表的序列化的时候,要生成一大堆的配置表解析代码,这部分代码在大型项目非常容易被滥用,从而导致在代码编译的时候,这部分的text段以及内存的占用比较惊人,使用lua代码之后,因为lua的对象都是hash表,所以基本上不需要任何序列化的class,直接解析就好了。