Mod里制作计时器(1)
很多人都在湖中群问过怎么制作一个定时的效果,即,在触发某条件后,X秒钟后执行某个操作。每次我被问到都会非常烦躁,因为很遗憾,这个问题并没有统一的答案。
时间是什么?
不,不是哲学问题。游戏内的时间与现实时间不同。
问这个问题的人很多时候头脑一片空白,根本没有仔细考虑过X秒钟的具体含义,我不引导行提问的话,他不会继续展开细节,但细节不确定的话是没有办法确定应该怎么写的。
这不只是现实一天24小时、游戏内一天只有24000tick那么简单的比例关系的不同。
也不只是平时游戏里1秒对应20tick,服务器/主机一卡顿就开始掉游戏刻,导致1:20的比例关系失效的问题。
最重要的是,游戏里的时间是可以因为各种原因各种暂停的。假如一个玩家身上记录了某种冷却时间,他从服务器里下线,过了一段时间再上线,离线时的计时是应该继续还是暂停?玩家玩单机,按Esc暂停游戏,他的计时这时候还走不走?你要考虑各种情况是否会造成计时中断。
有暂停就有恢复,如果你的这个计时要能跨越存档、读档,就势必需要某种方式序列化,存进磁盘。存盘也是个根据具体情况要具体分析的问题,足够单开一个系列的专栏了。
以上问题全都考虑的话,出八期视频都说不完。
根据事件的长短,具体的操作内容,以及其他的设计想法,我们需要采取不同的定时方案。常见的几种MC处理方案包括:玩家NBT,物品NBT,LivingUpdateEvent,WorldTickEvent,玩家物品冷却。至于计时的方式,我常用的有计数和时间戳两种。
所谓计数,就是我在某个地方存一个变量x=300,每个tick,通过某个方式让x减1,当x≤0的时候触发某个效果。适合时间比较短,不介意频繁修改这个变量(如果是磁盘IO那你就要慎重),或者时长能受到干扰(比如半途续杯,冷却被延长)的情况。
所谓时间戳,就是我存一个代表时间的数。他可以是mc的内部时间,一个long类型的tick号,也可以是现实时间,比如2023-05-08-05:22:20之类的。总之就是代表一个时刻的变量。我每个tick,都去检查当前系统时间是否在这个时间戳之后,如果是,就做某件事。时间戳怎么做可以去百度或者谷歌搜索,关键是这种思想。适合计数法不适合的情况。
我做一个魔法宝石,右键空气使用后等待3s,之后周围发生几次爆炸。
乍一看非常清晰明了,但实际上有很多问题没有考虑清楚。
如果我在这3s内又右键一次,该怎么样呢?发生两轮爆炸,还是推迟爆炸的时间?我补充一下需求好了。
我做一个魔法宝石,右键空气使用后等待3s,之后周围发生几次爆炸。在等待的3s里,该物品进入冷却期,不能使用。
够清楚了吗?不清楚。假如我有两个宝石,他们应该分别进入冷却,还是应该使用统一的冷却时间?两种其实都合理。
如果你的答案是统一冷却,那么恭喜,你可以使用mc自带的冷却系统,player.getCoolDownManger.setCoolDown(Item, int)。注意,相同item对象的物品会共享冷却,这就意味着靠nbt区分不行了。比如,你做了两把材质完全不同的匠魂镐,他俩的冷却……却是共同的。
关于细节的追问还没完。如果我设计的冷却是300s呢?只要把3改成300吗?可惜,并不是。
早年间我在做《理想境》的时候,设计的“复制术”技能就是具有几百秒的冷却。当时我完全不知道,玩家退出游戏后重进,此冷却会重置。也就是说,当你的冷却设置的时间,远大于玩家退出重进的时间的时候,那就会导致玩家靠频繁登入登出,或者时候复活什么的去刷冷却,设计就失灵了。玩家玩着也崩坏,设计者的目的也没达到。所以,如果你想做个技能,冷却超长的话,还得另外处理。怎么处理?啊,那当然是怎样都好。可以用nbt把当前冷却列表存在玩家身上,每次玩家登录时再手动冷却一次,要不然就参考其他几种情况的做法。
如果你的答案是多件物品分别冷却,那可就很痛苦了。最简单的想法是在ItemStack上附加一个专门的标签,在update或者tick事件里去手动操作标签。这个方法最恶心的地方在于,物品的nbt变化时,拿在手里会抖。抖。抖。烦死了。而且没什么好方法处理。现在我知道怎么处理了。这个是靠给物品覆写shouldCauseRequipAnimation为false的……当然前提是这个物品的类是你自己写的。要是别人或者原版的东西,那就寄。Mixin?谁搞mixin我枪毙谁。
如果为了解决抖动的问题二把冷却不存于物品nbt,那么理论上可以单独开一个管理类去管理,但非常困难。困难在哪呢?在于很难追踪ItemStack。
首先,判断ItemStack的相等,是他们的各个性质都相等,就算相等。比如你包里有两件nbt一模一样的物品,那么你对他们调用equal判断,会发现他们算是==的。你想让其中一件进入冷却,另一件不进入,那就非常麻烦。另外,ItemStack对象是随创建随用的。很多时候,同一件道具在不同的时候对应的是不同的ItemStack对象。另外,物品堆有时候是复数件东西。比如你有一堆物品,64个,你把它拆成两堆32个,此时,根据你操作的方式,他可能有一个是原本的ItemStack对象,也可能两个都不是了。再比如,在铁砧上修一下,那么ItemStack对象也换掉了。又或者,你把某个道具扔进箱子里,离开,再回来打开箱子,此时ItemStack对象也是重新创建的。因此,追踪ItemStack对象本身并不靠谱。
UUID?不错,实体和玩家都有UUID。但是ItemStack没有。或者说,你把钻石镐扔到铁砧上修一下,拿出来的镐子是不是同一把,该不该继承冷却?
先写到这里吧。