五邑隱俠,本名關健昌,12年游戲生涯。 本教程以 Unity 3D + VS Code + C# + xlua 為例。
如果你還沒有編程基礎,建議你先學習一些編程基礎。本文不是完全菜鳥教程,主要針對有其他語言經驗的開發者,如果想看菜鳥教程,建議看菜鳥教程的 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, BsnsPack) 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, Response) -- 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。
一般地,用冒號:定義成員方法member method(例如第23行的 function BsnsPack:setSerialNo(serialNo) )
1)調用 o:setSerialNo(serialNo) 時,這里的self是對象o,只改對象o的serialNo,其他對象的serialNo不受影響。
用點.定義構造對象的方法(例如第11行的 function BsnsPack.new()),以及靜態方法class method(例如第69行的 function BsnsPack.test() )
1)在其他地方通過 BsnsPack.new()構造BsnsPack對象
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
9、可以實現一個通用的類定義函數
1 --- define a class 2 --- t._className, a string array, define class name 3 --- t._super, a table, define super class 4 --- t.__ctor, a function(self, ...), define member fields 5 --- @param t any 6 function class(t) 7 -- extends super 8 if t._super ~= nil then 9 -- static fields and methods 10 local superStaticClone = {} 11 for k, v in pairs(t._super) do 12 if k == "_ctor" then 13 goto continue 14 end 15 16 superStaticClone[k] = v 17 18 ::continue:: 19 end 20 21 setmetatable(t, superStaticClone) 22 end 23 24 t.__index = t 25 -- new method 26 t.new = function(...) 27 local o = {} 28 invokeCtor(o, t, ...) 29 30 setmetatable(o, t) -- bind class 31 return o 32 end 33 34 function invokeCtor(o, cls, ...) 35 if cls._super ~= nil then 36 invokeCtor(o, cls._super, ...) 37 end 38 39 if cls._ctor ~= nil then 40 setmetatable(o, cls) 41 o:_ctor(...) 42 setmetatable(o, nil) 43 end 44 end 45 46 return t 47 end
有了這個函數可以這樣定義類
1 local TestClass = class({ 2 -- class name 3 _className = "TestClass", 4 5 -- static fields: 6 size = 2, 7 count = 0, 8 9 -- member: 10 _ctor = function(self, ...) 11 local params = {...} 12 self.count = params[1] 13 self.age = params[2] 14 end 15 }) 16 17 local TestClass2 = class({ 18 _className = "TestClass2", 19 ---@class TestClass 20 _super = TestClass, 21 22 _ctor = function(self, ...) 23 self.fdsl = 3256 24 end 25 })
10、簡單語法介紹
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)其他語法請查閱菜鳥教程
11、Lua代碼規范可參考 https://lua.ren/topic/172/