Hello Lua
在游戏开发中,Lua是一种比较热门的轻量级脚本语言。由C实现的Lua可以很好得和C/C++进行交互,同时它可以很方便地和和各种语言结合进行跨平台开发。
特别是在当前最流行的游戏Unity中,为了实现C#脚本热更新,Lua几乎是唯一的解决方案,各种基于Lua的库层出不穷。一直在写C#脚本,还是有必要学习Lua。
###Lua初见
一开始就直接打开Lua官方的Reference Manual
Lua Reference Manual
接下来下载好Lua的binary版本,开始学习基本的语法
Lua 基础教程
总体感觉Lua非常的轻巧
基础的数据类型也只有nil boolean number string function userdata thread table
(整形和浮点都是number 想起了SQLite)
动态类型语言和Javascript一样
结尾不需要;和Python一样
函数可以是多返回值
没有自带的++ +=等运算符,写的时候特别难受…T_T
但是有metatable可以进行任意的扩展,这个metatable给我的感觉就像是JS中的prototype
###定个小目标
在熟悉了基本的语法和运算后,准备试试Lua的热更新。之前在github发现了一个GUI库imgui,imgui是一个immediate mode gui库。什么是immediate mode gui呢,就是和通常的事件回调式的GUI框架不同的,所有的UI逻辑是线性的,和Unity中的EditorGUI一样,需要不断的刷新。大概就是下面代码的样子
1 | gui.Label("This is a label"); |
工作中一直在写一些Unity的插件及编辑器脚本,挺喜欢IMGUI这种快速代码实现UI的模式,但是也是有缺点的,比如UI逻辑太复杂时候,有可能会影响绘制帧率造成卡顿。或者是太复杂的UI结构代码的可读性就大大降低。
现在我们有了Lua 那么我们是不是可以用IMGUI绘制一个文本编辑器,然后在里面编写Lua脚本,通过Lua调用 C++的imgui的方法来更新UI呢。
这样就像写Unity的Editor扩展一样,但是不需要重新编译整个项目,加载不同的Lua脚本就能实现热更新UI。
总结起来我们需要做一下几步
- 在windows下开发 windows程序,调用imgui C++库
- 配置Lua环境 执行Lua脚本
- 编写Lua调用imgui的C函数,互调用操作
- 在imgui中刷新Lua脚本
开始实现
创建工程
imgui在github上的repo有demo 我们直接拿来改就行了。选了用opengl进行渲染,而不是dx。毕竟有GLFW GLEW比DX的初始化快多了。
配置Lua
把几个Lua头文件的目录加入Include
- Lua.h lualib.h 定义了Lua的基本方法
- lauxlib.h 里面有一些比较常用的扩展方法
- luaconf.h 用来控制模块的开关
Lib就只有一个lua53.lib
Lua和C++的互调用
处理Lua与C++的互调用都需要通过Lua C Api来进行
在Lua5.3中 需要维护一个lua_State 来进行互操作
Lua调用C++需要先定义对应的方法,所有方法都必须带有lua_State的参数,因为Lua和C交互是通过维护一个栈来实现的。Lua调用方法时会先把方法和参数一个个push入栈,对应的C方法会pop出参数执行完再把结果压入栈返回给Lua的脚本
1 | //声明方法 |
接下来就可以直接在Lua中调用luaTestFunc()
github上有imgui lua的wrap 我们可以直接用,就不需声明那么多方法了
C++调用Lua方法
使用luaL_loadstring(lua_State*,const char * luacode)
来加载代码
加载完成后我们要把需要调用的方法push到栈顶,然后调用lua_call()
就可以执行了。需要注意的是调用了lua_call
之后我们使用lua_getglobal()
push到栈顶的方法会被pop出栈。在imgui中每一帧循环都要调用imgui的绘制方法,所以如果要重复调用一个方法就需要在调用前讲其push到栈顶(不知道有没有其他的方法可以实现)。不这样操作可能会报错就会出现如
1 | lua syntax error:attempt to call a nil value |
这样的错误其实是调用lua_call 发现在栈顶的不是函数先报错 nil value,然后lua会把这个error string push到栈顶 所以下次调用call就会返回string value,无限循环下去…
更多的Lua C++的互调用可以参考
Lua C++互调用
** 重新加载编写好的Lua脚本 **
当我们更新了lua脚本之后需要重新加载,首先调用 lua_close()
回收lua_State,接着就是重新加载lua基本库和imgui的库。这一步加载库是比较耗时间的,不知道有没有其他的方式可以动态卸载掉加载的代码块,再重新加载Lua脚本。
###总结
Lua的栈操作还是不太习惯,总体感觉Lua是一个很简洁易用的脚本语言。模块管理,基本库,metadata等特性还需要再深入接触一下。
在调试互调用的时候还是踩了很多坑,需要好好了解下Lua C Api。基于Lua的imgui UI扩展还会继续完善完善。