cocos2d-x之lua脚本引擎深入分析

liushije 贡献于2016-12-13

作者   创建于2012-06-06 01:30:27   修改者  修改于2013-04-27 02:37:28字数13588

文档摘要:Cocos2d-x之LUA脚本引擎深入分析做为惯例,一切都是以HelloWorld的样例为准。我们今天学习用LUA来完成一版HelloWorld。大家既使没有看过我的“HelloWorld深入分析”一文,想必也无数次运行过Cocos2d-x里的HelloCpp工程,对于运行的结果画面熟烂于心。我们回想一下,这个画面里有什么。嗯,一个背景图精灵,一个文字标签,一个关闭按钮。OK,咱们就做这么个东西。首先,我们要知道LUA是个什么东西,至于官方怎么说可以百度去查,但我想告诉你的是LUA就是一种可以在不必修改C++代码的情况下实现逻辑处理的手段。稍微讲的再明白一点,就是你用指定语法写一些逻辑处理函数然后保存成文本格式,这个文件称为脚本文件,可以被游戏执行。经过若干年的发展,现在在LUA中写逻辑,除了调用注册到LUA的静态C函数外,也已经可以方便的访问到C++工程中的类的成员函数。这是游戏开发史上最重要的技术之一。其改变了很多设计方案,使游戏变的灵活强大而极具扩展性。
关键词:

本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 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: class CC_DLL CCScriptEngineProtocol : public CCObject { public: //取得LUA的全局指针,所有的LUA函数都需要使用这个指针来做为参数进行调用。 virtual lua_State* getLuaState(void) = 0; //通过LUA脚本ID移除对应的CCObject virtual void removeCCObjectByID(int nLuaID) = 0; //通过函数索引值移除对应的LUA函数。 virtual void removeLuaHandler(int nHandler) = 0; //将一个目录中的LUA文件加入到LUA资源容器中。 virtual void addSearchPath(const char* path) = 0; //执行一段LUA代码 virtual int executeString(const char* codes) = 0; //执行一个LUA脚本文件。 virtual int executeScriptFile(const char* filename) = 0; //调用一个全局函数。 virtual int executeGlobalFunction(const char* functionName) = 0; //通过句柄调用函数多种形态。 //通过句柄调用函数,参数二为参数数量。 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 virtual int executeFunctionByHandler(int nHandler, int numArgs = 0) = 0; //通过句柄调用函数,参数二为整数数据。 virtual int executeFunctionWithIntegerData(int nHandler, int data) = 0; //通过句柄调用函数,参数二为浮点数据。 virtual int executeFunctionWithFloatData(int nHandler, float data) = 0; //通过句柄调用函数,参数二为布尔型数据。 virtual int executeFunctionWithBooleanData(int nHandler, bool data) = 0; //通过句柄调用函数,参数二为CCObject指针数据和其类型名称。 virtual int executeFunctionWithCCObject(int nHandler, CCObject* pObject, const char* typeName) = 0; //将一个整数数值压栈做为参数。 virtual int pushIntegerToLuaStack(int data) = 0; //将一个浮点数值压栈做为参数。 virtual int pushFloatToLuaStack(int data) = 0; //将一个布尔数值压栈做为参数。 virtual int pushBooleanToLuaStack(int data) = 0; //将一个CCObject指针和类型名压栈做为参数。 virtual int pushCCObjectToLuaStack(CCObject* pObject, const char* typeName) = 0; // 执行单点触屏事件 virtual int executeTouchEvent(int nHandler, int eventType, CCTouch *pTouch) = 0; //执行多点触屏事件。 virtual int executeTouchesEvent(int nHandler, int eventType, CCSet *pTouches) = 0; // 执行一个回调函数。 virtual int executeSchedule(int nHandler, float dt) = 0; }; 这个接口类的功能函数的具体实现,我们要参看CCLuaEngine类。 现在我们打开CCLuaEngine.h: //加入lua的头文件,约定其中代码使用C风格 extern "C" { #include "lua.h" } //相应的头文件。 #include "ccTypes.h" #include "cocoa/CCObject.h" #include "touch_dispatcher/CCTouch.h" #include "cocoa/CCSet.h" #include "base_nodes/CCNode.h" #include "script_support/CCScriptSupport.h" 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 //使用Cocos2d命名空间 NS_CC_BEGIN // 由CCScriptEngineProtocol派生的实际功能类。 class CCLuaEngine : public CCScriptEngineProtocol { public: //析构 ~CCLuaEngine(); //取得LUA的全局指针,所有的LUA函数都需要使用这个指针来做为参数进行调用。 virtual lua_State* getLuaState(void) { return m_state; } …此处省略若干字。 // 加入一个多线程加载LUA脚本的实时回调函数,此函数用于ANDROID virtual void addLuaLoader(lua_CFunction func); //取得当前单件实例指针 static CCLuaEngine* engine(); private: //构造,单例,你懂的。 CCLuaEngine(void) : m_state(NULL) { } //初始化函数。 bool init(void); //将一个句柄压栈 bool pushFunctionByHandler(int nHandler); //唯一的LUA指针 lua_State* m_state; }; NS_CC_END 分析其CPP实现: //本类的头文件。 #include "CCLuaEngine.h" //这里用到了tolua++库,tolua++库是一个专门处理LUA脚本的第三方库,可以很好的完成LUA访问C++类及成员函数的功能。如果没有tolua++,这块要处理起来可是麻烦死了。 #include "tolua++.h" 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 //加入lua库的相应头文件。 extern "C" { #include "lualib.h" #include "lauxlib.h" #include "tolua_fix.h" } //加入Cocos2d-x所用的相应头文件。 #include "cocos2d.h" #include "LuaCocos2d.h" #include "cocoa/CCArray.h" #include "CCScheduler.h" //如果是ANDROID平台,加上对多线程加载LUA脚本的支持,使用相应的头文件。 #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #include "Cocos2dxLuaLoader.h" #endif //开始Cocos2d-x命名空间。 NS_CC_BEGIN //析构。 CCLuaEngine::~CCLuaEngine() { //结束对LUA指针的使用,关闭LUA。 lua_close(m_state); } //初始始。 bool CCLuaEngine::init(void) { //开始对LUA的使用,创建一个LUA指针。 m_state = lua_open(); //打开相应的库。 luaL_openlibs(m_state); //打开使用tolua封装的访问Cocos2d-x的库。 tolua_Cocos2d_open(m_state); tolua_prepare_ccobject_table(m_state); //如果是ANDROID平台,也加上对LUA进行多线程加载的库支持。 #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) addLuaLoader(loader_Android); #endif 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 return true; } //取得单例指针。 CCLuaEngine* CCLuaEngine::engine() { CCLuaEngine* pEngine = new CCLuaEngine(); pEngine->init(); pEngine->autorelease(); return pEngine; } //通过LUA脚本ID移除对应的CCObject void CCLuaEngine::removeCCObjectByID(int nLuaID) { tolua_remove_ccobject_by_refid(m_state, nLuaID); } //通过函数索引值移除对应的LUA函数。 void CCLuaEngine::removeLuaHandler(int nHandler) { tolua_remove_function_by_refid(m_state, nHandler); } //将一个目录中的LUA文件加入到LUA资源容器中。 void CCLuaEngine::addSearchPath(const char* path) { //取得全局表package lua_getglobal(m_state, "package"); //取得其中的path字段,压入栈顶。 lua_getfield(m_state, -1, "path"); //取得当前的目录字符串。 const char* cur_path = lua_tostring(m_state, -1); //参数出栈,恢复堆栈。 lua_pop(m_state, 1); //将新路径字符串加入到路径串列中,压入栈顶。 lua_pushfstring(m_state, "%s;%s/?.lua", cur_path, path); //设置path字段值路径 lua_setfield(m_state, -2, "path"); //参数出栈,恢复堆栈。 lua_pop(m_state, 1); } //执行一段LUA代码 int CCLuaEngine::executeString(const char *codes) { //执行一段LUA代码。返回值存放到nRet中。 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 int nRet = luaL_dostring(m_state, codes); //进行下拉圾收集。 lua_gc(m_state, LUA_GCCOLLECT, 0); //如果出错,打印日志。 if (nRet != 0) { CCLOG("[LUA ERROR] %s", lua_tostring(m_state, -1)); lua_pop(m_state, 1); return nRet; } return 0; } //执行一个LUA脚本文件。 int CCLuaEngine::executeScriptFile(const char* filename) { //执行一个LUA脚本文件。返回值存放到nRet中。 int nRet = luaL_dofile(m_state, filename); // lua_gc(m_state, LUA_GCCOLLECT, 0); //如果出错,打印日志。 if (nRet != 0) { CCLOG("[LUA ERROR] %s", lua_tostring(m_state, -1)); lua_pop(m_state, 1); return nRet; } return 0; } //调用一个全局函数。 int CCLuaEngine::executeGlobalFunction(const char* functionName) { //将全局函数放在栈顶 lua_getglobal(m_state, functionName); /* query function by name, stack: function */ //判断是否是函数。 if (!lua_isfunction(m_state, -1)) { CCLOG("[LUA ERROR] name '%s' does not represent a Lua function", functionName); lua_pop(m_state, 1); return 0; } //调用函数。 int error = lua_pcall(m_state, 0, 1, 0); /* call function, stack: ret */ // lua_gc(m_state, LUA_GCCOLLECT, 0); 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 if (error) { CCLOG("[LUA ERROR] %s", lua_tostring(m_state, - 1)); lua_pop(m_state, 1); // clean error message return 0; } // get return value //如果取得的第一个参数不是数字,返回错误。 if (!lua_isnumber(m_state, -1)) { lua_pop(m_state, 1); return 0; } //取得数字的参数存放在ret中。 int ret = lua_tointeger(m_state, -1); //参数出栈,恢复堆栈。 lua_pop(m_state, 1); /* stack: - */ return ret; } //通过句柄调用函数多种形态。 //通过句柄调用函数,参数二为参数数量。 int CCLuaEngine::executeFunctionByHandler(int nHandler, int numArgs) { if (pushFunctionByHandler(nHandler)) { if (numArgs > 0) { lua_insert(m_state, -(numArgs + 1)); /* stack: ... func arg1 arg2 ... */ } int error = 0; // try // { error = lua_pcall(m_state, numArgs, 1, 0); /* stack: ... ret */ // } // catch (exception& e) // { // CCLOG("[LUA ERROR] lua_pcall(%d) catch C++ exception: %s", nHandler, e.what()); // lua_settop(m_state, 0); // return 0; // } 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 // catch (...) // { // CCLOG("[LUA ERROR] lua_pcall(%d) catch C++ unknown exception.", nHandler); // lua_settop(m_state, 0); // return 0; // } if (error) { CCLOG("[LUA ERROR] %s", lua_tostring(m_state, - 1)); lua_settop(m_state, 0); return 0; } // get return value int ret = 0; //如果返回参数是数字转为整数。 if (lua_isnumber(m_state, -1)) { ret = lua_tointeger(m_state, -1); }//如果是布尔型转为true或false else if (lua_isboolean(m_state, -1)) { ret = lua_toboolean(m_state, -1); } //参数出栈,恢复堆栈。 lua_pop(m_state, 1); return ret; } else { return 0; } } //通过句柄调用函数,参数二为整数数据。 int CCLuaEngine::executeFunctionWithIntegerData(int nHandler, int data) { lua_pushinteger(m_state, data); return executeFunctionByHandler(nHandler, 1); } //通过句柄调用函数,参数二为浮点数据。 int CCLuaEngine::executeFunctionWithFloatData(int nHandler, float data) { 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 lua_pushnumber(m_state, data); return executeFunctionByHandler(nHandler, 1); } //通过句柄调用函数,参数二为布尔型数据。 int CCLuaEngine::executeFunctionWithBooleanData(int nHandler, bool data) { lua_pushboolean(m_state, data); return executeFunctionByHandler(nHandler, 1); } //通过句柄调用函数,参数二为CCObject指针数据和其类型名称。 int CCLuaEngine::executeFunctionWithCCObject(int nHandler, CCObject* pObject, const char* typeName) { tolua_pushusertype_ccobject(m_state, pObject->m_uID, &pObject->m_nLuaID, pObject, typeName); return executeFunctionByHandler(nHandler, 1); } //将一个整数数值压栈做为参数。 int CCLuaEngine::pushIntegerToLuaStack(int data) { //将整数值压入堆栈 lua_pushinteger(m_state, data); //返回参数的数量。 return lua_gettop(m_state); } //将一个浮点数值压栈做为参数。 int CCLuaEngine::pushFloatToLuaStack(int data) { //将数字值压入堆栈 lua_pushnumber(m_state, data); //返回参数的数量。 return lua_gettop(m_state); } //将一个布尔数值压栈做为参数。 int CCLuaEngine::pushBooleanToLuaStack(int data) { //将boolean值压入堆栈 lua_pushboolean(m_state, data); //返回参数的数量。 return lua_gettop(m_state); } //将一个CCObject指针和类型名压栈做为参数。 int CCLuaEngine::pushCCObjectToLuaStack(CCObject* pObject, const char* typeName) { 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 tolua_pushusertype_ccobject(m_state, pObject->m_uID, &pObject->m_nLuaID, pObject, typeName); return lua_gettop(m_state); } // 执行单点触屏事件 int CCLuaEngine::executeTouchEvent(int nHandler, int eventType, CCTouch *pTouch) { CCPoint pt = CCDirector::sharedDirector()->convertToGL(pTouch->getLocationInView()); //将参数压栈后调用函数。 lua_pushinteger(m_state, eventType); lua_pushnumber(m_state, pt.x); lua_pushnumber(m_state, pt.y); return executeFunctionByHandler(nHandler, 3); } //执行多点触屏事件。 int CCLuaEngine::executeTouchesEvent(int nHandler, int eventType, CCSet *pTouches) { //将类型参数压栈后调用函数。 lua_pushinteger(m_state, eventType); //创建一个表 lua_newtable(m_state); //将多个触点信息参数放入表中。 CCDirector* pDirector = CCDirector::sharedDirector(); CCSetIterator it = pTouches->begin(); CCTouch* pTouch; int n = 1; while (it != pTouches->end()) { pTouch = (CCTouch*)*it; CCPoint pt = pDirector->convertToGL(pTouch->getLocationInView()); //将位置x压入堆栈 lua_pushnumber(m_state, pt.x); //将栈顶的数值放入到表中对应索引n的数值中 lua_rawseti(m_state, -2, n++); //将位置x压入堆栈 lua_pushnumber(m_state, pt.y); //将栈顶的数值放入到表中对应索引n的数值中 lua_rawseti(m_state, -2, n++); ++it; } //以表做为第二参数压栈,调用函数。 return executeFunctionByHandler(nHandler, 2); 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 } //通过句柄调用函数,参数二为CCObject指针数据和其类型名称。 int CCLuaEngine::executeSchedule(int nHandler, float dt) { return executeFunctionWithFloatData(nHandler, dt); } // 加入一个多线程加载LUA脚本的实时回调函数,此函数用于ANDROID void CCLuaEngine::addLuaLoader(lua_CFunction func) { if (!func) return; //取得全局表 lua_getglobal(m_state, "package"); //取得全局表中的“loaders”表 lua_getfield(m_state, -1, "loaders"); //将设定的函数和参数压栈 lua_pushcfunction(m_state, func); //将参数压栈 for (int i = lua_objlen(m_state, -2) + 1; i > 2; --i) { //取得原"loaders"表第i-1个参数 lua_rawgeti(m_state, -2, i - 1); //将取出的值放到新"loaders"表中第i个数值 lua_rawseti(m_state, -3, i); } //将函数设为新"loaders"表的第2个参数 lua_rawseti(m_state, -2, 2); //把“loaders” 表放到全局表中 lua_setfield(m_state, -2, "loaders"); //参数出栈,恢复堆栈。 lua_pop(m_state, 1); } //将一个句柄压栈 bool CCLuaEngine::pushFunctionByHandler(int nHandler) { //找出注册函数表的第nHandler个数值 lua_rawgeti(m_state, LUA_REGISTRYINDEX, nHandler); /* stack: ... func */ //判断是否是函数。 if (!lua_isfunction(m_state, -1)) { CCLOG("[LUA ERROR] function refid '%d' does not reference a Lua function", nHandler); lua_pop(m_state, 1); return false; 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 } return true; } 然后我们来看一下CCScriptEngineManager,这个类被称为脚本引擎管理器,其实很简单,只是用来设定当前项目的唯一正在使用的脚本引擎。也许Cocos2d-x打算用它管理多种类型脚本引擎,比如python,js等。 class CC_DLL CCScriptEngineManager { public: //析构 ~CCScriptEngineManager(void); //取得单例指针 CCScriptEngineProtocol* getScriptEngine(void) { return m_pScriptEngine; } //设置使用的LUA管理器 void setScriptEngine(CCScriptEngineProtocol *pScriptEngine); //移除使用的LUA管理器。 void removeScriptEngine(void); //取得单例指针 static CCScriptEngineManager* sharedManager(void); //销毁单例 static void purgeSharedManager(void); private: //构造,单例,你懂的。 CCScriptEngineManager(void) : m_pScriptEngine(NULL) { } //使用的LUA脚本引擎 CCScriptEngineProtocol *m_pScriptEngine; }; 其对应的CPP实现: //全局唯一的 static CCScriptEngineManager* s_pSharedScriptEngineManager = NULL; //析构 CCScriptEngineManager::~CCScriptEngineManager(void) { 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 removeScriptEngine(); } //设置使用的LUA管理器 void CCScriptEngineManager::setScriptEngine(CCScriptEngineProtocol *pScriptEngine) { removeScriptEngine(); m_pScriptEngine = pScriptEngine; m_pScriptEngine->retain(); } //移除使用的LUA管理器。 void CCScriptEngineManager::removeScriptEngine(void) { if (m_pScriptEngine) { m_pScriptEngine->release(); m_pScriptEngine = NULL; } } //取得单例指针 CCScriptEngineManager* CCScriptEngineManager::sharedManager(void) { if (!s_pSharedScriptEngineManager) { s_pSharedScriptEngineManager = new CCScriptEngineManager(); } return s_pSharedScriptEngineManager; } //销毁单例 void CCScriptEngineManager::purgeSharedManager(void) { if (s_pSharedScriptEngineManager) { delete s_pSharedScriptEngineManager; s_pSharedScriptEngineManager = NULL; } } 现在我们来实际操作一下。 打开HelloLua工程中的AppDelegate.cpp: 在AppDelegate::applicationDidFinishLaunching()函数中看这几行代码: //取得LUA脚本引擎 CCScriptEngineProtocol* pEngine = CCLuaEngine::engine(); 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 //设置脚本引擎管理器使用新创建的LUA脚本引擎 CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine); //如果是ANDROID平台,获hello.lua内存到字符串然后执行字符串 #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) CCString* pstrFileContent = CCString::createWithContentsOfFile("hello.lua"); if (pstrFileContent) { pEngine->executeString(pstrFileContent->getCString()); } #else //如果不是ANDROID平台,取得hello.lua文件全路径并执行文件。 std::string path = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath("hello.lua"); pEngine->addSearchPath(path.substr(0, path.find_last_of("/")).c_str()); pEngine->executeScriptFile(path.c_str()); #endif 就这样,hello.lua中的脚本就可以被执行了。 现在我们将HelloLua工程目录拷出一份来,将目录和工程命名为StudyLua,并在程序运行目录中加入相关资源图片。之后我们打开hello.lua: -- 设置内存回收 collectgarbage("setpause", 100) collectgarbage("setstepmul", 5000) -- 取得窗口大小 local winSize = CCDirector:sharedDirector():getWinSize() -- 将Hello背景图加入 local function createLayerHello() local layerHello = CCLayer:create() -- 加入背景图 local bg = CCSprite:create("Hello.png") bg:setPosition(winSize.width / 2 , winSize.height / 2) layerHello:addChild(bg) -- 创建HelloWorld local label = CCLabelTTF:create("Hello Cocos2d-x", "Arial", 50) label:setPosition(winSize.width / 2 ,60) label:setVisible(true) 本文由麦可网http://www.microoh.com/收集整理,转载请注明出处 layerHello:addChild(label) return layerHello end --将关闭按钮菜单加入 local function createExitBtn() local layerMenu = CCLayer:create() --局部函数,用于退出 local function menuCallbackExit() CCDirector:sharedDirector():endToLua() end -- 创建退出按钮 local menuPopupItem = CCMenuItemImage:create("CloseNormal.png", "CloseSelected.png") -- 放在居上角附近 menuPopupItem:setPosition(winSize.width - 50, winSize.height - 50) -- 注册退出函数 menuPopupItem:registerScriptHandler(menuCallbackExit) -- 由菜单按钮项创建菜单 local menuClose = CCMenu:createWithItem(menuPopupItem) menuClose:setPosition(0, 0) menuClose:setVisible(true) -- 将菜单加入层中 layerMenu:addChild(menuClose) return layerMenu end --创建场景 local sceneGame = CCScene:create() --将Hello背景图加入 sceneGame:addChild(createLayerHello()) --将关闭按钮菜单加入 sceneGame:addChild(createExitBtn()) --运行场景 CCDirector:sharedDirector():runWithScene(sceneGame)

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档