MCBBS Wiki欢迎您共同参与编辑!在参与编辑之前请先阅读Wiki方针

如果在编辑的过程中遇到了什么问题,可以去讨论板提问。

为了您能够无阻碍地参与编辑 未验证/绑定过邮箱的用户,请尽快绑定/验证

MCBBS Wiki GitHub群组已上线!

您可以在回声洞中发表吐槽!

服务器状态监控。点击进入

本站由MCBBS用户自行搭建,与MCBBS及东银河系漫游指南(北京)科技有限公司没有从属关系。点此了解 MCBBS Wiki 不是什么>>

用户:MashKJo/1.12.2模组开发教程/6.ItemStack和Meta-hack

来自MCBBS Wiki
MashKJo留言 | 贡献2024年12月6日 (五) 19:15的版本 →‎ItemStack对象的使用:​ // Edit via Wikiplus
跳到导航 跳到搜索

前文说过,Item这一类型遵循享元设计模式——由此可以看出,每一个Item实例代表的其实是一个特定的物品类型。

那么读者肯定就要问了:那我们在游戏里,跟背包里的物品打交道的时候,不仅能得到背包里每个格子里的物品的种类信息,还能得到数量的信息啊!但是Item类中似乎没有代表堆叠数量的字段,这是什么回事呢?所以由此可见,Item实例所能提供的信息还是不够多。因此,实际上玩家在世界中,在99%的情况下,是和ItemStack对象而非Item打交道。

ItemStack包含的信息

一个ItemStack中所包含的信息有:物品种类(Item)、堆叠数量(int)、meta值(int)和附加NBT(NBTTagCompound)。物品种类和堆叠数量都不难理解,至于这个meta到底什么含义?这会在本节的最后一部分揭晓的,还请读者耐心看下去。至于附加NBT,这个熟悉原版命令的读者应该比较了解,在游戏中它一般是以一种类似JSON文件的格式出现的,即字符串形式的NBT,这块的内容可以参考Minecraft Wiki的有关条目;但在代码层面,NBT有着另一套用法。关于NBT的知识,后面会有专门一节来进行讲解。

ItemStack有着8种构造方法,其中有4种是我们会经常用到的:(Item item)(Item item, int count)(Item item, int count, int meta)(Item item, int count, int meta, @Nullable NBTTagCompound nbt)。还有3种构造方法是把刚才所述的前3种构造方法的第1个形参换成了Block,实际上这3种含Block的构造方法会自动获取传入的方块的物品形式(ItemBlock),再调用那3个含Item的构造方法——没错,我们认知中的拿在玩家手中的方块,实际上是方块的物品形式;被放置在世界中的方块,才是真正的方块。

还有个构造方法,只传入一个NBTTagCompound,这是怎么回事呢?实际上MC在序列化(Serialize)物品数据时,会先把ItemStack的物品类型、堆叠数量、meta值和附加NBT汇总起来,形成一个新的最终NBT,再进行存储操作;而这个构造器实际上就是执行了把最终NBT解成ItemStack的流程。

警告:ItemStack的附加NBT有可能为null,除非你调用含附加NBT的构造方法构造ItemStack,或调用setTagCompound显式指定附加NBT,亦或者这个ItemStack已经通过附加NBT存储了一些数据。所以用附加NBT前要先判空,否则大概率会抛NullPointerException崩游戏。

ItemStack对象的使用

ItemStack对象和Item不同,它是随建随用的。永远不要尝试去继承ItemStack类,因为这么做没有意义——事实上你也不可能去继承它,因为它是一个final class。

在设计物品的功能时,我们自然而然地会有给物品添加附加数据的需求。首先先明确一点——永远不要让附加数据以Item实例的实例变量的形式存在,因为Item代表的是物品类型,牵一发而动全身,数据的变化会影响所有物品类型为该Item实例的ItemStack——所以你应该把目光放在ItemStack的那几个字段上。能存储额外信息的字段,其实只有两个:Meta和附加NBT。但是除非你要存储的数据是该物品的损害值/耐久值,否则,通常不推荐用meta存储额外数据,因为它本身不是什么复杂的引用类型变量,只是一个int整数罢了,而且它的使用颇有些hack的味道(甚至于1.13开始它直接被抹去了)。因此,还是把目光放在附加NBT上为好。

另外,读者如果翻阅过ItemStack类的源代码,可能会发现该类中有个特殊的静态字段:EMPTY。它其实是代表“空”的ItemStack,即对应的Item为null的情况——实际上它被赋予的值就是new ItemStack((Item)null),如果对ItemStack.EMPTY调用getItem方法的话,却不会返回null,而是会返回Items.AIR(没错,这其中有一个三目运算符在判空)。

那么,Items.AIR又是何方神圣呢?望文生义,它似乎是空气方块的物品形式?唔,这个解释,对,也不对。因为所有作为方块的物品形式的物品,都是ItemBlock类的实例,但Items.AIR是ItemAir类的实例,ItemAir类却是直接继承了Item,并未继承ItemBlock——所以我们可以说,空气方块不存在对应的物品——这也是为什么在几乎所有Minecraft版本中我们均不能获得空气物品(除了极个别快照版本)。但另一方面,ItemAir类的构造方法接受一个Block参数,在Item类的registerItems这一静态方法中,Items.AIR又被这样赋了值:

registerItemBlock(Blocks.AIR, new ItemAir(Blocks.AIR));

Mojang的意思似乎又是:Items.AIR就是Blocks.AIR的物品形式,着实让人大惑不解。更何况既然都给ItemAir取名为“空气物品”了,又何必多此一举让一个Block传入进去呢?笔者对此属实是不能理解,只能理解为Items.AIR纯粹是Mojang用于占位的一个Item罢了,恰巧“无”的概念在Minecraft中和空气是相近的。

此外,下面列出了一些ItemStack类常用的方法,这些方法在实际开发中通常会很实用:

  • void addEnchantment(Enchantment ench, int level):用于给某个ItemStack添加一个固定等级的附魔,注意ItemStack类中并没有一个非静态的Map<Enchantment, Integer>字段来保存ItemStack对象的附魔情况,所以这个方法是怎么实际发挥作用的呢?答案是附魔信息存在了附加NBT里。
  • ItemStack copy():用于构造出一个各方面都与该ItemStack对象一样的另一个不同的ItemStack对象——只要读者懂得Java中关于变量、引用、对象的知识,就知道这个方法是用来干啥的
  • ItemStack setStackDisplayName(String displayName)void clearCustomName():设定或移除某个ItemStack的自定义名称——实际效果跟把某个ItemStack放在铁砧里重命名是一样的,自定义展示名这个参数同样是储存在附加NBT中的。注意!这里不能做本地化处理——实际上你做了也没用,比如你给这个自定义名称设定一个本地化键名“tutorial_mod.custom_name”,并在语言文件中写好相应的本地化文本,你在游戏中看到的这个ItemStack只会显示未本地化的原始键名
  • boolean isEmpty():判断该ItemStack是否为空,即是否和ItemStack.EMPTY“相等”——实际上这个词用得不好,因为ItemStack类并未覆写equals方法,实际上这里实际的判断是先判断this == ItemStack.EMPTY,再判断当前对象的物品类型

此外我们也可以看看Item类中那些未讲到的getter了。实际上这些getter很多都有传入一个ItemStack参数,所以如果你想实现Item属性的高级控制,你可以不用那些简单的setter,而是通过覆写这些getter,结合传入的ItemStack的信息来操作返回结果,借此我们可以实现一些很酷炫的效果。

以及你可能已经发现了,ItemStack类中有很多方法是和Item重合的,这些方法一般都通过getItem方法来代理给ItemStack对象对应的Item来做。