其实自从打开GameScene.lua文件以来,我一直都很想搞清楚的一个问题就是cc.Sprite这些是如何识别的。其实第一反应肯定就是:它肯定是一个全局变量,要不然怎么调用create方法呢。先不考究cocos的C++类方法是如何绑定到lua的,我就是想知道这个全局变量是什么时候注册的?
知道一点lua知识的都知道 lua里面的全局变量会保存到一个全局表_G中去,问题就转换为了 什么时候向_G中注册的?在使用cc.Sprite的create之前,我好像没有没有在lua中定义cc.Sprite这样的变量,所以不可能是lua定义的这个全局变量,而是cocos在初始化lua引擎的时候对它进行注册的。
让我们来看看lua引擎初始化的时候都做了什么操作。果然找到了_G的踪迹。
luaL_register(_state, "_G", global_functions);
这是在向lua中注册全局函数。(关键是此时的栈顶是 全局表 啊!!!这点很重要)。
让我们继续看下去,我们会看到各种注册:
register_all_cocos2dx(_state);
register_all_cocos2dx_extension(_state);
register_cocos2dx_extension_CCBProxy(_state);
tolua_opengl_open(_state);
register_all_cocos2dx_ui(_state);
register_all_cocos2dx_studio(_state);
register_all_cocos2dx_manual(_state);
register_all_cocos2dx_module_manual(_state);
register_all_cocos2dx_extension_manual(_state);
register_all_cocos2dx_coco_studio_manual(_state);
register_all_cocos2dx_ui_manual(_state);
register_all_cocos2dx_spine(_state);
register_all_cocos2dx_spine_manual(_state);
register_glnode_manual(_state);
每一个注册完成后,均会还原现场,即保持栈顶是_G表。让我们选一个来分析吧,就以register_all_cocos2dx 为例吧。
TOLUA_API int register_all_cocos2dx(lua_State* tolua_S)
{
tolua_open(tolua_S);
tolua_module(tolua_S,"cc",0);
tolua_beginmodule(tolua_S,"cc");
lua_register_cocos2dx_Ref(tolua_S);
lua_register_cocos2dx_Console(tolua_S);
...省略很多代码...
}
这里貌似看到一点苗头。cc和我们的cc.Sprite很近了。
基础函数介绍:
tolua_module:尝试注册以name为名的模块,每个模块都是在向父模块注册,但是cc没有父模块,所以它会认为_G是它该去注册的地方。
TOLUA_API void tolua_module (lua_State* L, const char* name, int hasvar)
{
if (name)
{
/* tolua module */
lua_pushstring(L,name);
lua_rawget(L,-2);//拿到name在当前模块表中是否进行了注册
if (!lua_istable(L,-1)) /* check if module already exists */
{
//没有注册过,new一个
lua_pop(L,1);
lua_newtable(L);
lua_pushstring(L,name);
lua_pushvalue(L,-2);
/*
下面这段测试表明该模块是加到(全局表)中
if(lua_istable(L,-4)){
<span style="white-space:pre"> </span>lua_getfield(L,-4,"print");
<span style="white-space:pre"> </span>if(lua_isnil(L,-1)){
<span style="white-space:pre"> </span>char a='a';
<span style="white-space:pre"> </span>}else{
<span style="white-space:pre"> </span>char b ='b'; //come here啦啦啦!!!
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>lua_pop(L,1);
<span style="white-space:pre"> </span>}*/
//注册到当前模块表中去
lua_rawset(L,-4); /* assing module into module */
}
}
}
从上面的测试代码就可以发现就是向_G表注册。所以我们的猜测是完全正确的。为什么是_G表呢?之前已经提示过了(注册全局函数的时候,会把_G放在栈顶)。
现在全局表中有这样的一个键值对:_GTable["cc"]=cc_table。
接下就看Sprite是如何注册进去的吧。在register_all_cocos2dx那堆注册的函数中,我们发现:
lua_register_cocos2dx_Sprite(tolua_S);
int lua_register_cocos2dx_Sprite(lua_State* tolua_S)
{
tolua_usertype(tolua_S,"cc.Sprite");
tolua_cclass(tolua_S,"Sprite","cc.Sprite","cc.Node",nullptr);
tolua_beginmodule(tolua_S,"Sprite");
tolua_function(tolua_S,"setSpriteFrame",lua_cocos2dx_Sprite_setSpriteFrame);
//...此处省略很多代码...
}
基础函数介绍:
tolua_usertype:向注册表中注册该用户类型,创建该类型的表。(table和type在注册表中是双向映射)。
tolua_cclass:设置该类的元表啊,父类,tolua_super表注册,tolua_ubox表注册,。并且以"Sprite"为key加入到当前所在父模板表中。
然后是向表中注册一些函数。
我们知道当前父模板表是cc_table,当前模板表是:sprite_table。cc_table["Sprite"] = sprite_table。sprite_table["setSpriteFrame"] = lua_cocos2dx_Sprite_setSpriteFrame。
现在就可以通过全局表去访问 到Sprite了,并且可以访问到里面的方法。
所以我觉得cc.Sprite.setSpriteFrame(...)的执行流程是:向_G表中拿到cc对应的cc_table,再看在cc_table中是否有Sprite,如果有就在sprite_table中去寻找setSpriteFrame方法。
以上纯属个人看法。本人也是在学习中,欢迎和一起交流。