<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      玩轉cocos2d-x lua-binding, 實現c++與lua混合編程

      引言

      城市精靈GO(http://csjl.teamtop3.com/)是一款基于cocos2d-x開發的LBS社交游戲, 通過真實地圖的探索, 發現和抓捕隱匿于身邊的野生精靈, 利用游戲中豐富的玩法提升和進化自己的精靈團隊, 一步一步成為精靈訓練大師. 

       

       

      本游戲的開發混合使用了c++和lua編程, 既發揮了c++高性能, 跨平臺系統兼容的優勢, 又享受了lua敏捷方便的開發效率. cocos2d-x提供了一套完備的lua-binding工具來幫助開發者實現c++和lua的代碼聯合, 可以方便實現兩者之間的數據通信和代碼互調. 本文就從lua-binding入手, 深入介紹c++和lua混合編程的相關細節, 探討其中可能存在的問題和發展. 本文原創發布于博客園(http://www.rzrgm.cn/dabaopku/p/5649294.html), 獨家授權XX轉載.

      本游戲開發基于cocos2d-x 3.2.0版本, 其他版本用戶請祥閱官方文檔和源代碼.

      原理介紹

      lua(https://www.lua.org/)作為一種輕量級的腳本語言, 以其簡單的語法結構, 方便的c++集成能力, 高效的執行效率收到廣大游戲開發者的熱愛, 也是cocos2d-x官方首次引入的腳本語言.

      作為一種腳本語言, lua是在一個運行時環境(State)里執行的, 這個運行時環境保存了腳本運行所需的內存空間, 創建的全局變量, 加載的庫文件等. 在這個運行時環境里還有一個棧空間(Stack), 其作用就是在lua和c語言進行數據傳遞和函數調用. lua原生實現了很多c api對棧空間進行操作, 讓開發者能夠方便地實現lua腳本代碼與c編譯代碼的雙向通信.

      本文的主題是lua和c++混合編程, 但背后其實是lua和c api的互相調用, 所有c++的功能都要通過一層c函數的包裝, 這點是要牢記在心的, 這也正是lua-binding的核心.

      cocos2d-x提供的lua-bingding工具使用libclang分析c++源碼, 提取語法樹, 將c++的類成員函數封裝為c函數, 然后根據參數類型自動調用lua c api, 實現對棧空間的操作, 將c++的數據傳遞給lua. lua腳本加載編譯好的c++庫, 就可以自由調用c++里面的類對象和成員函數了; c++的代碼則可以直接使用lua c api, 執行一段lua腳本, 并通過棧空間獲取返回結果.

      操作實戰

      本節我們來詳細介紹c++和lua混合編程的具體實現方法, 首先介紹如果利用cocos2d-x的工具自動把項目里的c++代碼導出為lua模塊, 然后介紹如果手動導出特殊類型的函數, 最后介紹實踐中的技巧和潛在隱患.

      自動生成lua模塊

      cocos2d-x提供的lua-binding工具位于項目 tools/tolua 目錄下, 可以看到里面有 genbindings.py, **.ini, userconf.ini等文件, 這些就是自定義代碼導出策略的配置文件.

      • userconf.ini: 這個文件配置了系統運行的環境變量, 比如adk路徑, 使用系統默認值即可
      • genbindings.py: 這是生成lua-binding代碼的腳本, 代碼最后的cmd_args參數配置了需要導出的不同lua模塊, 根據需要在這里添加條目即可. 括號里的第一個參數代表了模塊名(下文介紹), 第二個參數代表了生成文件的名字; 如果想要自定義生成目錄, 可以修改output_dir變量
      • **.ini: 這里是導出配置的關鍵, 我們以cocos2dx.ini為例, 詳細介紹每一個部分的作用
        • [cocos2d-x] 這里對應上文介紹的模塊名, 要和第一個參數保持一致
        • prefix 給所有生成的c函數添加前綴, 防止命名重復
        • target_namespace 導出的lua模塊的命名空間, 重要
        • 接下來是一些編譯參數, 用于輔助libclang尋找頭文件, 設置宏參數, 如果發現clang出錯, 可以考慮修改這里的參數
        • headers 這個參數可以設置一組頭文件, 程序根據這個頭文件及其包含的頭文件, 抽取出c++聲明的類, 作為導出對象, 重要
        • classes 這個參數配置基于正則表達式的模式列表, 過濾上一個參數提取出的類, 得到最終導出的類列表, 重要
        • skip 不想導出的類級函數, 用于處理一些和lua不兼容的c++參數, 比如 std::function, std::pair 等, 這些參數沒法通過棧空間傳遞給lua, 因此也就沒辦法導出給lua使用, 需要排除掉. 重要
        • rename_functions, rename_classed 重命名導出的函數和類, 用處不大
        • classes_have_no_parents, base_classes_to_skip, abstract_classes 一些小功能, 用處不大

      設置完這個文件, 運行 genbindings.py 文件, 就可以看到生成的c++代碼了. 把這個代碼加入到項目中, 就可以在lua腳本里直接使用c++的相關功能了.

      比如, c++里有這個一個類:

      class GuideManager
      {
      public:
          static GuideManager *getInstance();
          
      public:
          bool isAvailable(const std::string &id);
          void addGuide(const std::string &id);
          void finishGuide(const std::string &id);
          bool isGuideFinished(const std::string &id);
          void clear();
      protected:
          std::map<std::string, bool> _finishedGuides;
          std::mutex _lock;
      };
      

      通過lua-binding得到導出類, 就可以在lua代碼里直接使用:

      local manager = pp.GuideManager:getInstance()
      if manager:isAvailable("12345") then
          -- Show Guide
          manager:finishGuide("12345")
      end
      

      通過上述簡單的配置, 就可以把項目里上百個類, 幾千個成員函數直接導出, 提供給lua使用. 在生成c++文件的同時, 程序還會生成一系列沒有實際功能的lua文件, 每一個文件對應一個導出類, 列出了這個類導出的所有函數以及參數類型, 方便開發者驗證導出的方法是否滿足預期, 同時可以交給第三方插件來輔助IDE進行代碼高亮與提示.

      如果開發者更新了c++代碼, 只需要重新運行腳本, 更新導出文件即可.

      以上操作就是cocos2d-x推薦給開發者使用的lua-binding方案, 可以在官方網站和網絡上找到豐富的教程, 這里不再深入展開.

      手動導出lua模塊

      在實際應用中, 手動導出一個功能模塊也是很重要的需求, 比如在c++里面實現了一個網絡庫, 通過傳遞一個std::function作為回調函數, 函數原型如下:

      void get(const std::string &path, Json::Value &params, std::function<void(NetworkResponse *)> callback);

      但是lua里面的函數和c++的std::function并不兼容, 不能直接把lua的函數傳遞給c++使用, 因此lua-binding工具就不能自動生成代碼綁定了. 開發者需要手動實現參數的傳遞, 把lua函數轉換為c++的std::function.

      為了克服這個困難, 我們先來看一下lua-binding是怎樣自動生成代碼的:

      int lua_pocketpet_PetModel_getSkillById(lua_State* tolua_S)
      {
      // 1
          int argc = 0;
          PocketPet::PetModel* cobj = nullptr;
          bool ok  = true;
      
          cobj = (PocketPet::PetModel*)tolua_tousertype(tolua_S,1,0);

      // 2 argc = lua_gettop(tolua_S)-1; if (argc == 1) { std::string arg0; ok &= luaval_to_std_string(tolua_S, 2,&arg0); if(!ok) return 0;
      // 3 PocketPet::SkillModel* ret = cobj->getSkillById(arg0); object_to_luaval<PocketPet::SkillModel>(tolua_S, "pp.SkillModel",(PocketPet::SkillModel*)ret); return 1; }
      return 0; }

      我們可以看出, lua調用c++代碼一共包含3步:

      1. 獲取c++對象
      2. 獲取參數, 校驗參數類型
      3. 調用成員函數

      自動生成的代碼支持int, double等數值類型, 指針類型, std::string, std::map, std::vector, cocos2d::Map, cocos2d::Vector等模板類型, 超出這些范圍的, 就需要我們自己實現了. 參考上述代碼, 我們可以先實現以下這個函數:

      int lua_pocketpet_NetworkManager_getInLua(lua_State* tolua_S)
      {
          int argc = 0;
          PocketPet::NetworkManager* cobj = nullptr;
          bool ok  = true;
          
          cobj = (PocketPet::NetworkManager*)tolua_tousertype(tolua_S,1,0);
          
          argc = lua_gettop(tolua_S)-1;
          if (argc == 3)
          {
              std::string arg0;
              std::string arg1;
              LUA_FUNCTION arg2;
              
              ok &= luaval_to_std_string(tolua_S, 2,&arg0);
              
              ok &= luaval_to_std_string(tolua_S, 3,&arg1);
              
      // 1 arg2 = toluafix_ref_function(tolua_S, 4, 0); if(!ok) return 0; cobj->get(arg0, arg1, arg2); return 0; } return 0; }

      在這里, 我們首先通過 toluafix_ref_function 獲得一個LUA_FUNCTION(也就是 int)類型的lua函數指針, 將這個值作為參數傳遞給業務函數. 在業務函數里, 通過棧空間來回調這個函數指針, 如下所示:

      void NetworkManager::get(const std::string &path, const std::string &params, int callback)
      {
          auto func = [callback](NetworkResponse *response){
              auto engine = LuaManager::getInstance()->engine();
              engine->getLuaStack()->pushObject(response, "pp.NetworkResponse");
      // 1 engine->getLuaStack()->executeFunctionByHandler(callback, 1); }; Json::Value json; this->get(path, json, func); }

      我們創建了一個std::function對象func作為lua函數的封裝, 在func內部, 通過lua棧空間調用lua回調函數. 通過這兩層的封裝, 就實現了把lua的函數作為c++的回調函數進行使用.

      對于其他的特殊類型, 也都可以用類似的手段來解決.

      其他技巧與潛在風險

      通過lua-binding方案, 可以方便的把c++開發的功能導入到lua里面進行使用, 可以方便團隊從c++向lua轉型, 提高產品后期快速迭代更新的速度. 雖然現在鼓勵腳本開發, 但c++的應用無可避免, 比如渠道sdk, 比如跨平臺適配, 比如賬號安全維護等等, 都還是需要c++這把瑞士軍刀來應對一切挑戰. 在c++和lua之間, 除了通過棧空間傳遞數據, 我們還可以有多種機制來進行通信, 從而克服lua-binding的局限.

      一種方案是開辟一塊專門的內存空間, 通過鍵值對存儲臨時對象, 雙方通過這塊共享空間傳遞必要信息. 這種方式可以靈活的傳遞復雜的數據, 同時可以應對異步調用c++的問題, 防止cocos2d-x對象因為跨幀被autorelease.

      另一種方案可以使用消息分發, 通過數據對象的序列化與反序列化, 實現復雜數據的傳遞, 比如json對象, 但需要評估實現的性能損耗.

      在使用lua-binding時, 還需要考慮線程執行的問題,如果涉及到多層回調以及ui刷新, 要確保內容的更新在主線程完成.

      另外, 在c++中調用lua腳本會創建一個新的運行時環境, 不同運行時環境之間的數據是相互獨立的, 要格外留意腳本文件相關初始化工作是否正確執行.

      總結

      本文詳細介紹了使用cocos2d-x工具, 實現c++和lua混合編程的基本原理和實現方案, 希望對大家幫助.

       

      posted on 2016-07-07 16:47  大寶pku  閱讀(14597)  評論(0)    收藏  舉報

      導航

      主站蜘蛛池模板: 精品熟女少妇免费久久| 潮喷失禁大喷水无码| 樱桃熟了a级毛片| 开心五月婷婷综合网站| 人妻日韩精品中文字幕| 熟妇人妻不卡中文字幕| 国产精品99中文字幕| 丰满熟妇乱又伦在线无码视频| 亚洲岛国成人免费av| 国产精品久久欧美久久一区| 国产玖玖玖玖精品电影| 99久re热视频这里只有精品6| 扒开双腿疯狂进出爽爽爽| 精品一日韩美女性夜视频| 国产原创自拍三级在线观看| 婷婷色综合成人成人网小说 | 美女胸18大禁视频网站| 好吊妞| 人妻蜜臀久久av不卡| 草裙社区精品视频播放| 人妻一区二区三区人妻黄色| 国产精品一码在线播放| 东莞市| 国产99在线 | 免费| 久久精品A一国产成人免费网站| 国产精品中文字幕久久| 国产熟女一区二区三区蜜臀| 香蕉乱码成人久久天堂爱| 在线 国产 欧美 专区| 久久96热在精品国产高清| 97人妻免费碰视频碰免| 国产专区一va亚洲v天堂| 国产成人精品亚洲精品日日| 国产l精品国产亚洲区| 精品人人妻人人澡人人爽人人| 国产精品自拍一二三四区| 在线a久青草视频在线观看| 国产免费午夜福利在线播放 | 国产一级区二级区三级区| 国产亚洲精品AA片在线播放天| 亚洲日韩av无码中文字幕美国|