cocos2d-x 和 quick-cocos2d-x 的底层代码都是使用 C++ 语言开发的。为了使用 Lua 脚本语言进行开发,咱们利用 tolua++ 工具,将大量的 C/C++ API 导出到了 Lua 中。javascript
使用 tolua++ 的基本步骤:html
- 从 C/C++ 源代码复制头文件的内容到 .tolua(tolua++ 文档中称为 .pkg)文件中。
- 修改 .tolua 文件内容,去掉 tolua++ 没法识别的内容,以及不须要导出到 Lua 的定义。
- 运行 tolua++ 工具,根据 .tolua 文件生成 luabinding 接口文件(由 .cpp 文件和 .h 文件自称)。
- 在 AppDelegate.cpp 中加载 luabinding 文件。
- 在 AppDelegate 初始化 Lua 虚拟机后,调用 luabinding 接口文件中的 luaopen 函数,注册 C/C++ API。
根据实践,咱们建议采用以下的方案来完成整个导出工做。java
从 C/C++ 源文件建立 .tolua 文件
假设咱们的 MyClass.h 头文件内容以下:node
#ifndef __MY_CLASS_H_ #define __MY_CLASS_H_ class MyClass { public: 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++ 源文件冲突。python
建立 MyClass_luabinding.tolua 文件,并修改内容为:nginx
class MyClass : public CCObject
{
public:
static void addTwoNumber(float number1, float number2);
};
这里能够看到 .tolua 的内容有明显简化。详细的内容修改规则,会在本文后续部分说明。c++
生成 luabinding 接口文件
quick 为了简化这一步工做,提供了相应工具,咱们只须要建立一个脚本文件来调用工具便可。shell
-
建立 build_luabinding.sh 文件,内容以下:ruby
#!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "$DIR" OUTPUT_DIR="$DIR" MAKE_LUABINDING="$QUICK_COCOS2DX_ROOT"/bin/compile_luabinding.sh $MAKE_LUABINDING -E MyClass -d "$OUTPUT_DIR" MyClass_luabinding.tolua
记得在命令行中 chmod 755 build_luabinding.sh,不然没法执行该脚本。bash
Windows 版的批处理内容以下:
@echo off set DIR=%~dp0 set OUTPUT_DIR=%DIR% set MAKE_LUABINDING="%QUICK_COCOS2DX_ROOT%\bin\compile_luabinding.bat" pushd cd /d "%DIR%" call %MAKE_LUABINDING% -E MyClass -d %OUTPUT_DIR% MyClass_luabinding.tolua
注意:运行脚本前请确保已经下载了 quick-cocos2d-x,而且正确设置了 QUICK_COCOS2DX_ROOT 环境变量。环境配置请参考《入门指引》。
-
在命令行下运行咱们建立的脚本,若是一切顺利,咱们会看到以下输出信息:
creating file: MyClass_luabinding.cpp creating file: MyClass_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);
载入 luabinding 接口文件
打开咱们的项目,将 MyClass_luabinding.cpp 和 MyClass_luabinding.h 文件加入工程。而后修改 AppDelegate.cpp 文件:
-
在 AppDelegate.cpp 头部区域添加:
#include "MyClass_luabinding.h"
-
在 AppDelegate::applicationDidFinishLaunching() 函数内添加:
luaopen_MyClass_luabinding(L);
注意这一行代码应该添加在其余 luaopen 函数后面,例如:
// register lua engine CCLuaEngine *pEngine = CCLuaEngine::defaultEngine(); CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine); CCLuaStack *pStack = pEngine->getLuaStack(); lua_State* L = pStack->getLuaState(); // load lua extensions luaopen_lua_extensions(L); // load cocos2dx_extra luabinding luaopen_cocos2dx_extra_luabinding(L); // thrid_party luaopen_third_party_luabinding(L); // CCBReader tolua_extensions_ccb_open(L); // MyClass luaopen_MyClass_luabinding(L);
应该将咱们的代码追加到 tolua_extensions_ccb_open() 后面。
通过上述修改后,从新编译运行项目应该就能够在 Lua 脚本中使用咱们导出的 MyClass 对象极其方法了。
.tolua 文件内容的修改规则
前面的 MyClass 是一个很是简单的例子,但咱们实际游戏中的 C/C++ API 可能比较复杂。在修改 .tolua 文件内容时,应该仔细阅读如下内容。
删除全部无需在 Lua 中使用的内容
导出的 API 越多,在 Lua 虚拟机中占用的符号表空间就越多。所以咱们第一步要作的就是删除全部无需在 Lua 中使用的内容。
-
对于 enum、宏定义,若是须要导出,原文保留便可。但宏定义只能导出数值定义,例如:
#define kCCHTTPRequestMethodGET 0 #define kCCHTTPRequestMethodPOST 1
而非数值的宏定义没法导出,如下内容会导出失败:
#define kMyConstantString "HELLO"
-
删除全部没法识别的宏,例如 CC_DLL。
-
删除 C++ class 中全部非 public 的定义。
-
删除 C++ class 中的类成员变量。
-
删除 inline 关键词,以及 inline function 的实现,只保留声明。
处理 CCObject 继承类
CCObject 及其继承类都具有“引用计数”和“自动释放”机制。若是你的 C++ 对象是从 CCObject 继承的,那么必须告诉 tolua++ 作相应处理,不然可能出现内存泄漏等问题。
对于 quick,只须要在 build 脚本中经过 -E CCOBJECTS 参数指定这些 class 的名字便可。
例如前面 MyClass 的示例中,用 -E CCOBJECTS=MyClass 告诉 tolua++ 应该将 MyClass 看成 CCObject 的继承类进行处理。
若是有多个类,那么每一个类名之间用“,”分隔便可,例如:
$MAKE_LUABINDING -E CCOBJECTS=MyClass,MyClass2,MyClass3 -d "$OUTPUT_DIR" MyClass_luabinding.tolua
展开宏
有些宏是不能直接删除的,例如 CC_PROPERTY。对于这类宏,须要根据宏定义,将宏展开为声明。
CC_PROPERTY(float, m_fDuration, Duration)
展开为:
float getDuration(); 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 仍是不要用这些宏了。
处理名字空间
若是使用了名字空间,那么在 .tolua 的头部应该加入:
$using namespace myname;
这里用到的“$”符号,后续内容会原样放入 luabinding 文件。
添加必要的 #include 指令
若是生成的 luabinding 接口文件没法编译,须要检查是不是须要 include 相应的头文件,并添加以下代码:
$#include "MyClass.h"
修改函数参数和返回值类型,去除 const 修饰符
一些函数的参数或返回值,使用了 const 修饰符。因为 tolua++ 的限制,并不能很好的处理这类定义,因此咱们要从 .tolua 文件中移除 const 修饰符。惟一例外的就是 const char* 不须要修改成 char*。
例如:
CCPoint convertToNodeSpace(const CCPoint& worldPoint);
应该修改成:
CCPoint convertToNodeSpace(CCPoint& worldPoint);
这样修改的缘由是 tolua++ 把 const CCPoint 和 CCPoint 当作两个不一样的类型来处理。若是不作修改,那么调用函数时会报告参数类型不符。
从 C/C++ 函数返回多个值
若是一个函数的全部参数都是引用或指针类型,而且不是 const char*,那么在 luabinding 接口文件中,该函数会返回多个值。
例如:
void getPosition(float* x = 0, float* y = 0);
在 Lua 中调用这个函数,会获得两个返回值:
local x, y = node:getPosition()
将 Lua 函数传入 C/C++
quick 里,容许将 Lua 函数传入 C/C++,只要求 C/C++ 函数中使用 int 作参数类型。但在 .tolua 文件里,则必须使用 LUA_FUNCTION 作参数类型。
例如:
static CCHTTPRequest* createWithUrlLua(int listener, const 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() 方法。
在 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() 方法。
更多用法
关于利用 tolua++ 的更多用法,建议参考 lib/cocos2dx_extra 中的 CCCrypto、CCNative、CCHTTPRquest 等 class。这些 class 对 Lua 提供了良好的支持,具体用法上也覆盖了绝大多数 C/C++ 和 Lua 交互的需求。
- END -