posts - 225, comments - 62, trackbacks - 0, articles - 0
   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

集成Lua到游戏

Posted on 2013-09-06 15:12 魔のkyo 阅读(615) 评论(2)  编辑 收藏 引用

安装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
只有注册用户登录后才能发表评论。