本站文本内容除另有声明外,转载时均必须注明出处,并遵守CC BY-NC-SA 3.0协议。(转载须知
本站是中文Minecraft Wiki的镜像站,与Mojang Studios、Weird Gloop没有从属关系。(免责声明

全站通知:

Tutorial:NBT与JSON

来自Minecraft WIKI
(重定向自教程/NBT与JSON
跳到导航 跳到搜索
Disambig gray.svg本教程介绍的是NBT以及JSON的语法和用法。关于NBT在命令中的字符串形式,请见“SNBT格式”;关于JavaScript对象表示法(JSON),请见“JSON”;关于Java版1.21以前的NBT与JSON教程,请见“Tutorial:NBT与JSON/旧版”;关于旧版NBT标签的用例,请见“Tutorial:NBT命令标签”。
本页面不适合使用可视化编辑器编辑

本页含有大量的样式等技术性内容,可视化编辑器可能无法很好地处理。

Enchanted Book.gif
本教程包含Java版1.21.5至最新预览版以及基岩版部分内容

若发现版本内容不匹配等问题,请帮助我们扩充或修改

本教程主要讲述NBT以及JSON的语法和用法。本教程建议学习者事先具备命令方块基础

对NBT数据的简单查询

常见的获取NBT的情景大概有两种:获取实体的NBT数据以及获取方块的NBT数据。

查看实体的NBT数据
(图1)可自动补全指向实体的UUID

很简单,只需要将你的鼠标准星指向一个实体,然后按下/(斜杠键)打开聊天栏输入以下命令

data get entity

随后,按下空格,你将会看到语法提示,如图1所示。

(图2)查看绵羊的NBT数据

直接按下Tab ↹键即可补全第4个参数。此时补全的是你所指向实体的UUID,它是该实体的一个唯一标识。所以实际你在聊天栏输入的命令应为:

/data get entity <所指向实体的UUID>

你也可以用目标选择器选择实体,例如@n[type=sheep]。但注意,它必须是单类型的选择器,也就是说它必须只选中1个实体,比如@s@n,这两个选择器分别用于选择自己和选择最近的实体。但是,如果使用后者却不添加参数,则执行命令时选中的还是自己,因此要记得加上type=sheep之类的参数来限定。

接下来,你只需要按下↵ Enter键即可执行对该实体的数据查询命令,如图2所示。

查看方块的NBT数据

注意,一般只有方块实体才具有较为复杂的NBT数据结构,故/data命令只对方块实体有效。

另外,若你的鼠标准星指向了一个你能够触及的方块,则也可以通过Tab ↹键来自动补全该方块的坐标,请读者自行尝试一下:

/data get block <方块的X坐标> <方块的Y坐标> <方块的Z坐标>

之后的命令有的省略了前导斜杠,若在聊天栏运行要确保有前导斜杠。

SNBT的语法

主条目:SNBT格式

游戏内对SNBT格式的描述

在Minecraft命令中,你需要编辑一串带有一系列花括号、中括号和冒号等符号的文本,这一串文本便是SNBT(字符串化的NBT)

什么是字符串?其实就是由字符组成的“串”——想象一下你吃过的串串火锅和关东煮。

当你手持一个橡木栅栏物品,执行以下命令(下面这一行命令表示获取执行者手持物品的数据,最后一个参数是NBT路径,会在后面讲):

/data get entity @s SelectedItem

你会看到手持的橡木栅栏具有以下的SNBT数据: {count: 1, id: "minecraft:oak_fence"}

在这里,可以概括出这一串SNBT的一些特征:

  • 最外层是一对花括号{}。花括号中包含了多个数据。
  • 逗号,分隔了不同的标签
  • 冒号:分隔了标签的名称和值,前面的是标签名,后面的是标签值

那么这一串SNBT怎么得到的?先让我们来看看上文所述的/data命令大概偷偷地做了哪些事情:

  • 解析data命令,确保语法无误。
  • 根据命令的描述,找到对应的NBT标签。
  • 将这些NBT标签转换为SNBT。

根据以上过程可知,SNBT其实是由游戏内部的NBT标签转换过来的。从这个方面来说,SNBT侧面反映了游戏内部的NBT标签数据结构。

让我们使用/data get再来看看鸡的实体数据:

{<span class="format-b">IsChickenJockey</span>: <span class="format-6">0</span><span class="format-c">b</span>, <span class="format-b">Brain</span>: {<span class="format-b">memories</span>: {}}, <span class="format-b">HurtByTimestamp</span>: <span class="format-6">0</span>, <span class="format-b">Invulnerable</span>: <span class="format-6">0</span><span class="format-c">b</span>, <span class="format-b">FallFlying</span>: <span class="format-6">0</span><span class="format-c">b</span>, <span class="format-b">ForcedAge</span>: <span class="format-6">0</span>, <span class="format-b">PortalCooldown</span>: <span class="format-6">0</span>, <span class="format-b">AbsorptionAmount</span>: <span class="format-6">0.0</span><span class="format-c">f</span>, <span class="format-b">FallDistance</span>: <span class="format-6">0.0</span><span class="format-c">f</span>, <span class="format-b">InLove</span>: <span class="format-6">0</span>, <span class="format-b">DeathTime</span>: <span class="format-6">0</span><span class="format-c">s</span>, <span class="format-b">HandDropChances</span>: [<span class="format-6">0.085</span><span class="format-c">f</span>, <span class="format-6">0.085</span><span class="format-c">f</span>], <span class="format-b">PersistenceRequired</span>: <span class="format-6">0</span><span class="format-c">b</span>, <span class="format-b">UUID</span>: [<span class="format-c">I</span>; <span class="format-6">-979712766</span>, <span class="format-6">-1308996237</span>, <span class="format-6">-1267152056</span>, <span class="format-6">27464117</span>], <span class="format-b">Age</span>: <span class="format-6">0</span>, <span class="format-b">Motion</span>: [<span class="format-6">0.0</span><span class="format-c">d</span>, <span class="format-6">-0.0784000015258789</span><span class="format-c">d</span>, <span class="format-6">0.0</span><span class="format-c">d</span>], <span class="format-b">Health</span>: <span class="format-6">4.0</span><span class="format-c">f</span>, <span class="format-b">LeftHanded</span>: <span class="format-6">0</span><span class="format-c">b</span>, <span class="format-b">Air</span>: <span class="format-6">300</span><span class="format-c">s</span>, <span class="format-b">OnGround</span>: <span class="format-6">1</span><span class="format-c">b</span>, <span class="format-b">Rotation</span>: [<span class="format-6">219.4912</span><span class="format-c">f</span>, <span class="format-6">0.0</span><span class="format-c">f</span>], <span class="format-b">HandItems</span>: [{}, {}], <span class="format-b">ArmorDropChances</span>: [<span class="format-6">0.085</span><span class="format-c">f</span>, <span class="format-6">0.085</span><span class="format-c">f</span>, <span class="format-6">0.085</span><span class="format-c">f</span>, <span class="format-6">0.085</span><span class="format-c">f</span>], <span class="format-b">EggLayTime</span>: <span class="format-6">10841</span>, <span class="format-b">Pos</span>: [<span class="format-6">22.169742620891626</span><span class="format-c">d</span>, <span class="format-6">-60.0</span><span class="format-c">d</span>, <span class="format-6">17.89903512329325</span><span class="format-c">d</span>], <span class="format-b">Fire</span>: <span class="format-6">-1</span><span class="format-c">s</span>, <span class="format-b">ArmorItems</span>: [{}, {}, {}, {}], <span class="format-b">CanPickUpLoot</span>: <span class="format-6">0</span><span class="format-c">b</span>, <span class="format-b">attributes</span>: [{<span class="format-b">id</span>: "<span class="format-a">minecraft:generic.movement_speed</span>", <span class="format-b">base</span>: <span class="format-6">0.25</span><span class="format-c">d</span>}, {<span class="format-b">id</span>: "<span class="format-a">minecraft:generic.follow_range</span>", <span class="format-b">modifiers</span>: [{<span class="format-b">amount</span>: <span class="format-6">0.011031093445209002</span><span class="format-c">d</span>, <span class="format-b">id</span>: "<span class="format-a">minecraft:random_spawn_bonus</span>", <span class="format-b">operation</span>: "<span class="format-a">add_multiplied_base</span>"}], <span class="format-b">base</span>: <span class="format-6">16.0</span><span class="format-c">d</span>}], <span class="format-b">HurtTime</span>: <span class="format-6">0</span><span class="format-c">s</span>}

为什么要使用这么多不同的颜色?其实这几种颜色分别表示SNBT格式中各种不同的语义片段——不同的颜色对应不同的抽象含义:


  •  亮蓝色代表“标签名”。

  •  金色代表“数值”,数值之后的
     红色字母代表“数型后缀”。

  •  亮绿色代表“字符串值”。

  •  白色则代表“符号”(除了字母或数字以外的字符)。

以上的这些含义我们会在后文进行详细讲解。到这里,你会发现SNBT其实也就是一串带有某种规律的字符序列。之后的内容都将围绕这种“规律”。

小练习
1. 抄写下面这段SNBT中的Items标签的数据,抄完后请仔细对照是否一致(注意,这里说的“Items标签的数据”指的是Items这个字段冒号之后的方括号及所包含的数据):
{<span class="format-b">x</span>: <span class="format-6">16</span>, <span class="format-b">y</span>: <span class="format-6">-60</span>, <span class="format-b">Items</span>: [{<span class="format-b">count</span>: <span class="format-6">32</span>, <span class="format-b">Slot</span>: <span class="format-6">0</span><span class="format-c">b</span>, <span class="format-b">id</span>: "<span class="format-a">minecraft:slime_ball</span>"}, {<span class="format-b">count</span>: <span class="format-6">1</span>, <span class="format-b">Slot</span>: <span class="format-6">3</span><span class="format-c">b</span>, <span class="format-b">components</span>: {"<span class="format-b">minecraft:enchantments</span>": {<span class="format-b">levels</span>: {"<span class="format-b">test:test</span>": <span class="format-6">1</span>}}, "<span class="format-b">minecraft:damage</span>": <span class="format-6">10</span>}, <span class="format-b">id</span>: "<span class="format-a">minecraft:bow</span>"}, {<span class="format-b">count</span>: <span class="format-6">1</span>, <span class="format-b">Slot</span>: <span class="format-6">14</span><span class="format-c">b</span>, <span class="format-b">components</span>: {"<span class="format-b">minecraft:enchantments</span>": {<span class="format-b">levels</span>: {"<span class="format-b">test:test</span>": <span class="format-6">1</span>}}}, <span class="format-b">id</span>: "<span class="format-a">minecraft:bow</span>"}, {<span class="format-b">count</span>: <span class="format-6">8</span>, <span class="format-b">Slot</span>: <span class="format-6">20</span><span class="format-c">b</span>, <span class="format-b">id</span>: "<span class="format-a">minecraft:cooked_porkchop</span>"}, {<span class="format-b">count</span>: <span class="format-6">2</span>, <span class="format-b">Slot</span>: <span class="format-6">26</span><span class="format-c">b</span>, <span class="format-b">id</span>: "<span class="format-a">minecraft:rotten_flesh</span>"}], <span class="format-b">z</span>: <span class="format-6">28</span>, <span class="format-b">id</span>: "<span class="format-a">minecraft:chest</span>"}
2. 你觉得有什么技巧能使你如何抄得更快更准?
3. 请你放置一个箱子,放入任意的物品,通过/data get block <箱子的X坐标> <箱子的Y坐标> <箱子的Z坐标>获取箱子的SNBT数据到聊天栏,然后抄写聊天栏中Items标签的值(类似于上文的练习)。
4. 通过/setblock命令,放置一个带有你刚才抄写的SNBT的箱子。
5. 看看新放置的箱子中的物品是否与之前的箱子的物品一致。若不一致,则看看错误出在了哪。

认识NBT标签

主条目:NBT格式

在研究NBT如何转换为SNBT前,我们首先需要对NBT有足够清晰的了解。

键值对与标签

键值对(Key-value pair)就是把一个键(Key)和一个值(Value)组成一对,表示由键到值的映射关系。这就像对于一个物质,其“质量”这个物质属性可以看作,而质量的具体数值可以看作

在上面,我们已经在SNBT里初步看到了一个个标签。其实,一个有标签名的NBT标签就是一个键值对。标签名就是这个键值对里的标签值就是这个键值对里的

NBT的数据类型

NBT标签具有多种类型,可以描述不同性质的数据。以下是NBT的数据类型:

  • 字节型字节型:存储一个字节型数据,即一个包含8个二进制位的整数,范围是-128到127(闭区间)。
  • 字节型数组字节型数组:存储多个字节型数据。
  • 短整型短整型:存储一个短整型数据,即一个包含16个二进制位的整数,范围是-32,768到32,767(闭区间)。
  • 整型整型:存储一个整型数据,即一个包含32个二进制位的整数,范围是-2,147,483,648到2,147,483,647(闭区间)。
  • 整型数组整型数组:存储多个整型数据。
  • 长整型长整型:存储一个长整型数据,即一个包含64个二进制位的整数,范围是-9,223,372,036,854,775,808到9,223,372,036,854,775,807(闭区间)。
  • 长整型数组长整型数组:存储多个长整型数据。
  • 单精度浮点数单精度浮点型:存储一个单精度浮点数,即一个包含32个二进制位的单精度浮点数字,范围是-3.4E+38到+3.4E+38。
  • 双精度浮点数双精度浮点型:存储一个双精度浮点数,即一个包含64个二进制位的双精度浮点数字,范围是-1.7E+308是+1.7E+308。
  • 字符串字符串型:存储一串字符。
  • 同类元素列表列表型:存储多个同类型的数据。
  • NBT复合标签/JSON对象复合型:存储多个带名标签(类型可以不同),标签名不能重复,更详细的说明见下。

此外,虽然NBT实际上没有布尔型,但在本Wiki中,把只有0和非0值的字节型NBT标签表示为布尔型布尔型

每个实体都会有一些数据存储在这些不同类型的标签中。比如绵羊的羊毛颜色代码,存储在一个整型标签中;玩家手持的物品命名空间ID,存储在一个字符串型标签中。

复合标签

复合标签与其他标签的最大区别就是,复合标签中包含了若干个带名NBT标签,也就是若干个键值对。在同一个复合标签中,没有重复的标签名,每一个标签名都对应唯一一个标签值。

我们可以这样理解复合标签:它是NBT标签的核心,将多个不同的标签组织起来,给里面的每个标签赋予了唯一的标签名。可以说,它是一家之长,往往“管理”和“养育”着多个NBT标签。

使用SNBT来表达NBT

现在我们来具体了解一下各类型的NBT标签所对应的SNBT格式。当你熟悉本节内容后,你也能大致看懂data get命令所获取到的SNBT了。

以下列出一些与NBT有关的语句,让我们来看看这些语句如何使用SNBT来描述:

要表示的NBT结构 对应的SNBT
一个存储了数据123的字节型标签 123b
一个字节型数组标签,包含3个字节型数据 [B; 123B, 1234B, 12345B]
一个短整型标签,包含了123这个数据 123s
一个整型标签,存储了123这个数据 123
含有3个整型数值123、1234、12345的整型数组 [I; 123, 1234, 12345]
长整型标签,存储了123这个数字 123L
一个长整型数组,包含123、1234、12345这3个数据 [L; 123L, 1234L, 12345L]
一个单精度浮点型标签,存储数字123 123.0f
存储了123的双精度浮点型标签 123.0d
存储了123这个字符串的字符串型标签 "123"
包含3个元素123、1234、12345的列表型标签 [123, 1234, 12345]
包含1个字节型标签和1个整型标签的复合标签,这两个标签的名称分别为“标签1”和“标签2” {"标签1": 123b, "标签2":123}

以上示例基本涵盖了所有的NBT数据类型。现在,我们可以对照以上的对应关系,分析如下的SNBT:

{<span class="format-b">a</span>:<span class="format-6">1</span><span class="format-c">b</span>, <span class="format-b">b</span>:<span class="format-6">123</span>, <span class="format-b">c</span>: <span class="format-6">456</span><span class="format-c">L</span>, <span class="format-b">d</span>: <span class="format-6">520</span><span class="format-c">s</span>, <span class="format-b">e</span>: [<span class="format-c">I</span>; <span class="format-6">1</span>, <span class="format-6">2</span>, <span class="format-6">3</span>], <span class="format-b">f</span>: [<span class="format-c">B</span>; <span class="format-6">1</span><span class="format-c">B</span>, <span class="format-6">2</span><span class="format-c">B</span>, <span class="format-6">3</span><span class="format-c">B</span>], <span class="format-b">g</span>: [<span class="format-c">L</span>; <span class="format-6">1</span><span class="format-c">L</span>, <span class="format-6">2</span><span class="format-c">L</span>, <span class="format-6">3</span><span class="format-c">L</span>], <span class="format-b">h</span>: ["<span class="format-a">你</span>", "<span class="format-a">好</span>", "<span class="format-a">呀</span>"], <span class="format-b">i</span>: "<span class="format-a">233</span>", <span class="format-b">j</span>: {<span class="format-b">tag1</span>: <span class="format-6">1</span>, <span class="format-b">tag2</span>: <span class="format-6">2.0</span><span class="format-c">f</span>, <span class="format-b">tag3</span>: <span class="format-6">3.0</span><span class="format-c">d</span>}}

在这一串SNBT中,各SNBT片段的含义如下:

SNBT 含义
a:1b 一个名为a的字节型标签
b:123 一个名为b的整型标签
c:456L 一个名为c的长整型标签
d:520s 一个名为d的短整型标签
e: [I; 1, 2, 3] 一个名为e的整型数组
f: [B; 1B, 2B, 3B] 一个名为f的字节型数组
g: [L; 1L, 2L, 3L] 一个名为g的长整型数组
h: ["你", "好", "呀"] 一个名为h,内部元素为字符串的列表
i: "233", 一个名为i,存储了一串字符233的字符串标签。
j: {tag1: 1, tag2: 2.0f, tag3: 3.0d} 一个名为j的复合标签,其中包含了3个标签:整型标签tag1,单精度浮点数标签tag2,双精度浮点数标签tag3

其他关键概念

根标签

所有的标签都有一个“根”,即“根本性标签”。若将NBT所存储的数据想象成一棵树,那么根标签就是这棵树的“根”,即“最初所具有的部分”。

在上面的例子中:{var1: 1, var2: 2, test: 233, ABCDEFG: 666},这个复合标签是没有名称的。一般,我们称这种无名标签为根标签。根标签是一个数据结构的“起点”,在描述很多数据结构时,往往都从根标签开始。

嵌套

观察以下这一串SNBT:

{<span class="format-b">var1</span>: <span class="format-6">1</span>, <span class="format-b">var2</span>: <span class="format-6">2</span>, <span class="format-b">test</span>: <span class="format-6">233</span>, <span class="format-b">ABCDEFG</span>: {<span class="format-b">tag1</span>: <span class="format-6">11</span>, <span class="format-b">tag2</span>: <span class="format-6">22</span>, <span class="format-b">tag3</span>: <span class="format-6">33</span>}}

可以看到,其根标签是一个复合标签,而在这个复合标签中,还存在一个名为ABCDEFG的复合标签。像这样,在复合标签中包含复合标签的结构——花括号中有花括号,就是嵌套。对于ABCDEFG标签,我们可以说它嵌套于根标签中。

嵌套层级

对于包含嵌套的SNBT,你可以把每一个复合标签分离出来。比如:

{<span class="format-b">var1</span>: <span class="format-6">1</span>, <span class="format-b">var2</span>: <span class="format-6">2</span>, <span class="format-b">test</span>: <span class="format-6">233</span>, <span class="format-b">ABCDEFG</span>: {<span class="format-b">tag1</span>: <span class="format-6">11</span>, <span class="format-b">tag2</span>: <span class="format-6">22</span>, <span class="format-b">tag3</span>: <span class="format-6">33</span>}}

可以分离为以下两个复合标签:

{var1: 1, var2: 2, test: 233, <...>}
ABCDEFG: {tag1: 11, tag2: 22, tag3: 33}

对于var1标签来说,它直接包含在根标签中,所以它的嵌套层级为0。对于标签ABCDEFG来说,它本身也是直接包含在根标签中,所以它的嵌套层级也为0。而对于tag1标签来说,它包含在从根标签中分离出来的ABCDEFG标签中,所以它的嵌套层级为1。

简单来说,一个标签的嵌套层级就是它包含在第几个嵌套的复合标签内。

标签名和标签值的默认允许字符

标签名和标签值的默认允许字符只能为:

  • 0123456789:数字0到9。
  • abcdefghijklmnopqrstuvwxyz:小写英文字母a到z。
  • ABCDEFGHIJKLMNOPQRSTUVWXYZ:大写英文字母A到Z。
  • +:加号。
  • -:减号。
  • .:点号。
  • _:下划线。

如果标签名或标签值带有默认允许字符以外的字符,必须添加引号。被引号所括的SNBT标签名和标签值默认允许包含任意的可输入字符。例如:

"嗨 我的世界wiki": 123
123: "@{}<>!-=()[]*&^%$#/+`~.';"

以上的标签名和标签值写法都是允许的。

当然,对带有默认允许字符的标签名和标签值也可以加引号,但这有点画蛇添足:

"abc": 123等同于abc: 123

通过/data get所显示的SNBT也只会在必要的时候为标签名和标签值加上引号。

转义

接下来是一个特殊的情况:假如我想让标签名本身也带有引号呢?根据前文所述,我们首先需要在标签名外添加一对引号(因为引号本身也不属于SNBT标签名的默认允许字符,故需要使用引号包裹)但绝非简单地添加相同的引号,这肯定会出错。在SNBT中,既可以使用单引号,也可以使用双引号——到底使用哪个引号来包裹标签名?答:根据你的需要。因为单双引号在互相嵌套时可以分清彼此,故可以有如下的书写方式:

"'McPlayer'": 123
'"McPlayer"': -2

以上两种引号嵌套的方式均合法。若选择前者,则标签名实际为'McPlayer'。若选择后者,则标签名为"McPlayer"

另外一种方式是通过转义字符来实现。以下为以上两个例子所对应的等价写法(使用了转义字符\"\'):

'\'McPlayer\'': 123
"\"McPlayer\"": -2
小练习

1.观察以下SNBT,说说其中有哪些类型的标签?请把所有的标签的标签名全部写出来,然后给它们分类:

{<span class="format-b">Test</span>: <span class="format-6">123</span>, <span class="format-b">Test1</span>: <span class="format-6">0.0</span><span class="format-c">d</span>, <span class="format-b">Test2</span>: <span class="format-6">0</span>, <span class="format-b">a</span>: <span class="format-6">0.0</span><span class="format-c">f</span>, <span class="format-b">b</span>: <span class="format-6">3</span><span class="format-c">L</span>, <span class="format-b">c</span>: <span class="format-6">123456789</span>}

2.观察以下SNBT,说说其中有哪些类型的标签?请把所有的标签的标签名全部写出来,然后给它们分类:

{<span class="format-b">Test</span>: [<span class="format-6">1</span>, <span class="format-6">2</span>, <span class="format-6">3</span>], <span class="format-b">tag</span>: [{<span class="format-b">a</span>: <span class="format-6">1</span><span class="format-c">b</span>}, {<span class="format-b">c</span>: <span class="format-6">233.0</span><span class="format-c">f</span>}, {<span class="format-b">name</span>: "<span class="format-a">minecraft wiki</span>", <span class="format-b">y</span>: <span class="format-6">123456789</span>}]}, <span class="format-b">a</span>:{<span class="format-b">ccc</span>: <span class="format-6">0</span><span class="format-c">f</span>}, <span class="format-b">b</span>: [<span class="format-c">B</span>; <span class="format-6">1</span><span class="format-c">B</span>, <span class="format-6">2</span><span class="format-c">B</span>, <span class="format-6">3</span><span class="format-c">B</span>]}

3.观察以下SNBT,请问是否所有SNBT的书写都正确?若有误,请指出并改正:

{test: [0, 1L, 2L], fastList: [abc:{user: 233}]}
{test: 00123.000000d, fastList: [[0s], [0s], [1s], [1s], [2s], [2s]]}
{Test: 2E123456789d}

结构化数据的抽象表示

在以下的SNBT中,存在一些嵌套

{type:"block",block_state:{Name:"grass_block",Properties:{snowy:true}}}

从外到内,可拆分出共3层复合标签: {<...>}block_state:{<...>}Properties:{<...>}

为了整体上更加直观和清晰,我们将原SNBT标签书写为如下带缩进和换行的格式:

{
  <span class="format-b">type</span>: "<span class="format-a">block</span>",
  <span class="format-b">block_state</span>: {
    <span class="format-b">Name</span>: "<span class="format-a">grass_block</span>",
    <span class="format-b">Properties</span>: {
      <span class="format-b">snowy</span>: <span class="format-c">true</span>
    }
  }
}

我们将以上的格式所表达的逻辑抽象出来,使用树状图来表示,即为以上SNBT格式的抽象数据格式

  • NBT复合标签/JSON对象
    • 字符串typeblock
    • NBT复合标签/JSON对象block_state
      • 字符串Namegrass_block
      • NBT复合标签/JSON对象Properties
        • 字符串snowytrue

对抽象数据格式的理解

在wiki上,使用树状图来表示SNBT其实还有另一个原因:这种树状图往往直接来源于游戏内部的编解码格式。解编码格式其实同时反映了两个过程,即“编码”和“解码”。在游戏内部,很多程序对象都有解码和编码的行为。比如,将一个实体导出为SNBT的过程其实就是一种编码行为。程序对象经过编码所得到的文本格式即为编码格式,比如SNBT就算是一种编码格式。而利用SNBT这种编码格式来还原出一个实体就是一种解码行为。

在Minecraft内部,由编解码器来较为统一地实现编码和解码行为。它可以将由Java代码所定义的程序对象转换为另一种数据格式——比如SNBT或者JSON。从这个方面来说,SNBT只不过是一种导出形式——其间接表达了某些程序对象在内存中的数据结构。

在树状图中,有一些字段所定义的数据在某种情况下可能是无法被“导出”或“设置”的。这种字段在树状图中一般被标注了“可能不存在”或“不能通过/data命令修改”。这是因为有些字段所对应的程序对象还没有在内存中产生,所以你无法通过SNBT来访问这些数据。而某些程序对象可能因为一些程序内部的逻辑限制或安全问题而不允许玩家通过SNBT来进行修改。

常见树状图示例

数值型标签
抽象数据格式 SNBT格式
字节型test:存储了一个数值1。
<span class="format-b">test</span>: <span class="format-6">1</span><span class="format-c">b</span>
短整型test:存储了一个数值1。
<span class="format-b">test</span>: <span class="format-6">1</span><span class="format-c">s</span>
整型test:存储了一个数值123。
<span class="format-b">test</span>: <span class="format-6">123</span>
长整型test:存储了一个数值123456。
<span class="format-b">test</span>: <span class="format-6">123456</span><span class="format-c">L</span>
单精度浮点数test:存储了一个数值1.0。
<span class="format-b">test</span>: <span class="format-6">1.0</span><span class="format-c">f</span>
双精度浮点数test:存储了一个数值2.0。
<span class="format-b">test</span>: <span class="format-6">2.0</span><span class="format-c">d</span>
字符串型标签
抽象数据格式 SNBT格式
字符串testhello minecraft
<span class="format-b">test</span>: "<span class="format-a">hello minecraft</span>"
字符串test:有3种取值情况的字符串,可以为helloHi你好
<span class="format-b">test</span>: "<span class="format-a">hello</span>"

test: "Hi"test: "你好"

字符串test:一个取值为空字符串("")的字符串标签
<span class="format-b">test</span>: "<span class="format-a"></span>"
数组或列表型标签
抽象数据格式 SNBT格式
  • NBT列表/JSON数组test:元素为整型数值的列表,存储了三个整型数值1、2、3。
    • 整型:列表中的一个整数。
<span class="format-b">test</span>: [<span class="format-6">1</span>, <span class="format-6">2</span>, <span class="format-6">3</span>]
  • NBT列表/JSON数组test:一个空列表([]
<span class="format-b">test</span>: []
字节型数组test:存储了三个字节型数值1、2、3。
<span class="format-b">test</span>: [<span class="format-c">B</span>; <span class="format-6">1</span><span class="format-c">B</span>, <span class="format-6">2</span><span class="format-c">B</span>, <span class="format-6">3</span><span class="format-c">B</span>]
整型数组test:存储了三个整型数值1、2、3。
<span class="format-b">test</span>: [<span class="format-c">I</span>; <span class="format-6">1</span>, <span class="format-6">2</span>, <span class="format-6">3</span>]
长整型数组test:存储了三个长整型数值1、2、3。
<span class="format-b">test</span>: [<span class="format-c">L</span>; <span class="format-6">1</span><span class="format-c">L</span>, <span class="format-6">2</span><span class="format-c">L</span>, <span class="format-6">3</span><span class="format-c">L</span>]
复合型标签
抽象数据格式 SNBT格式
  • NBT复合标签/JSON对象world
    • NBT复合标签/JSON对象level
      • NBT复合标签/JSON对象player
        • 字符串id
{<span class="format-b">world</span>:{<span class="format-b">level</span>: {<span class="format-b">player</span>: {<span class="format-b">id</span>: "<span class="format-a">minecraft:player</span>"}}}}
  • NBT复合标签/JSON对象apple:描述了一个苹果。
    • 单精度浮点数weight:苹果的重量,以克为单位。
    • 字符串color:苹果的颜色。
<span class="format-b">apple</span>: {<span class="format-b">weight</span>: <span class="format-6">200</span>, <span class="format-b">color</span>: "<span class="format-a">red</span>"}
  • NBT复合标签/JSON对象apple:一个空复合标签({}
<span class="format-b">apple</span>: {}

当复合标签和列表进行嵌套时,可能会有些复杂:

抽象数据格式 SNBT格式
  • NBT列表/JSON数组orchard:一个果园。
    • NBT复合标签/JSON对象:果园中的一块农用地。
      • 字符串type:该农用地所种植的作物类型。
<span class="format-b">orchard</span>: [{<span class="format-b">type</span>: "<span class="format-a">apple</span>"}, {<span class="format-b">type</span>: "<span class="format-a">pear</span>"}, {<span class="format-b">type</span>: "<span class="format-a">banana</span>"}]
  • NBT复合标签/JSON对象:一所大学。
    • NBT列表/JSON数组<宿舍楼名称>:该大学的某宿舍楼。列表中的元素依次代表宿舍楼的哪一层(第一个元素代表第一层,以此类推)。
      • NBT复合标签/JSON对象:该宿舍楼中的某一层楼。
        • 整型major_number:该楼层中学生的不同专业数。
{"<span class="format-b">德馨苑B座</span>": [{<span class="format-b">major_number</span>: <span class="format-6">5</span>}, {<span class="format-b">major_number</span>: <span class="format-6">3</span>}, {<span class="format-b">major_number</span>: <span class="format-6">4</span>}, {<span class="format-b">major_number</span>: <span class="format-6">3</span>}, {<span class="format-b">major_number</span>: <span class="format-6">2</span>}]}

注:标签名以尖括号包括,表示该标签名是不定的,可根据尖括号内的要求和周围上下文的描述来较为灵活地指定其名称。

小练习
  • 1.观察以下的SNBT,解释一下为什么有这么多引号?你能将生活中的一些其他物品使用SNBT表示吗?
{"<span class="format-b">名称</span>": "<span class="format-a">我的电脑</span>", "<span class="format-b">重量</span>": <span class="format-6">233</span>, "<span class="format-b">颜色</span>": "<span class="format-a">黑色</span>", "<span class="format-b">显示屏内容</span>": [{"<span class="format-b">应用名</span>": "<span class="format-a">QQ</span>"}, {"<span class="format-b">应用名</span>": "<span class="format-a">资源管理器</span>"}, {"<span class="format-b">应用名</span>": "<span class="format-a">Minecraft 1.21</span>"}]}
  • 2.仔细阅读玩家数据格式,查询玩家的SelectedItem标签的作用,然后在游戏中使用/data get命令获取你自己的SelectedItem标签,并口述你看到的效果。
  • 3.使用/data get命令获取你自己的ShoulderEntityLeft标签。描述一下执行后有什么效果?查询玩家数据格式,解释一下这里“(可能不存在)”的含义?

使用SNBT

NBT路径

参见:NBT路径

NBT路径用以表示某个标签的所在位置。在学习NBT路径之前,你必须熟悉每一种NBT标签在SNBT语法层面上的表达形式,特别是NBT复合标签/JSON对象复合标签NBT列表/JSON数组列表

根节点和子节点

我们继续以此数据树状图为例:

  • NBT复合标签/JSON对象
    • 字符串typeblock
    • NBT复合标签/JSON对象block_state
      • 字符串Namegrass_block
      • NBT复合标签/JSON对象Properties
        • 字符串snowytrue

对于这一串NBT数据结构,NBT路径的起点就是根标签NBT复合标签/JSON对象。每一个标签的所在位置都可以表示为从根标签开始的一个路径,每条路径都由若干的节点(Node)构成。若有多个节点,则需要使用.分隔。

例如:

  • 字符串type的所在位置可以表示为{}.type
  • NBT复合标签/JSON对象block_state的所在位置可以表示为{}.block_state
  • 字符串Name的所在位置可以表示为{}.block_state.Name
  • NBT复合标签/JSON对象Properties的所在位置可以表示为{}.block_state.Properties
  • 字符串snowy的所在位置可以表示为{}.block_state.Properties.snowy

解释:

  • 这里的{}.type中的.子节点标识——表示后一个标签是前一个标签的子标签。
  • {}根节点(Root node),表示作为根标签的复合型标签。
  • type是根节点的一个子节点(Child node)——表示根复合标签中名为type的字符串型标签。

根节点是可以省略的。默认情况下,根节点就是{}。很多玩家在一般情况下也都习惯性地省略根节点。

例如:

  • 字符串snowy的所在位置也可以表示为block_state.Properties.snowy(省略了根标签)

接下来我们将使用命令存储(Storage)来进行实验——看看能否通过NBT路径来匹配到相应的NBT。

首先复制以下的命令到聊天栏然后执行:

/data merge storage test:test {node0: {e: 0}}

这会使得游戏创建一个名叫test:test的命令存储。之后你都可以通过以下命令来查看该命令存储的NBT结构:

/data get storage test:test

我们尝试在/data get命令中使用NBT路径来获取命令存储中的e标签:

/data get storage test:test node0.e

这里的node0.e即是e标签在test:test命令存储的NBT结构中的NBT路径。执行后,你将会在聊天栏看到e标签所存储的数据。

根节点{}可以匹配几乎任何根复合标签——无论复合标签中的内容如何,只要根标签存在,都能匹配到这个根复合标签。

但是如果根节点书写为:

  • {node0:{e:0}}

则仅当根标签存在,且根标签中包含node0:{e:0}这个键值对时才会成功匹配。注意,该路径仍然表示的是一个根标签——一个包含了node0:{e:0}键值对的根标签。

索引型元素节点

接下来我们将利用/data命令来操作命令存储中的列表。在操作过程中你将更加清楚与列表相关的NBT路径写法。另外,数组型标签其实类似于列表——只要你清楚了列表的NBT路径表示方法,那么数组也差不多。

先执行以下命令以便为test:test命令存储添加一个列表:

/data merge storage test:test {node1: {k: [1, 2, 3, 4, 5]}}

对于一个列表或数组,索引(Index)为一个整数。索引就是用来表示列表或数组中各元素位置的。索引可以大致分为以下两种:

  • 若索引大于等于0,则是常规的正向索引。这种索引从最左端的第一个元素开始,从0依次向右递增,每次增加1。
  • 若索引小于0,则是负向索引。这种索引从最右端的第一个元素开始,从-1开始依次向左递减,每次减少1。

在NBT路径中,数组或列表索引必须被方括号括起来,这就构成了一个索引型元素节点(Indexed node)。索引型元素节点可通过索引直接找到列表或数组中的元素,性能较好。

例如,对于我们存储在test:test中的命令存储:

{<span class="format-b">node1</span>: {<span class="format-b">k</span>: [<span class="format-6">1</span>, <span class="format-6">2</span>, <span class="format-6">3</span>, <span class="format-6">4</span>, <span class="format-6">5</span>]}}

我们可以使用正向索引来找到k列表中的元素5。表示成NBT路径即:

  • node1.k[4]

也可以使用负向索引:

  • node1.k[-1]

你可以依次执行以下两条data get命令,它们都可以获取到列表中的元素5:

/data get storage test:test node1.k[4]
/data get storage test:test node1.k[-1]

全元素节点

现在让我们来看另一种格式:

  • node1.k[]

你可以尝试通过data get命令来解析node1.k[]路径,看看能否获取到test:test命令存储中的一些数据:

/data get storage test:test node1.k[]

如果你在聊天栏执行了以上命令,你将会收到提示:“该参数只接受单个NBT值”。

这表明:

  • /data get命令只能获取单个NBT数据。
  • node1.k[]表示了多个NBT数据。

事实上,[]在NBT路径中表示全元素节点(All element node)——将会匹配某一个列表中的所有标签。如果你想通过/data get命令查看k列表中的值,你应该直接使用:

  • node1.k(这会直接匹配k列表本身,而非其中的每个元素)

匹配型元素节点

现在让我们为test:test命令存储融入一段新的数据:

{<span class="format-b">node2</span>: {<span class="format-b">k</span>: [{<span class="format-b">a</span>:<span class="format-6">1</span>}, {<span class="format-b">b</span>:<span class="format-6">2</span>}, {<span class="format-b">c</span>:<span class="format-6">3</span>}]}}

所以我们执行以下命令:

data merge storage test:test {node2: {k: [{a:1}, {b:2}, {c:3}]}}

这将会使得test:test命令存储的根标签下新增一个名为node2的复合标签,这个复合标签中包含一个名为k,元素为3个复合标签的列表。

怎么获取到b标签呢?可能你会想到用以下的NBT路径来表示b标签:

  • node2.k[1].b

但是使用以上的NBT路径有个前提:我已经知道了b标签所在的复合标签是位于列表中索引为1的位置。在实际应用时,一个列表中可能有一大堆复合标签——你得一个一个去数。显然,这非常耗费耐心。这个时候我们有必要使用一种新的节点写法:

  • node2.k[{b: 2}].b

上述写法中,[{b: 2}]就是一个匹配型元素节点(Match element node)。它将会按顺序逐一匹配列表中的所有复合标签,直到遍历完全部元素或发现一个带有b: 2键值对的复合标签后停止。由于此过程主要是线性遍历,所以在性能上不如列表索引。

为了方便理解,你可以将其当做是将“复合标签及其内容”作为了索引——这里的node2.k[{b: 2}]路径会选中列表中所有包含b:2键值对的复合标签——如果b标签所在的复合标签中还有其他标签:{node2:{k: [{a:1}, {b:2, bb: "e", bbb: "emmm"}, {c:3}]}},也可以通过node2.k[{b: 2}]来选中b标签所在的复合标签。

对NBT的修改

以下内容基本围绕/data modify命令展开。

“modify”即“修改”之意。其命令格式可以抽象为如下格式:

  • data modify <要修改的数据> <修改方式> <用来修改的数据>

所以data命令在语义上可概括为“使用一些数据,在特定的修改方式下修改另一些数据”。

数据操作对象

在Minecraft中,你可以通过命令操作一些对象的NBT数据。这些能被命令操作NBT的对象就是数据操作对象。游戏中有很多对象都带有可被命令操作的NBT数据:

其中,命令存储基本是专门为转存NBT数据而设计的。

这些基本数据源的NBT基本都允许通过/data modify命令来修改。但是要注意,这种修改操作可能对于个别NBT是无效的——游戏内部对这种NBT进行了限定,要求其不能被命令修改。

data modify命令中,要定义数据操作对象,必须遵循以下格式:

  • 定义数据操作对象为一个实体:entity <UUID目标选择器>
  • 定义数据操作对象为一个方块:block <X坐标> <Y坐标> <Z坐标>
  • 定义数据操作对象为一个命令存储:storage <命名空间ID>

设置实体的NBT值

修改绵羊的颜色为橘色

以下命令可以修改实体的NBT标签值:

data modify entity <实体> <NBT路径> set value <要设置的值>

例如,可以将绵羊整型Color设置为我们想要的一个整数值,以改变绵羊的颜色。

打开聊天栏,执行以下命令,可以将UUID为5eeef583-1340-4c37-ac75-3a77101918e6的绵羊的颜色修改为橙色(在试验时,你需要将其改为实际你指向的绵羊的UUID):

data modify entity 5eeef583-1340-4c37-ac75-3a77101918e6 Color set value 1

命令分析:

  • data modify即“数据 修改”。
  • entity 5eeef583-1340-4c37-ac75-3a77101918e6 Color即“实体 5eeef583-1340-4c37-ac75-3a77101918e6 颜色”——定义数据操作对象为实体5eeef583-1340-4c37-ac75-3a77101918e6,同时也通过NBT路径定义了要修改的数据。
  • set value 1即“设置 值 1”。这里的“值 1”即用来修改的数据。

连起来就是“数据 修改 实体 5eeef583-1340-4c37-ac75-3a77101918e6 颜色 设置 值 1”,我们再调整一下语言,其实就是“设置实体5eeef583-1340-4c37-ac75-3a77101918e6的Color标签的值为1”。

关于其他可以修改的标签,可以通过查看绵羊 § 实体数据来得知。对于其他实体其实也是类似的步骤:

设置方块的NBT值

请注意要修改的方块必须为方块实体,否则无效。

data modify block <方块的X坐标> <方块的Y坐标> <方块的Z坐标> <NBT路径> set value <要设置的值>

命令分析:

  • data modify即“数据 修改”。
  • block <方块的X坐标> <方块的Y坐标> <方块的Z坐标>即“位于(<方块的X坐标>,<方块的Y坐标>,<方块的Z坐标>)坐标的方块”。
  • <NBT路径> set value <要设置的值>即“设置由NBT路径所匹配的所有数据标签的值为<要设置的值>”。

连起来就是“设置(<方块的X坐标>,<方块的Y坐标>,<方块的Z坐标>)坐标处的方块的所有匹配<NBT路径>标签的值为<要设置的值>”。

批量修改

批量修改主要利用了NBT路径可一次性匹配多个值的特点。

假如有这样一段SNBT存储在test:test命令存储中:

{<span class="format-b">aL</span>: [<span class="format-6">1</span>, <span class="format-6">2</span>, <span class="format-6">3</span>, <span class="format-6">4</span>, <span class="format-6">5</span>]}

我们可以通过如下命令将其中的全部值修改为233:

data modify storage test:test aL[] set value 233

修改后:

{<span class="format-b">aL</span>: [<span class="format-6">233</span>, <span class="format-6">233</span>, <span class="format-6">233</span>, <span class="format-6">233</span>, <span class="format-6">233</span>]}

假如又有这样一段SNBT存储在test:test命令存储中:

{<span class="format-b">aL</span>: [{<span class="format-b">a</span>: <span class="format-6">1</span>}, {<span class="format-b">a</span>: <span class="format-6">1</span>}, {<span class="format-b">a</span>: <span class="format-6">1</span>}, {<span class="format-b">a</span>: <span class="format-6">2</span>}, {<span class="format-b">a</span>: <span class="format-6">2</span>}]}

我们可以通过以下命令将其中所有内容为a:1的复合标签中的a标签值修改为233:

data modify storage test:test aL[{a:1}].a set value 233

修改后:

{<span class="format-b">aL</span>: [{<span class="format-b">a</span>: <span class="format-6">233</span>}, {<span class="format-b">a</span>: <span class="format-6">233</span>}, {<span class="format-b">a</span>: <span class="format-6">233</span>}, {<span class="format-b">a</span>: <span class="format-6">2</span>}, {<span class="format-b">a</span>: <span class="format-6">2</span>}]}

针对列表和数组的修改

data modify命令有几种专门针对列表和数组的操作方式:

  • data modify ... append:在列表或数组中的最后一个数据的后面新增一个数据。
  • data modify ... prepend:在列表和数组的第一个数据前新增一个数据。
  • data modify ... insert:在列表和数组的某一个位置前新增一个数据。

下表展示了它们的具体效果:

修改前的SNBT 所执行的/data modify操作 修改后的SNBT
{<span class="format-b">aL</span>: [<span class="format-6">1</span>, <span class="format-6">2</span>, <span class="format-6">3</span>]}
... aL append value 6
{<span class="format-b">aL</span>: [<span class="format-6">1</span>, <span class="format-6">2</span>, <span class="format-6">3</span>, <span class="format-6">6</span>]}
{<span class="format-b">aL</span>: [<span class="format-6">1</span>, <span class="format-6">2</span>, <span class="format-6">3</span>]}
... aL prepend value 6
{<span class="format-b">aL</span>: [<span class="format-6">6</span>, <span class="format-6">1</span>, <span class="format-6">2</span>, <span class="format-6">3</span>]}
{<span class="format-b">aL</span>: [<span class="format-6">1</span>, <span class="format-6">2</span>, <span class="format-6">3</span>]}
... aL insert 1 value 6
{<span class="format-b">aL</span>: [<span class="format-6">1</span>, <span class="format-6">6</span>, <span class="format-6">2</span>, <span class="format-6">3</span>, <span class="format-6">3</span>]}

针对复合标签的修改

data modify ... merge命令是针对复合标签的修改:

  • 该命令就是把一个获取到的复合标签“融合”到你所指定的复合标签中——无论是来源数据还是目标数据,都必须是复合标签。
  • 若来源复合标签中存在一些目标复合标签中没有的标签名,则会将这些标签名及其对应的标签加入到目标复合标签中。
  • 会将目标复合标签中与来源复合标签重名的标签修改为来源复合标签中的重名的标签。
  • SNBT中的键值对一定是被包裹在一对花括号(“{}”)中的。因此如果使用merge+value修改方法时的后接参数需要与其他参数+value修改方法的后接参数作区分——前者不要忘记在最外层打上一对花括号。

下表展示了其具体效果:

修改前的SNBT 所执行的/data modify操作 修改后的SNBT
{}
... {} merge value {a: 1, b: 2}
{<span class="format-b">a</span>: <span class="format-6">1</span>, <span class="format-b">b</span>: <span class="format-6">2</span>}
{<span class="format-b">a</span>: <span class="format-6">1</span>, <span class="format-b">b</span>: <span class="format-6">2</span>}
... {} merge value {a: 1, b: 2, c: 3}
{<span class="format-b">a</span>: <span class="format-6">1</span>, <span class="format-b">b</span>: <span class="format-6">2</span>, <span class="format-b">c</span>: <span class="format-6">3</span>}
{<span class="format-b">a</span>: <span class="format-6">1</span>, <span class="format-b">b</span>: <span class="format-6">2</span>, <span class="format-b">c</span>: <span class="format-6">3</span>}
... {} merge value {a: 111, b: 2}
{<span class="format-b">a</span>: <span class="format-6">111</span>, <span class="format-b">b</span>: <span class="format-6">2</span>, <span class="format-b">c</span>: <span class="format-6">3</span>}

若使用data modify ... set value命令为一个复合标签设值,也会有类似“融合”的效果——若NBT路径无法匹配到任何标签,则会为这个复合标签添加一个符合这个NBT路径的标签。

修改前的SNBT 所执行的/data modify操作 修改后的SNBT
{}
... a set value 233
{<span class="format-b">a</span>: <span class="format-6">233</span>}
引用修改

data modify ... ... from命令允许引用一个来自于实体、方块或命令存储的来源数据。换句话说,用于修改的来源数据将不再由该条命令本身所决定。

小练习
1. 请你在wiki上搜索盔甲架的NBT数据,然后通过/data modify命令将一个盔甲架的护腿槽中的物品修改为皮革裤子。
2. 请你在wiki上搜索刷怪笼的NBT数据,然后通过/data modify命令把一个已有其他生物的刷怪笼修改为盔甲架刷怪笼。
3. 在排队时,往往会形成一条由若干人组成的长链。这条长链其实可以看作一个数组或列表。请你想象一下,排队的时候有什么需要遵守的规则?你能通过一系列的命令来模拟一个动态的队列吗?

SNBT在数据包中的应用

作为命令语法的一部分

很多命令都允许玩家使用SNBT来描述一个数据结构——这也是这些命令在语法上的要求。

  • /give命令中,要指定一个物品组件时。
  • 通过/data命令修改世界中方块、实体的NBT数据。
  • 使用/loot时,指定一个内联于命令内的战利品表
  • 使用/item时,指定一个内联于命令内的物品修饰器
  • 使用/execute if predicate时,指定一个内联于命令内的谓词
  • 使用/function执行宏函数时,可指定一个复合标签以传递宏函数变量。
添加自定义NBT数据

很多时候,我们需要存储我们自己定义的一些数据到一个NBT数据结构中,以方便我们利用/data/execute命令来进行逻辑上的判断和控制。

一个容易的方式是,通过使用custom_data物品组件来存储自定义的NBT数据到一个物品中。

用于实现文本组件的动态数据显示以及数据引用

很多时候,我们有动态的数据显示需求。这个时候可以利用具有nbt内容类型的文本组件来引用外部的NBT数据。由于外部的NBT数据可以通过/data命令修改,故文本组件所显示的内容也会随之变化。但是需要注意的是,可能需要对文本组件进行实时更新,否则文本组件可能仅会维持旧数据的显示。

SNBT、NBT文件和NBT程序对象

SNBT是游戏对象的一种序列化数据形式。而NBT文件则是用于长期保存游戏对象的数据内容的一种方法。NBT程序对象是NBT标签在游戏内部的程序模型,由Java代码所定义,可用于在游戏运行时存储从NBT文件中读取到的数据,或者存储解析自SNBT或JSON文件的数据,同时加载到内存中以便修改和其他程序方法的调用。

JSON的语法

基本语法

JSON(JavaScript Object Notation,JavaScript对象表示法)的语法与SNBT比较相似,但有以下区别:

  • JSON的数字不区分整型和浮点数等类型,因此不支持1b2s这样的表达,直接使用12即可。
  • 在JSON中,标签、标签名称、标签值常称为字段、键、值。在JSON中,表示键也必须用引号括起来,就像表示字符串一样。
  • JSON有布尔值true与false。
  • JSON的字符串只允许双引号,单引号字符串不接受,支持\n\u00a7等转义,未知的转义(如\a)仍将报错。JSON的字符串无论是键还是值在任何时候下都不可以省略双引号(NBT可以)。
  • JSON的语法很严格,比如,对象或列表的最后一项之后不能有逗号。
JSON键

JSON键通常表示为一个英文字段,该字段的含义往往就直接反映了其运作逻辑或作用效果。简单来说,JSON键就是某物体的“名称”。

示例:

"text": 233

以上示例中,"text"就是一个JSON键。你应该注意到,JSON键必须以双引号包围。在本wiki中,往往只提及JSON键的英文字段名称,而不带引号。如果你在某JSON格式中看到如下的表述:

“字段text的值为233”

你就要明确知道,这里的字段写为JSON后应该为:"text"

JSON数据值

JSON数据值可简称为JSON值。JSON数据值紧跟在JSON键后,不同类型的数据值有不同的格式。简单来说,JSON数据值就是某物体的“内容”。

示例:

"text": 233

以上示例中,233就是一个JSON数据值。

JSON键值对

JSON键和JSON值之间添加一个英文冒号(:)即可形成一个JSON键值对(JSON key-value pair)。多个键值对之间以逗号分隔。

示例:

"text": 233

上例展示了一个完整的JSON键值对。此键值对的“键”(JSON键)为"text",对应的“值”(JSON数据值)为233。在语义上,可以理解为:“text的值为233”或“text为233”。

另外,键、值和:之间允许添加任意数量的空白符。以下示例与以上示例等价:

"text"  :   233

各JSON数据值语法

JSON数据值共有以下几类:

  • 数值:233, 1.15
  • 布尔值:true
  • 字符串:"string"
  • 对象:{}
  • 列表:[]
JSON数值

JSON数值用以直接表示数字值。示例:

"text": 233

以上示例中,233就是一个JSON数值。一般地,若某字段的值是JSON数值,则可以说该字段为一个数值类型字段。如上例中,text就是一个数值类型字段。

JSON布尔值

JSON布尔值用以表示纯粹的二元逻辑(“真”或“假”),取值形式只可能为truefalse。示例:

"boy": true

以上示例中,true就是一个JSON布尔值。一般地,若某字段的值是JSON布尔值,则可以说该字段为一个布尔类型字段。如上例中,boy就是一个布尔类型字段。在语义上,上例可以理解为:“男生:真”或“是男孩”。

JSON字符串

JSON字符串用以存储文本信息或表示纯字符信息。示例:

"message": "我喜欢吃彩虹糖"

以上示例中,"我喜欢吃彩虹糖"就是一个JSON字符串。一般地,若某字段的值是JSON字符串,则可以说该字段为一个字符串类型字段。如上例中,message就是一个字符串类型的字段。在语义上,上例可以理解为:“消息:我喜欢吃彩虹糖”。

需要特别注意的是,JSON字符串值必须以双引号包围。或者说,以双引号包围的值一定是字符串值。以下例子中,text字段的值233是字符串而非数值。

"text": "233"

以双引号包围字符串值可以说是一种约定俗成规则。但是你需要明白,字符串一头一尾的双引号在大多数情况下只起到“标识”作用,即辅助计算机程序识别其数据类型为字符串,而不和数值类型混淆。程序在读取字符串时也不会真地读取一头一尾的双引号,双引号之间的字符才是字符串真正的“值”。

另外,字符串中不能出现单独的双引号。否则会引起语法错误。例如"text": "this is a "cat"."是错误的写法。此时如果确实需要保留字符串中的双引号,必须使用反斜杠\转义,来帮助程序进行识别,以便程序明白此处的双引号应作为字符串内容,而非一头一尾的字符串双引号标识。示例:

"text": "this is a \"cat\"."
JSON对象

JSON对象是对某个物体的抽象表达,其内部包含其所描述物体的若干“属性”(类似NBT的复合标签)。JSON对象是一种结构化使用一对花括号({})表示,内部包含若干个JSON键值对。

以下展示了一个JSON对象,其含有一个属性“text”。示例:

{
  "text": 233
}

习惯上,JSON的表达应该经常换行,并且适当缩进(通常将2个或4个空格作为一个缩进),例如以下是一个数据包的pack.mcmeta

{
  "pack": {
    "pack_format": 1,
    "description": "一个简单的数据包"
  }
}
JSON列表

JSON列表用以存储或表示一系列有序的匿名JSON数据值。示例:

{
  "players": [
    {
      "name": "LiHua"
      "sex": "female",
      "age": 10
    },
    {
      "name": "ZhangSan"
      "sex": "male",
      "age": 123456
    }
  ]
}

以上示例中,players的数据值类型为列表,列表中有两个JSON对象。一般地,若某字段的值是JSON列表,则可以说该字段的数据类型为列表。如上例中,players就是一个列表。在语义上,上例可以理解为:“有多个玩家,每个玩家都有姓名、性别和年龄三个属性”。

由于Minecraft读取命令的方式,在/tellraw/title等命令中的JSON本身不支持换行,但其内容可以用\n转义换行(一般情况下,复制一段带有缩进符号和换行符的文字,然后在游戏内按Ctrl + V粘贴,换行符和缩进符号会被自动过滤)。

编造比较复杂的JSON文本时,可以进入jsonlint.com等具有解析、纠错、转义、反转义、标准格式(更加美观)、压缩(去除多余空格、换行、缩进)等功能的网站,或使用支持类似功能的软件。

JSON的应用

JSON在数据包、资源包和JSON文本中有极大作用。参见Tutorial:制作资源包Tutorial:制作数据包Tutorial:文本组件

JSON与程序对象

JSON作为一种数据包文件格式,实质上就是保存了游戏内程序对象的一系列变量值。通过数据包,这些保存为JSON文件的变量值通过反序列化(Deserialization)可转换到对应的Java程序对象,Java程序对象也可通过序列化(Serialization)方法导出为JSON文件。

JSON和NBT的综合应用

在SNBT中的JSON

如何在SNBT中表示JSON?一个常见的场景是,当某个SNBT的数据值应表示一整段JSON原始片段时。在这方面,典型例子当属“文本组件”的应用:

SNBT
  • NBT复合标签/JSON对象
    • 字符串test:一个字符串,表示文本组件。

完整的SNBT应写为:

{<span class="format-b">test</span>: '<span class="format-a">[{"text":"123"}, "233"]</span>'}

以上,字符串test所指的内容是一个文本组件,而文本组件必须为JSON格式。这是一个“以NBT存JSON”的典型例子。为什么要用NBT字符串存JSON而不是其他类型?因为字符串相比其他NBT类型所能表示的内容是最灵活的。那么这个时候你可能又要问:为什么不能直接将JSON并入NBT结构中——JSON键当做NBT键?一个显然的原因是:JSON数组和NBT列表是不同的。

如下:

/tellraw @s [{"text":"123"}, "233"]

这是一段普通/tellraw命令,后面的部分是纯JSON文本。这一串JSON从整体来看就是一个JSON数组,列表中有两个元素,一个是JSON对象{"text":"123"},另一个是JSON字符串"233"。显然,当前JSON数组中有两种不同类型的元素——这在NBT中是不允许的。对于所有的NBT列表,都是同类元素列表同类型元素列表,即列表中每个元素的类型应保持一致。

另外还需注意的是,JSON和NBT本就是两种不同的格式规范。从本身的格式构成上来说,NBT本质上是一种二进制数据格式,只不过玩家可以使用其文本形式的SNBT来表示NBT,大多数时候SNBT还要经过游戏的一系列处理才能转化为他的NBT“父亲”。而JSON本身是就是一种文本格式。通过前面的学习你也可以发现,SNBT和JSON格式本就有诸多不同之处,它们只是在看起来比较相似而已。比如SNBT的数值有几种不同类型,且各自带有不同的数值类型后缀,而JSON中的数值则没有这种规定。

  • val: 123.0(这是JSON格式)
  • val: 123.0f(这是SNBT格式)

综上,为了保证完整的文本组件JSON格式,不能直接将JSON键当做NBT键写在SNBT中,而应将一串JSON看做一个NBT字符串。

接下来的问题,便是转义问题了。由于JSON格式中带有很多引号,所以在写入时应注意对引号进行转义:

{<span class="format-b">test</span>: "<span class="format-a">[{\"text\":\"123\"}, \"233\"]</span>"}

写转义反斜杠太麻烦?那你不要忘了本章节开头的写法:{test: '[{"text":"123"}, "233"]'}。该写法直接使用了单引号来包裹,避免了对字符串中的双引号字符添加转义反斜杠。当时你也得注意,这种单引号的用法仅限于SNBT,JSON格式中的引号全为双引号。

在JSON中的SNBT

接上文,在JSON中书写SNBT时,也一般将整段SNBT当做JSON字符串值,如下:

JSON
  • NBT复合标签/JSON对象
    • 字符串test:应为一段SNBT。
{
  "test": "{'minecraft:data': 1b}"
}

由于JSON中只能使用双引号,所以最外层直接包裹双引号。为了尽可能避免使用麻烦的反斜杠转义,对于所表示的SNBT中的引号,应尽量使用单引号来表示。

JSON用于基岩版命令

携带版1.1.0基岩版1.16.100.57加入了不同的物品堆叠组件,分别是can_place_oncan_destroyitem_lockkeep_on_death

它们仅适用于命令/give/replaceitem,语法如下:

  • {|"minecraft:can_place_on":{"blocks":["方块1","方块2"…]}}(使该物品可以放置在哪些方块旁)
  • {|"minecraft:can_destroy":{"blocks":["方块1","方块2"…]}}(使该物品可以破坏哪些方块)
  • {"minecraft:item_lock":{"mode":"lock_in_inventory"}}(将该物品锁定在玩家的物品栏)
  • {"minecraft:item_lock":{"mode":"lock_in_slot"}}(将该物品锁定在玩家物品栏的该槽位)
  • {"minecraft:keep_on_death":{}}(使该物品在玩家死亡时不会掉落)

注意,方块使用英文ID(如"air")而非数字ID(如0)。

示例:
  • /give @p iron_shovel 1 0 {"minecraft:can_destroy":{"blocks":["grass"]},"minecraft:item_lock":{"mode":"lock_in_slot"}}
给最近的玩家一把只能挖草方块且不能从物品栏移动、移除、丢弃或用于合成的铁锹。
  • /give @p netherite_pickaxe 1 0 {"minecraft:keep_on_death":{}}
给最近的玩家一把死亡后不会掉落的下界合金镐。
  • /replaceitem entity @s slot.hotbar 0 concrete 1 0 {"minecraft:can_place_on":{"blocks":["grass"]}}
将自己的快捷栏最左槽替换为一个只能放置在草方块上(在冒险模式下)的混凝土。
  • /replaceitem entity @s slot.weapon.mainhand 0 spawn_egg 64 110 {"minecraft:item_lock":{"mode":"lock_in_inventory"}}
将自己主手槽位替换为不能移除、丢弃或用于合成的一组溺尸刷怪蛋。

导航