Unity3D開發入門教程(二)—— Lua入門

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

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

 

如果你還沒有程式設計基礎,建議你先學習一些程式設計基礎。本文不是完全菜鳥教程,主要針對有其他語言經驗的開發者,如果想看菜鳥教程,建議看菜鳥教程的 Lua教程

先看一個簡單類的程式碼

  1 ---@class BsnsPack @Base class of business pack
  2 local BsnsPack = {
  3     -- class fields
  4     maxSerialNo = 0,
  5 }
  6 
  7 BsnsPack.__index = BsnsPack
  8 
  9 --- new()
 10 ---@return BsnsPack
 11 function BsnsPack:new()
 12     local o = {}
 13 
 14     -- member fields
 15     o.serialNo = 0
 16     o.mod = 0
 17     o.cmd = 0
 18     o.payload = nil
 19     
 20     -- bind BsnsPack methods
 21     setmetatable(o, self)
 22     return o
 23 end
 24 
 25 --- set serial No.
 26 ---@param serialNo number
 27 function BsnsPack:setSerialNo(serialNo)
 28     self.serialNo = serialNo
 29 end
 30 
 31 --- get serial No.
 32 ---@return number
 33 function BsnsPack:getSerialNo()
 34     return self.serialNo
 35 end
 36 
 37 --- set mod
 38 ---@param mod number
 39 function BsnsPack:setMod(mod)
 40     self.mod = mod
 41 end
 42 
 43 --- get mod
 44 ---@return number
 45 function BsnsPack:getMod()
 46     return self.mod
 47 end
 48 
 49 --- set cmd
 50 ---@param cmd number
 51 function BsnsPack:setCmd(cmd)
 52     self.cmd = cmd
 53 end
 54 
 55 --- get cmd
 56 ---@return number
 57 function BsnsPack:getCmd()
 58     return self.cmd
 59 end
 60 
 61 --- set payload
 62 ---@param payload any
 63 function BsnsPack:setPayload(payload)
 64     self.payload = payload
 65 end
 66 
 67 --- get payload
 68 ---@return any
 69 function BsnsPack:getPayload()
 70     return self.payload
 71 end
 72 
 73 function BsnsPack.test()
 74     print(BsnsPack.serialNo)
 75 end
 76 
 77 --- testPrivate
 78 ---@param self BsnsPack
 79 local function testPrivate(self)
 80     print(self.serialNo)
 81 end
 82 
 83 ---@alias Request BsnsPack @Request is BsnsPack
 84 local Request = BsnsPack
 85 
 86 ---@class Response : BsnsPack @Response class
 87 local Response = {}
 88 setmetatable(Response, BsnsPack) -- bind super methods
 89 Response.__index = Response
 90 
 91 --- new()
 92 ---@return Response
 93 function Response:new()
 94     local o = BsnsPack:new()
 95 
 96     -- member fields
 97     o.code = 0
 98 
 99     setmetatable(o, nil) -- remove original metatable
100     setmetatable(o, self) -- bind Response methods
101     return o
102 end
103 
104 --- get result code
105 ---@return number
106 function Response:getCode()
107     return self.code
108 end
109 
110 local bsnsPack = {}
111 bsnsPack.BsnsPack = BsnsPack
112 bsnsPack.Request = Request
113 bsnsPack.Response = Response
114 
115 return bsnsPack

 

 

1、第1行:這是一行註釋

Lua的行註釋以兩個減號 -- 開頭,

class註解:---@class TYPE[: PARENT_TYPE {, PARENT_TYPE}]  [@comment]

這樣有利於 VS Code 程式碼提示,例如將滑鼠放到程式碼中出現 BsnsPack 的地方上,會有小彈窗提示這個table的key,以及key的型別。

用得比較多的註解有 ---@class,---@return,---@param

 

2、第2行,定義一個叫 BsnsPack 的class(通過table模擬)。

Lua裡沒有類,Lua的結構化資料型別叫table,基於key-value結構。相對於其他語言的 map / dictionary。訪問一個table的key對應的值,可以用點 . 來訪問,也可以用中括號[]來訪問

Lua裡沒有單獨的陣列結構,也是基於table,陣列下標是key,陣列元素是value,要注意的是,Lua裡陣列下標是從1開始的。

建立table的最簡單方式就是花括號 {},建立一個空table。

Lua是指令碼語言,變數不需要指定型別,只需要給它賦值。所以 BsnsPack = {maxSerialNo = 0,} 相當於定義了個變數 BsnsPack,它的值是一個table。

Lua的變數預設是全域性的,載入到記憶體的Lua程式碼都可以訪問這個變數。為了避免命名衝突和意外修改,一般建議變數定義為區域性變數,限制它的作用域。在變數前面加個 local定義區域性變數。

這裡定義的 BsnsPack ,作用域只在這個檔案裡。其他檔案不能訪問。也可以在函式、程式碼塊裡用 local 定義區域性變數。

 

3、第7行,給 BsnsPack一個叫 __index 的 key 賦值為 BsnsPack 自己

Lua 的 table 裡,__index 是個特殊的key,這是 Lua實現類和繼承的關鍵。
Lua裡有個概念叫做元表。Lua定義了一些語言預設key,這些key一般跟 +、-、*、/等操作符、key的訪問、值更新關聯,這些key叫做元方法(Lua裡的函式/方法也可以作為值),這些元方法合起來叫元表。

Lua裡設定元表的方法是程式碼第 21 行的 function setmetatable(t, metatable),呼叫這個方法會讓引數 metatable 成為 引數 t 的元表。

設定元表後,在程式碼裡訪問引數 t 的一個key時會發生以下情況:

1)Lua先在 t 裡找,看有沒有這個key,如果有,就返回這個key的值。否則,執行2)

2)Lua看引數 metatable 有沒有 __index 這個key,如果有,就在 __index 對應的值裡查詢(__index的值如果是表,在這個表裡查詢;如果是函式,執行這個函式)

BsnsPack.__index = BsnsPack,如果其他的table設定 BsnsPack 為元表,就可以訪問 BsnsPack 所有的key。 這些key的值如果是方法,就可以呼叫這些方法。
所以 BsnsPack 可以定義方法的模板,這就跟class類似。BsnsPack定義的資料key,相當於靜態欄位class field。BsnsPack定義的方法key,可以是成員方法member method,也可以是class method,後面介紹
 

4、第11~23行,定義BsnsPack的一個叫new的key,它的值是一個方法

Lua裡一個程式碼塊不用花括號{},通過end作為結束標記。

定義方法用 function 開頭,用點.或者冒號:分割talbe名和方法名,

用冒號:定義的方法,會有個預設引數self,相當於呼叫者,如果外部用 BsnsPack:new() 這樣呼叫,則self == BsnsPack,如果用a:new()這樣呼叫,self==a。

用點.定義的方法,沒有預設引數,只能顯式寫引數self。

一般地,用冒號:定義構造物件的方法(例如第11行的 function BsnsPack:new()),以及成員方法member method(例如第23行的 function BsnsPack:setSerialNo(serialNo) )

1)當在其他地方呼叫 BsnsPack:new()時,方法BsnsPack:new()的定義裡的self是 BsnsPack,setmetatable(o, self)相當於設定物件 o 的類為 BsnsPack

2)呼叫 o:setSerialNo(serialNo) 時,這裡的self是物件o,只改物件o的serialNo,其他物件的serialNo不受影響。

用點.定義靜態方法class method(例如第69行的 function BsnsPack.test() )

1)在其他地方通過 BsnsPack.test()呼叫

建議,方法定義的時候是冒號:,呼叫的時候用冒號:,定義的時候是點.,呼叫的時候也用點.

構造方法不一定叫new,只是為了統一,建議都用new,與其他語言一致。

方法new裡面的 local o = {},定義的key都是成員欄位member field(每次呼叫方法 BsnsPack:new() 都建立一個新的 table並對其欄位初始化後返回)

Lua沒有私有的成員方法,可以定義檔案的 local 方法,把物件作為引數self顯式的傳進去(例如第79行 local function testPrivate(self)

 

5、第84行,給類BsnsPack定義一個別名 Request,其實就是把一個table賦值給另外一個變數

 

6、第87~102行,定義一個類Response,繼承基類BsnsPack

第87行,定義子類Response

第88行,設定元表,使得Response可以訪問BsnsPack所有的key(繼承BsnsPack所有的方法)

第89行,所有物件可以訪問Response所有的key(通過遞迴,包括BsnsPack所有的key)

第94行,通過呼叫BsnsPack構造方法初始化o,o是BsnsPack的物件,擁有基類所有的member field

第97行,定義Response相對於基類新增的欄位code

第99、100行,修改o的元表為Response,這樣o可以訪問Response所有的成員方法 member method(通過遞迴,包括BsnsPack所有的成員方法)

Response可以重寫BsnsPack的方法

1 function Response:getPayload()
2     return self.payload
3 end

由於Lua裡訪問key,先在table本身查詢,如果沒有才在元表查詢,所以會呼叫重寫的方法,實現多型。

 

7、第110~115行,匯出本檔案定義的類。

由於class的定義都是以 local 方式定義的區域性變數,外部不能訪問,所以要把它當作檔案的返回值 return

如果該檔案只匯出一個class,直接return這個區域性變數就可以,(例如,如果只匯出BsnsPack,return BsnsPack 就可以)

由於本檔案需要匯出3個class,這裡把他們放到一個table裡匯出

其他檔案需要訪問這個檔案的class,需要呼叫require方法匯入進來,該方法的引數是路徑(不含字尾.lua),返回值是這個lua檔案的return值

1 local bsnsPack = require("Assets.Lua.bsns.bsns_pack")
2 local Request = bsnsPack.Request
3 local Response = bsnsPack.Response

 

8、如果想模組化管理,把一個模組所有的類通過一個檔案暴露給其他模組,可以在這個檔案定義一個區域性變數,把這個模組所有的檔案的類,都通過require匯入進來,並賦值給跟類同名的key,最後返回這個區域性變數

檔案bsns.lua

 1 local bsns = {}
 2 
 3 local bsnsPack = require("Assets.Lua.bsns.bsns_pack")
 4 bsns.Request = bsnsPack.Request
 5 bsns.Response = bsnsPack.Response
 6 
 7 bsns.BsnsMod = require("Assets.Lua.bsns.bsns_mod")
 8 bsns.BsnsCenter = require("Assets.Lua.bsns.bsns_center")
 9 
10 return bsns

 

5、簡單語法介紹

1)Lua裡的語句不需要分號;結尾

2)Lua常用的基本資料型別有nil、boolean、number、string、function、table

3)獲取陣列長度用 #陣列變數,例如

1 local array = {"Lua", "Tutorial"}
2 local len = #array
3 print(len)

4)遍歷陣列用 

1 for i, v in ipairs(table) do
2 
3 end

5)遍歷物件用

1 for k, v in pairs(obj) do
2 
3 end

6)其他語法請查閱菜鳥教程

 

6、Lua程式碼規範可參考 https://lua.ren/topic/172/

 

相關文章