Cocos2dx 2.x 版本使用 tolua++ 进行 Lua 绑定,需要三个文件,包描述文件 *.tolua
,定制脚本 basic.lua
和可执行脚本 build.bat
。
每天进步一点点,能不退步太多;再进步一点,能不退步;再进步一点点,能有所进步;再进步一点点,方能成功。
Cocos2dx 版本非常乱,主要有 2.x 和 3.x 两个版本,另外还是 lua 版本还有 quick 分支。本文主要介绍低版本(2.x)cocos2d-x 对应的 quick 版本,如何使用 tolua++ 来导出自定义的 C++ 类,因为没找到 cocos2d-x 的 2.x 版本,所以不知道 2dx 和 quick 是不是一样。
工程结构
先来看一下 quick-cocos2d-x-2.x 的工程结构,
1 | |-quick-cocos2d-x |
最重要的就是上面列出的 bin, framework, lib, player
四个文件夹,另外还有几个不那么重要的文件夹,docs
是帮助文档,samples
是官方例子,template
是创建工程的默认模板,tool
貌似没什么用。
bin
保存了引擎用到的一些工具和可执行脚本,其中bin/win32
下有跟 Lua 相关的工具,包括 Lua 编译器luac
和luajit
,还有我们马上要用到的tolua++
;framework
为引擎的基础框架,即封装好的一些代码库集合,里面全是 Lua 文件,因为 quick 是纯 Lua 版本,引擎源码已经被编成一个“播放器”了,对项目而言,这些上层封装的 Lua 代码才是 framework;lib
是引擎源码存放的地方,包括cocos2d-x
目录下的源码和proj.win32, proj.android
等源码工程,以及用于将 C++ 导出到 Lua 的相关文件luabinding
;player
则是一个播放器工程,用于生成一个可直接运行的播放器,之后使用 quick 创建项目就只需要把这个编好的播放器拷过去就行,然后直接开撸 Lua 代码,不需要引擎的 C++ 源码;在player/proj.win32
下有个解决方案player.sln
,它包含自身的播放器项目player.vcxproj
和源码项目lib/proj.win32/cocos2dx.vcxproj
。
Lua 绑定
引擎的目录结构已经很清楚了,lib
是引擎源码,framework
是 quick 封装的上层 Lua 库,player
是播放器工程,bin
则是常用工具。进行 Lua 绑定需要用到 bin
和 lib
这两个文件夹,
bin
目录下有 Lua 绑定工具tolua++.exe
;lib/luabinding
为 Lua 绑定的工作目录,包括每个类的包描述文件*.tolua
、定制脚本basic.lua
和可执行脚本build.bat/build.sh
;lib/framework_precompiled
目录下 Lua framework 的预编译文件,下面只有framework_precompiled.zip
一个文件;lib/cocos2d-x
则为引擎的源码目录,tolua 导出的文件自然也是源码的一部分,所以也在这个目录下,其完整路径为lib/cocos2d-x/scripting/lua/cocos2dx_support
。
进行 Lua 绑定只需要一步,执行 lib/luabinding/build.bat/build.sh
即可,这个脚本的内容如下,
1 | @echo off |
其实就只做一件事,调用 bin/win32/tolua++.exe
,根据包描述文件 lib/luabinding/Cocos2d.tolua
和定制脚本 lib/luabinding/basic.lua
,生成文件 lib/cocos2d-x/scripting/lua/cocos2dx_support/LuaCocos2d.cpp
。
描述文件以 .tolua
为后缀(本来的后缀名应该是 *.pkg
,不过这不重要),语法和 C++ 头文件差不多,其作用是描述要导出的函数,每个 C++ 类都需要一个描述文件,但我们执行脚本的时候只指定了一个描述文件,这是因为描述文件和头文件一样,可以包含其它文件,下面是 Cocos2d.tolua
的内容,
1 | $#include "LuaCocos2d.h" |
在运行标准的 tolua++ 之前,还加载了额外的脚本 basic.lua
,这是由于 cocos2dx 在 Lua 绑定方面并未完全遵照 tolua++ 的默认做法,因此需要对其进行定制,basic.lua
主要做的事情是:
- 将所有导出的 CCXXXX 类的
push
函数(也就是将 cpp obj h传进 lua 时调用的函数)修改为自己的toluafix_pushusertype_ccobject
; - 将
function
和table
两种类型的to
和is
函数(分别是指将 lua obj 传进 cpp 时调的函数、判断一个变量是否为本类型时调的函数)修改为toluafix_is/to_funtion/table
。
自定义类导出
- 第一步,编写自己的 C++ 类
MyClass
,放在源码目录lib/cocos2d-x
下即可,无具体要求; - 第二步,编写包描述文件
MyClass.tolua
,放在 Lua 绑定工作目录lib/luabinding
下即可,无具体要求; - 第三步,在
Cocos2d.tolua
中包含MyClass.tolua
; - 第四步,在
cocos2d.h
中包含MyClass.h
; - 第五步,重新执行
lib/luabinding/build.bat
,这样就会将自定义类MyClass
也导出到LuaCocos2d
库中去,所以使用的时候不需要自己去导入库。
在 basic.lua
中定义了一个 table CCObjectTypes
,放在这个 table 里的类表示是 cocos2d-x 对象类,需要对其进行定制;如果我们的自定义继承自 cocos2d-x 的对象,则需要把我们的自定义类也加入到 CCObjectTypes
中去,否则不能加进去,cocos2d-x 的对象类会修改 toluafix_pushusertype_ccobject
,会使用到 m_uID
和 m_nLuaID
,而我们自定义的类没有这两个字段,将其当作 cocos2d-x 对象处理是编译不过去的。
第二步和第三步是编写包描述文件,仿照着已有文件写就行,这两步做好之后通常就可以进行导出了,通常容易忽略的是第四步。这里要讲一下 Lua 绑定的原理,就是在原来 C++ 类的基础上封装一层,为每个需要导出的方法都封装一个静态函数,然后 C++ 程序打开 Lua 环境的时候将这些静态函数全部注册到 Lua 环境中去,生成一个全局的 Lua 变量,Lua 代码通过这个全局变量访问到 C++ 静态函数,而这个静态函数内部才调用真正的类方法。所以,Lua 绑定的结果是根据原来的 C++ 文件,再生成一个 C++ 文件,新生成的文件需要包含原来的头文件,因为新文件定义的静态方法做的事情就是访问原来的类方法。因此才有了第四步,引用头文件 MyClass.h
,因为生成的文件 LuaCocos2d.cpp
默认会包含 cocos2d.h
,所以这里才在 cocos2d.h
中包含 MyClass.h
,当然也可以在其它地方包含,比如直接在 LuaCocos2d.cpp
中直接包含 MyClass.h
也行,或者在定制脚本 basic.lua
中写也行。
下面是一个导出方法的示例,是为 MyClass
的构造函数生成的一个静态函数,这个函数使用 C API 创建一个 MyClass
对象,注册到 Lua 环境中去。
1 |
|
单独导出
上面的做法是将自定义跟着系统库一起导出,另外一种做法是单独导出我们的自定义类,只需要模仿着系统导出写一套文件即可。最终的文件结构如下:
1 | |-quick-cocos2d-x |
创建一个独立的目录 lib/custom
,不干扰引擎原来的文件结构,然后第一步和第二步和原来一样,编写自定义的 C++ 类和包描述文件 MyClass.tolua
;之后编写自己的定制脚本 basic.lua
和可执行脚本 build.bat
,最终生成文件 LuaMyClass.hpp
,默认不会生成头文件,所以这里将生成的文件命名为 *.hpp
,这样使用的时候就直接包含就行了,如果生成 *.cpp
,则需要自己再手动写一个头文件。
build.bat
基本和原来一样,只是改一下包描述文件和生成文件,代码如下:
1 | @echo off |
定制脚本 basic.lua
也和原来差不多,直接从 lib/luabinding/basic.lua
拷一份出来,然后做一下删减即可。首先,我们的自定义不是 cocos2d-x 对象,所以 CCObjectTypes
部分直接删掉;然后改一下 replace
部分的代码,简化头文件的引入,不需要引入 cocos2d.h
,还有一些不必要的替换也去掉,最终的效果如下:
1 | -- usage: (use instead of ant) |
因为是单独导出,所以使用的时候要先进行注册,在生成的 LuaMyClass.hpp
中有一个 export function,
1 | TOLUA_API int tolua_MyClass_open (lua_State* tolua_S); |
调用这个函数就可以一键注册 MyClass
的所有方法到 Lua 环境中去。
首先,将 MyClass.h, MyClass.cpp, LuaMyClass.hpp
拷贝到项目中去,然后包含 LuaMyClass.hpp
,再调用函数 tolua_MyClass_open
。
1 |
|