Mod里制作計(jì)時(shí)器(1)
很多人都在湖中群?jiǎn)栠^怎么制作一個(gè)定時(shí)的效果,即,在觸發(fā)某條件后,X秒鐘后執(zhí)行某個(gè)操作。每次我被問到都會(huì)非常煩躁,因?yàn)楹苓z憾,這個(gè)問題并沒有統(tǒng)一的答案。
時(shí)間是什么?
不,不是哲學(xué)問題。游戲內(nèi)的時(shí)間與現(xiàn)實(shí)時(shí)間不同。
問這個(gè)問題的人很多時(shí)候頭腦一片空白,根本沒有仔細(xì)考慮過X秒鐘的具體含義,我不引導(dǎo)行提問的話,他不會(huì)繼續(xù)展開細(xì)節(jié),但細(xì)節(jié)不確定的話是沒有辦法確定應(yīng)該怎么寫的。
這不只是現(xiàn)實(shí)一天24小時(shí)、游戲內(nèi)一天只有24000tick那么簡(jiǎn)單的比例關(guān)系的不同。
也不只是平時(shí)游戲里1秒對(duì)應(yīng)20tick,服務(wù)器/主機(jī)一卡頓就開始掉游戲刻,導(dǎo)致1:20的比例關(guān)系失效的問題。
最重要的是,游戲里的時(shí)間是可以因?yàn)楦鞣N原因各種暫停的。假如一個(gè)玩家身上記錄了某種冷卻時(shí)間,他從服務(wù)器里下線,過了一段時(shí)間再上線,離線時(shí)的計(jì)時(shí)是應(yīng)該繼續(xù)還是暫停?玩家玩單機(jī),按Esc暫停游戲,他的計(jì)時(shí)這時(shí)候還走不走?你要考慮各種情況是否會(huì)造成計(jì)時(shí)中斷。
有暫停就有恢復(fù),如果你的這個(gè)計(jì)時(shí)要能跨越存檔、讀檔,就勢(shì)必需要某種方式序列化,存進(jìn)磁盤。存盤也是個(gè)根據(jù)具體情況要具體分析的問題,足夠單開一個(gè)系列的專欄了。
以上問題全都考慮的話,出八期視頻都說不完。
根據(jù)事件的長(zhǎng)短,具體的操作內(nèi)容,以及其他的設(shè)計(jì)想法,我們需要采取不同的定時(shí)方案。常見的幾種MC處理方案包括:玩家NBT,物品NBT,LivingUpdateEvent,WorldTickEvent,玩家物品冷卻。至于計(jì)時(shí)的方式,我常用的有計(jì)數(shù)和時(shí)間戳兩種。
所謂計(jì)數(shù),就是我在某個(gè)地方存一個(gè)變量x=300,每個(gè)tick,通過某個(gè)方式讓x減1,當(dāng)x≤0的時(shí)候觸發(fā)某個(gè)效果。適合時(shí)間比較短,不介意頻繁修改這個(gè)變量(如果是磁盤IO那你就要慎重),或者時(shí)長(zhǎng)能受到干擾(比如半途續(xù)杯,冷卻被延長(zhǎng))的情況。
所謂時(shí)間戳,就是我存一個(gè)代表時(shí)間的數(shù)。他可以是mc的內(nèi)部時(shí)間,一個(gè)long類型的tick號(hào),也可以是現(xiàn)實(shí)時(shí)間,比如2023-05-08-05:22:20之類的。總之就是代表一個(gè)時(shí)刻的變量。我每個(gè)tick,都去檢查當(dāng)前系統(tǒng)時(shí)間是否在這個(gè)時(shí)間戳之后,如果是,就做某件事。時(shí)間戳怎么做可以去百度或者谷歌搜索,關(guān)鍵是這種思想。適合計(jì)數(shù)法不適合的情況。
我做一個(gè)魔法寶石,右鍵空氣使用后等待3s,之后周圍發(fā)生幾次爆炸。
乍一看非常清晰明了,但實(shí)際上有很多問題沒有考慮清楚。
如果我在這3s內(nèi)又右鍵一次,該怎么樣呢?發(fā)生兩輪爆炸,還是推遲爆炸的時(shí)間?我補(bǔ)充一下需求好了。
我做一個(gè)魔法寶石,右鍵空氣使用后等待3s,之后周圍發(fā)生幾次爆炸。在等待的3s里,該物品進(jìn)入冷卻期,不能使用。
夠清楚了嗎?不清楚。假如我有兩個(gè)寶石,他們應(yīng)該分別進(jìn)入冷卻,還是應(yīng)該使用統(tǒng)一的冷卻時(shí)間??jī)煞N其實(shí)都合理。
如果你的答案是統(tǒng)一冷卻,那么恭喜,你可以使用mc自帶的冷卻系統(tǒng),player.getCoolDownManger.setCoolDown(Item, int)。注意,相同item對(duì)象的物品會(huì)共享冷卻,這就意味著靠nbt區(qū)分不行了。比如,你做了兩把材質(zhì)完全不同的匠魂鎬,他倆的冷卻……卻是共同的。
關(guān)于細(xì)節(jié)的追問還沒完。如果我設(shè)計(jì)的冷卻是300s呢?只要把3改成300嗎?可惜,并不是。
早年間我在做《理想境》的時(shí)候,設(shè)計(jì)的“復(fù)制術(shù)”技能就是具有幾百秒的冷卻。當(dāng)時(shí)我完全不知道,玩家退出游戲后重進(jìn),此冷卻會(huì)重置。也就是說,當(dāng)你的冷卻設(shè)置的時(shí)間,遠(yuǎn)大于玩家退出重進(jìn)的時(shí)間的時(shí)候,那就會(huì)導(dǎo)致玩家靠頻繁登入登出,或者時(shí)候復(fù)活什么的去刷冷卻,設(shè)計(jì)就失靈了。玩家玩著也崩壞,設(shè)計(jì)者的目的也沒達(dá)到。所以,如果你想做個(gè)技能,冷卻超長(zhǎng)的話,還得另外處理。怎么處理?啊,那當(dāng)然是怎樣都好??梢杂胣bt把當(dāng)前冷卻列表存在玩家身上,每次玩家登錄時(shí)再手動(dòng)冷卻一次,要不然就參考其他幾種情況的做法。
如果你的答案是多件物品分別冷卻,那可就很痛苦了。最簡(jiǎn)單的想法是在ItemStack上附加一個(gè)專門的標(biāo)簽,在update或者tick事件里去手動(dòng)操作標(biāo)簽。這個(gè)方法最惡心的地方在于,物品的nbt變化時(shí),拿在手里會(huì)抖。抖。抖。煩死了。而且沒什么好方法處理。現(xiàn)在我知道怎么處理了。這個(gè)是靠給物品覆寫shouldCauseRequipAnimation為false的……當(dāng)然前提是這個(gè)物品的類是你自己寫的。要是別人或者原版的東西,那就寄。Mixin?誰(shuí)搞mixin我槍斃誰(shuí)。
如果為了解決抖動(dòng)的問題二把冷卻不存于物品nbt,那么理論上可以單獨(dú)開一個(gè)管理類去管理,但非常困難。困難在哪呢?在于很難追蹤ItemStack。
首先,判斷ItemStack的相等,是他們的各個(gè)性質(zhì)都相等,就算相等。比如你包里有兩件nbt一模一樣的物品,那么你對(duì)他們調(diào)用equal判斷,會(huì)發(fā)現(xiàn)他們算是==的。你想讓其中一件進(jìn)入冷卻,另一件不進(jìn)入,那就非常麻煩。另外,ItemStack對(duì)象是隨創(chuàng)建隨用的。很多時(shí)候,同一件道具在不同的時(shí)候?qū)?yīng)的是不同的ItemStack對(duì)象。另外,物品堆有時(shí)候是復(fù)數(shù)件東西。比如你有一堆物品,64個(gè),你把它拆成兩堆32個(gè),此時(shí),根據(jù)你操作的方式,他可能有一個(gè)是原本的ItemStack對(duì)象,也可能兩個(gè)都不是了。再比如,在鐵砧上修一下,那么ItemStack對(duì)象也換掉了。又或者,你把某個(gè)道具扔進(jìn)箱子里,離開,再回來打開箱子,此時(shí)ItemStack對(duì)象也是重新創(chuàng)建的。因此,追蹤ItemStack對(duì)象本身并不靠譜。
UUID?不錯(cuò),實(shí)體和玩家都有UUID。但是ItemStack沒有?;蛘哒f,你把鉆石鎬扔到鐵砧上修一下,拿出來的鎬子是不是同一把,該不該繼承冷卻?
先寫到這里吧。