一:一个小程序
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
#include
#include
}
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
#include"LuaManager.h"
extern "C" {
#include
#include
#include
}
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
#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));
}