安装lua
lua有windows版的安装包,直接装好就有.h .lib .dll可以直接使用。
单纯使用原生的lua和C/C++交互有很大限制,向lua注册的函数必须符合特定的函数原型
int func (lua_State *L);
以及手动处理参数和返回值压栈出栈。
安装tolua++
tolua++是为了帮助我们更方便在lua和C/C++间交互,主要是让lua调用C/C++。
tolua++只提供源代码,要在本地编译,我下载的版本提供了vc7的项目文件,直接用vs2008打开自动转换工程即可编译出一个exe
├─bin 空的
├─debian 是linux的一个发行版我们用不到
├─doc 文档
├─include 只有一个tolua++.h文件
├─lib 空的
├─src
│ ├─bin 用来编译exe的源代码
│ │ └─lua
│ ├─lib 用来编译lib的源代码
│ └─tests 一些测试文件我们用不到
└─win32
└─vc7 提供了一个工程和解决方案文件,用来编译exe的
tolua++的用法是首先编写一个.pkg文件用来描述需要将C/C++绑定到lua使用的文件,让然后用
tolua++.exe -n pkgname -o myfile.c myfile.pkg
得到一个myfile.c,这个文件需要编译进游戏工程,然后在游戏中调用
int tolua_pkgname_open (lua_State*);
这个函数会按照你在.pkg中描述的内容把C/C++的函数或者类或者变量绑定到lua的上下文中,它会建立同名的方法或变量让lua看上去可以直接调用C/C++。
自动生成的myfile.c还需要调用tolua++的库函数完成绑定,所以需要一个lib支持。
pkg文件的编写格式在文档中有描述,我们的用法比较简单,使用
$#include "lua_interface.h"
$ifile "lua_interface.h"
然后把C++端所有需要提供给lua的接口,全部声明在lua_interface.h中。
每次当lua_interface.h发生改变,需要重新生成用于绑定的.c文件。
结合lua和tolua++,一个简单的用例如下
File “main.cpp”
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#ifdef __cplusplus
}
#endif
#include "tolua++.h"
TOLUA_API int tolua_lua_interface_open (lua_State* tolua_S);
int add(int a,int b)
{
return a+b;
}
int main()
{
lua_State* L = lua_open();
luaL_openlibs(L);
tolua_lua_interface_open(L);
luaL_loadfile(L, "test.lua");
lua_pcall(L, 0, 0, 0);
lua_close(L);
L = NULL;
system("pause");
}
File “lua_interface.h”
int add(int a,int b);
File “test.lua”内容如下
print(add(1,2))
运行结果会控制台输出
3
通过上面的例子我们已经完成了C/C++执行lua脚本文件,然后在lua调用C/C++函数,而对于游戏脚本,我们每帧需要在脚本和C/C++之间切换控制权,例如先由脚本执行游戏逻辑,然后C/C++渲染,如何实现这一点呢?这需要借助lua的协作例程(coroutine)机制,coroutine与线程比较类似,有自己的堆栈,自己的局部变量,自己的指令指针,但它和线程有一些区别,coroutine是非抢占式的,coroutine不会被外部挂起和调度,运行中的coroutine只会在运行完毕或主动挂起自己是交出控制权。
将一个coroutine从挂起切换到运行需要在C++调用lua_resume(L, 0);或在脚本调用coroutine.resume(co),调用会阻塞直到coroutine交出控制权后才返回,从运行切换到挂起需要调用coroutine.yiled()。再来看一段示例
File “Main.cpp”
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#ifdef __cplusplus
}
#endif
#include "tolua++.h"
TOLUA_API int tolua_lua_interface_open (lua_State* tolua_S);
int main()
{
lua_State* L = lua_open();
luaL_openlibs(L);
tolua_lua_interface_open(L);
luaL_loadfile(L, "test.lua");
int status = lua_status(L);
while(status == 0 || status == LUA_YIELD)
{
lua_resume(L, 0);
puts("render");
status = lua_status(L);
}
lua_close(L);
L = NULL;
system("pause");
}
File “test.lua”
print("logic1")
coroutine.yield()
print("logic2")
coroutine.yield()
print("logic3")
运行结果输出
logic1
render
logic2
render
logic3
render
render
在实际的游戏脚本中,脚本交出控制权往往是因为要等待某个条件(摄像机到某帧)或者事件(某个敌人死亡,某个光标被破坏),可以将一些常用的等待封装,在提供一个通用的等待实现,这样游戏逻辑脚本中就不需要出现yield这个层面语义的调用了。
function Wait( signal, param, param2 )
if signal == SIGNAL_NIL or signal == SIGNAL_GAME_TIME_GS then
local startTime = GameGetTime()
repeat
YieldCoroutine()
until GameGetTime()-startTime >= param
elseif signal == SIGNAL_CAM_MOT_ENDED then
local startTime = GameGetTime()
while not CameraIsMotionEnd() do
YieldCoroutine()
end
elseif signal == SIGNAL_CAM_MOT_FRAME then
while not (CameraGetMotionFrame() >= param) do
YieldCoroutine()
end
elseif signal == SIGNAL_ENEMY_MOT_ENDED then
while not EnemyIsMotionEnd(param) do
YieldCoroutine()
end
else
...
end
end
function WaitFor(func)
assert(type(func) == "function")
local r = func()
while type(r) == "boolean" and not r do
YieldCoroutine()
r = func()
end
end