cocos2d-x和 quick-cocos2d-x 的底层代码都是使用 C++ 语言开发的。为了使用 Lua脚本语言进行开发,我们利用 tolua++ 工具,将大量的 C/C++ API 导出到了 Lua中。
使用 tolua++的基本步骤:
从 C/C++源代码复制头文件的内容到 .tolua(tolua++文档中称为 .pkg)文件中。
.tolua 文件内容,去掉 tolua++无法识别的内容,以及不需要导出到 Lua 的定义。
运行 tolua++工具,根据 .tolua 文件生成 luabinding 接口文件(由 .cpp文件和 .h 文件自称)。
在 AppDelegate.cpp中加载 luabinding 文件。
在 AppDelegate初始化 Lua 虚拟机后,调用 luabinding 接口文件中的 luaopen函数,注册 C/C++ API。
根据实践,我们建议采用如下的方案来完成整个导出工作。
从 C/C++源文件创建 .tolua 文件
MyClass.h 头文件内容如下:
#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++源文件冲突。
创建 MyClass_luabinding.tolua文件,并修改内容为:
class MyClass
{
public:
static void addTwoNumber(float number1, float number2);
};
.tolua 的内容有明显简化。详细的内容修改规则,会在本文后续部分说明。
生成 luabinding接口文件
quick 为了简化这一步工作,提供了相应工具,我们只需要创建一个脚本文件来调用工具即可。
1.创建 build_luabinding.sh文件,内容如下:
#!/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 -d "$OUTPUT_DIR" MyClass_luabinding.tolua
记得在命令行中 chmod 755 build_luabinding.sh,否则无法执行该脚本。
Windows 版的批处理内容如下:
@echo off
set DIR=%~dp0
set OUTPUT_DIR=%DIR%
set MAKE_LUABINDING="%QUICK_COCOS2DX_ROOT%\bin\compile_luabinding.bat"
pushd
cd "%DIR%"
call %MAKE_LUABINDING% -d %OUTPUT_DIR% MyClass_luabinding.tolua
注意:运行脚本前请确保已经下载了 quick-cocos2d-x,并且正确设置了 QUICK_COCOS2DX_ROOT 环境变量。环境配置请参考《入门指引》。
2.在命令行下运行我们创建的脚本,如果一切顺利,我们会看到如下输出信息:
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 文件:
1.在 AppDelegate.cpp头部区域添加:
#include "MyClass_luabinding.h"
2.在 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。
3.删除 C++ class中所有非 public 的定义。
4.删除 C++ class中的类成员变量。
5.删除 inline关键词,以及 inline function 的实现,只保留声明。
展开宏
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修饰符。唯一例外的就是 constchar*不需要修改为 char*。
例如:
CCPoint convertToNodeSpace(const CCPoint& worldPoint);
应该修改为:
CCPoint convertToNodeSpace(CCPoint& worldPoint);
tolua++ 把const CCPoint和 CCPoint 当做两个不同的类型来处理。如果不做修改,那么调用函数时会报告参数类型不符。
从 C/C++函数返回多个值
constchar*,那么在 luabinding接口文件中,该函数会返回多个值。
例如:
void getPosition(float* x = 0, float* y = 0);
Lua 中调用这个函数,会得到两个返回值:
local x, y = node:getPosition()
将 Lua函数传入
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++很简单,使用 constchar*参数类型和 int类型参数分别指定二进制数据的指针和数据长度。
具体用法请参考 lib/cocos2dx_extra/extra/crypto/CCCrypto中的 decryptXXTEALua() 方法。
更多用法
关于利用 tolua++的更多用法,建议参考 lib/cocos2dx_extra 中的 CCCrypto、CCNative、CCHTTPRquest等 class。这些 class对 Lua 提供了良好的支持,具体用法上也覆盖了绝大多数 C/C++ 和 Lua交互的需求。
- END -