学习Cocos2d-x Lua:绑定自定义类到Runtime(Lua-binding)
在这个系列中,我们将深入学习Cocos2d-x Lua,总结Lua开发过程中涉及的知识点,以及如何在开发过程中使用Cocos Code IDE。本文将详细讲解如何将自定义类绑定到Runtime。
需求背景
在开发过程中,我们常常会在C++层定义一些类,为了将这些类导出给Lua使用,以满足在C++层实现较为容易的需求,我们需要将整个类作为模块导出。Cocos2d-x正是采用了这种思想,将Cocos中的类导出供用户使用,而不是重新编写一套Lua代码。用户可以使用Cocos导出的接口,在Lua脚本层编写游戏代码。
为了更好地理解这部分内容,建议先阅读《在Lua中调用C++函数》,了解C++中调用Lua的机制。在该文中,我们将C++中需要导出的函数放到了一个模块中,并且手动完成了一些工作。然而,Lua本质上是基于C语言的,其提供给C使用的API也是面向过程的C函数。因此,将C++类注册到Lua并形成一个一个的table环境并非易事。
在Cocos2d-x 2.x版本中,使用的是tolua++工具,但该工具使用起来较为繁琐。现在,我们使用的是bindings-generator工具(官方用Python编写的工具),其底层可能也使用了tolua++。
tolua++工具使用方法(了解)
虽然我们现在不使用tolua++工具,但了解其使用方法有助于我们更好地理解后续的操作。以下是tolua++工具的大致使用步骤:
- 编写C++类:按照正常的C++类编写方式编写自己的类。
- 编写.pkg文件:根据需要导出的C++类的头文件编写对应的.pkg文件,具体格式需参照tolua++的要求。
- 编写桥接类头文件:编写一个桥接类,只编写其头文件,cpp文件将使用tolua++工具生成。头文件的编写也需要遵循tolua++的格式。
- 编写桥接类.pkg文件:为桥接类编写一个.pkg文件。
- 生成桥接类.cpp文件:使用命令生成桥接类的.cpp文件。
- 使用桥接类:在程序中使用桥接类,并执行相关函数。
可以看出,使用tolua++工具至少需要编写三个文件:导出类的.pkg文件、桥接类的.h文件和桥接类的.pkg文件,操作较为繁琐。
使用bindings-generator工具绑定自定义类
1. 创建Lua工程
使用Cocos Code IDE创建一个Lua工程,在创建工程时,务必选中“add native code”选项。这样,工程目录下会生成“frameworks”文件夹,其中包含C++层的代码。
2. 打开工程
进入“frameworks”的工程目录,打开对应的工程。如果你使用的是Mac系统,打开的是Xcode工程。
3. 编写自定义类
在工程中编写自己的类,该类将作为导出给Lua层使用的接口。这里我们模拟一个简单的需求,编写一个名为“TestLua”的类。
TestLua.h文件内容
#ifndef __Test11__TestLua__
#define __Test11__TestLua__
#include "cocos2d.h"
USING_NS_CC;
class TestLua : public Ref
{
public:
bool init() { return true; };
CREATE_FUNC(TestLua);
int show(int);
};
#endif /* defined(__Test11__TestLua__) */
TestLua.cpp文件内容
#include "TestLua.h"
int TestLua::show(int arg)
{
log("xiaota show %d", arg);
return arg + 100;
}
编写完成后,为了方便操作,将这两个文件复制到“Classes”目录下。
4. 配置导出类接口
进入工程的“frameworks/cocos2d-x/tools/bindings-generator”目录,仔细阅读“README.md”文档,配置好必要的工具和环境变量。
进入“tolua”目录,原目录下只有“genbindings.py”文件,我们将其复制一份并命名为“genbindings_xiaota.py”,同时复制一个“cocos2dx.ini”文件,重新命名为“xiaota.ini”。“genbindings.py”是一个Python脚本,在执行过程中会读取.ini配置文件,并根据这些配置文件生成我们需要的桥接类。
修改genbindings.py文件
该文件相对简单,主要修改“output_dir”和“cmd_args”参数。“output_dir”指定生成的桥接类存放的文件夹,这里我们将其放置在与引擎存放桥接类相同的位置;“cmd_args”指定脚本执行过程中读取的配置文件,我们的配置文件是“xiaota.ini”,因此第一个参数为“xiaota.ini”,“lua_xiaota_auto”是最终生成的桥接类的名字。
修改xiaota.ini文件
打开“xiaota.ini”文件,需要修改以下五处内容:
- 中括号中的内容:与文件名相同。
- prefix:与文件名相同。
- target_namespace:代表模块名,例如Cocos中的模块名“cc”,这里我们使用的模块名是“tt”。
- headers:指定头文件的路径,我们引用的是“Classes”目录下的文件。
- classes:指定类名。
如果有多个文件,可以参照“cocos2d.ini”的写法进行编写。我们在编写这个文件时,是复制官方的文件并在此基础上进行修改,因此无论官方文件如何变化,我们只需复制并修改这几个关键地方即可。
5. 生成桥接类
使用Python脚本来生成桥接类。为了排除错误,建议先运行“genbindings.py”,如果运行不成功,说明环境配置可能存在问题;如果运行成功,再运行自己的Python文件“genbindings_xiaota.py”。如果此时运行不成功,80%的错误是由于.ini配置文件配置不正确导致的。
编译成功后,我们可以在指定的文件夹下查看生成的桥接类。在“auto”文件夹下,“lua_xiaota_auto.hpp”和“lua_xiaota_auto.cpp”是生成的桥接类;在“api”文件夹下,“TestLua.lua”是导出的Lua API。
6. 注册类到Lua环境
打开“AppDelegate.cpp”文件,包含桥接类头文件“lua_xiaota_auto.hpp”,然后在“applicationDidFinishLaunching”函数中添加如下代码:
register_all_xiaota(L);
其中,“register_all_xiaota”是桥接类中的一个重要函数,用于注册类。
7. 添加桥接类到工程
为了编译桥接类,我们需要将新生成的类添加到工程中。右键工程,添加“lua_xiaota_auto.hpp”和“lua_xiaota_auto.cpp”文件。此时,可能会出现桥接类的.cpp文件找不到“TestLua”类的问题,需要设置“search”路径。
8. 重新编译Runtime
完成以上步骤后,基本已经成功。打开Cocos Code IDE的工程,重新build一下runtime。需要注意的是,当我们修改C++层的代码时,需要重新build runtime,即重新编译C++文件,让Lua脚本运行在新的环境下。
9. 验证导出接口
打开工程的“main.lua”文件,在文件中使用导出的接口,验证是否成功。以下是示例代码:
local function main()
collectgarbage("collect")
-- avoid memory leak
collectgarbage("setpause", 100)
collectgarbage("setstepmul", 5000)
local val = tt.TestLua:create():show(10)
print(val)
end
运行程序,如果能够正常输出结果,则说明绑定自定义类到Runtime成功。以后的binding操作可以参照这个思路进行。