前言
原貼寫于饑荒游戲貼吧甜奄,為了使文章針對(duì)性更強(qiáng)儡陨,將原文切割并精簡(jiǎn)顾复。此貼主要為編程0基礎(chǔ)的modder講解一些編程的基礎(chǔ)知識(shí)宾符。至于說有關(guān)饑荒框架的介紹,則會(huì)放在另一篇文章里講解圣勒。
編程0基礎(chǔ)的人懂拾,要想學(xué)習(xí)制作MOD析恢,難度是比較大的查刻,因?yàn)槿狈σ恍┗镜木幊谈拍罴担欢脧?fù)制別人的代碼或者在它們的基礎(chǔ)上稍加改變,遇到稍微復(fù)雜一點(diǎn)的代碼穗泵,就束手無策了普气。對(duì)于MOD崩潰或錯(cuò)誤,也幾乎沒辦法自行處理佃延。但我也不推薦先去學(xué)一門編程語言之后再來學(xué)習(xí)MOD代碼现诀,這樣做花費(fèi)的時(shí)間精力都過多,又缺乏足夠的正反饋履肃,很容易半途而廢赶盔。事實(shí)上饑荒MOD里用到的基本編程知識(shí)都比較簡(jiǎn)單,所使用的lua語言相比c之類的強(qiáng)類型語言榆浓,也已經(jīng)做了許多簡(jiǎn)化。單純想要做MOD的話撕攒,只需要了解一些基本知識(shí)和概念就可以了陡鹃。
以下內(nèi)容全部基于lua語言烘浦。
標(biāo)識(shí)符
一個(gè)名字
給常量,變量萍鲸,函數(shù)闷叉,類一個(gè)名字,這樣我們才能通過名字來使用它脊阴。一般使用英文字母握侧、數(shù)字和下劃線的組合來命名。
推薦命名規(guī)則
- 常量:全大寫嘿期,單詞之間以下劃線隔開品擎。
- 變量、函數(shù)备徐、文件名:全小寫萄传,單詞之間以下劃線隔開。
- 類:所有單詞首字母大寫蜜猾。
這個(gè)只是個(gè)人推薦的命名規(guī)則秀菱,讀者可以根據(jù)自己的喜好決定命名規(guī)則。
變量
可以通過符號(hào)'='賦值改變的量蹭睡。
典型代表:人物的饑餓值衍菱。這個(gè)值在游戲里幾乎每時(shí)每刻都在不停變化著,這樣一來肩豁,我們就可以根據(jù)不同的變化脊串,設(shè)置不同的效果,比如沃爾夫?qū)煌酿囸I值會(huì)有不同的形態(tài)蓖救,這個(gè)就是通過檢測(cè)饑餓值來實(shí)現(xiàn)的洪规。
常量
程序運(yùn)行時(shí),不會(huì)被改變的量
實(shí)際上循捺,lua語言里沒法自己定義常量斩例。但是,對(duì)于某些量从橘,我們不需要在游戲運(yùn)行的過程中改變念赶,又需要引用它。比如說長(zhǎng)矛攻擊力恰力,在游戲過程中不需要改變叉谜,但官方所做的所有的武器的攻擊力都是長(zhǎng)矛攻擊力的某倍數(shù),這又需要引用它進(jìn)行計(jì)算踩萎。這時(shí)候停局,不妨就把長(zhǎng)矛攻擊力看作是一個(gè)常量,用一個(gè)變量將其定義下來(SPEAR_DAMAGE,這個(gè)定義在tuning.lua里)
作用域
變量的生效區(qū)域董栽。
在作用域以外的域內(nèi)码倦,如果你引用這個(gè)變量,又沒有域內(nèi)的同名變量锭碳,就會(huì)造成出錯(cuò)袁稽。
lua中對(duì)一個(gè)變量的作用域只有兩個(gè)選項(xiàng):local 和global。默認(rèn)不加local修飾的變量為global(全局)變量擒抛,加了local的為局部變量推汽。全局變量的作用域?yàn)檎麄€(gè)程序。局部變量的作用域則在所定義的域中歧沪。一個(gè)選擇控制結(jié)構(gòu)內(nèi)歹撒,一個(gè)函數(shù)體內(nèi),或者一個(gè)文件內(nèi)槽畔,都是一個(gè)域栈妆。
這個(gè)作用域的主要價(jià)值在于,使得系統(tǒng)免于混亂厢钧。比如說鳞尔,在被攻擊的時(shí)候,需要計(jì)算所受到的傷害早直,為了方便進(jìn)行多次計(jì)算寥假,我們把這個(gè)數(shù)值設(shè)置為一個(gè)變量。但是霞扬,面對(duì)的敵人的攻擊力會(huì)有變化糕韧,自身的防具減傷也會(huì)有變化,這時(shí)候我們就希望喻圃,計(jì)算結(jié)束萤彩,變量被引用到血量變化之后,這個(gè)變量能消失掉斧拍,不會(huì)影響我們下一次計(jì)算其他的傷害雀扶。這就是局部變量的重要作用。當(dāng)然肆汹,局部變量還有另一個(gè)好處就是讀取它的數(shù)據(jù)愚墓,要比全局變量快一些,不過昂勉,提高M(jìn)OD性能不是本篇教程的重點(diǎn)浪册,就不詳細(xì)展開了「谡眨總而言之村象,全局變量笆环,不到必不得已的情況,應(yīng)當(dāng)盡可能少用煞肾。
定義和聲明##
定義咧织,就是告訴系統(tǒng),我設(shè)置了的這個(gè)變量/常量是什么籍救。
聲明,就是告訴系統(tǒng)渠抹,我設(shè)置了一個(gè)變量/常量蝙昙,你給我記好了。
定義和聲明是不一樣的梧却,但常常會(huì)混在一起奇颠。如果你只是想寫寫mod的話,不了解他們的區(qū)別也沒關(guān)系放航,只認(rèn)為是定義就夠了烈拒。
引用##
引用就是告訴系統(tǒng),我要用這個(gè)變量/常量來做某某事广鳍。
比如說用于某個(gè)表達(dá)式的計(jì)算荆几,系統(tǒng)就會(huì)幫你讀取儲(chǔ)存在其中的數(shù)據(jù)。
賦值##
賦值就是告訴系統(tǒng)赊时,往這個(gè)變量/常量里存入你給出的數(shù)據(jù)吨铸。
注意,此處的數(shù)據(jù)祖秒,不僅僅指數(shù)字诞吱,可以是lua語言允許的任何類型,比如說一段文字(字符串)竭缝,一個(gè)布爾值(真或假)等等房维。在某些語言比如C語言中,定義和賦值是可以分開的抬纸。但在饑荒MOD的腳本語言lua中咙俩,這兩者是連在一起的。對(duì)一個(gè)變量的第一次賦值松却,就是對(duì)它的定義了暴浦。
數(shù)據(jù)類型
對(duì)變量賦值,就是給它寫入數(shù)據(jù)晓锻,那就涉及到了數(shù)據(jù)類型的問題歌焦。在初次賦值給一個(gè)變量時(shí),所給予的值的數(shù)據(jù)類型砚哆,就是變量的數(shù)據(jù)類型独撇。此后再給這個(gè)變量賦值,就必須賦予同一數(shù)據(jù)類型的值,如果值不同纷铣,就會(huì)導(dǎo)致系統(tǒng)崩潰(除了nil)卵史。這里不展開細(xì)講,只針對(duì)lua語言搜立,簡(jiǎn)單地列出MOD中常用的幾種類型
- nil:表示無效值以躯,可以給任何數(shù)據(jù)類型的變量賦這個(gè)值。實(shí)際效果相當(dāng)于刪除這個(gè)變量
- boolean:包含兩個(gè)值:false和true(假和真)
- string:字符串啄踊,用一對(duì)雙引號(hào)或單引號(hào)括起來
- function:函數(shù)忧设,這個(gè)會(huì)在下面講
- table:表,這個(gè)概念會(huì)在后面講
函數(shù)
這是編程里的一個(gè)非常重要的概念颠通。
函數(shù)與變量的區(qū)別址晕,可以做這樣的類比:一個(gè)變量,就好比是一個(gè)屬性顿锰,你可以給一個(gè)客體以某個(gè)屬性谨垃,讓它可以被描述,比如說硼控,屬性:可以被燒毀刘陶。而函數(shù),則是一種操作方法淀歇,你讓一個(gè)客體擁有一個(gè)函數(shù)易核,就是讓它有某種操作。比如說浪默,操作方法:被燒毀的具體步驟和操作牡直。
函數(shù)由函數(shù)名,參數(shù)表和函數(shù)體組成纳决。函數(shù)也和變量一樣碰逸,能被引用,也有作用域阔加。不過與變量不同的是饵史,函數(shù)需要單獨(dú)定義,在不同的編程語言中胜榔,函數(shù)的定義格式不一樣胳喷,但都少不了上面所說的三個(gè)基本組成。在lua中夭织,函數(shù)也可以看成是變量吭露,可以被賦值。另外尊惰,函數(shù)可以有返回值讲竿,也就是把計(jì)算的結(jié)果返回泥兰,供另一個(gè)函數(shù)或者表達(dá)式使用。
Lua中题禀,函數(shù)定義的基本格式如下為:
function 函數(shù)名(參數(shù)表)
函數(shù)體
end
如果希望函數(shù)的作用域是局部的鞋诗,則在function 前面添加local。這樣迈嘹,你將無法在其作用域之外調(diào)用該函數(shù)削彬。
函數(shù)是怎樣工作的呢?首先秀仲,你需要明白吃警,定義函數(shù)并不會(huì)讓函數(shù)工作。只有執(zhí)行了函數(shù)語句才會(huì)讓它工作啄育。還是拿計(jì)算傷害來做例子。你定義了怎么計(jì)算傷害的函數(shù)拌消,參數(shù)為攻擊者的攻擊力和防御者的護(hù)甲挑豌。這個(gè)函數(shù)在定義之后,本身并不會(huì)立刻工作墩崩。只有你設(shè)定了一系列的流程氓英,讓函數(shù)在出現(xiàn)攻擊狀態(tài)的時(shí)候觸發(fā),才能算是執(zhí)行了這個(gè)函數(shù)鹦筹。函數(shù)執(zhí)行的時(shí)候铝阐,輸入了兩個(gè)參數(shù):攻擊者的攻擊力和防御者的護(hù)甲。在函數(shù)體中铐拐,經(jīng)過一系列的計(jì)算徘键,得到了結(jié)果,利用return返回來遍蟋,由變量接收或者加在各種表達(dá)式里使用吹害。需要注意的是,即使是沒有參數(shù)的函數(shù)虚青,在執(zhí)行時(shí)它呀,也必須寫成這樣的形式: 函數(shù)名(實(shí)際參數(shù)表)
代碼例子:
--定義了函數(shù)caldamage,但沒有執(zhí)行
function caldamage(attack,armor)
return attack-armor
end
local damage = caldamage(10,8) --這里執(zhí)行了函數(shù)caldamage棒厘,并把計(jì)算的結(jié)果返回纵穿,賦值給damage
函數(shù)的作用是什么呢?就是使得你的編程顯得更有邏輯奢人,模塊化谓媒,還能減少代碼的使用量。定義好一個(gè)函數(shù)之后达传,就不再需要管這個(gè)函數(shù)里面詳細(xì)的執(zhí)行過程(也就是函數(shù)體寫了什么)篙耗,我們只需要知道這個(gè)函數(shù)的名字迫筑,參數(shù)表和返回值,和這個(gè)函數(shù)有什么作用宗弯。因?yàn)樵陴嚮牡腗OD中脯燃,大量的函數(shù)是沒有返回值的(也就是返回值為nil),執(zhí)行這樣的函數(shù)蒙保,目的在于使用它的功能辕棚。
函數(shù)是做饑荒MOD時(shí)最重要的東西。我們做MOD邓厕,主要的目標(biāo)就是修改或者向游戲添加函數(shù)逝嚎。了解這些函數(shù)在什么時(shí)候會(huì)觸發(fā),需要哪些參數(shù)详恼,有什么功能补君,返回值是什么,是非常重要的昧互。
我們是在原游戲的基礎(chǔ)上做MOD挽铁,也就是說,有很多已經(jīng)定義好了的函數(shù)可以供我們使用敞掘。打個(gè)比方叽掘,饑荒這個(gè)游戲,就好比一部車玖雁,函數(shù)就是這部車上面的零件更扁,它讓這部車能夠擁有某些功能:?jiǎn)?dòng),剎車等等赫冬。我們現(xiàn)在覺得這部車不能滿足我們的需要了浓镜,那么,很顯然的面殖,做適當(dāng)?shù)母难b竖哩,要比重新造一部車容易。做MOD脊僚,就好比是做一些改裝相叁。既然是改裝,那你就有必要了解到辽幌,你所需要改裝的部分增淹,需要哪些零件。有些核心零件是非要弄清楚不可的乌企。
現(xiàn)在饑荒MOD本身的結(jié)構(gòu)是非常開放的虑润,但官方?jīng)]有給出詳細(xì)的說明文檔,當(dāng)我們想要實(shí)現(xiàn)一項(xiàng)功能的時(shí)候加酵,我們不知道官方有沒有給出來拳喻,怎么辦呢哭当?我的建議是,思考一下游戲里的各種功能冗澈,以及他人已經(jīng)發(fā)布的MOD钦勘,有沒有和你的需求類似的,去參考一下相應(yīng)的代碼亚亲。易寧修改也是一個(gè)很好的參考彻采,但易寧修改畢竟是直接修改游戲的核心文件,與MOD還是有一些區(qū)別的捌归,所以要使用的話肛响,前提是理解其含義。
表
表不是一門編程語言的必須概念惜索,但這個(gè)概念是lua內(nèi)置的核心數(shù)據(jù)結(jié)構(gòu)特笋,在饑荒MOD里使用得非常頻繁。游戲的整個(gè)框架巾兆,也非常依賴于表雹有。表的重要作用也和函數(shù)一樣,是為了讓你的編程顯得邏輯清晰臼寄。比方說,現(xiàn)在有4個(gè)個(gè)體溜宽,a,b,c,d吉拳,有多項(xiàng)屬性描述:health、sanity适揉、hunger留攒、damage、armor嫉嘀、attack_period炼邀、walkspeed、runspeed剪侮。這些屬性拭宁,對(duì)于4個(gè)個(gè)體來說,有的有瓣俯,有的沒有杰标,我們要怎么組織起來呢?用多張表連起來彩匕,就是一個(gè)好主意腔剂。首先,我們來給屬性分一下類驼仪,health掸犬、sanity袜漩、hunger是饑荒中的三大基本屬性,各自單獨(dú)成一類湾碎,damage宙攻、armor、attack_period是和戰(zhàn)斗有關(guān)的胜茧,分類為combat粘优,至于walkspeed、runspeed則是和移動(dòng)有關(guān)的呻顽,分類為locomotor雹顺。那么,我們就有多張表了:
一張總表:
個(gè)體屬性表
屬性 | a | b | c | d |
---|---|---|---|---|
health | a的血 | b的血 | c的血 | d的血 |
sanity | a的精神 | b的精神 | c的精神 | d的精神 |
hunger | a的饑餓 | b的饑餓 | c的饑餓 | d的饑餓 |
combat | a的戰(zhàn)斗屬性 | b的戰(zhàn)斗屬性 | c的戰(zhàn)斗屬性 | d的戰(zhàn)斗屬性 |
locomotor | a的移動(dòng)屬性 | b的移動(dòng)屬性 | c的移動(dòng)屬性 | d的移動(dòng)屬性 |
上表中的每一個(gè)元素廊遍,都是一張表嬉愧,現(xiàn)在不妨取a的戰(zhàn)斗屬性表出來,是這樣的:
戰(zhàn)斗屬性表
damage | armor | attack_period | |
---|---|---|---|
數(shù)值 | 20 | 50 | 3 |
那么喉前,我們想要引用a1的damage的時(shí)候没酣,怎么辦呢?先在總表第一橫欄中找到a,然后在豎欄中找到combat卵迂,這樣我們就得到了提示:轉(zhuǎn)去找a1的戰(zhàn)斗屬性表裕便。然后在戰(zhàn)斗屬性表中,我們?cè)跈M欄中找到了damage见咒,這時(shí)候豎欄中只有一項(xiàng)偿衰,就不必再查找了。我們?cè)诓檎疫^程中改览,尋找的a,combat,damage 就是所謂的索引下翎。我們按先橫后豎的順序查找的,a為1級(jí)索引宝当,combat為2級(jí)视事,damage為3級(jí)。按順序最后找到damage的具體的值(20)庆揩,就是所謂的值俐东。這個(gè)值不僅僅是數(shù)值,比如說在總表中找到的a的戰(zhàn)斗屬性表订晌,也可以稱為值犬性。再拓展一些,如果說戰(zhàn)斗屬性表中的豎表不只有一項(xiàng)腾仅,而是有兩項(xiàng):max,min乒裆,此時(shí)我們想要查damage最大值,該如何呢推励?那就要增加一個(gè)四級(jí)索引max鹤耍。當(dāng)你想要引用a的傷害最大值時(shí)肉迫,在編程里的調(diào)用語句,你就可以寫a.combat.damage.max
結(jié)合饑荒Mod編程稿黄,我們常常會(huì)看到類似這樣的一條語句
inst.components.sanity:DoDelta(-10)`
這句話的意思是當(dāng)前對(duì)象的精神減10喊衫。
具體是怎么操作的呢?首先杆怕,游戲里這么多個(gè)體族购,要在茫茫人海中找到你,必須要有個(gè)名字陵珍,這個(gè)名字就是inst寝杖,然后,inst下有很多屬性類互纯,我們需要的精神值瑟幕,歸類為components,也就是組件留潦。組件這個(gè)概念只盹,是饑荒為了編程上的邏輯清晰而創(chuàng)造出來的一個(gè)概念,會(huì)在介紹饑荒編程框架的文章里詳細(xì)說明兔院。然后我們繼續(xù)在components表里找包含著精神值和操作精神值的函數(shù)的表殖卑,就是sanity。這時(shí)候你可以看到sanity后面是冒號(hào):而不是之前的那些點(diǎn)號(hào). 這是因?yàn)榉宦埽覀兪窍M麍?zhí)行這個(gè)函數(shù)懦鼠。如果是想要引用這個(gè)函數(shù)做其他操作的話,還是要用點(diǎn)號(hào). 的屹堰。學(xué)過C++的人都會(huì)了解類的概念,對(duì)這個(gè)肯定不陌生街氢。沒學(xué)過的人呢扯键,看我在下面關(guān)于類的解釋。
類和面向?qū)ο缶幊?/h2>
類這個(gè)概念珊肃,就是在面向?qū)ο缶幊痰乃枷肷习l(fā)展起來的荣刑。為什么要使用類呢?就是因?yàn)槊嫦驅(qū)ο缶幊田@得邏輯結(jié)構(gòu)清晰伦乔,易于實(shí)現(xiàn)厉亏、互動(dòng)和維護(hù)。那么烈和,什么是一個(gè)類呢爱只?在編程上,可以理解為一些變量和函數(shù)的集合招刹。這個(gè)集合是封裝起來的恬试,其中有一些變量和函數(shù)你可以訪問和引用窝趣,稱為公有變量和公有函數(shù),還有一些是你訪問不到训柴,也無法使用的哑舒,就是私有變量和私有函數(shù)。需要注意的是幻馁,類本身只是一個(gè)邏輯結(jié)構(gòu)洗鸵,并不是實(shí)體。用現(xiàn)實(shí)的東西舉個(gè)例子仗嗦,自行車就是一個(gè)類的概念膘滨,它有很多基本屬性:顏色,材質(zhì)等等儒将,也有很多操作:騎吏祸、前進(jìn)、剎車等等钩蚊。屬性就是變量贡翘,操作就是函數(shù)。顏色砰逻、材質(zhì)鸣驱,是你能夠看到的,就是公有變量蝠咆,而內(nèi)部的轉(zhuǎn)盤的顏色你看不到踊东,就是私有變量。而騎和剎車的操作刚操,是你能夠決定的闸翅,就是公有函數(shù)。而前進(jìn)這個(gè)操作菊霜,你沒法直接進(jìn)行坚冀,你必須要反復(fù)踩踏板,才能讓自行車前進(jìn)鉴逞,所以前進(jìn)就是私有函數(shù)记某。在頭腦中想到自行車這個(gè)概念,就是類构捡。而想到你的自行車液南,就是一個(gè)具體的實(shí)體。
結(jié)合到饑荒MOD里勾徽,sanity這就是一個(gè)類滑凉,這里面有很多屬性:當(dāng)前精神值,最大精神值等等,也有很多操作:精神增加/減少譬涡,設(shè)置當(dāng)前精神值闪幽,設(shè)置最大精神值等等。而具體到一個(gè)人物的sanity涡匀,那就是這個(gè)類的實(shí)體了盯腌。
實(shí)際上,在lua里陨瘩,只有表腕够,沒有類這個(gè)概念。但是饑荒的游戲制作者為了編程方便舌劳,還是用某種手段帚湘,在表的基礎(chǔ)上,類這個(gè)概念創(chuàng)造出來了甚淡。我們只需要認(rèn)識(shí)到大诸,怎樣使用一個(gè)類就可以了。
引用類中的變量贯卦,操作方法就和引用表中元素一樣资柔。如果想要調(diào)用函數(shù),則需要將點(diǎn)號(hào).改成冒號(hào):撵割,并且在函數(shù)名后面添加(函數(shù)參數(shù)表)贿堰。
比如說人物的當(dāng)前精神值,人物的最大精神值等等啡彬,同時(shí)也有一些操作函數(shù)羹与,比如上面舉例的DoDelta。我之前說過了庶灿,在lua里纵搁,函數(shù)也可以看成變量。如果你想要引用這個(gè)函數(shù)往踢,比如說引用去給一個(gè)函數(shù)賦值腾誉,那么,上面的冒號(hào):就要改成點(diǎn)號(hào).菲语,而且后面的"(-10)"也要去掉。如果你想要執(zhí)行這個(gè)函數(shù)惑灵,那就要用冒號(hào): 并且添加相應(yīng)的參數(shù)山上。