Unity3D開發入門教程(四)——用Lua實現元件

五邑隱俠發表於2022-01-13

五邑隱俠,本名關健昌,12年遊戲生涯。 本教程以 Unity 3D + VS Code + C# + tolua 為例。

 

一、Lua元件基類

1、在 Assets/Lua 目錄下新建com目錄用於存放Lua元件

2、在Assets/Lua/com 目錄新建Component.lua檔案,新增Lua元件基類Component,實現Unity3D元件的生命週期

Assets/Lua/com/Component.lua

 1 ---@class Component @Component class
 2 local Component = {}
 3 
 4 --- Awake
 5 function Component:Awake()
 6 end
 7 
 8 --- OnEnable
 9 function Component:OnEnable()
10 end
11 
12 --- Start
13 function Component:Start()
14 end
15 
16 --- Update
17 function Component:Update()
18 end
19 
20 --- FixedUpdate
21 function Component:FixedUpdate()
22 end
23 
24 --- LateUpdate
25 function Component:LateUpdate()
26 end
27 
28 --- OnGUI
29 function Component:OnGUI()
30 end
31 
32 --- OnDisable
33 function Component:OnDisable()
34 end
35 
36 --- OnDestroy
37 function Component:OnDestroy()
38 end
39 
40 --- ExtendComponent
41 ---@return Component
42 function ExtendComponent()
43     return CreateComponent(Component)
44 end
45 
46 --- CreateComponent
47 ---@param componentClass Component
48 ---@return Component
49 function CreateComponent(componentClass)
50     local o = {}
51     
52     for k, v in pairs(componentClass) do
53         o[k] = v
54     end
55 
56     return o
57 end
58 
59 local com = {
60     ExtendComponent = ExtendComponent,
61     CreateComponent = CreateComponent,
62 }
63 
64 return com

3、基類 Component 只是實現了空的生命週期方法,子類只需要實現所需的生命週期方法,子類沒有實現的生命週期方法會有預設的空實現。

4、由於通過C#呼叫Lua的方法時,以元表方式繼承的方法會報空。這裡通過拷貝key方式繼承基類,呼叫com.ExtendComponent()方法返回一個繼承Component的子類table。

5、子類通過 com.CreateComponent 方法建立物件。

6、下面給出一個簡單的樣例元件 TestComponent

Assets/Lua/com/TestComponent.lua
 1 local com = require("Assets.Lua.com.Component")
 2 
 3 ---@class TestComponent @TestComponent class
 4 TestComponent = com.ExtendComponent()
 5 
 6 function TestComponent.new(paramList)
 7     local o = com.CreateComponent(TestComponent)
 8 
 9     -- member fields
10     o.num = paramList[0] -- Array by C#, index begin from 0
11     return o
12 end
13 
14 function TestComponent:Awake()
15     print(self.num)
16     print("TestComponent:Awake")
17 end
18 
19 function TestComponent:Start()
20     print("TestComponent:Start")
21 end
22 
23 function TestComponent:OnDestroy()
24     print("TestComponent:OnDestroy")
25 end

注意:

1) 這裡 TestComponent 是全域性的變數,因為C#直接訪問的是全域性變數,區域性變數無法直接訪問。

2)C#傳過來的陣列引數 paramList,下標從0開始

 

二、通用C#元件指令碼

 1 using UnityEngine;
 2 using LuaInterface;
 3 
 4 public class LuaComponent : MonoBehaviour
 5 {
 6     public string luaClassName = "";
 7     public string[] paramList = null;
 8 
 9     private LuaState luaState = null;
10     private LuaTable luaObj = null;
11 
12     void Awake()
13     {
14         LuaClient luaClient = LuaClient.Instance;
15         this.luaState = luaClient.GetLooper().luaState;
16         this.luaState.DoFile(this.luaClassName + ".lua");
17         this.luaObj = callLuaNew();
18 
19         callLuaFunc("Awake");
20     }
21 
22     void OnEnable()
23     {
24         callLuaFunc("OnEnable");
25     }
26 
27     // Start is called before the first frame update
28     void Start()
29     {
30         callLuaFunc("Start");
31     }
32 
33     // Update is called once per frame
34     void Update()
35     {
36         callLuaFunc("Update");
37     }
38 
39     void FixedUpdate()
40     {
41         callLuaFunc("FixedUpdate");
42     }
43 
44     void LateUpdate()
45     {
46         callLuaFunc("LateUpdate");
47     }
48 
49     void OnGUI()
50     {
51         callLuaFunc("OnGUI");
52     }
53 
54     void OnDisable()
55     {
56         if (LuaClient.Instance != null)
57         {
58             callLuaFunc("OnDisable");
59         }
60     }
61 
62     void OnDestroy()
63     {
64         if (LuaClient.Instance != null)
65         {
66             callLuaFunc("OnDestroy");
67         }
68 
69         this.luaState = null;
70         this.luaObj = null;
71     }
72 
73     public LuaTable callLuaNew()
74     {
75         LuaFunction luaFunc = luaState.GetFunction(this.luaClassName + "." + "new");
76         luaFunc.BeginPCall();
77         luaFunc.Push(this.paramList);
78         luaFunc.PCall();
79         LuaTable table = luaFunc.CheckLuaTable();
80         luaFunc.EndPCall();
81         luaFunc.Dispose();
82         luaFunc = null;
83         return table;
84     }
85 
86     private void callLuaFunc(string funcName)
87     {
88         LuaFunction luaFunc = luaState.GetFunction(this.luaClassName + "." + funcName);
89         luaFunc.BeginPCall();
90         luaFunc.Push(this.luaObj);
91         luaFunc.PCall();
92         luaFunc.EndPCall();
93         luaFunc.Dispose();
94         luaFunc = null;
95     }
96 }

 

1、這個元件給 Unity3D暴露兩個欄位:luaClassName、paramList。可以在Unity3D中設定Lua類名、初始化引數列表(引數個數可在Unity3D調整)

2、在 Awake 生命週期載入跟 luaClassName 同名的 .lua 檔案,呼叫 Lua的 luaClassName 類的 new 方法,把 paramList 當作引數傳過去,得到 Lua物件。

3、其他生命週期方法都是呼叫 Lua 類的同名方法,從而可以在 Lua 實現具體的生命週期邏輯。

 

三、新增 Lua 元件搜尋路徑

為了讓 Lua虛擬機器知道元件的路徑,在Main.cs重寫父類的 Awake 生命週期方法,新增搜尋路徑 Lua/com

Assets/CSharp/Main.cs

1 new void Awake()
2 {
3     base.Awake();
4 
5     // search path
6     string fullPath = Application.dataPath + "/Lua/com";
7     luaState.AddSearchPath(fullPath);
8 }

 

四、測試效果

1、在GameObject選單,選擇Create Empty,新增一個空GameObject

2、在屬性皮膚給這個空GameObject新增Lua Component元件,設定 Lua Class Name 為 TestComponent,下標0的引數為 12

3、執行效果日誌

 

相關文章