cocos2d-x 3.3 lua 相关修改

从 quick 转向 cocos2d-x 3.3 lua 中,我提到会将转换过程中积累的经验进行分享。这是其中一篇。要查看更多,请点击:查看所有 quick 移植到 cocos2d-x lua 的文章

  • 2015-02-26更新: 修正解决方案。

cocos2d-x 3.3 的 lua 项目在运行时,会出现一些不符合人类习惯的问题。下面是我碰到的以及解决方案。

1. SpriteFrameCache.addSpriteFrameWithFile 调用错误

问题描述:

SpriteFrameCache 的 addSpriteFramesWithFile 方法是支持重载的。

在 tolua 中,addSpriteFrameWithFile 也同样支持重载。从 lua_cocos2dx_auth.cpp 中的 lua_cocos2dx_SpriteFrameCache_addSpriteFramesWithFile 可以看出:

:::C++
int lua_cocos2dx_SpriteFrameCache_addSpriteFramesWithFile(lua_State* tolua_S)
{
    int argc = 0;
    cocos2d::SpriteFrameCache* cobj = nullptr;
    bool ok  = true;
#if COCOS2D_DEBUG >= 1
    tolua_Error tolua_err;
#endif

#if COCOS2D_DEBUG >= 1
    if (!tolua_isusertype(tolua_S,1,"cc.SpriteFrameCache",0,&tolua_err)) goto tolua_lerror;
#endif
    cobj = (cocos2d::SpriteFrameCache*)tolua_tousertype(tolua_S,1,0);
#if COCOS2D_DEBUG >= 1
    if (!cobj)
    {
        tolua_error(tolua_S,"invalid 'cobj' in function 'lua_cocos2dx_SpriteFrameCache_addSpriteFramesWithFile'", nullptr);
        return 0;
    }
#endif
    argc = lua_gettop(tolua_S)-1;
    do{
        if (argc == 2) {
            std::string arg0;
            ok &= luaval_to_std_string(tolua_S, 2,&arg0, "cc.SpriteFrameCache:addSpriteFramesWithFile");

            if (!ok) { break; }
            std::string arg1;
            ok &= luaval_to_std_string(tolua_S, 3,&arg1, "cc.SpriteFrameCache:addSpriteFramesWithFile");

            if (!ok) { break; }
            cobj->addSpriteFramesWithFile(arg0, arg1);
            return 0;
        }
    }while(0);
    ok  = true;
    do{
        if (argc == 1) {
            std::string arg0;
            ok &= luaval_to_std_string(tolua_S, 2,&arg0, "cc.SpriteFrameCache:addSpriteFramesWithFile");

            if (!ok) { break; }
            cobj->addSpriteFramesWithFile(arg0);
            return 0;
        }
    }while(0);
    ok  = true;
    do{
        if (argc == 2) {
            std::string arg0;
            ok &= luaval_to_std_string(tolua_S, 2,&arg0, "cc.SpriteFrameCache:addSpriteFramesWithFile");

            if (!ok) { break; }
            cocos2d::Texture2D* arg1;
            ok &= luaval_to_object<cocos2d::Texture2D>(tolua_S, 3, "cc.Texture2D",&arg1);

            if (!ok) { break; }
            cobj->addSpriteFramesWithFile(arg0, arg1);
            return 0;
        }
    }while(0);
    ok  = true;
    CCLOG("%s has wrong number of arguments: %d, was expecting %d \n",  "cc.SpriteFrameCache:addSpriteFramesWithFile",argc, 2);
    return 0;

#if COCOS2D_DEBUG >= 1
    tolua_lerror:
    tolua_error(tolua_S,"#ferror in function 'lua_cocos2dx_SpriteFrameCache_addSpriteFramesWithFile'.",&tolua_err);
#endif

    return 0;
}

但是,在 lua 里面调用 addSpriteFramesWithFile 时,如果第二个参数传递 Texture2D,就会报错。信息如下:

cocos2d: error: cc.SpriteFrameCache:addSpriteFramesWithFile argument #3 is 'cc.Texture2D'; 'string' expected.

解决方案:

不解决。

问题分析:

因为这是个误报。

从上面 lua_cocos2dx_SpriteFrameCache_addSpriteFramesWithFile 的绑定代码可以看出,第二个参数是字符串的重载版本(版本1)是先调用的。当使用第二个参数是 Texture2D 的重载版本时,版本1也会被调用,但不会执行。

不过参数判断总是会执行,因此就出现了误报。

误报的输出发生在 LuaBasicConversions.cppluaval_to_native_err 函数中:

:::C++
void luaval_to_native_err(lua_State* L,const char* msg,tolua_Error* err, const char* funcName)
{
    if (NULL == L || NULL == err || NULL == msg || 0 == strlen(msg))
        return;

    if (msg[0] == '#')
    {
        const char* expected = err->type;
        const char* provided = tolua_typename(L,err->index);
        if (msg[1]=='f')
        {
            int narg = err->index;
            if (err->array)
                CCLOG("%s\n     %s argument #%d is array of '%s'; array of '%s' expected.\n",msg+2,funcName,narg,provided,expected);
            else
                CCLOG("%s\n     %s argument #%d is '%s'; '%s' expected.\n",msg+2,funcName,narg,provided,expected);
        }
        else if (msg[1]=='v')
        {
            if (err->array)
                CCLOG("%s\n     %s value is array of '%s'; array of '%s' expected.\n",funcName,msg+2,provided,expected);
            else
                CCLOG("%s\n     %s value is '%s'; '%s' expected.\n",msg+2,funcName,provided,expected);
        }
    }
}

终极解决方案就是取消这个方法的自动导出,改为手动导出。但本着尽量少修改 cocos2d-x 源码的初衷,知道就好,不必改动。

2. 不能载入 luac 文件

问题描述:

在载入 main.lua 文件的时候,明明已经载入成功,但却一直会收到下面的提示:

can not get file data of main.luac

如果有多个 lua 文件,那么载入每个 lua 文件都会收到这个提示,只是扩展名变成了 luac

解决方案:

找到 Cocos2dxLuaLoader.cpp 文件,找到其中的 cocos2dx_lua_loader 方法,将下面这段删除或注释:

:::C++
chunkName = prefix.substr(0, pos) + filename + BYTECODE_FILE_EXT;
if (utils->isFileExist(chunkName))
{
    chunk = utils->getFileData(chunkName.c_str(), "rb", &chunkSize);
    break;
}

注意,上面的代码是一段 if ... else 结构的一部分,因此删除或注释后,还需要做一些其它的修改才能通过编译。

2015-02-26 修正解决方案:

上面的解决方案会导致进行正式打包(使用 luac 格式)的时候无法显示界面。因此,正确的修改应该是在 main.lua 中加入这一行:

:::lua
cc.FileUtils:getInstance():setPopupNotify(false)

问题分析:

cocos2d-x 的 lua 载入代码会优先载入 luac 文件,然后再处理 lua 文件。取消载入 luac 文件即可解决这个问题。

3. print 内容净化

问题描述:

使用 lua 的 print 方法打印出来的内容,在控制台中显示如下:

cocos2d: [LUA-print] -------- require(root.pretreatent.init)

这个 cocos2d: [LUA-print] 看起来相当讨厌,要干掉它。

解决方案:

CCLuaStack.cpp 中找到 lua_print 方法,将其中的:

:::C++
CCLOG("[LUA-print] %s", t.c_str());

改为:

:::C++
CCLOG("%s", t.c_str());

CCConsole.cpp 中找到 _log 方法,将其中的:

:::C++
fprintf(stdout, "cocos2d: %s", buf); 

改为:

:::C++
fprintf(stdout, "%s", buf); 

4. print 中加入时间戳显示

问题描述:

以前使用 quick 的时候,每一句 print 之前,都有一个当前 print 时刻的时间戳显示。单位是从程序执行开始的秒数。

cocos2d-x 3.3 lua 版本中没有这个时间显示。

解决方案:

CCLuaStack.cpp 中,在全局命名空间中(namespace { 之下)加入下面的内容,定义一个全局变量和一个全局函数:

:::C++
static timeval lua_print_lasttime = {0};

std::string lua_print_gettime()
{
    timeval now;
    float deltatime = 0;
    if(gettimeofday(&now, nullptr) != 0)
    {
        CCLOG("lua_print_gettime() - error in gettimeofday");
    }
    else
    {
        if (lua_print_lasttime.tv_sec)
        {
            deltatime = now.tv_sec - lua_print_lasttime.tv_sec + (now.tv_usec - lua_print_lasttime.tv_usec) / 1000000.0f;
        }
        else
        {
            lua_print_lasttime = now;
            deltatime = 0;
        }
    }
    
    std::string t("[");
    char timestr[32];
    memset(timestr, 0, sizeof(timestr));
    sprintf(timestr, "%.4f", deltatime);
    t += timestr;
    t += "] ";
    return t;
}

LuaStack::init 方法的第一行加入 gettimeofday(&lua_print_lasttime, nullptr); ,修改完毕的函数如下所示:

:::C++
bool LuaStack::init(void)
{
    gettimeofday(&lua_print_lasttime, nullptr);

    //.........原来的内容
    _state = lua_open();
    luaL_openlibs(_state);
    toluafix_open(_state);

    //.........原来的内容
}

修改 lua_print 方法,将其中的:

:::C++
std::string t;

改为:

:::C++
std::string t(lua_print_gettime());