Mod里制作劍(三)
淺海:所以到現(xiàn)在我還是不知道怎么做一把劍。需要很多代碼嗎?
深湖:不需要。實(shí)際上就跟做盔甲差不多,理想情況下你做一個(gè)ItemSwordBase,起一個(gè)工具質(zhì)地……也可以不起,然后在ModItems里列舉就可以了,和我講做盔甲的那篇文章差不多。
淺海:那我們已經(jīng)進(jìn)入第三篇了,還沒有開始做劍,豈不是三紙無(wú)驢?
深湖:不。這個(gè)實(shí)際上是有必要的。我看到群里一些人的提問之后才決定說這些。另一個(gè)原因就是,在物品描述里,盔甲本身顯示的就是加成,和代碼里的是一致的;劍與工具顯示的不一致,所以理解起來要繞一個(gè)彎子。還有就是,盔甲那課有幾個(gè)坑我沒有講。那個(gè)看起來詳細(xì),但仍然不完備。
淺海:比如?
深湖:一會(huì)兒你會(huì)在劍的部分看到盔甲教程里藏著的漏洞。
淺海:好吧……不過確實(shí),就像費(fèi)曼所說,要想讓人在不理解原理的情況下,遵守一大堆莫名其妙的規(guī)矩,根本是天方夜譚。
深湖:是的,不過那個(gè)是產(chǎn)生核爆炸,咱們的充其量是炸個(gè)mod或者玩家的客戶端……嗯當(dāng)然也不是什么好事。扯遠(yuǎn)了。我們繼續(xù)說屬性修飾符的事。
屬性修飾符(Attribute Modifier,參閱https://minecraft.fandom.com/zh/wiki/%E5%B1%9E%E6%80%A7),以前我也經(jīng)常說成“屬性修改器”,但這并不是什么外掛之類的。
每一條屬性,都有一個(gè)基礎(chǔ)值,這個(gè)基礎(chǔ)值正常來說,只會(huì)在實(shí)體初始化時(shí)設(shè)置一次,之后便不再更改。雖然從技術(shù)上來說,想修改它很容易,但你一般都不應(yīng)這樣做。
淺海:這是為什么呢?
深湖:設(shè)想你做了一個(gè)功能叫做在水下時(shí)加1點(diǎn)攻擊;另一個(gè)人做了一件RPG風(fēng)格的盔甲,效果是穿上之后+30%攻擊;玩家此時(shí)手里拿著一把鐵劍,那他最后應(yīng)該多少攻擊力?更重要的,假如你先因?yàn)樵谒锒?1攻擊,玩家又穿上那件+30%的攻擊的盔甲,你的攻擊加成就變成+1.3的話,你怎么知道你離開水的時(shí)候要扣除的是1點(diǎn)還是1.3點(diǎn)攻擊呢?
淺海:還真是夠麻煩的。如果每個(gè)人都去試圖修改基礎(chǔ)值,就會(huì)亂套了。不過剛才說的百分比那事,我不能讓我的+1攻擊不受那30%影響嗎?
深湖:很遺憾,受不受是由百分比那哥們決定的,不是由+1這頭決定的,這個(gè)我們后面再談。但總之,我們需要分別記錄下每項(xiàng)修改,使得它們分別生效又撤掉后屬性不會(huì)亂掉,而這是只靠一個(gè)單純的float變量所無(wú)法解決的?!霸谟晏旃?1”,“在草中攻擊x2”,你總不能在玩家進(jìn)草叢后下雨,然后雨停了又出草叢,結(jié)果攻擊永久+1消不掉了吧。
淺海:那我們規(guī)定永遠(yuǎn)先乘除后加減……之類的呢?
深湖:如果只有一個(gè)變量來記錄屬性,這東西的順序不是你說了算的。你不知道玩家時(shí)先拿起劍,還是先進(jìn)草叢攻擊x2。屬性改變的事情隨著游戲流程自然進(jìn)行,順序基本無(wú)法控制。所以,我們只得把對(duì)屬性的每一條修改都用一個(gè)類存儲(chǔ)起來。這個(gè)類就是我們的屬性修改器。
屬性修改器主要有三個(gè)要素:
UUID。正如前文所述,同一個(gè)UUID的修改只能存在一項(xiàng)。此外,特殊UUID在物品上的描述會(huì)發(fā)生變化。
具體值。是+3還是-2?
類別。是加,還是加百分比?這里有0、1、2三種模式。
0是增加固定值;
1是百分比,但是所有1類的百分比會(huì)加在一起,就是兩個(gè)+50%的疊加結(jié)果是+100%;
2也是百分比,但是所有2類的百分比會(huì)乘在一起,就是兩個(gè)+50%的結(jié)果疊加是150%x150%=225%。
還有兩個(gè)不重要的,分別是注釋欄和是否存盤。我想你有很多問題,我們一個(gè)個(gè)來。
淺海:不不不,修飾符所修改的具體屬性呢?只有+3,那它到底是加攻擊還是加血量呢?
深湖:哈哈,修飾符的對(duì)象里不存儲(chǔ)它到底修飾什么屬性。屬性知道他下面有什么修飾符,但是反過來是不知道的。你甚至可以給一個(gè)修飾符對(duì)象同時(shí)掛在兩個(gè)屬性下面,但是很蠢,盡量不要這樣做。
淺海:為何要UUID呢?比如說A劍傷害是+5,那我卸掉A劍時(shí)沒有必要找UUID匹配,只要找個(gè)+5的卸掉不就得了?
深湖:乍一看確實(shí)如此,不過這種用值當(dāng)索引的管理很混亂……更關(guān)鍵的,你假定一把劍的傷害值是不變的。比如有一把劍,他的攻擊力加成等于玩家的等級(jí)數(shù)。玩家從5級(jí)升7級(jí),此時(shí)你應(yīng)該如何?
淺海:把+5的刪了,換一個(gè)+7進(jìn)去,不行嗎?
深湖:那你就必須在處理+7的時(shí)候記住前一刻是+5的。而這一點(diǎn),很多時(shí)候其實(shí)不容易辦到。你知道現(xiàn)在是玩家時(shí)7級(jí),但你未必知道變動(dòng)之前是幾級(jí)。他可能是6級(jí)升上來的,或者8級(jí)掉下去的,或者5級(jí)連升兩級(jí)來的。
淺海:確實(shí),這樣管理起來就必須要經(jīng)常緩存前一刻的狀態(tài)了,很麻煩。等等,攻擊力加成等于玩家等級(jí)的劍?屬性修飾符的檢查會(huì)在玩家升級(jí)時(shí)更新嗎?

深湖:不會(huì)。你很聰明,發(fā)現(xiàn)了這個(gè)設(shè)計(jì)的漏洞。物品屬性修飾符,只有在裝備欄里的物品ItemStack變動(dòng)——也就是數(shù)量、耐久、類別、或者nbt有一個(gè)不相等時(shí)檢查。值得注意的是,這里玩家就緩存了前一刻的狀態(tài)。每次我們更改玩家裝備欄的物品的時(shí)候,會(huì)更新【裝備欄】,而這實(shí)際上和玩家的【庫(kù)存欄】(含盔甲、主副手、玩家背包)并不是一個(gè)東西。生物會(huì)在每個(gè)tick(EntityLivingBase::onUpdate)的時(shí)候檢查,如果裝備欄和庫(kù)存欄對(duì)應(yīng)格子的東西不匹配,就解除掉庫(kù)存欄舊東西的所有屬性修飾符,然后把新的修飾符掛上去,再把庫(kù)存欄的內(nèi)容更新到和裝備欄一致。另外就是除了tick,它在序列化存檔的時(shí)候也會(huì)做一遍這件事,不過這不重要。
玩家升級(jí)并不會(huì)導(dǎo)致物品的任何一項(xiàng)內(nèi)容發(fā)生變化,所以這修改器不會(huì)實(shí)時(shí)刷新。更可怕的是,它在物品描述的時(shí)候是刷新了的,你一看,他的描述會(huì)在升級(jí)后變化,但是這只是用于顯示的東西變化了,實(shí)際上影響數(shù)值的修改器沒變。這就寫出了一個(gè)很隱蔽的bug。
淺海:解決方式呢?
深湖:每升一級(jí),就強(qiáng)制給手里的這種物品變一下nbt,哪怕只是個(gè)無(wú)意義的內(nèi)容在“1”、“2”之間來回變(你必須做出不同,1變1是不會(huì)有用的),就是為了觸發(fā)更新,這樣就足夠了?;蛘?,干脆別做這種不依賴于耐久、nbt、數(shù)量、物品種類的設(shè)計(jì)。當(dāng)然,這里有另一個(gè)問題,就是物品的nbt變了之后會(huì)導(dǎo)致手里的物品抖一下,有個(gè)類似換物品的刷新動(dòng)畫。你要是想屏蔽那個(gè)動(dòng)畫的話要覆寫Item::shouldCauseReequipAnimation返回false。這玩意原版從來沒動(dòng)過,不過你動(dòng)了也沒什么問題,如果你的設(shè)計(jì)需要的話就應(yīng)該動(dòng)。
淺海:好險(xiǎn),差點(diǎn)就寫了個(gè)bug出來。
深湖:所以我說,
大師們寫出來的代碼并不一定更加復(fù)雜,他們只是知道哪些寫法與設(shè)計(jì)會(huì)出問題,而這從最終的成品里很難看出來。簡(jiǎn)單的代碼,其實(shí)蘊(yùn)含了大量的規(guī)避問題的思考。
淺海:我想我需要休息一下。
深湖:再見?!都t燒天堂》,啟動(dòng)!