在游戏引擎中集成Lua

一:一个小程序

Lua程序非常简单易懂,下面时一小段Lua代码,非常简洁明了。

function PrintHello()
    print("Hello Lua!!!")
end

二:动态数据类型定义

Lua的数据类型定义是动态的,不需要声明变量或者定义变量的类型,只需要拿来用就行了,例如:

secondTime = 24.5
str = "i'm Lua"

三:自动的内存管理

Lua会自动的对内存进行管理,当某个值不再被引用,垃圾收集机制就可以把他处理掉。这样,就不需要专门去释放任何内存了。

四: Lua状态

本质上讲,LuaState是Lua解释器的一个单独的操作环境,包括堆栈,执行状态和全局变量等等。当把Lua嵌入到游戏中时,LuaState就是一个接口对象,大多数程序只需要一个LuaState。

Lua和C语言的接口

1 Lua中调用C语言代码

很多程序员需要完成的第一件事就是把他们的引擎中的功能对Lua公开,并对这些功能实行基本的脚本控制。只需要Lua来注册实现一个"glue(接口沾合)"例程,就可以完成上述工作。

例如:

lua

Camera.SetTargetPos(100.0, 40.0, 320.0)

C++

static int LuaSetTargetPos(lua_State * luaState)
{
    float x,y,z;
    x = (float)lua_tonumber(luaState, 1);
    y = (float)lua_tonumber(luaState, 2);
    z = (float)lua_tonumber(luaState, 3);

    CameraSetTargetPos(x, y, z);
    return 0;
}

Lua调用C++函数的时候他会把脚本文件里找到的参数顺序地推入到通信栈中。然后再调用为这个C函数注册的glue例程,要传递给该例程一个指向LuaState的指针,脚本就在Lua_State中运行了。我们需要知道LuaState只是一个对象,它代表了Lua解释器的整体状态,包括所有的数据,通信栈和任何用Lua注册的函数。

然后这个例子中glue例程会从栈中取出3个值 分别是x,y,z。我们调用lua_toxxx()函数从堆栈中读取数据并且把它转换成相应的C语言数据类型。最后glue例程会返回一个0值,表示没有把任何数据放回到通信栈中作为返回Lua脚本的返回值。

由于Lua的类型定义是动态的,所以必须知道所期待的参数是什么类型的,并进行适当的转换。在前面的例子中,我们需要3个浮点值表示相机的位置。Lua把所有的数字值都设定为double,但我们仍然要在把它强制转换成float。

2将值推送到Lua

现在,假设Lua需要查询玩家的当前得分。在Lua中

if MainPlayer.GetPlayerScore() > MAX_SCORE then
    showPrompt("Player Score !!!")
end

为了向Lua脚本返回玩家的得分,需要创建一个glue例程。这个例程就会把一个数值推到通信栈中,

static int LuaGetPlayerScore(lua_State* L)
{
    lua_pushnumber(L, GetMainPlayer()->GetScore());
    return (L);
}

在这种情况下,glue例程没有从通信栈取走任何参数,而是把一个值放到了堆栈中,然后返回L并通知Lua,实际上,Lua的函数可以有多个返回值,C语言可以向通信栈返回多个值,这是完全合法的。

3注册glue例程

注册glue例程要使用Lua函数库管理调用,例子如下:

static const luaL_reg cameraLib[] = {
    { "SetMode", LuaSetMode },
    { "GetMode", LuaGetMode },
    { "SetTargetPos", LuaSetTargetPos },
    { "SetFov", LuaSetFov },
    { NULL, NULL }
};

接下来,调用

luaL_openlib(luaState, "Camera", cameraLib, 0)

第一个参数是正在使用Lua State,第二个参数是这个库起的名称,第三个参数是函数指针和函数名称组成的数组。这个调用后,Lua就可以调用了,Lua会以库的名称作为前缀。

游戏中嵌入lua

理解了如何在Lua和游戏引擎中创建应用接口,,下面就将在游戏中嵌入Lua

1创建和销毁state

为了能够在游戏中使用Lua,我们必须首先创建一个LuaState

luaState* luaState = luaL_newstate();

要关闭游戏时,我们就关闭这个state

lua_close(lua_State);

上述语句会释放解释器所使用的内存,并进行垃圾回收。

2加载并执行代码

一旦lua初始化完成,那就要开始执行Lua代码了,Lua提供相应的支持,可以从任何数据源向解释器提供数据下面可以执行一个脚本

lua_dofile(luaState, "LuaScriptFile.lua");

如果存在某些执行点,这样的语句会让系统对脚本进行编译和执行。如果某个文件什么都没有,只有函数定义,那么就会定义所有的这些函数,并把他们添加到LuaState中,但不会执行任何实际的代码。

同样的,字符串也是

lua_dostring(luaState, "x=1;y=2;");

Lua发行中有个luac预译器,它可以把脚本编译为二进制文件,这样开发人员就可以把一些重要的东西编译成二进制问文件,另外不重要的可以用文本解析。

我们也可以加载代码和翻译代码,而不去执行代码,实现如下:

lua_loadfile(luaState, "LuaScriptFile.lua");

3在C中调用Lua函数

前面我们提到我们可以先把脚本加载到Lua中但不去执行,而是等以后需要的时候在执行实际的代码,这个功能非常有用。关于这一点,在启动时,每一个实体类可以加载一个Lua脚本,这个脚本定义了该实体在游戏中的行为。可能有几个Lua例程:

function PlayerMove(vec, elapsedSec)
    timer = timer - elapsedSec
    MainPlayer.Move(vec);
end

function PlayerRender()
    visible = MainPlayer.IsVisible()
    if(visible) then
        MainPlayer.RenderModel()    
    end
end

当这个实体类被初始化后,他举可以使用luaL_loadfile()来读取并编译脚本,然后在游戏循环中,这个实体C代码可以根据需要调用lua例程。要想在C代码中调用指定的Lua代码,首先将这个Lua函数及其所有的参数放到通信栈中,在调用lua_call(),一个调用PlayerMove()例程如下

lua_getglobal(luaState, "PlayerMove")
lua_pushlightuserdata(luaState, this)
lua_push_number(luaState, elapsedSec)
lua_call(luaState, 2 ,0)

第一个调用会将一个全局变量推入到通信栈中,该变量名字是"PlayerMove."。这正好是初始化之后加载的那个函数的名字。接下来会把C++实体this指针放进通信栈,供给Lua使用。事实上,Lua不会直接使用这个值,因为它根本不理解这个C++对象,相反,Lua只是吧这个值回传给C的glue函数,这样,这些函数就知道使用哪个对象指针了。为了把this指针推进通信栈,需要调用lua_pushlightuserdata()."light user data"是一个Lua特殊的类型,当C代码需要传递哥Lua某个它不能理解的数据时,就可以使用这个数据类型。大多数情况,就像上面这例子,会用light user data 类型来保存指向C/C++对象和结构的指针。

然后调用lua_pushnumber().将从上一帧到现在过去的时间秒数推入通信栈,函数和所有的参数都已经正确进入通信栈,就可以调用lua_call(luaState, 2, 0)。

其中第二个参数(2)告送Lua有多少哥参数被推入通信栈,第三个参数(0)告送Lua,我们向得到多少哥返回值。

实时性能方面考虑

当使用lua_dofile()加载并执行 lua chunk(段),或者使用lua_call()函数时,只有当请求被Lua脚本处理完毕的时候才会返回。对于一些应用和架构,我们需要时间来做应用。

一个基于时间的例子:

例如,游戏需要每隔段时间播放一段动画或者视频,移交控制权,我们可以使用Lua中的协程。

    print("-----脚本开始了-----");
    print("-----250秒后开始播放一段声音-----");
    while (1) do
        Script.WaitSec(250);
        Script.Beep(440, 20);
        Print_State(Script.GetState());
        print("Script1: Beep");

        print("-----播放动画-----")
        print("-----剧情开始-----")
        print("-----控制失效-----")
        print("......DoSomething.....")
    end;

Lua协程支持可以让Lua脚本中途随时终止脚本的执行,并把他的控制权力交给C程序,使用方法时在Lua中使用yield,或者在C语言APi中使用lua_yield()。

我们使用的时C语言的lua_yield的调用,在Script.WaitSec()包含对lua_yield()的调用,他会把控制权交还给游戏,这样游戏在挂起的同时,我们就可以做别的事情,如果脚本系统觉得已经过去了足够的时间,就可以使用lua_resume()函数来重新唤起。这样就可以继续执行该执行的函数。

多脚本支持

运行多个脚本,我们可以使用lua_newthread(mainLuaState),在Lua中,Lua线程可以看作为主lua_State中的一个子state,它只运行自己的脚本。每个新创建的State可以共享最初lua_State所有的全局函数和变量,并且新的state都有自己的堆栈和执行状态,而且每个state可以独立地向调用它的程序交还控制权。这样,系统可以管理很多的脚本。每个脚本可以自己管理自己状态,适当的交还控制权。

管理类:

LuaManager

LuaManager管理器类通过luaL_newstate()来创建一个lua_State。它同过提供CreateScript()来创建作为脚本唯一的方法,维护一个正在运行的LuaScript的链表。管理器每游戏循环一次就会执行一个Update()函数,在Update函数中检测脚本的所属状态,唤醒需要继续执行的代码。

class LuaScript;
typedef struct lua_State lua_State;

class LuaManager
{
public:
    lua_State* masterState;

    LuaManager();
    ~LuaManager();

    LuaScript* CreateScript();
    void UnLinkScript(LuaScript* s);
    void Update(float elapsedSec);
    int NumScripts();
private:
    LuaScript* head;
};
#include "LuaManager.h"
#include"LuaLib.hpp"
#include"LuaScript.h"

extern "C" {
#include<lua.h>
#include<lualib.h>
#include<lauxlib.h>
}

LuaManager::LuaManager()
{
    masterState = luaL_newstate();
    if (masterState)
    {
        luaopen_base(masterState);
        luaopen_math(masterState);
#if _DEBUG
        luaopen_debug(masterState);
#endif
        LuaOpenScriptLib(masterState);
    }
    head = nullptr;
}

LuaManager::~LuaManager()
{
    LuaScript* s;
    LuaScript* next;

    s = head;
    while (s)
    {
        next = s->next;
        delete s;
        s = next;
    }

    lua_close(masterState);
}

LuaScript* LuaManager::CreateScript()
{
    LuaScript* s;
    s = new LuaScript(this);
    s->manager = this;
    s->next = head;
    head = s;
    return s;
}

void LuaManager::UnLinkScript(LuaScript* s)
{
    LuaScript* prev;

    if (head == s)
    {
        head = head->next;
        return;
    }
    prev = head;
    while (prev)
    {
        if (prev->next == s)
        {
            prev->next = s->next;
            return;
        }
        prev = prev->next;
    }
}
void LuaManager::Update(float elapsedSec)
{
    LuaScript* s;

    s = head;
    while (s)
    {
        s = s->Update(elapsedSec);
    }
}

int LuaManager::NumScripts()
{
    LuaScript* s;
    
    s = head;
    int i = 0;
    while (s)
    {
        if (s->next != nullptr)
        {
            i++;
        }
        s = s->next;
    }
    return i;
}

脚本对象:

LuaScript脚本对象是从LuaManger中masterState中派生的lua_State子对象。每个脚本对象可以作为一个线程来执行,并根据需要移交控制权。并且每一个脚本还有本身的执行状态。

class LuaManager;
typedef struct lua_State luaState;

typedef enum luaScriptStateTag {
    LSS_WAITFRAME,//Lua等待多少帧
    LSS_WAITTIME,//Lua等待时间
    LSS_RUNNING,//Lua正在运行
    LSS_NOTLOAD,//Lua未加载
    LSS_DONE //Lua执行完成
}LUASCRIPTSTATE;

class LuaScript
{
public:
    LuaManager* manager;
    LuaScript* next;
    LUASCRIPTSTATE state;
    //等待多少时间
    float waitTimsStamp;
    //等待多少帧
    int waitFrame;
    //时间
    float time;

    LuaScript(LuaManager* mgr);
    ~LuaScript();

    LuaScript* Update(float elapsedSec);
    void RunFile(char* fileName);
    int RunString(char* commandString);
    void CallFn(char* fnName, int iParam);
    void AbortWait();

private:
    lua_State* threadState;
    char lastErrorString[256];
    void ResumeScript(float param);
    void FormatError();
    void OutputError(char* strType);
};

#include "LuaScript.h"
#include<string.h>
#include"LuaManager.h"

extern "C" {
#include<lua.h>
#include<lualib.h>
#include<lauxlib.h>
}

LuaScript::LuaScript(LuaManager* mgr)
{
    manager = mgr;
    state = LSS_NOTLOAD;
    time = 0;
    strcpy(lastErrorString, "No Error. \n");

    //创建一个对象state 
    threadState = lua_newthread(manager->masterState);

    lua_pushlightuserdata(manager->masterState, threadState);
    lua_pushlightuserdata(manager->masterState, this);
    lua_settable(manager->masterState, LUA_GLOBALSINDEX);
}

LuaScript::~LuaScript()
{
    manager->UnLinkScript(this);
}

void LuaScript::RunFile(char* fileName)
{
    int status;

    lua_assert(manager->masterState);
    lua_assert(threadState);

    status = luaL_loadfile(threadState, fileName);
    if (status == 0)
    {
        ResumeScript(0.0f);
    }
    else 
    {
        FormatError();
        OutputError((char*)"Syntax Error");
    }

}

void LuaScript::FormatError()
{
    const char* msg;

    msg = lua_tostring(threadState, -1);
    if (msg == NULL)
        msg = "(error with no message!)";
    lua_pop(threadState, 1);

    strcpy(lastErrorString, msg);
}

void LuaScript::OutputError(char* strType)
{
    printf("%s %s \n", strType, lastErrorString);
}

void LuaScript::CallFn(char* fnName, int iParam)
{
    int status;

    lua_getglobal(threadState, fnName);

    lua_pushnumber(threadState, iParam);

    status = lua_pcall(threadState, 1, 0, 0);

    if (status)
    {
        FormatError();
        OutputError((char*)"RunTime Error:");
    }
}

void LuaScript::ResumeScript(float param)
{
    int status;
    state = LSS_RUNNING;

    lua_pushnumber(threadState, param);

    status = lua_resume(threadState, 1);

    if (status == LUA_ERRRUN)
    {
        FormatError();
        OutputError((char*)"Runtime Error!!");
    }
}

void LuaScript::AbortWait()
{
    //快速执行
    printf("快速干涉执行");
    ResumeScript(0.0f);
}

int LuaScript::RunString(char* commandString)
{
    int status;

    lua_assert(manager->masterState);
    lua_assert(threadState);

    status = luaL_loadbuffer(threadState, commandString, strlen(commandString), "Console");
    if (status == 0)
        status = lua_pcall(threadState, lua_gettop(threadState) - 1, 0, 0);

    if (status)
    {
        FormatError();
        return -1;
    }
    return 0;
}

LuaScript* LuaScript::Update(float elapsedSec)
{
    time += elapsedSec;

    switch (state)
    {
    case LSS_WAITTIME:
        if (time >= waitTimsStamp)
            ResumeScript(0.0f);
        break;
    case LSS_WAITFRAME:
        waitFrame--;
        if (waitFrame <= 0)
            ResumeScript(0.0f);
        break;
    case LSS_NOTLOAD:
        break;
    default:
        break;
    }
    return next;
}

yield例程

#include<windows.h>
#include"LuaScript.h"

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

static LuaScript* GetScriptObject(lua_State* L);
static int          LuaBeep(lua_State* l);
static int          LuaWaitFrame(lua_State* l);
static int          LuaWaitTime(lua_State* l);
static int          LuaWaitSec(lua_State* l);
static int          LuaGetState(lua_State* L);
static int          LuaGetFrameNum(lua_State* L);
//static float        LuaGetTime(lua_State* l);

static const luaL_reg scriptLib[] = {
    {"Beep", LuaBeep},
    {"WaitFrame", LuaWaitFrame},
    {"WaitTime", LuaWaitTime},
    {"WaitSec", LuaWaitSec},
    {"GetState", LuaGetState},
    {"GetFrameNum", LuaGetFrameNum},
    {NULL, NULL}
};

void LuaOpenScriptLib(lua_State* L)
{
    luaL_openlib(L, "Script", scriptLib, 0);
}

static int LuaGetState(lua_State* L)
{
    LuaScript* s;

    s = GetScriptObject(L);
    lua_pushnumber(L, s->state);
    return 1;
}

static int LuaBeep(lua_State* L)
{
    Beep((int)luaL_checknumber(L, 1), (int)luaL_checknumber(L, 2));
    return 0;
}

static int          LuaGetFrameNum(lua_State* L)
{
    LuaScript* s;

    s = GetScriptObject(L);
    lua_pushnumber(L, s->waitFrame);
    return 1;
}

static int LuaWaitFrame(lua_State* L)
{
    LuaScript* s;

    s = GetScriptObject(L);
    s->waitFrame = (int)luaL_checknumber(L, 1);
    s->state = LSS_WAITFRAME;

    return (lua_yield(L,1));
}

static LuaScript* GetScriptObject(lua_State* L)
{
    lua_pushlightuserdata(L, L);
    lua_gettable(L, LUA_GLOBALSINDEX);
    return ((LuaScript*)lua_touserdata(L, -1));
}

static int LuaWaitSec(lua_State* L)
{
    LuaScript* s;

    s = GetScriptObject(L);
    s->waitTimsStamp = s->time + (float)luaL_checknumber(L, 1);
    s->state = LSS_WAITTIME;

    return (lua_yield(L, 1));
}

static int LuaWaitTime(lua_State* L)
{
    LuaScript* s;

    s = GetScriptObject(L);
    s->waitFrame = (float)luaL_checknumber(L, 1);
    s->state = LSS_WAITTIME;

    return (lua_yield(L, 1));
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值