刚开始弄cocos2d-x 3.x的lua框架。看了很多网上的文章和例子。到最后自己手动写 的时候也遇到很多困难。现在就把这个学习过程记录下来。
首先我看完了 lua脚本在c++的舞步。这篇文章很好,很详细的讲述了c++与lua是如何互相调用的。之后我也看了笨木头的5个例子,简单的实现了一下印证了一些想法。
1)Lua指针的初始化
lua_State* pL =lua_open();
luaL_openlibs(pL);
Lua指针的释放
lua_close(pL);
而在cocos2d-x中 lua_State*被封装在LuaStack对象中,它的构造和析构也执行了相同的调用。不同的地方是它的初始化中所执行的初始化语句更多,包装了更多的绑定相关的内容。
2)lua本身是脚本语言,加载lua本身的时候才会编译。所以,推荐大家在加载文件的时候尽量放在程序的初始化中,因为当你执行luaL_dofile()函数的时候,Lua会启用语法分析器,去分析你的脚本语法是否符合Lua规则,如果你胡乱的传一个文件过去,Lua就会告诉你文件语法错误,无法加载。如果你的Lua脚本很大,函数很多,语法分析器会比较耗时,所以,加载的时候,尽量放在合适的地方。
int nRet =luaL_dofile(_state, fullPath.c_str());
if (nRet !=0)
{
CCLOG("[LUA ERROR] %s",lua_tostring(_state, -1));
lua_pop(_state,1);
return nRet;
}
这一段指明执行完lua文件后返回若非0则出错,lua_tostring从栈中获得错误信息描述,并输出。
说到这里简单借用一下lua的栈描述栈底为1,往栈顶顺序增加。栈顶为-1,往栈底顺序减少。执行文件出错,lua会将出错信息放入栈顶,因此为-1。
3)对lua进行栈操作的常用函数介绍
压入元素到栈里
void lua_pushnil (lua_State *L); void lua_pushboolean (lua_State *L, int bool); void lua_pushnumber (lua_State *L, double n); void lua_pushlstring (lua_State *L, const char *s, size_t length); void lua_pushstring (lua_State *L, const char *s); void lua_pushcfunction (lua_State *L, lua_CFunction fn);
查询栈里的元素
lua_isnil (lua_State *L, int index); lua_isboolean (lua_State *L, int index); int lua_isnumber (lua_State *L, int index); int lua_isstring (lua_State *L, int index); int lua_isfunction (lua_State *L, int index); int lua_istable (lua_State *L, int index); int lua_isuserdata (lua_State *L, int index); lua_islightuserdata (lua_State *L, int index); lua_isthread (lua_State *L, int index);
转换栈里的元素
int lua_toboolean (lua_State *L, int index); double lua_tonumber (lua_State *L, int index); const char * lua_tostring (lua_State *L, int index); const char * lua_tolstring (lua_State *L, int idx, size_t *len); size_t lua_strlen (lua_State *L, int index); lua_CFunction lua_tocfunction (lua_State *L, int idx); void * lua_touserdata (lua_State *L, int idx); lua_State * lua_tothread (lua_State *L, int idx);
Lua栈的维护
int lua_gettop (lua_State *L); 取得栈顶元素的索引,即栈中元素的个数 void lua_settop (lua_State *L, int index); 设置栈顶索引,即设置栈中元素的个数,如果index<0,则从栈顶往下数,下同 void lua_pushvalue (lua_State *L, int index); 把栈中指定索引的元素复制一份到栈顶 void lua_remove (lua_State *L, int index); 删除指定索引的元素 void lua_insert (lua_State *L, int index); 移动栈顶元素到指定索引的位置,栈中数目没有改变 void lua_replace (lua_State *L, int index); 从栈顶弹出元素值并将其设置到指定索引位置,栈中的数目减一 int lua_checkstack (lua_State *L, int extra); 确保堆栈上至少有 extra 个空位。如果不能把堆栈扩展到相应的尺寸,函数返回 false 。这个函数永远不会缩小堆栈。 int lua_pop(L,n) 从栈顶弹出n个元素,它是一个lua_settop的包装:#define lua_pop(L,n) lua_settop(L, -(n)-1)
void lua_newtable (lua_State *L); 新建一个空的table并压入栈顶。 void lua_settable (lua_State *L, int idx); lua_settable以table在栈中的索引作为参数,并将栈顶的key和value出栈,用这两个值修改table。 void lua_gettable (lua_State *L, int idx); lua_gettable以table在栈中的索引作为参数,弹出栈顶的元素作为key,返回与key对应的value并压入栈顶。 最后,Lua告别针对table提供了存取函数 void lua_rawgeti (lua_State *L, int idx, int n) 取得table[n]并放到栈顶,例如lua_pushnumber(L,i);lua_gettable(L,-2);可以用lua_rawgeti(L,-1,i)代替。 lua_getfield (lua_State *L, int idx, const char *k) 取得table.k并放到栈顶,例如lua_pushstring(L,"u");lua_gettable(L,-2);可以替换lua_getfield(L,-1,"u")。
void lua_setfield (lua_State *L, int idx, const char *k) 把栈顶的数据作为value放入table.k中,形如lua_pushstring(L, "key");lua_pushstring(L,value);lua_settable(L, -3);可以改成lua_pushstring(L, value);lua_setfield(L,-2,"key");的形式。
void lua_rawseti (lua_State *L, int idx, int n) 把栈顶的数据作为value放入table[n]中,形如lua_pushnumber(L,value);lua_rawseti(L,-2,key);
raw操作针对table的key均为数字,类似数组.filed操作针对table的key为string,类似map.
4) c++中获取lua中的全局变量
lua_getglobal(L, "var"); //变量的值现在栈顶
5) c++中设置lua中的全局变量
lua_pushnumber(L, 10);
lua_setglobal(L, "z");
操作结束后设置lua中的全局变量z = 10,即使z原本不在lua的全局变量表中也会创建。
6) c++中设置lua中表的值
lua_getglobal( L, "myTable" ); // 获取要设置值的table
lua_pushstring( L, "hp" ); // "hp"在栈上的位置为-1
lua_pushnumber( L, 211 ); // "hp"在栈上的位置变为-2,而211则是-1
lua_settable( L, -3 ); // 值被正确的设置到全局变量(表)的myTable中
如果我是想把hp这个值设置到全局表中呢?一般通过调用lua_setglobal宏
lua_pushnumber( L, 211 );
lua_setglobal( L, "hp" );
virtual int executeGlobalFunction(const char* functionName);
方法,该方法会执行一个不带参数的全局lua函数,且返回一个整形值
lua中代码如下:
function testByWp()
print("-----wptest------")
return 1;
end
c++中代码如下:
auto engine = LuaEngine::getInstance();
int ret = engine->executeGlobalFunction("testByWp");
可以看到LuaEngine中封装了LuaStack中的executeGlobalFunction函数。
我们若要执行带参数的函数怎么办呢。那么就简单的改造一下LuaStack中的函数
int LuaStack::executeGlobalFunction(const char* functionName)
{
lua_getglobal(_state, functionName); /* query function by name, stack: function */
if (!lua_isfunction(_state, -1))
{
CCLOG("[LUA ERROR] name '%s' does not represent a Lua function", functionName);
lua_pop(_state, 1);
return 0;
}
return executeFunction(0);
}
这个是原函数,我们要传入参数 改造如下int LuaStack::executeGlobalFunction(const char* functionName,int value ,int numArgs)
{
lua_getglobal(_state, functionName); /* query function by name, stack: function */
if (!lua_isfunction(_state, -1))
{
CCLOG("[LUA ERROR] name '%s' does not represent a Lua function", functionName);
lua_pop(_state, 1);
return 0;
}
pushInt(value);
return executeFunction(numArgs);
}
其实这里numArgs可以不用传递,作为例子我们只是简单的为函数传入一个参数,因此numArgs的值为1.若要传入不同的参数,就得自己封装一下。
lua中代码如下:
function testByWp(int val)
print("-----wptest------"..val)
return 1;
end
c++中代码如下:
auto engine = LuaEngine::getInstance();
int ret = engine->executeGlobalFunction("testByWp",10,1);
若要传递用户自定义类型作为参数,那么首先要求该自定义类型继承自cocos::Ref ,然后通过genbindings绑定工具绑定到lua中以使得lua中能识别。然后我们再封装一个执行得函数
int LuaStack::executeGlobalFunction(const char* functionName,cocos2d::Ref *objectValue, const char *typeName)
{
lua_getglobal(_state, functionName); /* query function by name, stack: function */
if (!lua_isfunction(_state, -1))
{
CCLOG("[LUA ERROR] name '%s' does not represent a Lua function", functionName);
lua_pop(_state, 1);
return 0;
}
pushObject(objectValue, typeName);
return executeFunction(1);
}
这里更加简单一些,传入一个自定义对象 以及类型得名称。
lua中代码如下:
function testByWp(user_data)
print("-----wptest------")
if user_data then
user_data = tolua.cast(user_data, "TestScene");
print (user_data:GetFlag());
end
return 100;
end
c++中代码如下:
auto engine = LuaEngine::getInstance();
TestScene *pScene = new TestScene;
pScene->SetFlag(108);
int ret = engine->executeGlobalFunction("testByWp",pScene,"TestScene");
CC_SAFE_DELETE(pScene);