- C# 啟動 Lua,並註冊 C# 函數給 Lua (讓 Lua Script 可以呼叫)
- 從 iOS 載入 Lua Script 檔案並由 Lua 執行
- Lua Script 呼叫 C# 函數
- C# 函數印出訊息
最後在 Xcode 的 Output 可以看到 C# 函數印出的訊息,此外值得注意的是,Unity3D 產生的程式碼在模擬器上無法呼叫 Plugins ,所以實作的步驟需要真正的實機才能測試(因此在開始之前必須做好 iOS Developer 相關的設定),底下是整個實作的流程:
1. 開啟 Unity3D 並建立一個空的專案,在專案路徑下建立 Plugins/iOS 目錄,如下圖所示:
2. 到 http://www.lua.org/ftp/ 下載原始碼 lua-5.2.1.tar.gz。解開壓縮檔之後可以看到原始碼在 src 目錄下,將該目錄下所有檔案(除了 Makefile、lua.c、luac.c 之外)複製到 Plugins/iOS 。
3. 建立一個新的 C# Script,命名為 luaTest,此時 Unity3D 的專案目錄呈現如下:
luaTest 內容分成三大部分,一是和 Lua 介接的部分,二是和 lua 之間建立 callback 的部分,三是 Unity3D 程式的部分分別敘述如下:
luaTest 內容分成三大部分,一是和 Lua 介接的部分,二是和 lua 之間建立 callback 的部分,三是 Unity3D 程式的部分分別敘述如下:
一、和 Lua 介接的部分:
luaL_newstate、luaL_openlibs 和 lua_close 分別為初始化及關閉 Lua 時呼叫使用。
[DllImport ("__Internal")] public static extern IntPtr luaL_newstate(); [DllImport ("__Internal")] public static extern void luaL_openlibs(IntPtr lua_State); [DllImport ("__Internal")] public static extern void lua_close(IntPtr lua_State);
lua_pushcclosure 和 lua_setglobal 為註冊 C# 函數給 Lua 時使用。
[DllImport ("__Internal")] public static extern void lua_pushcclosure(IntPtr lua_State, LuaFunction func, int n); [DllImport ("__Internal")] public static extern void lua_setglobal(IntPtr lua_State, string s);
其中 LuaFunction 定義如下:
public delegate int LuaFunction(IntPtr pLuaState);
lua_pcallk 和 luaL_loadfilex 為載入及執行 Lua Script 檔案時使用。
[DllImport ("__Internal")] public static extern int lua_pcallk(IntPtr lua_State, int nargs, int nresults, int errfunc, int ctx, LuaFunction func); [DllImport ("__Internal")] public static extern int luaL_loadfilex(IntPtr lua_State, string s, string mode);
luaL_checklstring 為 C# 函數被 Lua 呼叫時,取得 Lua 所給的參數時使用。
[DllImport ("__Internal")] public static extern IntPtr luaL_checklstring(IntPtr lua_State, int idx, IntPtr len);
此外,使用 DllImport 時需要引入 System.Runtime.InteropServices 名稱空間:
使用 IntPter 時需要引用 System 名稱空間:
上述 Lua API 在註冊 C# 函數及載入/執行 Lua Script 的部分可寫成 lua_register 及 luaL_dofile 分別如下:
二、和 lua 之間建立 callback 的部分
1. 建立簡單的 Lua Script ( lua.txt ) 檔案,其內容為:
將檔案置於專案的 LuaScript 路徑底下,如圖所示:
2. 在 luaTest 內加入 callback 函數如下:
其中 MonoPInvokeCallbackAttribute 可以預防 mono 在傳遞函數指標時啟動 JIT 的功能(相關參考連結),其定義如下:
luaL_checklstring 負責取得 Lua 在呼叫此函數時給的 "Hello world" 字串指標
三、Unity3D 程式的部分
1. 在 Start 函數加入程式碼:
程式碼初始化 Lua 並註冊函數 TestDisplay 給 Lua,最後則是載入 lua.txt 並執行。其中 GetAppPath 函數定義如下:
2. 在 OnApplicationQuit 函數加入程式碼:
3. 將 luaTest 拖曳至 Main Camera 上,此時在 Inspector 可以看到 luaTest 成為 Camera 的 Component:
編譯時需要稍微做一些設定如下:
1. 在 Build Settings 裡 Switch Platform 至 iOS 之後按下 Build,選擇欲產生的目錄之後 Unity3D 將自動產生 Xcode 專案,開啟後觀察專案底下 Libraries 是否引入 Lua 原始碼:
2. 用滑鼠拖曳 LuaScript 目錄到 Unity-iPhone 上出現訊息如下:
記住此處選擇的是 Create folder references for any added folders。按下 Finish 之後 LuaScript 出現如圖所示:
3. 將裝置接上電腦,編譯的目標選擇 Unity-iPhone > (裝置名稱) 之後按下 Run。
4. 此時觀察 output 視窗的訊息應該顯示如下:
由訊息 "This line was plotted by TestDisplay() : msg =Hello world" 可知 Lua Script 執行成功。範例程式碼可以在 https://github.com/phardera/unity3d_lua_ios 取得。
using System.Runtime.InteropServices;
使用 IntPter 時需要引用 System 名稱空間:
using System;
上述 Lua API 在註冊 C# 函數及載入/執行 Lua Script 的部分可寫成 lua_register 及 luaL_dofile 分別如下:
public static void lua_register(IntPtr pLuaState, string strFuncName, LuaFunction pFunc) { lua_pushcclosure(pLuaState, pFunc, 0); lua_setglobal(pLuaState, strFuncName); } public static int luaL_dofile(IntPtr lua_State, string s) { if (luaL_loadfilex(lua_State, s, null) != 0) return 1; return lua_pcallk(lua_State, 0, -1, 0, 0, null); }
二、和 lua 之間建立 callback 的部分
1. 建立簡單的 Lua Script ( lua.txt ) 檔案,其內容為:
TestDisplay("Hello world");
將檔案置於專案的 LuaScript 路徑底下,如圖所示:
[MonoPInvokeCallbackAttribute (typeof (LuaFunction))] public static int TestDisplay(IntPtr pLuaState) { IntPtr retPtr =luaL_checklstring(pLuaState, 1, IntPtr.Zero); string retStr = Marshal.PtrToStringAnsi(retPtr); Debug.Log("This line was plotted by TestDisplay() : msg ="+retStr); return 0; }
其中 MonoPInvokeCallbackAttribute 可以預防 mono 在傳遞函數指標時啟動 JIT 的功能(相關參考連結),其定義如下:
[System.AttributeUsage(System.AttributeTargets.Method)] public sealed class MonoPInvokeCallbackAttribute : Attribute { private Type type; public MonoPInvokeCallbackAttribute (Type t) { type =t;} }
luaL_checklstring 負責取得 Lua 在呼叫此函數時給的 "Hello world" 字串指標
三、Unity3D 程式的部分
1. 在 Start 函數加入程式碼:
void Start() { m_luaState = luaL_newstate(); luaL_openlibs(m_luaState); lua_register(m_luaState, "TestDisplay", TestDisplay); luaL_dofile(m_luaState, GetAppPath() + "LuaScript/lua.txt"); }
程式碼初始化 Lua 並註冊函數 TestDisplay 給 Lua,最後則是載入 lua.txt 並執行。其中 GetAppPath 函數定義如下:
public static string GetAppPath() { return Application.dataPath.Substring(0, Application.dataPath.Length-4); }m_luaState 定義如下:
public static IntPtr m_luaState = IntPtr.Zero;
2. 在 OnApplicationQuit 函數加入程式碼:
void OnApplicationQuit() { lua_close(m_luaState); }
3. 將 luaTest 拖曳至 Main Camera 上,此時在 Inspector 可以看到 luaTest 成為 Camera 的 Component:
編譯時需要稍微做一些設定如下:
1. 在 Build Settings 裡 Switch Platform 至 iOS 之後按下 Build,選擇欲產生的目錄之後 Unity3D 將自動產生 Xcode 專案,開啟後觀察專案底下 Libraries 是否引入 Lua 原始碼:
好文章! 谢谢您的分享!
回覆刪除