Mod里制作计时器(2)
我本来是要接着昨天的事说起ItemStack判定相等的,但恰好有新人的提问与计时相关,我就改变了看法,决定先说点别的。
第一个问题:前后端
或者直观一点来说,isRemote。当你在做一个事情的时候,你要判断这个东西是在前端(客户端,isRemote==true)还是后端(服务端 !isRemote)。多数逻辑操作(比如扣除物品耐久)都需要在后端执行,因此当你在使用LivingUpdateEvent等事件的时候,要记得判断当前是在哪一端,避免两个端各执行一次。这样,原本一秒执行20次的事件,就变成一秒40次(前后端各20次)了。前后端也是一个值得展开讨论出四五篇专栏的事,这里先说这么多。
第二个问题:你的计数变量到底都有谁再用
模组制作新手,包括曾经的我在内,都曾经错误地设计类的成员变量。如果你在函数体内设计一个局部变量,那么这个变量一般不会出什么理解的问题(当然也有人把ItemStack给搞出传值传引用分不清的问题,但这个先不说了),使用他的地方就在这个函数里。
但是,如果你把类里自己写一个变量,比如叫什么int tickCounter,那往往问题就大了。不错,在初学编程的时候,我们是要把各种问题与概念抽象成int、float、string这种计算机变量予以处理,这些变量只会用于解决我们要做的那道题,并且只有短短的那一小段程序使用。
不过,MC里有很多类使用了享元设计,典型的是Item、Block、Enchantment、Biome、DimensionType,还有玩家自己的事件监听函数收容类EventXXXX;典型的非享元是ItemStack、Entity,TileEntity。
如果你在Item里开了一个成员变量int counter,那么多数人一定是像这样顺着写下去:
乍一看很直白,在背包里这个ItemTimerStaff就会开始充能,充到99个tick(5秒)就会做某件事。粗略一测试,你可能会发现确实如你所想。
对,没错,onUpdate这个函数确实是在背包里,每秒钟执行20次。但是你有没有想过,所有的这类物品,他们使用的都是同一个ItemTimerStaff对象?如果你背包里有两个分开放置的这东西,那么update每秒就会执行40次,破坏这个东西1秒20次的对应关系。
你可能会说,有两个就让他双倍充能,也不是不行。
那么,如果你有十个同类物品堆叠在一起,那么因为是每一堆都执行一次update,所以结果还是每秒20次,分成两堆就变成40次。你现在还觉得并无不可吗?
你可能会说,我这物品和斧子一样,堆叠上限是1,所以不会有重叠物品的问题。那么如果两个玩家背包里各有一件这个物品,那么因为他们共享了同一个ItemTimerStaff对象,他们两个的充能是同步的,而且是互相都会充能。简直是乱了套了。
最可怕的是,新人往往写出了这种有严重问题的代码而全然不自知,测试一下还自我感觉良好,把隐患埋了下去。还有一些猖狂点的,甚至会自称这代码没问题,跟有经验的modder顶嘴,后果不堪设想。
那么正确的方式是什么呢?
正确的方法是意识到每一堆物品共享Item对象,但是都有自己的ItemStack对象。如果想给每件或者说每堆物品标记自己的counter,应该把东西存进ItemStack的信息里。通用一点的方式是存进nbt,粗暴一点的方式是使用耐久条标记充能进度,这两种方式都可以。更好的是,这两种方式都可以被mc保存,不用处理SL的序列化、反序列化问题。注意,这种情况就是典型的跟物品不跟玩家。我张三把法杖充好了不用,直接扔给李四,李四就能吃现成的。因为冷却记在了物品而非玩家身上;如果张三把法杖用掉了,扔给李四,那李四还是要充能。
但是,如果希望某一个玩家的某一类物品共享充能,就不能通过写入ItemStack弄了。假如有个传送宝石,一使用就可以瞬移。连续使用太变态了,于是我们给他设一个十秒冷却。但是结果有一个玩家,它直接搞了一排的传送宝石,轮流用,结果又是一秒传送一次了。当然了,不管怎么说,这种情况,张三使用完了,不应该耽误李四使用。
这种情况下,这个冷却的数据就应该记录在玩家上。反正肯定不是你随便在哪扔一个int变量就能解决的。
你可以选择随便找个地方设一个字典对象,比如一个HashMap,里面存<Player,Integer>的映射,或者更加稳妥一点,选择玩家的UUID(因为掉线、复活后Player会被重新构造成新的对象,不会一直沿用)做一个<UUID,Integer>的映射。当然,你自己的变量只会存在内存里。如果服务器关闭,你得自己设法存储。才囚学园的mod“定分仪”存储玩家的冷却表就是用的这种方式,序列化的时候会存在TileEntity的NBT里。如果你是跟着玩家,你可以序列化到玩家的持久化NBT里,或者……随便找个文件IO一下。
你可以选择使用原版的playerCoolDownManager。但正如我说,这东西下线就清空,不要存太长的东西。
你甚至可以选择给玩家挂nbt。缺点是实体的nbt前端感受不到,想用于控制显示的时候这玩意不太好使。
给玩家挂buff等游戏内间接方式也不是不行,只不过小心玩家一灌牛奶,或者用什么其他的东西把你这个buff给清掉。
……正如你所见,方法有很多,各有各的长短。今晚先说到这里吧。累了。