在Unity中使用Lua指令碼:語言層和遊戲邏輯粘合層處理
阿新 • • 發佈:2019-02-11
前言:為什麼要用Lua
首先要說,所有程式語言裡面,我最喜歡的還是C#,VisualStudio+C#,只能說太舒服了。所以說,為什麼非要在Unity裡面用Lua呢?可能主要是閒的蛋疼。。。。。另外還有一些次要原因:- 方便做功能的熱更新;
- Lua語言的深度和廣度都不大,易學易用,可以降低專案成本。
C#與Lua互相呼叫的方案
坦白來將,我並沒有對現在C#與Lua互相呼叫的所有庫進行一個仔細的調研,大概搜了一下,找到這樣幾個: 最後我選用了uLua,主要原因是:uLua方案比較成熟,它並沒有太多自己的程式碼,主要是把LuaInterface和Lua直譯器整合了一下,都是比較成熟的程式碼,相對會穩定一些。另外,個人很欣賞LuaInterface這個庫。接下來我們就看一下uLua。:)uLua的基本使用
遊戲邏輯粘合層設計
uLua外掛解決了語言層面的問題:C#與LUA兩種語言程式碼互相呼叫,以及引數傳遞等相關的一系列底層問題。而我們遊戲邏輯開發中,到底如何使用LUA是上層的一個問題。下面給出我摸索的一個方案,個人認為:夠簡單,夠清晰,是很薄很薄的一層,不可能更薄了。使用幾個LuaState?
曾經看過一個網友的方案,每次執行指令碼就new一個LuaState,個人認為這種方案十分不妥。整個遊戲的Lua程式碼應該執行在一個LuaState之上,原因有二:- 執行在同一LuaState的Lua程式碼才能互相呼叫啊。相信一個遊戲總會有一定的程式碼量的,如果不同的lua檔案之中的程式碼,完全獨立執行,不能互相呼叫或者互相呼叫很麻煩,則遊戲邏輯組織平添很多障礙;
- 混合語言程式設計中原則之一就是:儘量減少程式碼執行的語言環境切換,因為這個的代價往往比程式碼字面上看上去要高很多。我的目標是:既然用了Lua,就儘量把UI事件響應等遊戲上層邏輯放到Lua程式碼中編寫。
實現LuaComponent
首先說一下我的目標:- 既然C#對於Unity來說是指令碼層了,那麼Lua應該和C#指令碼程式碼具有相同的邏輯地位;
- Lua整合的程式碼應該很少,應儘量保持簡單;
using UnityEngine; using System.Collections; using LuaInterface; /// /// Lua元件 - 它呼叫的Lua指令碼可以實現類似MonoBehaviour派生類的功能 /// [AddComponentMenu("Lua/LuaComponent")] public class LuaComponent : MonoBehaviour { private static LuaState s_luaState; // 全域性的Lua虛擬機器 [Tooltip("繫結的LUA指令碼路徑")] public TextAsset m_luaScript; public LuaTable LuaModule { get; private set; } LuaFunction m_luaUpdate; // Lua實現的Update函式,可能為null /// /// 找到遊戲物件上繫結的LUA元件(Module物件) /// public static LuaTable GetLuaComponent(GameObject go) { LuaComponent luaComp = go.GetComponent(); if (luaComp == null) return null; return luaComp.LuaModule; } /// /// 向一個GameObject新增一個LUA元件 /// public static LuaTable AddLuaComponent(GameObject go, TextAsset luaFile) { LuaComponent luaComp = go.AddComponent(); luaComp.Initilize(luaFile); // 手動呼叫指令碼執行,以取得LuaTable返回值 return luaComp.LuaModule; } /// /// 提供給外部手動執行LUA指令碼的介面 /// public void Initilize(TextAsset luaFile) { m_luaScript = luaFile; RunLuaFile(luaFile); //-- 取得常用的函式回撥 if (this.LuaModule != null) { m_luaUpdate = this.LuaModule["Update"] as LuaFunction; } } /// /// 呼叫Lua虛擬機器,執行一個指令碼檔案 /// void RunLuaFile(TextAsset luaFile) { if (luaFile == null || string.IsNullOrEmpty(luaFile.text)) return; if (s_luaState == null) s_luaState = new LuaState(); object[] luaRet = s_luaState.DoString(luaFile.text, luaFile.name, null); if (luaRet != null && luaRet.Length >= 1) { // 約定:第一個返回的Table物件作為Lua模組 this.LuaModule = luaRet[0] as LuaTable; } else { Debug.LogError("Lua指令碼沒有返回Table物件:" + luaFile.name); } } // MonoBehaviour callback void Awake() { RunLuaFile(m_luaScript); CallLuaFunction("Awake", this.LuaModule, this.gameObject); } // MonoBehaviour callback void Start() { CallLuaFunction("Start", this.LuaModule, this.gameObject); } // MonoBehaviour callback void Update() { if (m_luaUpdate != null) m_luaUpdate.Call(this.LuaModule, this.gameObject); } /// /// 呼叫一個Lua元件中的函式 /// void CallLuaFunction(string funcName, params object[] args) { if (this.LuaModule == null) return; LuaFunction func = this.LuaModule[funcName] as LuaFunction; if (func != null) func.Call(args); } }
這段程式碼非常簡單,實現以下幾個功能點:
- 管理一個全域性的LuaState;
- 負責將MonoBehavior的呼叫轉發到相應的LUA函式;
- 提供了GetComponent()、AddComponent()對應的LUA指令碼版本介面;這點非常重要。
LUA程式碼約定
為了很好的和LuaComponent協作,Lua指令碼需要遵循一些約定:- LUA指令碼應該返回一個Table,可以是LUA的Module,也可以是任何的Table物件;
- 返回的Table物件應該含有MonoBehaviour相應的回撥函式;
require "EngineMain"
local demoComponent = {}
function demoComponent:Awake( gameObject )
Debug.Log(gameObject.name.."Awake")
end
return demoComponent
LuaComponent回撥函式中,主動將GameObject物件作為引數傳遞給Lua層,以方便其進行相應的處理。
Lua元件之間的互相呼叫(在Lua程式碼中)
基於以上結構,就很容易實現Lua元件之間的互相呼叫。在Demo工程中,有一個“Sphere”物件,綁定了如下指令碼:
require "EngineMain"
local sphereComponent = {}
sphereComponent.text = "Hello World"
function sphereComponent:Awake( gameObject )
Debug.Log(gameObject.name.."Awake")
end
return sphereComponent
還有另外一個“Cube”物件,綁定了如下指令碼,用來演示呼叫上面這個Lua元件的成員:
require "EngineMain"
local demoComponent = {}
function demoComponent:Awake( gameObject )
Debug.Log(gameObject.name.."Awake")
end
function demoComponent:Start( gameObject )
Debug.Log(gameObject.name.."Start")
--演示LuaComponent程式碼互相呼叫
local sphereGO = GameObject.Find("Sphere")
local sphereLuaComp = LuaComponent.GetLuaComponent(sphereGO)
Debug.log("Sphere.LuaDemoB:"..sphereLuaComp.text)
end
return demoComponent
完整版DEMO下載地址: 最後,順帶總結一下:在設計上次遊戲邏輯框架時,比較好的思路是:在透徹的理解Unity自身架構的前提下,在其架構下進行下一層設計,而不是想一種新的框架。因為Unity本身就是一個框架。