在Unity中使用Lua指令碼:語言層和遊戲邏輯粘合層處理

來源:互聯網
上載者:User

標籤:遊戲   指令碼   unity3d   lua   

前言:為什麼要用Lua首先要說,所有程式設計語言裡面,我最喜歡的還是C#,VisualStudio+C#,只能說太舒服了。所以說,為什麼非要在Unity裡面用Lua呢?可能主要是閑的蛋疼。。。。。另外還有一些次要原因:
  • 方便做功能的熱更新;
  • Lua語言的深度和廣度都不大,易學易用,可以降低項目成本。
C#與Lua互相調用的方案坦白來將,我並沒有對現在C#與Lua互相調用的所有庫進行一個仔細的調研,大概搜了一下,找到這樣幾個:
  1. slua:https://github.com/pangweiwei/slua
  2. Nlua:http://nlua.org/
  3. UniLua:https://github.com/xebecnan/UniLua
  4. uLua外掛程式
以上這些方案的具體內容,不是本文的重點,這裡就不說了,感興趣的同學,點開自己去看就行了。
最後我選用了uLua,主要原因是:uLua方案比較成熟,它並沒有太多自己的代碼,主要是把LuaInterface和Lua解譯器整合了一下,都是比較成熟的代碼,相對會穩定一些。另外,個人很欣賞LuaInterface這個庫。接下來我們就看一下uLua。:)uLua的基本使用uLua外掛程式的使用非常簡單,基本上看一下他內建的幾個例子就明白了。
遊戲邏輯粘合層設計uLua外掛程式解決了語言層面的問題:C#與LUA兩種語言代碼互相調用,以及參數傳遞等相關的一系列底層問題。而我們遊戲邏輯開發中,到底如何使用LUA是上層的一個問題。下面給出我摸索的一個方案,個人認為:夠簡單,夠清晰,是很薄很薄的一層,不可能更薄了。

使用幾個LuaState?曾經看過一個網友的方案,每次運行指令碼就new一個LuaState,個人認為這種方案十分不妥。整個遊戲的Lua代碼應該運行在一個LuaState之上,原因有二:
  1. 運行在同一LuaState的Lua代碼才能互相調用啊。相信一個遊戲總會有一定的代碼量的,如果不同的lua檔案之中的代碼,完全獨立運行,不能互相調用或者互相調用很麻煩,則遊戲邏輯組織平添很多障礙;
  2. 混合語言編程中原則之一就是:盡量減少代碼執行的語言環境切換,因為這個的代價往往比代碼字面上看上去要高很多。我的目標是:既然用了Lua,就盡量把UI事件響應等遊戲上層邏輯放到Lua代碼中編寫。
基於以上原因,我覺得遊戲的Lua代碼全都跑在一個LuaState之上。這也是本文方案的基礎。

實現LuaComponent首先說一下我的目標:
  • 既然C#對於Unity來說是指令碼層了,那麼Lua應該和C#指令碼代碼具有相同的邏輯地位;
  • Lua整合的代碼應該很少,應盡量保持簡單;
基於以上的目標,我實現了LuaComponet類,它的實作類別似MonoBehavior,只不過我們沒有C++原始碼,只能由C#層的MonoBehavior來轉寄一下調用。這樣,我們的Lua代碼的實現方式就是寫和寫一個C#指令碼組件完全一致了,可以說達到了和引擎天衣無縫的整合。:)OK,先上代碼!
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")endreturn 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")endreturn sphereComponent
還有另外一個“Cube”對象,綁定了如下指令碼,用來示範調用上面這個Lua組件的成員:

require "EngineMain"local demoComponent = {}function demoComponent:Awake( gameObject )Debug.Log(gameObject.name.."Awake")endfunction 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)endreturn demoComponent


完整版DEMO:

http://download.csdn.net/detail/neil3d/8583799

http://pan.baidu.com/s/1bnk3ATD 密碼: pqih


最後,順帶總結一下:在設計上次遊戲邏輯架構時,比較好的思路是:在透徹的理解Unity自身架構的前提下,在其架構下進行下一層設計,而不是想一種新的架構。因為Unity本身就是一個架構。

在Unity中使用Lua指令碼:語言層和遊戲邏輯粘合層處理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.