《quick-cocos2d-x-luabinding 教程.docx》由会员分享,可在线阅读,更多相关《quick-cocos2d-x-luabinding 教程.docx(16页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、Quick-cocos2d-x luabinding 教程-基于quick-cocos2d-x 3.3rc1版本目录Quick-cocos2d-x luabinding 教程11.lua绑定原理21.1什么是lua21.2Lua绑定原理22.tolua+绑定原理62.1什么是tolua+62.2为什么要使用tolua+来注册C类62.3如何使用tolua+63.quick 中导出C/C+ API给LUA使用83.1基本步骤83.2实战演示93.3tolua 文件内容的修改规则133.3.1删除所有无需在 Lua 中使用的内容133.3.2处理 CCObject 继承类133.3.3展开宏133
2、.3.4处理名字空间143.3.5添加必要的 #include 指令143.3.6修改函数参数和返回值类型,去除 const 修饰符143.3.7从 C/C+ 函数返回多个值143.3.8将 Lua 函数传入 C/C+153.3.9在 Lua 和 C/C+ 间交换二进制数据153.3.10更多用法16前言本文主要目的是帮助使用quick-cocos2d-x(简称quick)的开发者快速的掌握在在quick如何绑定C-C+到lua,quick或者cocos2d-x对于lua绑定其实是基于tolua+进行封装的,为了读者更好的了解绑定原理,文章的开始会简单介绍lua绑定和tolua+(第三方软件软
3、件包)绑定原理。文中关于quick的luabinding说明完全基于quick开发团队官网关于luabinding的说明,为了方便理解做了一下简单调整和说明,如果你已经了解并且只想知道quick中luabinding的使用可以直接从第三章开始阅读。1. lua绑定原理在直接讲解lua绑定原理前,我们先进行一下扫盲,照顾一下一些对lua不是很了解的读者。并且下面的分析对lua脚本整合于游戏引擎的模式进行分析。1.1 什么是lualua是一种免费的、轻量的、独立的、可扩展的嵌入式程序语言,具有变量无类型、动态定义类型、面向对象、编译产生中间代码和内存自动回收的特点。正因为其这新特点常被作为一种脚本
4、嵌入到其他系统中。Lua是由标准的ANSIC语言实现的一个静态库,因此有对应各种操作系统的版本,简单的说只要支持编制C的操作系统都支持。Lua语法简单但是功能强大,可移植性高,被广泛的作为脚本语言嵌入于主程序中。1.2 Lua绑定原理Lua是一种嵌入式语言,依靠虚拟机运行。Lua系统是在state的机制上运行,state中包含运行环境的借口信息,而且每个state只能容纳一个脚本文件装载至内存中,要同时执行多个脚本必须初始化多个state。所以lua与其他系统共享数据是通过栈来实现的。将函数和参数等信息压栈来相互引用。下图展示了lua引用引擎的函数,我们用图文结合的方式进行说明。从图中可以看出
5、要在引擎中调用lua脚本,必须先起一个state,在lua中要调用引擎中的脚本,需要引擎中先注册,引擎中注册后在乱中就可以调用了。引擎中注册的函数,对于lua而言相当于一个系统函数。在引擎中引用lua函数式同样的原理,依然通过state。见下图在引擎中调用lua函数,首先需要将函数名压站,然后将参数压栈,然后调用lua_call执行,最后是从栈中取返回值图中没有画出。看到这里相信大家对于lua与主程是如何相互引用的原理有了一定认识,下面我们直接通过代码演示,加深一下印象。例1:纯C环境下,注册C函数进LUA环境Main.c#include #include #include int foo(l
6、ua_State *L) int n = lua_tonumber(L, 1);foo函数参数 lua_pushnumber(L, n + 1);foo 返回值 return 1; foo函数返回值个数int main() lua_State *L = lua_open(); 初始化一个state luaL_openlibs(L); 加载lua基本库 lua_register(L, foo, foo);注册C函数foo luaL_dofile(L, a.lua);装载,检查,并立即执行a.lua lua_close(L);与lua_open对应,关闭state return 0;文件a.lua
7、 print(foo(99)文件a.lua中就一条语句,加入lua虚拟机编译运行后,在屏幕就看到输出100例2:C环境下,调用lua函数#include #include #include int main() lua_State *L = lua_open(); 初始化一个state luaL_openlibs(L); 加载lua基本库 luaL_dofile(L, b.lua); lua_getglobal(L, add);将函数名压栈 lua_pushnumber(L, 51);参数压栈lua_pushnumber(L, 49);lua_call(L,2,1);调用最近一次压栈的函数in
8、t result = (int) lua_tonumber(L,1); 从栈获取返回值lua_pop(L,1);从栈中清除返回值 lua_close(L); return 0;b.luafunction add(nubmer1, number2)print(number1)print(number2)return number1 + number2end例2输出的结果是51 49 100通过这两个例子相信大家已经看到lua和主程序相互调用非常的简单,这里用的是c语言的一个示例,使用起来很简单,但是我们一般需要帮定给lua的函数有很多,比如真个cocos2d-x的库函数,这么多函数要绑定注册lu
9、a,那么需要我们重复写多少上面一样的代码,而且每个库绑定都需要重新写写,这些繁琐的工作也是一个非常痛苦的事情,更疼苦的是lua的本质是C,提供的函数都是C函数,要把一个C+的类注册进lua形成一个table是不是要做更多工作来转换,但是这些仍然是重复性。既然是重复性的工作,那么有没有什么工具或者库来帮我们完成这样的工作呢。答案当然是肯定的,这就是我们接下来要讲的tolua+,一个三方软件包。2. tolua+绑定原理2.1 什么是tolua+tolua+是一种第三方的软件包,可以为Lua提供面向对象的特性,更直接的说tolua+ 是一个将 C/C+ 的函数和对象导出给 Lua 脚本使用的工具,
10、即帮我们完成我们上面的重复性的工作。2.2 为什么要使用tolua+来注册C类因为Lua的本质是C,不是C+,Lua提供给C用的API也都是基于面向过程的C函数来用的,要把C+类注册进Lua形成一个一个的table环境是不太容易一下子办到的事,因为这需要绕着弯地把C+类变成各种其他类型注册进Lua,相当于用面向过程的思维来维护一个面向对象的环境。这其中的细节就不去深究了,总之正是因为如此,所以单纯地手写lua_register()等代码来注册C+类是行不通的、代价高昂的,所以需要借助toLua+这个工具。2.3 如何使用tolua+使用这个工具的基本步骤: 将要导出的 C/C+ 函数和对象定义
11、写入 .pkg 文件; 运行 tolua+ 工具,将 .pkg 文件编译为目标 .cpp 文件; 将目标 .cpp 文件加入项目,在启用 Lua 虚拟机后调用目标文件中的 open() 函数注册导出的内容。看到这里相信大家最想知道的什么是pkg文件,pkg文件内容怎么写, 简单说pkg文件就是一个遵循一定规则的文本文件,规则很简单和我们的头文件差不多,只是表明我们那些类,那些函数需要暴露给lua使用,具体的规则和使用可以去官网上学习,或者参考tolua+源码中的例子,其他的不多说了,直接给大家来个例子就明白了。例1:tolua+的使用/ file: MyClass.h#include clas
12、s my_classpublic: void greet() std:cout “Hello World!” tolua+ n mylib o mylib.cpp mylib.pkg然后将生成mylib.cpp 文件加入你要编译的工程中编译,并且链接tolua+库/ file: Main.cpp#include #include extern “C” #include #include #include “MyClass.h”int main()int tolua_mylib_open (lua_State*) ;声明 lua_State* L = lua_open(); luaL_openl
13、ibs(); tolua_mylib_open(L); / 打开mylib,该函数在cpp中有实现。 luaL_dofile(L, “mytest.lua”); / 执行脚本文件 lua_close(L); return 0;测试lua文件/mytest.lualocal my = my_class()my:greet()看到这里大家对tolua+的用法应该已经明白了吧,把上面生成cpp的步骤写个脚本是不是更方便,还有int tolua_mylib_open (lua_State*) ;要放在我们自己的文件声明是不是很怪,其实解决这个问题很简单只要tolua+在生成一个头文件就可以了。我们接下
14、来的quick就为我们提供了这样的支持,在quick上述得操作就有事一个脚本和一个.tolua的文件,然后会生成一个cpp和.h 文件,只要我们把生成的cpp和.h加入工程就可以了,是不是很方便了,并且quick和cocos2dx本身就提供了对lua的支持,所以我们使用quick需要导入我们自己的类就可以了。就不在这里多说,下一章我们将详细讲解quick中的luabinding工作原理,也是我们本篇文章的重点。3. quick 中导出C/C+ API给LUA使用本章主要是重点是讲解在quick-cocos2d-x 中将自定义C-C+ API导出给lua使用。大家会很奇怪,这片文章主要是讲qui
15、ck中luabinding的使用,为什么要将前两章的内容呢,其实前两章非常重要,只有了解lua注册的具体细节,才能理解使用tolua+这个工具的必要性,同时才会思考tolua+带来的优缺点,然后才能理解quick中compile-luabinding.bat帮助我们到处C-C+ API脚本的好处,才能真正了解怎么使用。到目前为止quick和cocos2d-x + lua 都是基于tolua+的。未来可能会摆脱对tolua+的依赖,反正quick团队是这样说的。如果你阅读本文只是为了学习lua和tolua的绑定原理,那么接下来的内容就没有必要看了,接下来内容只对使用quick-cocos2d-x
16、进行开发的人有用。我们进入正题。3.1 基本步骤cocos2d-x 和 quick-cocos2d-x 的底层代码都是使用 C+ 语言开发的。为了使用 Lua 脚本语言进行开发,我们利用 tolua+ 工具,将大量的 C/C+ API 导出到了 Lua 中。只是各自进行实现了一下脚本帮助cocos2d-x和quick的使用者。下面是quick中使用tolua+的基本步骤:1. 从 C/C+ 源代码复制头文件的内容到 .tolua(tolua+ 文档中称为 .pkg)文件中。2. 修改 .tolua 文件内容,去掉 tolua+ 无法识别的内容,以及不需要导出到 Lua 的定义。3. 运行 to
17、lua+ 工具,根据 .tolua 文件生成 luabinding 接口文件(由 .cpp 文件和 .h 文件自称)。4. 在 AppDelegate.cpp 中加载 luabinding 文件。5. 在 AppDelegate 初始化 Lua 虚拟机后,调用 luabinding 接口文件中的 luaopen 函数,注册 C/C+ API。3.2 实战演示我们通过实际的例子先来演示quick中tolua+的使用,然后再去解释其细节。1. 创建一个quick-cocos2d-x 3.3rc1的工程,打其目录,看看我们需要在哪里进行操作。如创建一个名为game3.3rc1的工程。其目录结构如下。
18、我们需要要关注的目是:在这个目录中我们可以看到两个脚本,还有一些.tolua的文件,以及一些只是扩展名不同的.h和.cpp文件。对的,你猜对了。这些tolua文件就是pkg文件,对应的.h和.cpp就是对应的使用tolua+生成的。这个脚本看其名字就应该知道是干什么的吧。当然是看不出来,我们先来看一下这个脚本build.bat的内容:这个脚本的功能就是讲刚刚的.tolua文件转换成对应的.和.cpp文件,还指定了一些参数。对于脚本compile_luabinding.bat 实际上就是封装了tolua+的使用,其实其下层脚本是用php封装的,有兴趣的读者可以去研究一些封装的细节。对应使用者我们
19、只需要搞清楚这几个参数的意义然后依葫芦画瓢就可以。2. compile-luabinding.bat 脚本使用参数的意义从上面build.bat 中我们可以知道将.tolua文件转换成.h和cpp的实际上compile_luabinding这个脚本的,下面对其参数进行说明:-prx cc ,将这些API或者类导入到lua后使用前缀-d ,指定生成cpp和h文件存放的路径-E,指定某个类需要按照某种方式处理,否则可能会出现内存泄露等问题。CCObject 及其继承类都具备“引用计数”和“自动释放”机制。如果你的 C+ 对象是从 CCObject 继承的,那么必须告诉 tolua+ 做相应处理,否
20、则可能出现内存泄漏等问题。对于 quick,只需要在 build 脚本中通过 -E CCOBJECTS 参数指定这些 class 的名字即可。如果有多个类,那么每个类名之间用“,”分隔即可ompile-luabinding.bat -E CCOBJECTS=MyClass,MyClass2,MyClass3 -d $OUTPUT_DIR MyClass_luabinding.tolua3. 从C/C+源文件编写一个.tolua+文件假设我们的 MyClass.h 头文件内容如下:#ifndef _MY_CLASS_H_#define _MY_CLASS_H_class MyClass :pub
21、lic CCObjectpublic: static void addTwoNumber(float number1, float number2);private: MyClass(void) ;#endif / _MY_CLASS_H_为了便于维护,应该将 .h 文件对应的 tolua 命名为 XXX_luabinding.tolua。这样生成的 luabinding 接口文件名就是 XXX_luabinding.cpp 和 XXX_luabinding.h,不会和已有的 C/C+ 源文件冲突。创建 MyClass_luabinding.tolua 文件,详细的内容修改规则,会在本文后续部
22、分说明。并修改内容为:class MyClass : public CCObjectpublic: static void addTwoNumber(float number1, float number2);4. 生成 luabinding 接口文件在刚刚的build.bat脚本中添加下一行,或者自己新建一个脚本:call %MAKE_LUABINDING% -E MyClass pfx my -d %OUTPUT_DIR% MyClass_luabinding.tolua如果一切顺利:creating file: MyClass_luabinding.cppcreating file: M
23、yClass_luabinding.h/ add to AppDelegate.cpp#include MyClass_luabinding.h/ add to AppDelegate:applicationDidFinishLaunching()CCLuaStack* stack = CCScriptEngineManager:sharedManager() -getScriptEngine() -getLuaStack();lua_State* L = stack-getLuaState();luaopen_MyClass_luabinding(L);5. 载入 luabinding 接口
24、文件打开我们的项目,将 MyClass_luabinding.cpp 和 MyClass_luabinding.h 文件加入工程。然后修改 AppDelegate.cpp 文件:在 AppDelegate.cpp 头部区域添加:#include MyClass_luabinding.h在 AppDelegate:applicationDidFinishLaunching() 函数内添加:luaopen_MyClass_luabinding(L);注意这一行代码应该添加在其他 luaopen 函数后面,例如:/ register lua engineCCLuaEngine *pEngine =
25、CCLuaEngine:defaultEngine();CCScriptEngineManager:sharedManager()-setScriptEngine(pEngine);CCLuaStack *pStack = pEngine-getLuaStack();lua_State* L = pStack-getLuaState();/ load lua extensionsluaopen_lua_extensions(L);/ load cocos2dx_extra luabindingluaopen_cocos2dx_extra_luabinding(L);/ thrid_partyl
26、uaopen_third_party_luabinding(L);/ CCBReadertolua_extensions_ccb_open(L);/ MyClassluaopen_MyClass_luabinding(L);经过上述修改后,重新编译运行项目应该就可以在 Lua 脚本中使用我们导出的 MyClass 对象极其方法了。6. 在lua中使用导出的类/ test.luamy.MyClass.addTwoNumber(1,2);3.3 tolua 文件内容的修改规则前面的 MyClass 是一个非常简单的例子,但我们实际游戏中的 C/C+ API 可能比较复杂。在修改 .tolua 文件
27、内容时,应该仔细阅读以下内容。3.3.1 删除所有无需在 Lua 中使用的内容导出的 API 越多,在 Lua 虚拟机中占用的符号表空间就越多。因此我们第一步要做的就是删除所有无需在 Lua 中使用的内容。1.1 对于 enum、宏定义,如果需要导出,原文保留即可。但宏定义只能导出数值定义,例如:define kCCHTTPRequestMethodGET 0#define kCCHTTPRequestMethodPOST 1而非数值的宏定义无法导出,以下内容会导出失败:#define kMyConstantString HELLO1.2 删除所有无法识别的宏,例如 CC_DLL。1.3 删除
28、 C+ class 中所有非 public 的定义。1.4 删除 C+ class 中的类成员变量1.5 删除 inline 关键词,以及 inline function 的实现,只保留声明。3.3.2 处理 CCObject 继承类CCObject 及其继承类都具备“引用计数”和“自动释放”机制。如果你的 C+ 对象是从 CCObject 继承的,那么必须告诉 tolua+ 做相应处理,否则可能出现内存泄漏等问题。对于 quick,只需要在 build 脚本中通过 -E CCOBJECTS 参数指定这些 class 的名字即可。例如前面 MyClass 的示例中,用 -E CCOBJECTS
29、=MyClass 告诉 tolua+ 应该将 MyClass 当作 CCObject 的继承类进行处理。如果有多个类,那么每个类名之间用“,”分隔即可,例如:ompile-luabinding.bat -E CCOBJECTS=MyClass,MyClass2,MyClass3 -d $OUTPUT_DIR MyClass_luabinding.tolua3.3.3 展开宏有些宏是不能直接删除的,例如 CC_PROPERTY。对于这类宏,需要根据宏定义,将宏展开为声明。CC_PROPERTY(float, m_fDuration, Duration)展开为:float getDuration(
30、);void setDuration(float v);需要如此处理的宏包括:CC_PROPERTY_READONLY, CC_PROPERTY, CC_PROPERTY_PASS_BY_REF,CC_SYNTHESIZE_READONLY,CC_SYNTHESIZE_READONLY_PASS_BY_REF,CC_SYNTHESIZE,CC_SYNTHESIZE_PASS_BY_REF,CC_SYNTHESIZE_RETAIN。幸运的是这些宏大多只用在 cocos2d-x 基础代码里,我们自己的 C+ class 还是不要用这些宏了。3.3.4 处理名字空间如果使用了名字空间,那么在 .to
31、lua 的头部应该加入:$using namespace myname;这里用到的“$”符号,后续内容会原样放入 luabinding 文件。3.3.5 添加必要的 #include 指令如果生成的 luabinding 接口文件无法编译,需要检查是否是需要 include 相应的头文件,并添加如下代码:$using namespace myname; $#include MyClass.h3.3.6 修改函数参数和返回值类型,去除 const 修饰符一些函数的参数或返回值,使用了 const 修饰符。由于 tolua+ 的限制,并不能很好的处理这类定义,所以我们要从 .tolua 文件中移除
32、 const 修饰符。唯一例外的就是 const char* 不需要修改为 char*。例如:CCPoint convertToNodeSpace(const CCPoint& worldPoint);应该修改为:CCPoint convertToNodeSpace(const CCPoint& worldPoint);这样修改的原因是 tolua+ 把 const CCPoint 和 CCPoint 当做两个不同的类型来处理。如果不做修改,那么调用函数时会报告参数类型不符。3.3.7 从 C/C+ 函数返回多个值如果一个函数的所有参数都是引用或指针类型,并且不是 const char*,那么
33、在 luabinding 接口文件中,该函数会返回多个值。例如:void getPosition(float* x = 0, float* y = 0);在 Lua 中调用这个函数,会得到两个返回值:local x, y = node:getPosition()3.3.8 将 Lua 函数传入 C/C+quick 里,允许将 Lua 函数传入 C/C+,只要求 C/C+ 函数中使用 int 做参数类型。但在 .tolua 文件里,则必须使用 LUA_FUNCTION 做参数类型。例如:static CCHTTPRequest* createWithUrlLua(int listener, co
34、nst char* url, int method = kCCHTTPRequestMethodGET);listener 参数用于保存传入的 Lua 函数,所以 .tolua 文件里要改写为:static CCHTTPRequest* createWithUrlLua(LUA_FUNCTION listener, const char* url, int method = kCCHTTPRequestMethodGET);具体用法请参考 lib/cocos2dx_extra/extra/network/CCHTTPRequest 中的 createWithUrlLua() 方法。3.3.9
35、在 Lua 和 C/C+ 间交换二进制数据要从 C/C+ 返回二进制数据给 Lua,函数返回值类型必须是 int,而 .tolua 文件中修改返回值为 LUA_STRING。函数中,需要用 CCLuaStack:pushString() 将二进制数据放入 Lua stack。然后返回“需要传递给 Lua 的值”的数量。具体用法请参考 lib/cocos2dx_extra/extra/network/CCHTTPRequest 中的 getResponseDataLua() 方法。从 Lua 传递二进制数据给 C/C+ 很简单,使用 const char* 参数类型和 int 类型参数分别指定二进制数据的指针和数据长度。具体用法请参考 lib/cocos2dx_extra/extra/crypto/CCCrypto 中的 decryptXXTEALua() 方法。3.3.10 更多用法关于利用 tolua+ 的更多用法,建议参考 lib/cocos2dx_extra 中的 CCCrypto、CCNative、CCHTTPRquest 等 class。这些 class 对 Lua 提供了良好的支持,具体用法上也覆盖了绝大多数 C/C+ 和 Lua 交互的需求。