cocos2d-x 以c++为主 引入LUA 与动态更新

来源:互联网 发布:淘宝古装衣服 编辑:程序博客网 时间:2024/06/15 15:06

cocos升级到2.1.4之后,越来越能看到开发团队对LUA的重视,恨不得除了个别组件用cpp处理,其他的内容都用LUA来写。

LUA的好处是很多的,比如更新方便,开发快捷,学习成本低等等。

手头有一个项目,启动时采用cpp作为核心解决方案,但最近客户反映需要引入LUA脚本,并且要求LUA脚本的动态更新。查了一些资料,现将解决方案汇总如下:

1,关于LUA

我开发的环境是VC2010+cocos2d-x2.1.4

因为创建工程时没有勾选[LUA脚本支持],所以这里要手动配置,在 项目属性的 [VC++ 目录]里的[包含目录]里,添加[你的cocos项目路径\scripting\lua]路径。

在[链接器->输入]里添加[liblua.lib  lua51.lib]两个lib文件。

在需要读取LUA脚本的cpp里,添加 #include "cocos2dx_support/CCLuaEngine.h"。

这样,就可以使用LUA脚本了。

查看了cocos2d-x自带的HelloLUA工程和TestsLUA工程,发现它们都是以LUA为主体的,而我的项目是以CPP为主体,以LUA为辅助的。

我的需求是:在cpp中调用LUA脚本,生成一个界面(这个界面要频繁的在线更新),在cpp工程中,我可以得到这个界面的指针。

查看了CCLuaEngine.h之后,并没有发现合适的接口来实现这一功能。

于是,我决定在函数virtual int executeGlobalFunction(const char* functionName);的基础上,自己写一个函数,来满足我的要求。

virtual bool executeGlobalFunctionX(const char* functionName, const char* _Format, ... );

{
//获得State
lua_State* pMyState = m_stack->getLuaState();
lua_getglobal(pMyState, functionName);       /* query function by name, stack: function */
if (!lua_isfunction(pMyState, -1))
{
CCLOG("[LUA ERROR] name '%s' does not represent a Lua function", functionName);
lua_pop(pMyState, 1);
return false;
}

int inArgsNum = 0,  outArgsNum = 0;
va_list args;
va_start(args, _Format);
char* buff = new char(strlen(_Format) + 1);
strcpy(buff, _Format);
char *ptr = NULL;
ptr = strtok( buff, "%" );

//get in args
do 
{
if (strcmp(ptr, "d") == 0 )  //int
{
lua_pushnumber(pMyState, va_arg(args, int));
inArgsNum++;
ptr = strtok( NULL,  "%" );
continue;
}


else if (strcmp(ptr, "f") == 0 )  //int
{
lua_pushnumber(pMyState, va_arg(args, float));
inArgsNum++;
ptr = strtok( NULL,  "%" );
continue;
}


else if (strcmp(ptr, "b") == 0 )  //bool
{
lua_pushnumber(pMyState, va_arg(args, bool));
inArgsNum++;
ptr = strtok( NULL,  "%" );
continue;
}


else if (strcmp(ptr, ":") == 0 )  //end in args
{
ptr = strtok( NULL,  "%" );
break;
}


else if (strcmp(ptr, "") == 0 )  //""
{
continue;
}
else
{
tolua_pushusertype(pMyState,va_arg(args, void *), ptr);
inArgsNum++;
}
ptr = strtok( NULL,  "%" );
} while (ptr != NULL );  
//get out args num


for (int num = 0 ; num < strlen(_Format); num++ )
{
if(*(_Format + num) == '%')
{
outArgsNum ++;
}
}
outArgsNum = outArgsNum - inArgsNum - 1;
if (outArgsNum < 0)
{
outArgsNum = 0;
}
int error = lua_pcall(pMyState, inArgsNum, outArgsNum, 0);             /* call function, stack: ret */
// lua_gc(m_state, LUA_GCCOLLECT, 0);


if (error)
{
CCLOG("[LUA ERROR] %s", lua_tostring(pMyState, - 1));
lua_pop(pMyState, 1); // clean error message
va_end(args);
return false;
}


int popNum =  outArgsNum * (-1);
//get out args
while(ptr != NULL ){
if (strcmp(ptr, "d") == 0 )  //int
{
if (!lua_isnumber(pMyState, popNum))
{
lua_settop(pMyState, popNum);
va_end(args);
return false;
}
*va_arg(args, int *) = (int)lua_tonumber(pMyState, popNum);
popNum++;
ptr = strtok( NULL,  "%" );
continue;
}


if (strcmp(ptr, "b") == 0 )  //bool
{
if (!lua_isboolean(pMyState, popNum))
{
lua_settop(pMyState, popNum);
va_end(args);
return false;
}
*va_arg(args, bool *) = (bool)lua_toboolean(pMyState, popNum);
popNum++;
ptr = strtok( NULL,  "%" );
continue;
}


if (strcmp(ptr, "f") == 0 )  //float
{
if (!lua_isnumber(pMyState, popNum))
{
lua_settop(pMyState, popNum);
va_end(args);
return false;
}
*va_arg(args, float*) = (float)lua_tonumber(pMyState, popNum);
popNum++;
ptr = strtok( NULL,  "%" );
continue;
}
}
va_end(args);
return true;
}

在CCLuaEngine.h里添加这个函数之后,要重新编译工程liblua,这里额外需要注意的是,加入该函数后,必须添加头文件tolua++.h

回到我的工程,在需要使用LUA脚本的cpp里,添加全局变量

CCLuaEngine* g_pEngine=NULL; //全局LUA引擎(单例)

并在init函数里,添加

if(g_pEngine==NULL)//这样做是为了保证g_pEngine只被初始化一次
{
g_pEngine = CCLuaEngine::defaultEngine();
CCScriptEngineManager::sharedManager()->setScriptEngine(g_pEngine);
}

在scene()函数里,将

scene->addChild(layer);

修改成

scene->addChild(layer,1,_MAIN_LAYER_TAG_);

这一步是为了,将场景的主Layer设置Tag,以便LUA脚本查找

在需要读取LUA脚本的位置,添加如下代码

if(getChildByTag(_LUA_CREATE_NODE_TAG_)==NULL)//这个if语句是为了防止LUA构造的Node被添加多次
{
std::string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("MyLua.lua");//LUA脚本的名字

g_pEngine->executeScriptFile(path.c_str());

int nResult=-1;
//执行脚本内函数 传入主Layer的Tag值 和 要构造节点的Tag值
g_pEngine->executeGlobalFunctionX("AddSomethingToRoot", "%d%d%:%d", _MAIN_LAYER_TAG_ ,_LUA_CREATE_NODE_TAG_, &nResult);
//输出返回值
CCLOG("AddSomething Result is %d", nResult);

}

下面是我LUA脚本的内容:

function AddSomethingToRoot(nRootTag,nAddTag)
    local spriteDog = CCSprite:create("dog.png");--声明一个精灵

    spriteDog:setTag(nAddTag);--将精灵的Tag设置为输入的Tag

local CurrentScene = CCDirector:sharedDirector():getRunningScene();--获得当前场景
local RootLayer = CurrentScene:getChildByTag(nRootTag);--通过输入Tag获得主Layer指针
RootLayer = tolua.cast(RootLayer,"CCLayer");--转换成CCLayer指针
RootLayer:addChild(spriteDog);--添加子节点
return 0;--返回值
end

我们可以通过

CCSprite *pDog = dynamic_cast<CCSprite*>(getChildByTag(_LUA_CREATE_NODE_TAG_));
if(pDog)
{
pDog->setPosition(ccp(200,200));
}
else
{
CCLog("Can't Find Dog");
}

来校验LUA脚本是否被正确的执行了。


2.在线更新。

cocos2d-x2.1.4里自带的libExtensions组件里包含了动态更新的类AssetsManager,Himi的博客里也介绍了它的基本用法。

不过,因为AssetsManager不是多线程执行的,所以在下载过程中画面会卡死,这当然不是我们希望看到的。

所以,这里要介绍cocos2d-x的多线程问题。

我们都知道,在类Unix系统中(Android和IOS都是),#include "pthread.h"是处理多线程问题的常用头文件,而win32下的头文件与之不同,所以,cocos的第三方库中,提供了一个模拟pthread.h的接口。

具体用法如下:

在 属性->VC++目录 里 的包含目录里 添加 你的cocos路径\cocos2dx\platform\third_party\win32\pthread

在 链接器->输入 里 添加pthreadVCE2.lib

然后,你就可以像Unix系统一样进行多线程编程了。

另外,我们要在 属性->VC++目录 里 添加  你的cocos路径\extensions

在 链接器->输入 里 添加 libExtensions.lib

这样是为了使用AssetsManager

这里 ,我结合 多线程 和 在线更新 给出简单的范例代码

static void* ThreadUpdate(void *r) //在线更新线程 注意:在类内必须是static声明的函数才可以作为线程函数

{

//构造下载类
AssetsManager *pAssetsManager = new AssetsManager("https://raw.github.com/minggo/AssetsManagerTest/master/package.zip",//cocos提供的资源包
 "https://raw.github.com/minggo/AssetsManagerTest/master/version",//版本校验地址
 g_pathToSave.c_str());//这个要注意 需要在工程主线程里,为其赋值CCFileUtils::sharedFileUtils()->getWritablePath()
//删除版本 (测试阶段保证每次都会更新)
pAssetsManager->deleteVersion();


//开始下载
if(pAssetsManager->update()==false)
{
CCLog("update code = %d",g_nAssetsManagerStatus);
CC_SAFE_DELETE(pAssetsManager);
return NULL;
}


CC_SAFE_DELETE(pAssetsManager);

}

关于CCFileUtils::sharedFileUtils()->getWritablePath()这里要注意,因为Android和IOS有明确的路径操作限制,所以必须将下载的文件放入这里

由此,就基本实现了多线程下载

额外需要接收下载信息的同学,可以自行修改AssetsManager类,增加全局变量或者线程资源池,这里不做赘述。


4,关于解决方案的Android版本发布问题

关键点有两条,一是Android.mk的修改

LOCAL_PATH := $(call my-dir)


include $(CLEAR_VARS)


LOCAL_MODULE := game_shared


LOCAL_MODULE_FILENAME := libgame


LOCAL_SRC_FILES := hellocpp/main.cpp \
                   ../../Classes/AppDelegate.cpp \
                   ../../Classes/HelloWorldScene.cpp
                   
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes 
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../../scripting/lua
           
LOCAL_WHOLE_STATIC_LIBRARIES := cocos2dx_static cocosdenshion_static cocos_extension_static
LOCAL_WHOLE_STATIC_LIBRARIES += cocos_lua_static           
include $(BUILD_SHARED_LIBRARY)


$(call import-module,CocosDenshion/android) \
$(call import-module,cocos2dx) \
$(call import-module,extensions) \
$(call import-module,scripting/lua/proj.android)

这是我的范例,请同学们自行对比

二是要记得在Eclipse里加入网络访问权限


过程中有疑问的,可以加我QQ详细聊

Q:2813610155

常年承接cocos2d-x培训 外包

原创粉丝点击