在Unity中使用Lua腳本


前言:為什么要用Lua
首先要說,所有編程語言里面,我最喜歡的還是C#,VisualStudio+C#,只能說太舒服了。所以說,為什么非要在unity里面用Lua呢?可能主要是閑的蛋疼。。。。。另外還有一些次要原因:
方便做功能的熱更新;
Lua語言的深度和廣度都不大,易學易用,可以降低項目成本。

C#與Lua互相調用的方案
坦白來將,我並沒有對現在C#與Lua互相調用的所有庫進行一個仔細的調研,大概搜了一下,找到這樣幾個:
slua:https://github.com/pangweiwei/slua
Nlua:http://nlua.org/
UniLua:https://github.com/xebecnan/UniLua
uLua插件
以上這些方案的具體內容,不是本文的重點,這里就不說了,感興趣的同學,點開自己去看就行了。

最后我選用了uLua,主要原因是:uLua方案比較成熟,它並沒有太多自己的代碼,主要是把LuaInterface和Lua解釋器整合了一下,都是比較成熟的代碼,相對會穩定一些。另外,個人很欣賞LuaInterface這個庫。接下來我們就看一下uLua。:)
uLua插件的使用非常簡單,基本上看一下他自帶的幾個例子就明白了。

游戲邏輯粘合層設計
uLua插件解決了語言層面的問題:C#與LUA兩種語言代碼互相調用,以及參數傳遞等相關的一系列底層問題。而我們游戲邏輯開發中,到底如何使用LUA是上層的一個問題。下面給出我摸索的一個方案,個人認為:夠簡單,夠清晰,是很薄很薄的一層,不可能更薄了。

使用幾個LuaState?
曾經看過一個網友的方案,每次運行腳本就new一個LuaState,個人認為這種方案十分不妥。整個游戲的Lua代碼應該運行在一個LuaState之上,原因有二:
運行在同一LuaState的Lua代碼才能互相調用啊。相信一個游戲總會有一定的代碼量的,如果不同的lua文件之中的代碼,完全獨立運行,不能互相調用或者互相調用很麻煩,則游戲邏輯組織平添很多障礙;
混合語言編程中原則之一就是:盡量減少代碼執行的語言環境切換,因為這個的代價往往比代碼字面上看上去要高很多。我的目標是:既然用了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;  
   
/// <summary>  
/// Lua組件 - 它調用的Lua腳本可以實現類似MonoBehaviour派生類的功能  
/// </summary>  
[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  
   
    /// <summary>  
    /// 找到游戲對象上綁定的LUA組件(Module對象)  
    /// </summary>  
    public static LuaTable GetLuaComponent(GameObject go)  
    {  
        LuaComponent luaComp = go.GetComponent<luacomponent>();  
        if (luaComp == null)  
            return null;  
        return luaComp.LuaModule;  
    }  
   
    /// <summary>  
    /// 向一個GameObject添加一個LUA組件  
    /// </summary>  
    public static LuaTable AddLuaComponent(GameObject go, TextAsset luaFile)  
    {  
        LuaComponent luaComp = go.AddComponent<luacomponent>();  
        luaComp.Initilize(luaFile);  // 手動調用腳本運行,以取得LuaTable返回值  
        return luaComp.LuaModule;  
    }  
   
    /// <summary>  
    /// 提供給外部手動執行LUA腳本的接口  
    /// </summary>  
    public void Initilize(TextAsset luaFile)  
    {  
        m_luaScript = luaFile;  
        RunLuaFile(luaFile);  
   
        //-- 取得常用的函數回調  
        if (this.LuaModule != null)  
        {  
            m_luaUpdate = this.LuaModule["Update"] as LuaFunction;  
        }  
    }  
   
    /// <summary>  
    /// 調用Lua虛擬機,執行一個腳本文件  
    /// </summary>  
    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);  
    }  
   
    /// <summary>  
    /// 調用一個Lua組件中的函數  
    /// </summary>  
    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相應的回調函數;

例如:

[AppleScript]  純文本查看 復制代碼
?
1
2
3
4
5
6
7
8
9
require "EngineMain" 
   
local demoComponent = { } 
   
function demoComponent : Awake ( gameObject ) 
     Debug.Log ( gameObject. name .. "Awake" ) 
end 
   
return demoComponent 



LuaComponent回調函數中,主動將GameObject對象作為參數傳遞給Lua層,以方便其進行相應的處理。

Lua組件之間的互相調用(在Lua代碼中)
基於以上結構,就很容易實現Lua組件之間的互相調用。在Demo工程中,有一個“Sphere”對象,綁定了如下腳本:

[AppleScript]  純文本查看 復制代碼
?
01
02
03
04
05
06
07
08
09
10
11
require "EngineMain" 
   
local sphereComponent = { } 
   
sphereComponent. text = "Hello World" 
   
function sphereComponent : Awake ( gameObject ) 
     Debug.Log ( gameObject. name .. "Awake" ) 
end 
   
return sphereComponent 



還有另外一個“Cube”對象,綁定了如下腳本,用來演示調用上面這個Lua組件的成員:

[AppleScript]  純文本查看 復制代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
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 




最后,順帶總結一下:在設計上次游戲邏輯框架時,比較好的思路是:在透徹的理解Unity自身架構的前提下,在其架構下進行下一層設計,而不是想一種新的框架。因為Unity本身就是一個框架。更多內容請參見作者博客:http://blog.csdn.net/neil3d/article/details/44200821


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM