Mod里制作計時器(2)
我本來是要接著昨天的事說起ItemStack判定相等的,但恰好有新人的提問與計時相關(guān),我就改變了看法,決定先說點(diǎn)別的。
第一個問題:前后端
或者直觀一點(diǎn)來說,isRemote。當(dāng)你在做一個事情的時候,你要判斷這個東西是在前端(客戶端,isRemote==true)還是后端(服務(wù)端 !isRemote)。多數(shù)邏輯操作(比如扣除物品耐久)都需要在后端執(zhí)行,因此當(dāng)你在使用LivingUpdateEvent等事件的時候,要記得判斷當(dāng)前是在哪一端,避免兩個端各執(zhí)行一次。這樣,原本一秒執(zhí)行20次的事件,就變成一秒40次(前后端各20次)了。前后端也是一個值得展開討論出四五篇專欄的事,這里先說這么多。
第二個問題:你的計數(shù)變量到底都有誰再用
模組制作新手,包括曾經(jīng)的我在內(nèi),都曾經(jīng)錯誤地設(shè)計類的成員變量。如果你在函數(shù)體內(nèi)設(shè)計一個局部變量,那么這個變量一般不會出什么理解的問題(當(dāng)然也有人把ItemStack給搞出傳值傳引用分不清的問題,但這個先不說了),使用他的地方就在這個函數(shù)里。
但是,如果你把類里自己寫一個變量,比如叫什么int tickCounter,那往往問題就大了。不錯,在初學(xué)編程的時候,我們是要把各種問題與概念抽象成int、float、string這種計算機(jī)變量予以處理,這些變量只會用于解決我們要做的那道題,并且只有短短的那一小段程序使用。
不過,MC里有很多類使用了享元設(shè)計,典型的是Item、Block、Enchantment、Biome、DimensionType,還有玩家自己的事件監(jiān)聽函數(shù)收容類EventXXXX;典型的非享元是ItemStack、Entity,TileEntity。
如果你在Item里開了一個成員變量int counter,那么多數(shù)人一定是像這樣順著寫下去:
乍一看很直白,在背包里這個ItemTimerStaff就會開始充能,充到99個tick(5秒)就會做某件事。粗略一測試,你可能會發(fā)現(xiàn)確實如你所想。
對,沒錯,onUpdate這個函數(shù)確實是在背包里,每秒鐘執(zhí)行20次。但是你有沒有想過,所有的這類物品,他們使用的都是同一個ItemTimerStaff對象?如果你背包里有兩個分開放置的這東西,那么update每秒就會執(zhí)行40次,破壞這個東西1秒20次的對應(yīng)關(guān)系。
你可能會說,有兩個就讓他雙倍充能,也不是不行。
那么,如果你有十個同類物品堆疊在一起,那么因為是每一堆都執(zhí)行一次update,所以結(jié)果還是每秒20次,分成兩堆就變成40次。你現(xiàn)在還覺得并無不可嗎?
你可能會說,我這物品和斧子一樣,堆疊上限是1,所以不會有重疊物品的問題。那么如果兩個玩家背包里各有一件這個物品,那么因為他們共享了同一個ItemTimerStaff對象,他們兩個的充能是同步的,而且是互相都會充能。簡直是亂了套了。
最可怕的是,新人往往寫出了這種有嚴(yán)重問題的代碼而全然不自知,測試一下還自我感覺良好,把隱患埋了下去。還有一些猖狂點(diǎn)的,甚至?xí)苑Q這代碼沒問題,跟有經(jīng)驗的modder頂嘴,后果不堪設(shè)想。
那么正確的方式是什么呢?
正確的方法是意識到每一堆物品共享Item對象,但是都有自己的ItemStack對象。如果想給每件或者說每堆物品標(biāo)記自己的counter,應(yīng)該把東西存進(jìn)ItemStack的信息里。通用一點(diǎn)的方式是存進(jìn)nbt,粗暴一點(diǎn)的方式是使用耐久條標(biāo)記充能進(jìn)度,這兩種方式都可以。更好的是,這兩種方式都可以被mc保存,不用處理SL的序列化、反序列化問題。注意,這種情況就是典型的跟物品不跟玩家。我張三把法杖充好了不用,直接扔給李四,李四就能吃現(xiàn)成的。因為冷卻記在了物品而非玩家身上;如果張三把法杖用掉了,扔給李四,那李四還是要充能。
但是,如果希望某一個玩家的某一類物品共享充能,就不能通過寫入ItemStack弄了。假如有個傳送寶石,一使用就可以瞬移。連續(xù)使用太變態(tài)了,于是我們給他設(shè)一個十秒冷卻。但是結(jié)果有一個玩家,它直接搞了一排的傳送寶石,輪流用,結(jié)果又是一秒傳送一次了。當(dāng)然了,不管怎么說,這種情況,張三使用完了,不應(yīng)該耽誤李四使用。
這種情況下,這個冷卻的數(shù)據(jù)就應(yīng)該記錄在玩家上。反正肯定不是你隨便在哪扔一個int變量就能解決的。
你可以選擇隨便找個地方設(shè)一個字典對象,比如一個HashMap,里面存<Player,Integer>的映射,或者更加穩(wěn)妥一點(diǎn),選擇玩家的UUID(因為掉線、復(fù)活后Player會被重新構(gòu)造成新的對象,不會一直沿用)做一個<UUID,Integer>的映射。當(dāng)然,你自己的變量只會存在內(nèi)存里。如果服務(wù)器關(guān)閉,你得自己設(shè)法存儲。才囚學(xué)園的mod“定分儀”存儲玩家的冷卻表就是用的這種方式,序列化的時候會存在TileEntity的NBT里。如果你是跟著玩家,你可以序列化到玩家的持久化NBT里,或者……隨便找個文件IO一下。
你可以選擇使用原版的playerCoolDownManager。但正如我說,這東西下線就清空,不要存太長的東西。
你甚至可以選擇給玩家掛nbt。缺點(diǎn)是實體的nbt前端感受不到,想用于控制顯示的時候這玩意不太好使。
給玩家掛buff等游戲內(nèi)間接方式也不是不行,只不過小心玩家一灌牛奶,或者用什么其他的東西把你這個buff給清掉。
……正如你所見,方法有很多,各有各的長短。今晚先說到這里吧。累了。