用户:MashKJo/1.12.2模组开发教程/8.NBT系统的使用:修订间差异

// Edit via Wikiplus
(// Edit via Wikiplus)
(// Edit via Wikiplus)
|-NBTTagLongArray
|-NBTTagString
 
除去NBTPrimitive的所有NBTBase的子类,都覆写了toString方法,借此你可以轻松得到某个NBTBase实例的字符串形式,也即用于命令系统中的那种NBT的形式。
 
我们可以通过NBTBase下的<code>static createNewByType(byte id)</code>来创建新的NBTBase实例,具体类型由传入的byte幻数决定,Forge在Constants类下提供了相应的常量,用这个方法的时候直接传入Forge提供的常量即可。不过我们一般也可以自己手动new一个就行。
 
== 我们该用什么? ==
虽然那个NBTBase的继承树很长,但是我们几乎在99.9%的情况下,都只会用到NBTTagCompound和NBTTagList。前者是NBT复合标签,可以在里面再嵌套NBTBase对象;后者则是经过简单封装的专门用于存储NBTBase的ArrayList。
 
NBTPrimitive的子类、NBTTagByteArray、NBTTagIntArray、NBTTagLongArray和NBTTagString都封装了一些很常用的类型,那为什么要对它们进行封装呢?这些都是为NBTTagCompound和NBTTagList服务的。至于NBTTagEnd,它用于标记一个NBTTagCompound的结束,它的toString方法则返回<code>END</code>。正常情况下,我们不该直接构造NBTTagEnd,因为Minecraft在处理NBT数据时会自动给NBTTagCompound末尾加上一个NBTTagEnd的。一般也很少直接构造NBTPrimitive的子类、NBTTagByteArray、NBTTagIntArray、NBTTagLongArray和NBTTagString,对它们的操作一般是封装在NBTTagCompound中的setter和getter中的。
 
== 在什么时候我们会需要操作NBT? ==
那得看Minecraft中什么游戏元素的额外信息是以NBT格式保存的。实际上以NBT格式保存的游戏元素的基类都实现了接口INBTSerializable<? extends NBTBase>,它们包括但不限于:ItemStack、方块实体(TileEntity)<ref>方块实体是个只体现在代码层面的概念,用于给方块存储理论上无限的数据,它的生成与销毁是伴随着特定方块的,这块内容会在后面讲到。</ref>、实体(Entity)、世界附加数据(WorldSavedData)、村庄(Village)等等。另外,Forge引入的Capability系统,其数据的持久化,也是依托于NBT格式的。INBTSerializable的泛型参数,代表游戏元素序列化之后产生的NBTBase的实际类型。
 
== 具体应用 ==
=== NBTTagCompound ==
NBTTagCompound的底层实现,实际上是依托于一个非静态HashMap<String, NBTBase>字段 —— 这就是为何Minecraft要把某些常用的类型进行封装,毕竟设定成HashMap<String, Object>总不合适吧?
 
之前说过,该类有若干getter和setter供我们使用。如setString这一setter,该setter会根据传入的value构造出一个NBTTagString,再把传入的key和这个NBTTagString放到那个hashMap中。所以这也印证了前文所述——我们不需要直接new那些封装性质的NBTBase子类。
 
此外,该类还有诸多好用的方法,如hasKey、removeTag等等。
 
== NBTTagList ==
NBTTagList的使用很简单,它本质上就是对ArrayList<NBTBase>的简单封装,我们可以通过appendTag、removeTag这两个方法来完成对某个NBTTagList中保存的NBTBase对象的增添或移除。
 
== 实例 ==
前文说过,ItemStack的信息实际上是以NBT格式存储的。比如一把附魔了耐久三的满耐久钻石镐,其字符串形式的NBT标签应该是这样的:
 
<code>{id:"minecraft:diamond_pickaxe", Count:1b, Damage:0s, tag:{ench:[{id:34s, lvl:3s}]}}</code>
 
注意:前文所述的ItemStack的附加NBT标签,实际上在这个例子中,对应"tag"这一键名对应的复合标签——附魔的信息被存储在该复合标签中,这也印证了前面的说法。
 
虽然我们可以通过ItemStack的构造方法和addEnchantment方法构造出这么一个ItemStack,再调用serializeNBT方法即可得到该SNBT标签所对应的NBTTagCompound对象,不过笔者这里还是用代码手动构造一个,以帮助读者更好地理解:如何在代码层面构造NBTTagCompound。
 
public NBTTagCompound get(){
NBTTagCompound nbt = new NBTTagCompound();
nbt.setString("id", "minecraft:diamond_pickaxe");
nbt.setByte("Count", (byte)1);
nbt.setShort("Damage", (short)0);
NBTTagCompound tag = new NBTTagCompound();
NBTTagList enchList = new NBTTagList();
NBTTagCompound enchTag = new NBTTagCompound();
enchTag.setShort("id", (short)Enchantment.getEnchantmentID(Enchantments.UNBREAKING));
enchTag.setShort("lvl", (short)3);
enchList.appendTag(enchTag);
tag.setTag("ench", enchList);
nbt.setTag("tag", tag);
return nbt;
}
 
== 最后 ==
# 对NBT对象的构造一定要发生于逻辑服务端正在运行的时候,否则逻辑客户端肯定会抛NPE并崩游戏。
# NBTTagCompound中存储的额外信息,如果不是给Minecraft原版的代码用的,而是为你自己的Mod的逻辑用的,那么存储的额外信息所对应的NBTBase对象,所对应的键名,'''并无特别要求'''。那为什么存储附魔信息的必须是一个NBTTagList,并存于ItemStack的附加NBT中,对应的键名必须为“ench”呢?因为这是Minecraft原版读取ItemStack中附魔信息的相关代码所决定的。如果你放进NBT中的信息是给你自己的Mod用的,那键名任你取,就没有什么要求了。不过你还是最好以你的ModName为名称开个子标签,再写入你自己的数据,这是为了防止别的模组也向该NBT中添加额外数据,且键名发生冲突的情况。
行政员、​优秀编辑者、​界面管理员、​监督员、​管理员、​小部件编辑者
3,334

个编辑