Cocos2d-x之LUA脚本引擎深入分析

另:本章所用Cocos2d-x版本为:

Cocos2d-2.0.2

http://cn.cocos2d-x.org/download

大家好,又是一周过去了,这一周忙的有点焦头烂额,除了工作照例每天加班到九点外,工具箱又做了大幅改进,新的论坛游戏兔子game2z也上线了,Cocos2d-x的学习时间被压缩的很少了,现在是凌晨一点零六分,看着妻子睡熟的样子,我也只能告诉自已,坚持到底。

好了,不说废话,本周奉上一篇初级入门教程博文,Cocos2d-x中的LUA引导与入门。

做为惯例,一切都是以HelloWorld的样例为准。我们今天学习用LUA来完成一版HelloWorld。

大家既使没有看过我的“HelloWorld 深入分析”一文,想必也无数次运行过Cocos2d-x里的HelloCpp工程,对于运行的结果画面熟烂于心。我们回想一下,这个画面里有什么。嗯,一个背景图精灵,一个文字标签一个关闭按钮。OK,咱们就做这么个东西。

首先,我们要知道LUA是个什么东西,至于官方怎么说可以百度去查,但我想告诉你的是LUA就是一种可以在不必修改C++代码的情况下实现逻辑处理的手段。稍微讲的再明白一点,就是你用指定语法写一些逻辑处理函数然后保存成文本格式,这个文件称为脚本文件,可以被游戏执行。经过若干年的发展,现在在LUA中写逻辑,除了调用注册到LUA的静态C函数外,也已经可以方便的访问到C++工程中的类的成员函数。这是游戏开发史上最重要的技术之一。其改变了很多设计方案,使游戏变的灵活强大而极具扩展性。

在Cocos2d-x中,有两个类来完成对于LUA脚本文件的处理。

1. ccluaEngine:LUA脚本引擎

2. ccScriptEngineManager:脚本引擎管理器。

ccluaEngine类的基类是一个接口类,叫做ccScriptEngineProtocol,它规定了所有LUA引擎的功能函数,它和ccScriptEngineManager都存放在libcocos2d下的script_support目录中的ccScriptSupport.h/cpp中。

首先我们来看一下ccScriptEngineProtocol:


  1. classCC_DLLccScriptEngineProtocol:publicCCObject
  2. {
  3. public:
  4. //取得LUA的全局指针,所有的LUA函数都需要使用这个指针来做为参数进行调用
  5. virtuallua_State*getLuaState(void)=0;
  6. //通过LUA脚本ID移除对应的CCObject
  7. virtualvoidremoveCCObjectByID(intnLuaID)=0;
  8. //通过函数索引值移除对应的LUA函数
  9. voidremoveLuaHandler(intnHandler)=0;
  10. //将一个目录中的LUA文件加入到LUA资源容器中。
  11. voidaddSearchPath(constchar*path)=0;
  12. //执行一段LUA代码
  13. virtualintexecuteString(char*codes)=0;
  14. //执行一个LUA脚本文件
  15. intexecuteScriptFile(char*filename)=0;
  16. //调用一个全局函数
  17. intexecuteGlobalFunction(char*functionName)=0;
  18. //通过句柄调用函数多种形态。
  19. //通过句柄调用函数,参数二为参数数量
  20. intexecuteFunctionByHandler(intnHandler,intnumArgs=0)=0;
  21. intexecuteFunctionWithIntegerData(intdata)=0;
  22. intexecuteFunctionWithFloatData(floatdata)=0;
  23. intexecuteFunctionWithBooleanData(booldata)=0;
  24. 名称。
  25. intexecuteFunctionWithCCObject(char*typeName)=0;
  26. //将一个整数数值压栈做为参数。
  27. intpushIntegerToLuaStack(intdata)=0;
  28. //将一个浮点数值压栈做为参数。
  29. intpushFloatToLuaStack(//将一个布尔数值压栈做为参数。
  30. intpushBooleanToLuaStack(//将一个CCObject指针和类型名压栈做为参数。
  31. intpushCCObjectToLuaStack(CCObject*pObject,87); background-color:inherit; font-weight:bold">char*typeName)=0;
  32. //执行单点触屏事件
  33. intexecutetouchEvent(inteventType,CCTouch*pTouch)=0;
  34. //执行多点触屏事件。
  35. intexecutetouchesEvent(touches)=0;
  36. //执行一个回调函数
  37. intexecuteSchedule(floatdt)=0;
  38. };

这个接口类的功能函数的具体实现,我们要参看ccluaEngine类。

现在我们打开ccluaEngine.h:

copy
    //加入lua的头文件,约定其中代码使用C风格
  1. extern"C"{
  2. #include"lua.h"
  3. }
  4. //相应的头文件
  5. #include"ccTypes.h"
  6. #include"cocoa/CCObject.h"
  7. #include"touch_dispatcher/CCTouch.h"
  8. #include"cocoa/CCSet.h"
  9. #include"base_nodes/CCNode.h"
  10. #include"script_support/ccScriptSupport.h"
  11. //使用Cocos2d命名空间
  12. NS_CC_BEGIN
  13. //由ccScriptEngineProtocol派生的实际功能类。
  14. classccluaEngine:publicccScriptEngineProtocol
  15. //析构
  16. ~ccluaEngine();
  17. //取得LUA的全局指针,所有的LUA函数都需要使用这个指针来做为参数进行调用
  18. void){
  19. returnm_state;
  20. …此处省略若干字。
  21. //加入一个多线程加载LUA脚本的实时回调函数,此函数用于ANDROID
  22. voidaddLuaLoader(lua_CFunctionfunc);
  23. //取得当前单件实例指针
  24. staticccluaEngine*engine();
  25. private:
  26. //构造,单例,你懂的。
  27. ccluaEngine(void)
  28. :m_state(NULL)
  29. {
  30. //初始化函数
  31. boolinit(void);
  32. //将一个句柄压栈
  33. boolpushFunctionByHandler(intnHandler);
  34. //唯一的LUA指针
  35. lua_State*m_state;
  36. };
  37. NS_CC_END

分析其CPP实现:

copy
    //本类的头文件
  1. #include"ccluaEngine.h"
  2. //这里用到了tolua++库,tolua++库是一个专门处理LUA脚本的第三方库,可以很好的完成LUA访问C++类及成员函数功能。如果没有tolua++,这块要处理起来可是麻烦死了。
  3. #include"tolua++.h"
  4. //加入lua库的相应头文件
  5. extern"C"{
  6. #include"lualib.h"
  7. #include"lauxlib.h"
  8. #include"tolua_fix.h"
  9. }
  10. //加入Cocos2d-x所用的相应头文件
  11. #include"cocos2d.h"
  12. #include"LuaCocos2d.h"
  13. #include"cocoa/CCArray.h"
  14. #include"CCScheduler.h"
  15. //如果是ANDROID平台,加上对多线程加载LUA脚本的支持,使用相应的头文件
  16. #if(CC_TARGET_PLATFORM==CC_PLATFORM_ANDROID)
  17. #include"Cocos2dxLuaLoader.h"
  18. #endif
  19. //开始Cocos2d-x命名空间。
  20. NS_CC_BEGIN
  21. //析构。
  22. ccluaEngine::~ccluaEngine()
  23. //结束对LUA指针的使用,关闭LUA。
  24. lua_close(m_state);
  25. //初始始。
  26. boolccluaEngine::init(void)
  27. //开始对LUA的使用,创建一个LUA指针。
  28. m_state=lua_open();
  29. //打开相应的库。
  30. luaL_openlibs(m_state);
  31. //打开使用tolua封装的访问Cocos2d-x的库。
  32. tolua_Cocos2d_open(m_state);
  33. tolua_prepare_ccobject_table(m_state);
  34. //如果是ANDROID平台,也加上对LUA进行多线程加载的库支持
  35. #if(CC_TARGET_PLATFORM==CC_PLATFORM_ANDROID)
  36. addLuaLoader(loader_Android);
  37. #endif
  38. returntrue;
  39. //取得单例指针。
  40. ccluaEngine*ccluaEngine::engine()
  41. ccluaEngine*pEngine=newccluaEngine();
  42. pEngine->init();
  43. pEngine->autorelease();
  44. returnpEngine;
  45. voidccluaEngine::removeCCObjectByID(intnLuaID)
  46. tolua_remove_ccobject_by_refid(m_state,nLuaID);
  47. //<spanstyle="font-family:Arial,sans-serif;">通过函数索引值移除对应的LUA函数。</span>
  48. voidccluaEngine::removeLuaHandler(intnHandler)
  49. tolua_remove_function_by_refid(m_state,nHandler);
  50. voidccluaEngine::addSearchPath(char*path)
  51. //取得全局表package
  52. lua_getglobal(m_state,"package");
  53. //取得其中的path字段,压入栈顶。
  54. lua_getfield(m_state,-1,"path");
  55. //取得当前的目录字符串。
  56. char*cur_path=lua_tostring(m_state,-1);
  57. //参数出栈,恢复堆栈。
  58. lua_pop(m_state,1);
  59. //将新路径字符串加入到路径串列中,压入栈顶。
  60. lua_pushfstring(m_state,"%s;%s/?.lua",cur_path,path);
  61. //设置path字段值路径
  62. lua_setfield(m_state,-2,0); background-color:inherit">//执行一段LUA代码
  63. intccluaEngine::executeString(char*codes)
  64. //执行一段LUA代码。返回值存放到nRet中。
  65. intnRet=luaL_dostring(m_state,codes);
  66. //进行下拉圾收集。
  67. lua_gc(m_state,LUA_GCCOLLECT,0);
  68. //如果出错,打印日志。
  69. if(nRet!=0)
  70. cclOG("[LUAERROR]%s",lua_tostring(m_state,-1));
  71. lua_pop(m_state,1);
  72. returnnRet;
  73. return0;
  74. intccluaEngine::executeScriptFile(char*filename)
  75. //执行一个LUA脚本文件。返回值存放到nRet中。
  76. intnRet=luaL_dofile(m_state,filename);
  77. //lua_gc(m_state,0);
  78. //调用一个全局函数
  79. intccluaEngine::executeGlobalFunction(char*functionName)
  80. //将全局函数放在栈顶
  81. /*queryfunctionbyname,stack:function*/
  82. //判断是否是函数
  83. if(!lua_isfunction(m_state,-1))
  84. cclOG("[LUAERROR]name'%s'doesnotrepresentaLuafunction",functionName);
  85. //调用函数
  86. interror=lua_pcall(m_state,1,0);/*callfunction,stack:ret*/
  87. if(error)
  88. //cleanerrormessage
  89. //getreturnvalue
  90. //如果取得的第一个参数不是数字,返回错误
  91. if(!lua_isnumber(m_state,-1))
  92. //取得数字的参数存放在ret中。
  93. intret=lua_tointeger(m_state,-1);
  94. //参数出栈,恢复堆栈。
  95. /*stack:-*/
  96. returnret;
  97. intccluaEngine::executeFunctionByHandler(intnumArgs)
  98. if(pushFunctionByHandler(nHandler))
  99. if(numArgs>0)
  100. lua_insert(m_state,-(numArgs+1));/*stack:...funcarg1arg2...*/
  101. interror=0;
  102. //try
  103. //{
  104. error=lua_pcall(m_state,numArgs,0);/*stack:...ret*/
  105. //}
  106. //catch(exception&e)
  107. //cclOG("[LUAERROR]lua_pcall(%d)catchC++exception:%s",nHandler,e.what());
  108. //lua_settop(m_state,0);
  109. //return0;
  110. //catch(...)
  111. //cclOG("[LUAERROR]lua_pcall(%d)catchC++unkNownexception.",nHandler);
  112. lua_settop(m_state,0);
  113. intret=0;
  114. //如果返回参数是数字转为整数。
  115. if(lua_isnumber(m_state,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> ret=lua_tointeger(m_state,-1);
  116. }//如果是布尔型转为true或false
  117. elseif(lua_isboolean(m_state,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> ret=lua_toboolean(m_state,153); background-color:inherit; font-weight:bold">else
  118. intccluaEngine::executeFunctionWithIntegerData(intdata)
  119. lua_pushinteger(m_state,data);
  120. returnexecuteFunctionByHandler(nHandler,1);
  121. intccluaEngine::executeFunctionWithFloatData(floatdata)
  122. lua_pushnumber(m_state,参数二为布尔型数据。
  123. intccluaEngine::executeFunctionWithBooleanData(booldata)
  124. lua_pushboolean(m_state,参数二为CCObject指针数据和其类型名称
  125. intccluaEngine::executeFunctionWithCCObject(char*typeName)
  126. tolua_pushusertype_ccobject(m_state,pObject->m_uID,&pObject->m_nLuaID,pObject,typeName);
  127. intccluaEngine::pushIntegerToLuaStack(//将整数值压入堆栈
  128. lua_pushinteger(m_state,data);
  129. //返回参数的数量
  130. returnlua_gettop(m_state);
  131. intccluaEngine::pushFloatToLuaStack(//将数字值压入堆栈
  132. lua_pushnumber(m_state,87); background-color:inherit; font-weight:bold">intccluaEngine::pushBooleanToLuaStack(//将boolean值压入堆栈
  133. lua_pushboolean(m_state,87); background-color:inherit; font-weight:bold">intccluaEngine::pushCCObjectToLuaStack(CCObject*pObject,87); background-color:inherit; font-weight:bold">intccluaEngine::executetouchEvent( CCPointpt=CCDirector::sharedDirector()->convertToGL(pTouch->getLocationInView());
  134. //将参数压栈后调用函数
  135. //执行多点触屏事件。
  136. intccluaEngine::executetouchesEvent(touches)
  137. //将类型参数压栈后调用函数
  138. //创建一个
  139. lua_newtable(m_state);
  140. //将多个触点信息参数放入表中。
  141. CCDirector*pDirector=CCDirector::sharedDirector();
  142. CCSetIteratorit=ptouches->begin();
  143. CCTouch*pTouch;
  144. intn=1;
  145. while(it!=ptouches->end())
  146. pTouch=(CCTouch*)*it;
  147. CCPointpt=pDirector->convertToGL(pTouch->getLocationInView());
  148. //将位置x压入堆栈
  149. //将栈顶的数值放入到表中对应索引n的数值中
  150. lua_rawseti(m_state,n++);
  151. ++it;
  152. //以表做为第二参数压栈,调用函数
  153. intccluaEngine::executeSchedule(floatdt)
  154. returnexecuteFunctionWithFloatData(nHandler,dt);
  155. //加入一个多线程加载LUA脚本的实时回调函数,此函数用于ANDROID
  156. voidccluaEngine::addLuaLoader(lua_CFunctionfunc)
  157. if(!func)return;
  158. //取得全局表
  159. //取得全局表中的“loaders”表
  160. "loaders");
  161. //将设定的函数和参数压栈
  162. lua_pushcfunction(m_state,func);
  163. //将参数压栈
  164. for(inti=lua_objlen(m_state,-2)+1;i>2;--i)
  165. //取得原"loaders"表第i-1个参数
  166. lua_rawgeti(m_state,i-1);
  167. //将取出的值放到新"loaders"表中第i个数值
  168. //将函数设为新"loaders"表的第2个参数
  169. lua_rawseti(m_state,2);
  170. //把“loaders”表放到全局表中
  171. boolccluaEngine::pushFunctionByHandler(intnHandler)
  172. //找出注册函数表的第nHandler个数值
  173. lua_rawgeti(m_state,LUA_REGISTRYINDEX,nHandler);/*stack:...func*/
  174. cclOG("[LUAERROR]functionrefid'%d'doesnotreferenceaLuafunction",nHandler);
  175. false;
  176. }

然后我们来看一下ccScriptEngineManager,这个类被称为脚本引擎管理器,其实很简单,只是用来设定当前项目的唯一正在使用的脚本引擎。也许Cocos2d-x打算用它管理多种类型脚本引擎,比如python,js等。


copy
    classCC_DLLccScriptEngineManager
  1. ~ccScriptEngineManager(void);
  2. //取得单例指针
  3. ccScriptEngineProtocol*getScriptEngine(void){
  4. returnm_pScriptEngine;
  5. //设置使用的LUA管理器
  6. voidsetScriptEngine(ccScriptEngineProtocol*pScriptEngine);
  7. //移除使用的LUA管理器。
  8. voidremoveScriptEngine(staticccScriptEngineManager*sharedManager(//销毁单例
  9. staticvoidpurgeSharedManager( ccScriptEngineManager( :m_pScriptEngine(NULL)
  10. //使用的LUA脚本引擎
  11. ccScriptEngineProtocol*m_pScriptEngine;
  12. 其对应的CPP实现:
  13. //全局唯一的
  14. staticccScriptEngineManager*s_pSharedScriptEngineManager=NULL;
  15. //析构
  16. ccScriptEngineManager::~ccScriptEngineManager( removeScriptEngine();
  17. voidccScriptEngineManager::setScriptEngine(ccScriptEngineProtocol*pScriptEngine)
  18. removeScriptEngine();
  19. m_pScriptEngine=pScriptEngine;
  20. m_pScriptEngine->retain();
  21. //移除使用的LUA管理器。
  22. voidccScriptEngineManager::removeScriptEngine(if(m_pScriptEngine)
  23. m_pScriptEngine->release();
  24. m_pScriptEngine=NULL;
  25. //取得单例指针
  26. ccScriptEngineManager*ccScriptEngineManager::sharedManager(if(!s_pSharedScriptEngineManager)
  27. s_pSharedScriptEngineManager=newccScriptEngineManager();
  28. returns_pSharedScriptEngineManager;
  29. voidccScriptEngineManager::purgeSharedManager(if(s_pSharedScriptEngineManager)
  30. deletes_pSharedScriptEngineManager;
  31. s_pSharedScriptEngineManager=NULL;
  32. 现在我们来实际操作一下。

    打开HelloLua工程中的AppDelegate.cpp:

    在AppDelegate::applicationDidFinishLaunching()函数中看这几行代码

    copy
      //取得LUA脚本引擎
    1. ccScriptEngineProtocol*pEngine=ccluaEngine::engine();
    2. //设置脚本引擎管理器使用新创建的LUA脚本引擎
    3. ccScriptEngineManager::sharedManager()->setScriptEngine(pEngine);
    4. //如果是ANDROID平台,获hello.lua内存到字符串然后执行字符串
    5. CCString*pstrFileContent=CCString::createWithContentsOfFile("hello.lua");
    6. if(pstrFileContent)
    7. pEngine->executeString(pstrFileContent->getCString());
    8. #else
    9. //如果不是ANDROID平台,取得hello.lua文件全路径并执行文件
    10. std::stringpath=CCFileUtils::sharedFileUtils()->fullPathFromrelativePath("hello.lua");
    11. pEngine->addSearchPath(path.substr(0,path.find_last_of("/")).c_str());
    12. pEngine->executeScriptFile(path.c_str());
    13. #endif

    就这样,hello.lua中的脚本就可以被执行了。

    现在我们将HelloLua工程目录拷出一份来,将目录和工程命名为StudyLua,并在程序运行目录中加入相关资源图片。之后我们打开hello.lua:


    [html] copy
      --设置内存回收
    1. collectgarbage("setpause",100)
    2. collectgarbage("setstepmul",5000)
    3. --取得窗口大小
    4. localwinSize=CCDirector:sharedDirector():getWinSize()
    5. --将Hello背景图加入
    6. localfunctioncreateLayerHello()
    7. locallayerHello=cclayer:create()
    8. --加入背景图
    9. localbg=CCSprite:create("Hello.png")
    10. bg:setPosition(winSize.width/2,winSize.height/2)
    11. layerHello:addChild(bg)
    12. --创建HelloWorld
    13. locallabel=cclabelTTF:create("HelloCocos2d-x","Arial",50)
    14. label:setPosition(winSize.width/2,60)
    15. label:setVisible(true)
    16. layerHello:addChild(label)
    17. returnlayerHello
    18. end
    19. --将关闭按钮菜单加入
    20. localfunctioncreateExitBtn()
    21. locallayerMenu=cclayer:create()
    22. --局部函数,用于退出
    23. localfunctionmenuCallbackExit()
    24. CCDirector:sharedDirector():endToLua()
    25. --创建退出按钮
    26. localmenuPopupItem=CcmenuItemImage:create("Closenormal.png","CloseSelected.png")
    27. --放在居上角附近
    28. menuPopupItem:setPosition(winSize.width-50,winSize.height-50)
    29. --注册退出函数
    30. menuPopupItem:registerScriptHandler(menuCallbackExit)
    31. --由菜单按钮项创建菜单
    32. localmenuClose=Ccmenu:createWithItem(menuPopupItem)
    33. menuClose:setPosition(0,0)
    34. menuClose:setVisible(true)
    35. --将菜单加入层中
    36. layerMenu:addChild(menuClose)
    37. returnlayerMenu
    38. --创建场景
    39. localsceneGame=CCScene:create()
    40. --将Hello背景图加入
    41. sceneGame:addChild(createLayerHello())
    42. sceneGame:addChild(createExitBtn())
    43. --运行场景
    44. CCDirector:sharedDirector():runWithScene(sceneGame)

    运行一下:



    我只能说,一切好极了,下课!

    本文出自:http://blog.csdn.net/honghaier/article/details/8700574

    相关文章

        本文实践自 RayWenderlich、Ali Hafizji 的文章《...
    Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@1...
    第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从C...
        Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发...
    1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《...
       Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试...