本帖最后由 Dahesor 于 2021-9-8 03:37 编辑

[命令] 命令教程“真”从零开始 ( X ) 我就不信不能用大白话讲清楚NBT


声明:
1. 本系列教程默认读者拥有关于Minecraft游戏本身的基础了解。
2. 本系列全部教程绝对适用于当前Java最新版(1.17)。
3. 本系列教程致力于基础原理而非使用方法,因为某些原因,这是本声明里最重要的一条。


你可能发现,本帖上方的声明少了一条。
对,就是那条”本教程默认读者读过之前的内容“的那一条。
本帖是特例。下一帖那个声明还会回来的。
为什么没了呢?
因为NBT是一个很多人在问的问题,而我想让这一帖再普及一些——
所以,在本帖中,我会尽可能地减少看懂它所需要的知识背景——
让我们开始吧。




前言
  • 浮空文字。
  • 自定义怪物。
  • 锋利lv.9999的钻石剑。
  • 自定义刷怪笼。
  • 可以让玩家穿过的“幽灵方块”。
  • 自定义村民。
  • 自定义装备属性。
——打开任何一个我的世界的交流社区,你见到的最多的问题无外乎以上几个。(现在好点了,以前这是到处都是。)

所有这些东西,使用我们今天的教程——你都可以明白如何制作。
我们使用的技术,叫做 “NBT”
我在这里保证只要你能从头到尾读完就一定能明白NBT怎么用。
我就不信不能用大白话讲清楚NBT了——
——如果你看完玩本帖还是不明白,我就去吃【哔——】。停停停,不能插旗,毕竟万事有特例。
而且,NBT不是什么特别难的东西——我完全不用发此毒誓嘛。


但先不要着急。在说明NBT之前,我们得先了解,这玩意可以用在什么上面。
所以,在开始前,请容我花费较少的篇幅介绍一条指令——

1. 召唤指令

你知道么,我这人是很少用刷怪蛋的,因为我早已熟悉使用/summon指令来生成 实体

本指令可以在确定的位置,生成一个实体,并可指定NBT标签:
格式:
  1. /summon <实体ID> [<x> <y> <z>]
复制代码
(指令/summon格式,简化版)

如果你有好好看系列之前的内容,本指令的含义不言而喻:
只要将"<实体ID>"替换为一个实体,即可在后方的坐标处将其召唤。
比如,烈焰人的ID是“blaze”,所以:

例:
  1. /summon blaze 10 5 -412
复制代码
在坐标(10, 5 , -412)处召唤一只烈焰人。

当然,我告诉你烈焰人的ID是blaze,那么如何找到你想要的ID呢?
比如,僵尸的。
各位可以点此链接前往Wiki这里来查询所有实体的ID:


不过嘛,我非常好心地把这些ID列了出来。
为避免篇幅太长,这些ID被折叠了起来:
看看这些实体吧……你知道画和物品展示框实际上是实体而非方块吗?



你可能注意到了位置是个可选的元素。当你不指定坐标时,这个实体会被召唤在指令的执行地:
  1. /summon tnt
复制代码
在执行者的位置召唤一个已激活的TNT
(呵呵,你得小心了)

同理,“ zombie ”是僵尸:
  1. /summon zombie
复制代码
在执行者的位置召唤一个僵尸。 【注1】



现在,就进入正题了——
你所召唤的僵尸,只是一个普普通通的僵尸,受重力影响,也没有穿着很酷的装备——
因为你没有指定其NBT。
当不指定一项NBT时,游戏将将其设定为它的默认值。


下面,就让我们来探讨一下,如何加入NBT。

2. NBT



NBT(二进制命名标签, N amed B inary T ags) 是Minecraft中用于向文件中存储数据的一种存储格式。
NBT (Named Binary Tag) is a tag based binary format designed to carry large amounts of binary data with smaller amounts of additional data.

NBT是一种基于二进制的存储格式,可以用少量的代码来存储大量的信息。

——Notch
上面的是Notch对于NBT的做出的规范。
然而—— 跟我们一点关系都没有。

实际上,上面说的“NBT”是一种经过编译后的,人类完全看不懂的语言。
而我们今天要说的,是这种语言的“人类能看懂的格式”。
这种格式叫做 SNBT(字符串化的二进制命名标签, S tringified NBT
本格式与上面的“.nbt”表达的是一种东西,只是用的是不同的两种语言罢了——一种给人读,一种给机器读。

——但是你也不用担心什么时候是不是该叫“SNBT”,什么时候又该叫“NBT”——
在命令相关的语境中,“NBT”总是在指其SNBT的格式。

2.1 标签,与键值对

那么,回到正题上来。
NBT长啥样呢?它规定了什么?又如何加入到命令中呢?

我在本系列教程的第1帖提到过一个有关奶的例子。
这个例子说的是,如何精确地描述一杯奶。
我们在这里,可以使用像下方所示的一样,用多个项目与值来描述这杯奶:

  • 奶的种类 牛奶
  • 奶的量 400ml
  • 装奶的容器是否密封
  • 奶的产源 卖奶的老奶奶
  • 离过期还有 约3天

这样一来,你应该对我桌子上放的这杯奶有点了解了。

NBT差不多就是一样的东西:
让我们简单地描述某一只僵尸:

  • 当前生命值 16.8滴血 #
  • 是不是小僵尸:
  • 是否可以破坏门:
  • 剩余着火的时长: 5秒(100游戏刻)
  • 是否无敌:
  • 转变成溺尸之前的剩余时间 * 10秒(200游戏刻)
  • 头部的装备: 钻石头盔
  • 腿部的装备:……
  • 面向的方向:……

注释:
#此处的生命值为当前生命值,而非生命值上限。注意虽然不会显示,但是生命值是有小数的。
*当僵尸在水中时,会在一定时间后转变为溺尸。

由于可以说的东西太多,我只列了一些。但各位可以从上面的描述中,想象出一只确定的僵尸了吧。
NBT说的大概就是这样的东西。
上面的每一个 描述 ,都被称为“ 键值对(Key-Value Pair)

这是个挺起来好像很强的名字,但实际上说的只是——
冒号 前面的“ 描述 ”被称为 “键” ,也叫 “标签名” ——通俗地说就是“ 描述了本项目在 讲什么
冒号 后面的东西就是 ——通俗地说就是 “对于这个 描述 (或者说键 标签名 ),我们要它是 多少
因为 “键”与 “值” 总是 成对出现 ,所以我们叫每一对为 “键值对”

相信你理解了吧。

那接下来的事情非常简单,我们只需要把上面的描述换成 NBT所接受的特定的英文 的就好了。

方便起见,让我们把上面的列出的9个键值对简化为以下4个:

  • Health : 16.8                                  (当前生命值16.8滴血)
  • IsBaby: 1                                       (是小僵尸)
  • Fire: 100 ( 剩余着火事件(刻): 100游戏刻 )
  • DrownedConversionTime: 200 ( 转变成溺尸之前的剩余时间为 200游戏刻)

我们只是把上面的中文换成了特定的英文罢了。

——以上面的“
转变成溺尸之前的剩余时间 ”为例——
  1. DrownedConversionTime:200
复制代码
恭喜你,得到了第一个NBT。
这个标签描述了一个只剩10秒(200刻)就会变成溺尸的僵尸 【注2】

那么,举一反三,看一下剩下的3个吧。

生命值 Health ”是 16.8 滴血。
燃烧时间 Fire ”是 100 游戏刻(20秒)。
至于最后的是否是小僵尸“ IsBaby ”,对于这种“要么是要么不是”的真或假二选一问题。我们 用0代表否,1代表是
所以, 是否是小僵尸 IsBaby ”为 1 (是小僵尸)。

所以说,我们很轻松地就得到了以下三项:

Health : 16.8
Fire : 100
IsBaby : 1

所以上面的三对标签分别描述了“生命值为16.8,剩余燃烧时间100刻”,与“是小僵尸”喽?
不不不,不是。我们不这样写。
我们要加上一些东西:

Health : 16.8 f
Fire : 100 s
IsBaby : 1 b

啊?
什么鬼。
哪三个字母“ f ”,“ b ”,“ s ”是什什么意思?
为什么要在特定的标签后添加这3个字母?为什么前面的“ DrownedConversionTime:200 ”后面就什么都不用添加? 【3】
这些字母是“ 数据类型(Data Type) ”。

2.2 数据类型

什么是加在数据后面的字母?
这种加在数字后面的字母你应该不陌生。
比如——
400 L
—— 400升

我的意思是,那些数字后的字母,有点像是这个数字的“单位”。
当然,这完全不是一种东西。
它们是“ 数据类型 ”。
什么是数据类型?

容我这么解释:

电脑存储数据是需要空间的,而不同的数据需要的“ 空间 ”的 大小 类型 有所不同。

比如说,“ 是否是小僵尸 ”这个标签只有两种可能—— 要么是 要么不是 。我们只需要一个很小的空间就能把它存进去——因为只有两种可能,0或1。
但,像是“ 着火时间 ”这个标签所需要的电脑空间就要大得多,因为我们要存储可能的大量不同的时间值,而不仅仅是“0或1”。
但由于“ 着火时间 ”这个标签的单位是游戏的最小时间单位“游戏刻”,所以,是不可能出现小数的。
这一点就和“ 生命值 ”不同。虽然我们无法在血条上看到小数的生命值,但是只要你对游戏有一定的理解就知道生命值是有小数的。
这里就又是一个不同之处。“ 着火时间 ”是不会有小数的,我们为它分配的存储空间也不需要担心小数的问题,可以节省很多空间。
但是“ 生命值 ”是有小数的,我们需要一个不同类型的空间来存储这个数值。
以上这多种不同的存储空间,就被称为“ 数据类型(Data Type)

上方在数字后面的字母,就代表了, 该标签使用的是什么样的数据类型
我们共有7种不同的数据类型,用于存储不同种类,不同大小的数据 ,均列在下表:

图标 英文 学名
白话 格式 储值范围 备注
byte.png Byte 字节型 只能存非常小的整数 <数字> b -128~127 上面的“ IsBaby : 1 b ”后方的“ 1 b ”就是本类型
short.png Short 短整型 能存较小的整数 <数字> s -32768~32767 上面的“ Fire : 100 s ”中的“ 100 s ”就是本类型
Int.png Int 整型 存储整数 <数字>
不需要添加字母后缀
-2147483648~
2147483647
上面的“ DrownedConversionTime : 200 ”中的 200 就是本类型。注意这种类型没有字母后缀。
long.png Long 长整型 存很大的整数 <数字> l

-2 63 ~2ˆ 63 -1 本类型与上面的Int一样用于存储整数,但是范围要大得多。
f.png Float 单精度浮点型 小数 <数字> f 根据数据精度而定,最大为3.4*10 38
上面的“ Health : 16.8 f ”中的“ 16.8 f ”就是本类型。
d.png Double 双精度浮点型 范围更大的小数 <数字> d 根据数据精度而定,最大为1.8*10 308 本类型与上面的Float一样用于存储小数,但是范围要大得多。
string.png String 字符串 一串文字 " 字符 " ' 字符 '
单双引号均可。
65,535个字节(大部分中文占用3个字节) 存储一串文字。有时会使用JSON文本。


就这样。很简单。
生命值 是" 单精度浮点型 ",或者说“ 小数 ”,所以我们要在后面加上“ f ”:
(先不要管该如何知道一个标签的数据类型是什么的问题,这个我们后面说。)
剩下的3个标签同理:

Health : 16.8 f
Fire : 100 s
IsBaby : 1 b
DrownedConversionTime : 200

恭喜!
你终于得到了真正的标签。
上面的4对标签正确地描述了一只僵尸的生命值,燃烧事件,小僵尸,以及转化溺尸的时间。
但这里注意,冒号“:”是英文的冒号,千万别打错成中文的。
还有,NBT是大小写敏感的,别看错大小写字母,但是数据类型的后缀是不需要区分的。IsBaby:1b与IsBaby:1B都可以。


接下来,我们要做的就是把它们安进命令里就好了:
让我们看向,拥有NBT时的/summon指令格式:

  1. /summon <实体> [<x> <y> <z>] [<NBT>]
复制代码

指令格式标出了NBT应该被加入的位置——命令最后面。

欸欸额,别着急动手,我们还有一个问题——怎么添加NBT?
<NBT>是一个元素。但是我们要添加的标签足足有上面的4个。
总不能写
"/summon zombie ~ ~ ~ Health:16.8f Fire:100s IsBaby:1b DrownedConversionTime:200"
吧。
我们该如何将4个标签整合成一个元素?
要用到的东西是, 复合标签(Compound)




2.3 复合标签


所谓复合标签,实际上就是把多个键值对整合为一个元素。

听起来好像很高大上,但实际上非常简单:

就像我们有一堆文件,怎么打包处理呢?只要放进文件夹里就好了:

使用 “ {} ”作为开头与结尾,不同的标签间用“ , ”相隔

这玩意你只要看一下例子就知道了。
该如何把我们上面的4个标签组合为1个复合标签呢?
根据上面的说明,十分简单。
我们用大括弧”{}“代表开头与结尾:
{}
再把上面的4个标签装进去:

{ Health : 16.8 f Fire : 100 s IsBaby : 1 b DrownedConversionTime : 200 }

最后用 英文的逗号”,“ 将不同的标签隔开:

{ Health : 16.8 f , Fire : 100 s , IsBaby : 1 b , DrownedConversionTime : 200 }

完成。
你得到了一个真正的NBT! (而不是刚才的”真正的标签“)
简单地把上面的东西按到命令中——

  1. /summon zombie ~ ~ ~ {Health:16.8f, Fire:100s, IsBaby:1b, DrownedConversionTime:200}
复制代码
在原地召唤一只生命值16.8,会继续着5秒的火,并会在10秒后变成溺尸的小僵尸。

有一件要注意的事情是,在NBT这对”{}“中是无视空格的。所以我在每一个“ , ”后都加了一个空格,为了便于阅读。
还有,NBT的顺序是完全无所谓的,哪一个放前面都行。
就这样。


同样,我们可以生成一只无敌而且有手臂的盔甲架。
ShowArms ”是是否拥有可以持有东西的手臂,是字节型“ Byte ”。
Invulnerable ”是是否无敌。同样是字节型“ Byte ”。
所以,根据上面的例子举一反三——
  1. /summon armor_stand ~ ~ ~ {Invulnerable:1b,ShowArms:1b}
复制代码
生成一个无敌且有手的盔甲架。


2.4 复合内的复合标签


你可能知道有一条命令叫做“/give”,其作用是给予玩家物品:
  1. /give <目标> <物品ID> [<数量>]
复制代码


非常简单,“<目标>”应是一位在线玩家的ID或选择器。物品ID与在第9帖讲过的方块ID是一个东西,只要按下F3+H打开更详细的提示即可查看(详见第 系列第十帖
数量为该物品的数量,不输入默认为1个:
例:
  1. /give @a stone 10
复制代码
给与所有玩家10颗石头。

  1. /give Dahesor diamond
复制代码
给予Dahesor(我)1颗钻石。

很好理解,不多说。
各位,本指令也是可以添加NBT的。
但是你可能会发现,命令格式中并没有NBT的位置。
在本命令中,NBT应被添加到物品ID的后面,没有空格,因为这相当于是一个元素:
  1. /give 目标 物品ID{NBT} 数量
复制代码

比如,弩有一个标签Byte类型的标签“Charged”用于表示弩有没有上弦。
根据1为“真”,0为“假”的规律,我们就很容易得到:

  1. /give @a Dahesor minecraft:crossbow{Charged:1b} 1
复制代码
给予所有玩家一个上弦的弩(虽然射不出来,因为没有指定装填了什么。)

好,你应该理解了。


接下来,你可能知道,掉落物也是一种实体,其实体ID为“item”。
我们也是可以用上面说过的/summon指令在某处召唤一个掉落物的。

但这很奇怪,因为“item”只是物品的意思,并没有说“这是什么物品”。

这是因为,“ 是什么物品 ”这项信息被包含在了它的NBT" Item "中。这是一个 字符串 的数据类型。
比如,我们要生成一个掉落的物品。什么物品呢?就是上面那个上弦的弩。
那么,它写作
Item : " crossbow "
Item 为标签名“是什么物品”,而 crossbow 则是"弩"。
因为这是一个字符串。所以根据上面的表格,我们要把它用双引号引起来(单引号也可以)。



接下来,我们还可以在其中包含“ 是否上弦 ”这个信息,就像我们在/give指令中做的那样——

Charged : 1 b

我们可以生成一个“掉落在地面上的,上弦的弩”。
同理,我们也可以包含“这一坨掉落物有几个”这个信息——

Count : 2 b

其中,“ Count ”为标签名,即“数量”的意思。2b则是一个Byte类型的值,代表了有两个。

但现在问题来了。
什么物品 ”是“ 掉落的物品 ”这个实体的NBT。
但是“ 弩是否上弦 ”又是“ 物品 ”的NBT的一部分。
我们该怎么办?
难道是写:
/summon item ~ ~ ~ {Item:"minecraft:crossbow", Charged:1b, Count:2b}
这样?

不不不,不是不是。
这样乱了主次了。
是否上弦 ”,与“ 数量 ”是用来形容物品的,而不是掉落的物品这个实体的!这样直接写乱了主次。

这怎么办呢?

答案很简单——我们在这个大的复合标签里,再套一个复合标签:

{ Item : { id : " minecraft:crossbow " , Count : 2 b } }

最外侧的,被我用绿色,较大号字体标记出的,就是NBT复合标签的那对大括号。
里面的“ Item : ”则是标签名。
其冒号后面的内容就是它的值。但是在这里我们无法简单地使用一个数字来表示它的值,
所以这里,值是又一个复合标签的大括号,就是紫色的那对。
其中包含了两个另外的两对键值对:
id : " minecraft:crossbow " 告诉我们“ 这是一个弩 ”。id就是“物品ID”的意思。
Count : 2 b 告诉我们,“ 弩有两个 ”。
这样就对了。 id Count 分别是两个不同的标签,但是两者整个又是 Item 的值,也叫 子标签

你可想象成,在文件夹里夹另一个文件夹。

所以:
  1. /summon item ~ ~ ~ {Item:{id:"minecraft:crossbow", Count:2b}}
复制代码
召唤一个掉落物,为两个弩。

欸欸,等等,我们不是说好了要包含“ 是否上弦 ”这个信息的么?
那,各位,怎么加入这个NBT?
这样?:

{ Item : { id : " minecraft:crossbow " , Count : 2 b , Charged : 1 b } }

哦不不不不。这样主次又乱了
我们的“ 是否上弦 ”是用来形容“弩”的!
如果像上面那样写,就变成是 形容物品 的啦。
仔细看看就能明白,以下三个信息根本就不是并列的:
  • 物品为弩。
  • 数量两个。
  • 弩上弦了。

那怎么办呢?
简单,我们在这个被套进 复合标签 复合标签 中,再套一个 复合标签

{ Item : { id : " minecraft:crossbow " , Count : 2 b , tag: { Charged : 1 b } } }

请注意我用字体大小表示出的主次关系。
我们新增的标签 tag: ,即“标签”,描述了本物品包含的标签。
物品包含了什么标签呢?

Charged : 1 b
弩已上弦。

所以:
  1. /summon item ~ ~ ~ {Item:{id:"minecraft:crossbow", Count:2b, tag: {Charged:1b}}}
复制代码
召唤一个掉落物,为两个上弦的弩。


这样,你懂了吗?
没懂?
我再把上面的东西换种写法:

物品(Item):
物品ID(id):弩(crossbow)
物品的数量(Count):2(2b)
物品的NBT(tag):
是否装填(Charged):是(1b)


这样,是不是有立体感了?


2.5 列表


举个例子。

如何召唤一个拥有特定旋转角度的僵尸?
比如说,面向北方,仰角为30度什么的。
当然当然,我们可以召唤后再用 /tp 指令来进行旋转。
但是这里说的是,直接召唤时就是这种角度?

旋转也是用NBT“Rotation”存储的,而且数据格式是float,也就是“小数”(因为朝向是有小数的)。
但是熟悉数学的读者可能知道,再3维空间中的旋转需要用两个数字表示——
说白了就是,我们需要两个数字来表示一个方向,一个是水平旋转(东南西北),一个是垂直旋转(上下)。

但是你可能发现了。旋转只有一个标签名(或键)“Rotation”,但是我们有两个数值要存储。
难道要用上面的“复合内的复合”那种形式存储吗?
不是。
我们要用的是,列表(List)。
与复合标签一样,列表也可以用简单的一句话概括:
使用中括号“ [] ”作为开头与结尾,中间输入多个相同类型的元素,用逗号“ , ”隔开。

让我们比较傻地用“小朋友与糖”的例子说明:
假设有5名小盆友,他们每人有5块糖~
那么,如何用NBT的"列表"来表示呢?

小盆友的糖:[5, 5, 5, 5, 5]

像这样。这表示了每个小盆友都有5颗糖~
那么:

小盆友的糖:[1, 2, 3, 4, 5]

这是什么意思呢?
这代表了,第一个小盆友有1颗糖,第二个小盆友有2颗糖………以此类推。

我们把它放进NBT复合标签的大括号里,这事就成了:

{ 小盆友的糖 :[ 1 , 2 , 3 , 4 , 5 ] }

列表就这么简单。

那么,下面是旋转的NBT:

{ Rotation : [<水平旋转> f , <垂直旋转> f ] }

注意在列表里也是要加上数据类型的字母后缀的。
很容易得到——

  1. /summon zombie ~ ~ ~ {Rotation:[180.0f, 35.5f]}
复制代码
召唤一只面向北,仰角为35.5度的僵尸。
(关于旋转问题,为什么180.0是北,详见 系列第二帖

接下来,这里有个有趣的东西。
“列表”中添加的是“ 相同类型 的元素。
可没说“只有数字”。

实际上,我们可以,在里面添加任何类型的元素。
不只是float(小数)
比如,数个“Int”。(整数)
或者数个long。(长整型)

或者……
数个复合标签。

对。就像我们可以在复合标签内套复合标签一样,我们也可以在列表中套复合标签。
一样是用英文逗号“,”隔开:

{ example:[ {xxx:yyy, aaa:bbb}, {xxx:yyy, aaa:bbb} , {xxx:yyy, aaa:bbb} , {xxx:yyy, aaa:bbb} ] }

注意我用字体大小表现出的主次关系。


那么,什么时候用这种形式呢?
当我们有多个并列的项目,而且每一个项目都有复合标签的需要的时候——
比如,附魔。
附魔是用NBT“ Enchantments ”存储的。该标签是一个列表:

{ Enchantments : [ { id :" sharpness ", lvl : 67 },{ id :" knockback ", lvl : 4 }] }

在这个名为“ Enchantments ”的列表中包含了两个复合标签。每一个代表一个附魔。

在每一个复合标签中,“ id ”是附魔ID:
" sharpness "是锋利,而 " knockback "是“击退”。
" lvl "则是等级(Int整形)。

所以,上面的标签实际上描述了2个附魔——
锋利67与击退IV。

所以——

  1. /give @a diamond_sword{Enchantments:[{id:"sharpness", lvl: 67},{id:"knockback", lvl: 4}]}
复制代码
给予所有玩家一把锋利Lv.67,击退IV的钻石剑(diamond_sword)。

我们只是添加了两个附魔,当然还可以再列表中加入更多。但这里就不举例了。
(附魔ID请见附录。)

这样,你理解了么?

当然上面的例子不是最难的。
我们只是在一个列表里套了两个复合标签。
但实际上,我们还可以在列表中的复合标签中套另一个列表,里面再套复合标签……

咳咳,不折磨你们了。


2.6 数列


有一个有趣的事情,就是如果列表内装的数据类型是"Int(整数)","Byte(贼小的数(字节型))",或"Long(贼大的数(长整型))",我们的列表会有变化。

因为某种代码上的,跟我们无关的原因,这时我们需要在最前面加上一个东西:

example: [ B; 1 b , 2 b , 4 b , 6 b , 3 b , 14 b , 3 b , 4 b , 5 b , 7 b ]

B; 代表了整个列表都是Byte。
……但是里面的东西你一样要加上“b”用来代表byte的数据类型。

同样,我们还可以有“ I; ”或“ L; ”,分别为“Int(整数)”与“Long(长整型)”。
注意这些前缀是大写的,别弄错了。


这些东西被称作 “数列”(Array)
而根据其列的数值的不同(最前面的字母不同),分为 I; 整型数列(int Array) B; 字节型数列(byte Array) ,与 L; 长整型数列(long Array)

这玩意是比较少见的,一般最常用的就是UUID吧。


2.7 查看


教程的最后,让我们解决最根本的问题——

我该如何知道一个NBT叫啥名,该填什么值,又是什么数据类型?
简单简单。遇事不决看Wiki。

nbt234.png


图中所示的是僵尸页面的“实体数据”段落。
在这里,你可以看到所有的标签,以及他们的类型

不就是Byte? 我们再上面的列表内也有相似的图标。


但是要补充几个:


com.png 意味着这是一个复合标签。
list.png 为列表。
byte_array.png 是Byte数列。
int_array.png 是Int数列。
long_array.png 是Long数列。
就这些。
只要你看着上面的列表,你就能很容易给出我们在最开始举的那个僵尸的例子。
不过嘛,本教程刚刚才只是讲述了NBT最基础的格式——
接下来,我会举上几个例子来帮助你详细理解~

3. 示例

在开始看实例之前,我需要让你认清楚一件事——
你比不上命令生成器。
你可能知道,有一些可以用来生成NBT命令的网站,非常好用(虽然经常更新不及时。)
我想告诉你,对于复杂一些的NBT,人肯定是没有机器快的。使用这些生成器可以让你在很短的时间里,用可视化的方法快速生成指令。
所以,你看过本帖对NBT有了解之后,请记住善用这些生成器吧。
但是你要懂其中的原理——
不然对于生成器不全的,更新不及时的,或者出BUG的地方,你就无能为力了。

3.1 装有32个面包 16颗石头 的箱子

现在,我们要放置1只箱子,其中第3格放着32个面包,第5格放着16颗石头。

我们在 系列教程第十帖 讲过了用于放置方块的命令/setblock与方块状态(没看过的请回去看)。
当时我们说到,对于方块实体,方块状态无法涵盖所有内容。

那么,我们首先要找到箱子的NBT。像我们之前说的,这些玩意总是记录在Wiki上(只要没有忘了更新):

chestNBT.png

你看到的是箱子的NBT结构。你可以发现除了我们要的“ Items:当前容器内物品的列表。 ”外,还有诸如“上锁”之类的神奇玩意。
但我们今天不管这些。我们只要在第3格放着32个面包,第5格放着16颗石头就好。

看向上图,我们可以发现用于存储箱子物品的NBT“ Item ”是一个由 数个复合标签 组成的列表,每一个复合标签代表了一个栏位的物品。
而包含在每个复合标签中的,用于存储物品信息的“ 物品共通标签 ”实际上我们已经讲过了,就是上面的“ 两个弩 ”的掉落物的那个,是一样的。
我们可以看到字段“ id ”存储了该物品是什么,标签“ Count ”存储了物品数量,而标签“Slot”则是该物品放在箱子的哪一格。

这里注意 Slot:1b 并不是箱子的第一格,而是第二格,因为栏位是 从0开始算 的。
所以依此类推,第3格是 Slot:2b ,第五格是 Slot:4b

所以:

{ Items : [ { Slot : 2 b , id : " bread " , Count : 32 b } , { Slot : 4 b , id : " stone " , Count : 16 b } }

这就是我们要的NBT。
上面“ bread ”是面包,而“ stone ”是石头。
我们可以直观地看到,每一个物品都是一个复合标签,而它们都被装在一个列表里。


指令/setblock的NBT和/give一样,紧贴在物品的后面,而且你大概知道“chest”是箱子,那么:

  1. /setblock ~ ~ ~ chest{Items:[{Slot:2b, id:"bread", Count:32b}, {Slot:4b, id:"stone", Count:16b}]}
复制代码

这就是我们要的指令。


3.2 超级无敌宇宙最强太古霸王剑

一个提示:3.2的内容在1.12.2及以下有一定差异

咳咳,第二个实例是,给予自己一把攻击力+64的钻石剑,其自定义名称为“超级无敌宇宙最强太古霸王剑”。

我们该寻找可以达成这一目标的标签名是什么呢?
在Wiki页面“ Player.dat格式 ”与“ 教程/NBT与命令标签 ”中,给出了我们要更改的两项:
即自定义名称(来给剑加上中二的名字),与属性修饰器(给与剑“攻击+64”的属性)。
首先,让我们看向自定义名称:

display.png

我给的图片是镜像Wiki的,其数据类型的图标与刚才说的有区别,但没关系,它们大体相似。

我们可以看见“控制物品的自定义显示信息”的是一个复合标签,其中可以拥有3个子标签:
字符串 Name ”,用于控制 物品的名字 字符串 Lore ”,用于控制物品的描述;与最后的 整型Int " color ",这个是控制皮革铠甲的颜色的。在本例中用不上。

这个文字要求是 JSON文本 ,由于我们还没说到JSON,所以这里我们就用它的无特殊格式简写,及用双引号引起来。
注意NBT本身还有一个用来表示这是个字符串的单引号,所以结果是我们要把文字用“'"”,即外侧单引号加上内侧的双引号。

我们不计划在本物品中添加自定义描述,所以" Lore "我们不填。
我们想要的自定义名称是“超级无敌宇宙最强太古霸王剑”——
所以,如果你看懂了本教程,应该能明白——

diamond_sword { display : { Name :' " 超级无敌宇宙最强太古霸王剑 "' } }

描述了一个名称为“超级无敌宇宙最强太古霸王剑”的钻石剑(diamond_sword)。其中,由于自定义名称是字符串,所以我们用了引号""来引起来。 Name 是“ 自定义名称 ”的标签名,而它又是 display 的子标签。

这个解决了,看下一个,更改属性:

这里我们要用的是 “属性修饰器”(AttributeModifiers) 来更改属性。
这里要注意的是,属性修饰器这个东西不止你在用,游戏也在用。
比如,当你疾跑时获得的速度加成实际上就是属性修饰器的一种。

我们可以在Wiki的 Player.dat格式#属性修饰符 页面找到它的NBT格式:

ing.gil.png

我们可以把上面的这个内容整理一下:

  • AttributeModifiers : (列表) 包含了物品的修饰属性。它将修改佩戴者或持有者的属性。
    • 一个修饰器
      • AttributeName : (字符串) 修饰的属性。
      • Name : (字符串) 修饰符的名称。
      • Slot : (字符串) 指定修饰符产生效果的槽位。值只能为" mainhand (主手)"、" offhand (副手)"、" feet (装备栏:靴子)"、" legs (装备栏:护腿)"、" chest (装备栏:胸甲)"或" head (装备栏:偷窥)"。若不指定将会全装备栏通用。
      • Operation : (Int) 详见 属性修饰符 。0是“增加”,1是“乘以”,2是“最终倍乘”
      • Amount : (Double) 这个修饰符的数额。
      • UUID (Int Array) 属性的 UUID ,以4个32位整数的形式存储。


上表改编自MC中文Wiki。

看一下上表,我们可以得到:

AttributeModifiers 就是我们属性修饰符的根标签,它是一个列表,可以包含数个属性修饰符,而每一个修饰符有以下标签:

AttributeName 是要修饰的属性,你可以在 Wiki这里查找
这里我们要加的是攻击力“ generic.attack_damage ”。

Name 是该属性修饰器的名字。这个你不用填,我们以后会说;

Slot 是“该属性在哪里发挥作用。”因为这是个武器,所以肯定是主手“ mainhand ”发挥作用。
Operation 是运算,就是你要干什么。根据上表,我们是“增加”,所以填“ 0 ”。
Amount 就是修饰器要更改的值:我们要加“ 64 ”。

最后 UUID ,是这个属性修饰器的 唯一身份识别码(Universal User Identity)
我们有说过属性修饰符不只是你打的指令在用,游戏也在用。这个UUID就是游戏用来区分不同的属性修饰符的。
UUID是一个由4个数字——整型(int)组成的数列。
如果你感兴趣的话 可以在Wiki这里 找到关于UUID更多信息。
不过这里我们不多说。在这里你只要随便填上4个数就好:比如:
[I; 1,2,3,4]

这个数基本上可以乱编,因为你相当于在创建一个自定义的属性修饰符。你只要注意不用重复的就好了。
但是呢,有一些特定的UUID你是不能用的,因为它们被游戏占用了。它们被列在了Wiki 这里 。(不用担心,随便填撞见它们的概率微乎其微……)

综合以上标签,我们可以得到:


{ AttributeModifiers : [ { AttributeName : " generic.attack_damage " , Slot : " mainhand " , Amount : 64 d , Operation : 0 , UUID : [ I; 1 , 2 , 3 , 4 ] } ] }


这描述了一个可以在玩家将其放置于主手时增加64点攻击力的属性修饰器。

好,就这样,把上面的两个标签合起来:


{ AttributeModifiers : [ { AttributeName : " generic.attack_damage " , Slot : " mainhand " , Amount : 64 d , Operation : 0 , UUID : [ I; 1 , 2 , 3 ,4] } , display : { Name :' " 超级无敌宇宙最强太古霸王剑 "' } ] }



加入指令头,目标等,我们可以得到最终的命令:
  1. /give dahesor minecraft:diamond_sword{display:{Name:'"超级无敌宇宙最强太古霸王剑"'},AttributeModifiers:[{AttributeName:"generic.attack_damage",Slot:"mainhand",Amount:64d,Operation:0,UUID:[I;1,2,3,4]}]} 1
复制代码

结束。

(待补充其他示例……)


附录,注释,与外部链接

附录1  附魔ID







注释

【1】本帖内不区分执行者与执行地
【2】本标签一旦大于0,溺尸的转变就是不可逆的,离开水也没用。
【3】理论上,在一些情况中,即使你不在数字后添加用于确定数据类型的字母,系统也会自动补全。有些标签甚至接受不同类型的输入。 但是! 不填字母容易出BUG。所以,请总是填入字母。



所以就这样吧。
最后,各位,给点人气(盛满怨念)
以上。


#更新日志:
Java 1.16.5/a 页面发布
Java 1.16.5/b 感谢的提醒,修改了一处错误
Java 1.16.5/c 添加了1个示例
Java 1.16.5/d 更改了关于JSON的错误。
Java 1.16.5/e 添加了新示例。
Java 1.16.5/f  感谢@SPGoding 的纠错,改正了关于数列的很严重的错误。

Java 1.17/a 1.17版本升级完成