五邑隱俠,本名關健昌,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裡設定元表的方法是程式碼第 21 行的 function setmetatable(t, metatable),呼叫這個方法會讓引數 metatable 成為 引數 t 的元表。
設定元表後,在程式碼裡訪問引數 t 的一個key時會發生以下情況:
1)Lua先在 t 裡找,看有沒有這個key,如果有,就返回這個key的值。否則,執行2)
2)Lua看引數 metatable 有沒有 __index 這個key,如果有,就在 __index 對應的值裡查詢(__index的值如果是表,在這個表裡查詢;如果是函式,執行這個函式)
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/