cocos2d-x 和 quick-cocos2d-x 的底層代碼都是使用 C++ 語言開發的。爲了使用 Lua 腳本語言進行開發,咱們利用 tolua++ 工具,將大量的 C/C++ API 導出到了 Lua 中。javascript
使用 tolua++ 的基本步驟:html
- 從 C/C++ 源代碼複製頭文件的內容到 .tolua(tolua++ 文檔中稱爲 .pkg)文件中。
- 修改 .tolua 文件內容,去掉 tolua++ 沒法識別的內容,以及不須要導出到 Lua 的定義。
- 運行 tolua++ 工具,根據 .tolua 文件生成 luabinding 接口文件(由 .cpp 文件和 .h 文件自稱)。
- 在 AppDelegate.cpp 中加載 luabinding 文件。
- 在 AppDelegate 初始化 Lua 虛擬機後,調用 luabinding 接口文件中的 luaopen 函數,註冊 C/C++ API。
根據實踐,咱們建議採用以下的方案來完成整個導出工做。java
從 C/C++ 源文件建立 .tolua 文件
假設咱們的 MyClass.h 頭文件內容以下:node
#ifndef __MY_CLASS_H_ #define __MY_CLASS_H_ class MyClass { public: static void addTwoNumber(float number1, float number2); private: MyClass(void) {} }; #endif // __MY_CLASS_H_
爲了便於維護,應該將 .h 文件對應的 tolua 命名爲 XXX_luabinding.tolua。這樣生成的 luabinding 接口文件名就是 XXX_luabinding.cpp 和 XXX_luabinding.h,不會和已有的 C/C++ 源文件衝突。python
建立 MyClass_luabinding.tolua 文件,並修改內容爲:nginx
class MyClass : public CCObject
{
public:
static void addTwoNumber(float number1, float number2);
};
這裏能夠看到 .tolua 的內容有明顯簡化。詳細的內容修改規則,會在本文後續部分說明。c++
生成 luabinding 接口文件
quick 爲了簡化這一步工做,提供了相應工具,咱們只須要建立一個腳本文件來調用工具便可。shell
-
建立 build_luabinding.sh 文件,內容以下:ruby
#!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "$DIR" OUTPUT_DIR="$DIR" MAKE_LUABINDING="$QUICK_COCOS2DX_ROOT"/bin/compile_luabinding.sh $MAKE_LUABINDING -E MyClass -d "$OUTPUT_DIR" MyClass_luabinding.tolua
記得在命令行中 chmod 755 build_luabinding.sh,不然沒法執行該腳本。bash
Windows 版的批處理內容以下:
@echo off set DIR=%~dp0 set OUTPUT_DIR=%DIR% set MAKE_LUABINDING="%QUICK_COCOS2DX_ROOT%\bin\compile_luabinding.bat" pushd cd /d "%DIR%" call %MAKE_LUABINDING% -E MyClass -d %OUTPUT_DIR% MyClass_luabinding.tolua
注意:運行腳本前請確保已經下載了 quick-cocos2d-x,而且正確設置了 QUICK_COCOS2DX_ROOT 環境變量。環境配置請參考《入門指引》。
-
在命令行下運行咱們建立的腳本,若是一切順利,咱們會看到以下輸出信息:
creating file: MyClass_luabinding.cpp creating file: MyClass_luabinding.h // add to AppDelegate.cpp #include "MyClass_luabinding.h" // add to AppDelegate::applicationDidFinishLaunching() CCLuaStack* stack = CCScriptEngineManager::sharedManager() ->getScriptEngine() ->getLuaStack(); lua_State* L = stack->getLuaState(); luaopen_MyClass_luabinding(L);
載入 luabinding 接口文件
打開咱們的項目,將 MyClass_luabinding.cpp 和 MyClass_luabinding.h 文件加入工程。而後修改 AppDelegate.cpp 文件:
-
在 AppDelegate.cpp 頭部區域添加:
#include "MyClass_luabinding.h"
-
在 AppDelegate::applicationDidFinishLaunching() 函數內添加:
luaopen_MyClass_luabinding(L);
注意這一行代碼應該添加在其餘 luaopen 函數後面,例如:
// register lua engine CCLuaEngine *pEngine = CCLuaEngine::defaultEngine(); CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine); CCLuaStack *pStack = pEngine->getLuaStack(); lua_State* L = pStack->getLuaState(); // load lua extensions luaopen_lua_extensions(L); // load cocos2dx_extra luabinding luaopen_cocos2dx_extra_luabinding(L); // thrid_party luaopen_third_party_luabinding(L); // CCBReader tolua_extensions_ccb_open(L); // MyClass luaopen_MyClass_luabinding(L);
應該將咱們的代碼追加到 tolua_extensions_ccb_open() 後面。
通過上述修改後,從新編譯運行項目應該就能夠在 Lua 腳本中使用咱們導出的 MyClass 對象極其方法了。
.tolua 文件內容的修改規則
前面的 MyClass 是一個很是簡單的例子,但咱們實際遊戲中的 C/C++ API 可能比較複雜。在修改 .tolua 文件內容時,應該仔細閱讀如下內容。
刪除全部無需在 Lua 中使用的內容
導出的 API 越多,在 Lua 虛擬機中佔用的符號表空間就越多。所以咱們第一步要作的就是刪除全部無需在 Lua 中使用的內容。
-
對於 enum、宏定義,若是須要導出,原文保留便可。但宏定義只能導出數值定義,例如:
#define kCCHTTPRequestMethodGET 0 #define kCCHTTPRequestMethodPOST 1
而非數值的宏定義沒法導出,如下內容會導出失敗:
#define kMyConstantString "HELLO"
-
刪除全部沒法識別的宏,例如 CC_DLL。
-
刪除 C++ class 中全部非 public 的定義。
-
刪除 C++ class 中的類成員變量。
-
刪除 inline 關鍵詞,以及 inline function 的實現,只保留聲明。
處理 CCObject 繼承類
CCObject 及其繼承類都具有「引用計數」和「自動釋放」機制。若是你的 C++ 對象是從 CCObject 繼承的,那麼必須告訴 tolua++ 作相應處理,不然可能出現內存泄漏等問題。
對於 quick,只須要在 build 腳本中經過 -E CCOBJECTS 參數指定這些 class 的名字便可。
例如前面 MyClass 的示例中,用 -E CCOBJECTS=MyClass 告訴 tolua++ 應該將 MyClass 看成 CCObject 的繼承類進行處理。
若是有多個類,那麼每一個類名之間用「,」分隔便可,例如:
$MAKE_LUABINDING -E CCOBJECTS=MyClass,MyClass2,MyClass3 -d "$OUTPUT_DIR" MyClass_luabinding.tolua
展開宏
有些宏是不能直接刪除的,例如 CC_PROPERTY。對於這類宏,須要根據宏定義,將宏展開爲聲明。
CC_PROPERTY(float, m_fDuration, Duration)
展開爲:
float getDuration(); void setDuration(float v);
須要如此處理的宏包括:CC_PROPERTY_READONLY, CC_PROPERTY, CC_PROPERTY_PASS_BY_REF, CC_SYNTHESIZE_READONLY, CC_SYNTHESIZE_READONLY_PASS_BY_REF, CC_SYNTHESIZE, CC_SYNTHESIZE_PASS_BY_REF, CC_SYNTHESIZE_RETAIN。
幸運的是這些宏大多隻用在 cocos2d-x 基礎代碼裏,咱們本身的 C++ class 仍是不要用這些宏了。
處理名字空間
若是使用了名字空間,那麼在 .tolua 的頭部應該加入:
$using namespace myname;
這裏用到的「$」符號,後續內容會原樣放入 luabinding 文件。
添加必要的 #include 指令
若是生成的 luabinding 接口文件沒法編譯,須要檢查是不是須要 include 相應的頭文件,並添加以下代碼:
$#include "MyClass.h"
修改函數參數和返回值類型,去除 const 修飾符
一些函數的參數或返回值,使用了 const 修飾符。因爲 tolua++ 的限制,並不能很好的處理這類定義,因此咱們要從 .tolua 文件中移除 const 修飾符。惟一例外的就是 const char* 不須要修改成 char*。
例如:
CCPoint convertToNodeSpace(const CCPoint& worldPoint);
應該修改成:
CCPoint convertToNodeSpace(CCPoint& worldPoint);
這樣修改的緣由是 tolua++ 把 const CCPoint 和 CCPoint 當作兩個不一樣的類型來處理。若是不作修改,那麼調用函數時會報告參數類型不符。
從 C/C++ 函數返回多個值
若是一個函數的全部參數都是引用或指針類型,而且不是 const char*,那麼在 luabinding 接口文件中,該函數會返回多個值。
例如:
void getPosition(float* x = 0, float* y = 0);
在 Lua 中調用這個函數,會獲得兩個返回值:
local x, y = node:getPosition()
將 Lua 函數傳入 C/C++
quick 裏,容許將 Lua 函數傳入 C/C++,只要求 C/C++ 函數中使用 int 作參數類型。但在 .tolua 文件裏,則必須使用 LUA_FUNCTION 作參數類型。
例如:
static CCHTTPRequest* createWithUrlLua(int listener, const char* url, int method = kCCHTTPRequestMethodGET);
listener 參數用於保存傳入的 Lua 函數,因此 .tolua 文件裏要改寫爲:
static CCHTTPRequest* createWithUrlLua(LUA_FUNCTION listener, const char* url, int method = kCCHTTPRequestMethodGET);
具體用法請參考 lib/cocos2dx_extra/extra/network/CCHTTPRequest 中的 createWithUrlLua() 方法。
在 Lua 和 C/C++ 間交換二進制數據
要從 C/C++ 返回二進制數據給 Lua,函數返回值類型必須是 int,而 .tolua 文件中修改返回值爲 LUA_STRING。函數中,須要用 CCLuaStack::pushString() 將二進制數據放入 Lua stack。而後返回「須要傳遞給 Lua 的值」的數量。
具體用法請參考 lib/cocos2dx_extra/extra/network/CCHTTPRequest 中的 getResponseDataLua() 方法。
從 Lua 傳遞二進制數據給 C/C++ 很簡單,使用 const char* 參數類型和 int 類型參數分別指定二進制數據的指針和數據長度。
具體用法請參考 lib/cocos2dx_extra/extra/crypto/CCCrypto 中的 decryptXXTEALua() 方法。
更多用法
關於利用 tolua++ 的更多用法,建議參考 lib/cocos2dx_extra 中的 CCCrypto、CCNative、CCHTTPRquest 等 class。這些 class 對 Lua 提供了良好的支持,具體用法上也覆蓋了絕大多數 C/C++ 和 Lua 交互的需求。
- END -