Lua绑定进阶篇
之前已经写过两篇文章
《Cocos2dx Lua 绑定》详细介绍了,如何在lua中调用c++;
《Cocos2dx 插入广告》详细介绍了,对于打包成apk需要注意的事项;
而对于今天这篇文章,是在对lua绑定有了进一步的理解之后做的一篇详细记录
准备工作请参考第一篇文章,里面有详细的介绍。
还是用一个简单的例子,来开始这篇文章
本文是基于cocos3.6的版本,vs2012
首先建立一个cocoslua工程,然后在
工程\frameworks\runtime-src\Classes
下建立一个Custom文件夹放我们自定义的c++代码文件,并且在Custom下再建立一个Lua文件夹,用来放lua和c++的桥接文件,接着回到vs2012中,建立对应的筛选器(筛选器和实际的物理路径不是一个意思,筛选器只是方便我们管理代码而已,筛选器A完全可以把B文件夹里的文件放进来,这个设定我倒是不太明白为什么)
如此之后,vs2012是这个结构了
我们这里写两个自定义类,Test和Student,你为我为什么是Student,我只好告诉你,我英语不好。。只会这个单词!
Test.h
class Test
{
private:
int _handler;
public:
int test();
void run();
void registerScript(int handler);
static Test* create();
};
Test.cpp
#include "Test.h"
#include "cocos2d.h"
#include "CCLuaEngine.h"
#include "Student.h"
int Test::test()
{
return 100;
}
Test* Test::create()
{
Test * p = new Test();
return p;
}
void Test::registerScript(int handler)
{
_handler = handler;
}
void Test::run()
{
auto engine = cocos2d::LuaEngine::getInstance();
//_handler的三个参数,第一个是一个指针,用来传递对象,第二个是bool类型,第三个是int类型
auto st = Student::create(10,"nevermore");
engine->getLuaStack()->pushObject((cocos2d::Ref*)st,"Ref");
engine->getLuaStack()->pushBoolean(true);
engine->getLuaStack()->pushInt(32);
//执行_handler函数,并且说明该函数需要的参数个数
engine->getLuaStack()->executeFunctionByHandler(_handler,3);
}
函数 | 解释 |
---|---|
static Test* create() | 在lua中对于对象的操作实际都是用指针来完成的,所以需要一个create函数来创建一个对象 |
int test() | 简单的返回一个数字,没有特殊的意义 |
void registerScript(int handler) | 将一个lua函数注册到这里来,将在run中执行这个函数 |
void run() | 传递参数给_handler并执行_handler |
在run函数中需要传递一个student对象,这里贴上student的代码
Student.h
#include "cocos2d.h"
class Student:public cocos2d::Ref
{
private:
int _age;
std::string _name;
public:
static Student * create(int age,std::string name);
std::string getName();
void setName(std::string);
int getAge();
void setAge(int);
};
Student.cpp
#include "Student.h"
Student* Student::create(int age,std::string name)
{
Student * st = new Student();
st->setAge(age);
st->setName(name);
return st;
}
void Student::setAge(int age)
{
_age = age;
}
void Student::setName(std::string name)
{
_name = name;
}
int Student::getAge()
{
return _age;
}
std::string Student::getName()
{
return _name;
}
写好了c++代码之后,我们需要做的,便是生成桥接文件,对于桥接文件的生成,使用的tolua的工具
我们首先找到
E:\LuaGame_3\frameworks\cocos2d-x\tools\tolua
tolua.png
这个文件夹下有很多ini文件,以及有一个叫做 genbindings.py 的文件,这个文件的作用,就是生成桥接文件的关键,这是一个python文件
def main():
cur_platform= '??'
llvm_path = '??'
ndk_root = _check_ndk_root_env()
# del the " in the path
ndk_root = re.sub(r"\"", "", ndk_root)
python_bin = _check_python_bin_env()
platform = sys.platform
if platform == 'win32':
cur_platform = 'windows'
elif platform == 'darwin':
cur_platform = platform
elif 'linux' in platform:
cur_platform = 'linux'
else:
print 'Your platform is not supported!'
sys.exit(1)
if platform == 'win32':
x86_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.3/prebuilt', '%s' % cur_platform))
if not os.path.exists(x86_llvm_path):
x86_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.4/prebuilt', '%s' % cur_platform))
else:
x86_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.3/prebuilt', '%s-%s' % (cur_platform, 'x86')))
if not os.path.exists(x86_llvm_path):
x86_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.4/prebuilt', '%s-%s' % (cur_platform, 'x86')))
x64_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.3/prebuilt', '%s-%s' % (cur_platform, 'x86_64')))
if not os.path.exists(x64_llvm_path):
x64_llvm_path = os.path.abspath(os.path.join(ndk_root, 'toolchains/llvm-3.4/prebuilt', '%s-%s' % (cur_platform, 'x86_64')))
if os.path.isdir(x86_llvm_path):
llvm_path = x86_llvm_path
elif os.path.isdir(x64_llvm_path):
llvm_path = x64_llvm_path
else:
print 'llvm toolchain not found!'
print 'path: %s or path: %s are not valid! ' % (x86_llvm_path, x64_llvm_path)
sys.exit(1)
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
cocos_root = os.path.abspath(os.path.join(project_root, ''))
cxx_generator_root = os.path.abspath(os.path.join(project_root, 'tools/bindings-generator'))
# save config to file
config = ConfigParser.ConfigParser()
config.set('DEFAULT', 'androidndkdir', ndk_root)
config.set('DEFAULT', 'clangllvmdir', llvm_path)
config.set('DEFAULT', 'cocosdir', cocos_root)
config.set('DEFAULT', 'cxxgeneratordir', cxx_generator_root)
config.set('DEFAULT', 'extra_flags', '')
# To fix parse error on windows, we must difine __WCHAR_MAX__ and undefine __MINGW32__ .
if platform == 'win32':
config.set('DEFAULT', 'extra_flags', '-D__WCHAR_MAX__=0x7fffffff -U__MINGW32__')
conf_ini_file = os.path.abspath(os.path.join(os.path.dirname(__file__), 'userconf.ini'))
print 'generating userconf.ini...'
with open(conf_ini_file, 'w') as configfile:
config.write(configfile)
# set proper environment variables
if 'linux' in platform or platform == 'darwin':
os.putenv('LD_LIBRARY_PATH', '%s/libclang' % cxx_generator_root)
if platform == 'win32':
path_env = os.environ['PATH']
os.putenv('PATH', r'%s;%s\libclang;%s\tools\win32;' % (path_env, cxx_generator_root, cxx_generator_root))
try:
tolua_root = '%s/tools/tolua' % project_root
output_dir = '%s/../runtime-src/Classes/Custom/Lua' % project_root
cmd_args = {#'cocos2dx.ini' : ('cocos2d-x', 'lua_cocos2dx_auto'), \
# 'cocos2dx_extension.ini' : ('cocos2dx_extension', 'lua_cocos2dx_extension_auto'), \
# 'cocos2dx_ui.ini' : ('cocos2dx_ui', 'lua_cocos2dx_ui_auto'), \
# 'cocos2dx_studio.ini' : ('cocos2dx_studio', 'lua_cocos2dx_studio_auto'), \
# 'cocos2dx_spine.ini' : ('cocos2dx_spine', 'lua_cocos2dx_spine_auto'), \
# 'cocos2dx_physics.ini' : ('cocos2dx_physics', 'lua_cocos2dx_physics_auto'), \
# 'cocos2dx_experimental_video.ini' : ('cocos2dx_experimental_video', 'lua_cocos2dx_experimental_video_auto'), \
# 'cocos2dx_experimental.ini' : ('cocos2dx_experimental', 'lua_cocos2dx_experimental_auto'), \
# 'cocos2dx_controller.ini' : ('cocos2dx_controller', 'lua_cocos2dx_controller_auto'), \
# 'cocos2dx_cocosbuilder.ini': ('cocos2dx_cocosbuilder', 'lua_cocos2dx_cocosbuilder_auto'), \
# 'cocos2dx_cocosdenshion.ini': ('cocos2dx_cocosdenshion', 'lua_cocos2dx_cocosdenshion_auto'), \
# 'cocos2dx_3d.ini': ('cocos2dx_3d', 'lua_cocos2dx_3d_auto'), \
# 'cocos2dx_audioengine.ini': ('cocos2dx_audioengine', 'lua_cocos2dx_audioengine_auto'), \
# 'cocos2dx_csloader.ini' : ('cocos2dx_csloader', 'lua_cocos2dx_csloader_auto'), \
# 'cocos2dx_experimental_webview.ini' : ('cocos2dx_experimental_webview', 'lua_cocos2dx_experimental_webview_auto'), \
'custom_test.ini' : ('custom_test', 'custom_test'), \
'custom_student.ini' : ('custom_student', 'custom_student'), \
}
target = 'lua'
generator_py = '%s/generator.py' % cxx_generator_root
for key in cmd_args.keys():
args = cmd_args[key]
cfg = '%s/%s' % (tolua_root, key)
print 'Generating bindings for %s...' % (key[:-4])
command = '%s %s %s -s %s -t %s -o %s -n %s' % (python_bin, generator_py, cfg, args[0], target, output_dir, args[1])
_run_cmd(command)
print '---------------------------------'
print 'Generating lua bindings succeeds.'
print '---------------------------------'
except Exception as e:
if e.__class__.__name__ == 'CmdError':
print '---------------------------------'
print 'Generating lua bindings fails.'
print '---------------------------------'
sys.exit(1)
else:
raise
# -------------- main --------------
if __name__ == '__main__':
main()
对于我们来说要修改的地方一个是output_dir ,一个是cmd_args
- output_dir
这是桥接文件的输出目录,还记得我们之前说的Lua文件夹么,它的实际路径是
工程目录\frameworks\runtime-src\Classes\Custom\Lua
如何定位到这个目录?代码中是这么写的
output_dir = '%s/../runtime-src/Classes/Custom/Lua' % project_root
%s在python中是用来格式化的,和lua中的string.format是一个意思
print '%s,你好'%'最怕认真'
将输出最怕认真,你好
project_root在代码中是这样写的
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
这是求路径的代码
os.path.dirname(__file__)
得到的是当前文件路径,也就是genbindings.py 的路径
E:\LuaGame_3\frameworks\cocos2d-x\tools\tolua
而..的意思是返回上一层路径,所以最终project_root 指向的是
E:\LuaGame_3\frameworks\cocos2d-x
如此,对于output_dir 要指向我们的lua文件夹,就很简单了
- cmd_args
这是一个数组,每一个item由3部分组成
'custom_test.ini' : ('custom_test', 'custom_test'),
内容 | 含义 |
---|---|
第一部分custom_test.ini | 我们配置的关键,是用来配置我们的c++代码到桥接文件的说明文件 |
第二部分custom_test | custom_test.ini的section名称,不明白没关系,等下我们会详细说下ini文件 |
第三部分custom_test | 导出的桥接文件的文件名 |
ini文件要怎么写?
我们首先在外层复制一个cocos2dx.ini文件,更名为custom_test.ini
对于这个文件有5个地方需要修改
第N部分 | 解释 |
---|---|
1 | 这个就是上面我们说的section了 |
2 | 和1一致 |
3 | 包含到哪个模块中,比如写 = cc那么调用test的时候就是cc.Test,如果不写就直接Test来调用 |
4 | 需要生成桥接文件的头文件,cocosdier是在genbindings中赋值的,最终跟踪其就是project_root |
5 | 需要生成桥接文件的名称,如果有多个,则用空格隔开,但是不推荐多个文件共用一个配置文件 |
按照如此我们就配置好了我们的Test文件的ini文件,接着再依葫芦画瓢写好我们的Student的ini文件
Student.png接着我们回到genbindings的cmd_args中,我们用#注释了很大一部分内容,是因为这些文件都已经生成过了,我们没必要再生成一遍,我们只要添加我们需要生成的两个文件就行了,添加好了这两行,就在genbindings所在的目录下右键执行cmd,然后在cmd中运行genbindings,当出现
---------------------------------
Generating lua bindings succeeds.
---------------------------------
表示生成成功了,失败的话多半是ini那五部分内容写错了,仔细找下都不会太难解决的。
然后在我们的Lua文件下就生成了对应的4个文件,2个cpp2个hpp,hpp和h文件是一个意思
然后在vs2012的lua筛选器中
右键-添加现有项-将4个文件包含进来
对于2个cpp文件,其中
#include "Test.h"和#include "Student.h"修改成正确的路径
然后打开AppDelegate.cpp
文件,把我们的c++类注册到lua中去
在函数applicationDidFinishLaunching
里调用了函数register_all_packages
,在这个register_all_packages
里完成我们的注册,首先在AppDelegate.cpp
添加我们的2个hpp头文件
#include "Custom\Lua\custom_test.hpp"
#include "Custom\Lua\custom_student.hpp"
将register_all_packages
改写如下
static int register_all_packages(lua_State* L)
{
register_all_custom_test(L);
register_all_custom_student(L);
return 0; //flag for packages manager
}
同时在函数applicationDidFinishLaunching
里传入lua_State
如此,我们所有的准备工作算是完成了,然后编译,如果有错误根据错误提示改正,一般按照文章内容一步步做的话是能编译通过的,接下来就是使用了。
首先测试test函数,我们在一个场景中如下测试
local t = Test:create()
local res = t:test()
local txt = ccui.Text:create()
txt:setFontSize(70)
txt:setPosition(568,320)
self:addChild(txt)
txt:setString(tostring(res))
用一个文本来显示返回值,运行
test函数.png可以看到文本成功的显示了100
然后再来测试我们的注册函数到c++的功能
function MainScene:func(student, bool, int)
print(student:getAge())
print(student:getName())
print(bool)
print(int)
end
添加一个函数,然后在test函数测试后面加两句
t:registerScript(handler(self, self.func))
t:run()
运行,不出意料的话,应该是报错的,这是因为传递函数和int这种基础类型不一样,对于这个错误我在c++如何调用lua函数有详细的说明,按照修正以后,编译运行
函数注册测试.png可以看到输出的值和我们在c++往里面传递的值是一样的,至此,绑定的所有过程就完成了,掌握了这些我们就可以写底层的通信,复杂的计算,然后在lua中去调用,同时发挥lua和c++各自的优点!
打包android apk的注意事项
以上是完成了在win32平台的所有工作,但是我们打包成apk的时候,会提示找不到文件,我们在上面添加了2个自定义文件,又生成了2个桥接文件(都是指cpp文件),所以需要配置这几个文件的路径
找到目录
E:\LuaGame_3\frameworks\runtime-src\proj.android\jni
这个目录有一个android.mk文件
mk文件.png需要添加这4个cpp的路径,以及新加的文件夹的路径,如此,打包apk才能顺利通过