博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Cocos2d-x之LUA脚本引擎深入分析
阅读量:6001 次
发布时间:2019-06-20

本文共 16911 字,大约阅读时间需要 56 分钟。

FROM:http://www.2cto.com/kf/201303/197171.html

原文内容如下:


首先,我们要知道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;        //通过句柄调用函数多种形态。  //通过句柄调用函数,参数二为参数数量。  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"    //使用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实现:

[cpp] //本类的头文件。  #include "CCLuaEngine.h"  //这里用到了tolua++库,tolua++库是一个专门处理LUA脚本的第三方库,可以很好的完成LUA访问C++类及成员函数的功能。如果没有tolua++,这块要处理起来可是麻烦死了。  #include "tolua++.h"    //加入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      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中。  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);        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;          // }          // 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)  {      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)  {      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);  }  //通过句柄调用函数,参数二为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;      }      return true;  }

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

 

[cpp] 

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)  

{  

    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()函数中看这几行代码:

[cpp] 

 //取得LUA脚本引擎  

CCScriptEngineProtocol* pEngine = CCLuaEngine::engine();  

//设置脚本引擎管理器使用新创建的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)  

    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)  

运行一下:

20130322104434724.jpg

转载地址:http://tcbmx.baihongyu.com/

你可能感兴趣的文章
JavaScript基础---函数
查看>>
前端每日实战:120# 视频演示如何用纯 CSS 创作锡纸撕开的文字效果
查看>>
Laravel实用小功能
查看>>
Linux系统上传下载工具rz/sz
查看>>
matplotlib绑定到PyQt5(有菜单)
查看>>
利用Powershell和ceye.io实现Windows账户密码回传
查看>>
如何清理EBS R12 middle-tier cache
查看>>
Windows 8.1 今年 1 月市场份额超 Vista
查看>>
《设计团队协作权威指南》—第1章1.5节总结
查看>>
【PMP认证考试之个人总结】第 5 章 项目时间管理
查看>>
Chair:支付宝前端团队推出的Node.js Web框架
查看>>
port-forward v1.0.1 发布,端口转发工具
查看>>
《Total Commander:万能文件管理器》——第3.8节.后续更新
查看>>
BSD vi/vim 命令大全(下)[转]
查看>>
css3中变形与动画(一)
查看>>
[XMove-自主设计的体感解决方案] 系统综述
查看>>
设计模式 ( 十五 ) 中介者模式Mediator(对象行为型)
查看>>
【LINUX学习】磁盘分割之建立primary和logical 分区
查看>>
【YUM】第三方yum源rpmforge
查看>>
IOS(CGGeometry)几何类方法总结
查看>>