Skip to content

数据包简述与上手指北

TIP

本文是给有编程基础与经验的读者准备的快速上手介绍。

若您没有任何基础,请阅读零基础阅读推荐

配置工作环境

Minecraft

首先,您需要先获取Minecraft游戏。虽然1.13版本就添加了数据包,但我们强烈推荐您在刚上手时直接使用当前的正式版最新版。不出意外的话,本文也会按照当前的最高版本维护。

Minecraft Wiki上包含了大量的重要参考信息,它们在绝大多数情况下也都适用于最新版。

同时,虽然数据包能在大部分情况下和模组兼容,但刚上手时我们强烈建议您使用原版游戏以避免可能由模组带来的问题。

VS Code

虽然有些正在开发或是测试中的其他提供数据包开发环境的项目,目前的主流方案仍然是使用VS Code和以下插件:

  • Datapack Helper Plus by Spyglass
    • 简称"Spyglass", "DHP", 或"大憨批"。
    • 为数据包和资源包提供智能补全,纠错,着色等IntelliSense类支持。
    • spgoding.datapack-language-server
  • Mcdoc Syntax Highlighting
    • mcdoc语言支持。这是一种非我的世界官方的Schema文件。
    • 大憨批能够读取mcdoc文件为数据包的字典式自定义结构体提供自定义的补全和纠错。
    • misodee.vscode-mcdoc
  • NBT Viewer
    • 提供.nbt文件的查看与修改功能。
    • nbt是Minecraft存档使用的主要格式,用于存储地形,世界设置,世界数据等。
    • misodee.vscode-nbt

大憨批在使用时会连接其不在中国大陆的服务器下载部分数据。在中国大陆有时会因为网络问题下载失败,导致插件无法正常使用(如大量爆红,不提供补全等)。

若您遇到了该问题并且不知道如何解决,尝试加入在本页面中列出的任意QQ群聊寻求帮助。

创建您的第一个项目

数据包需要对于每个世界单独安装,也因此不是全局生效的。想要创建一个新的数据包,首先在游戏中创建一个新的世界。

创建世界时,请注意打开"允许命令"选项。虽然该选项不影响数据包的使用,但可以方便您调试。

在游戏中,按/打开聊天栏,输入命令

mcfunction
/datapack create mypack example_datapack

游戏就会为您在当前世界中创建一个新的,名为mypack的数据包。

这时打开游戏存档文件夹,点开datapacks文件夹,你的数据包mypack就躺在里面,用VS Code打开它吧。

数据包基础结构

数据包的根目录有一个pack.mcmeta的文件。

该文件包含本包的基础信息,例如适用的Minecraft版本号和描述等。游戏也通过寻找并阅读本文件辨识不同版本的数据包。

26.1.2使用上方的方法创建数据包时,它的pack.mcmeta应该长这样:

json
{
  "pack": {
    "description": "example_pack",
    "min_format": [
      101,
      1
    ],
    "max_format": 101
  }
}

其中description为数据包的显示描述信息,可以任意修改。min_formatmax_format分别为本数据包兼容的最高和最低的数据版本。pack.mcmeta的详细结构可以通过阅读wiki页面获取,但在本入门简述中您无需修改它。

资源包的结构不会在本简述中体积,但它有和数据包极为相似的元信息文件。

文件组织

数据包中的文件命名有严格的限制。所有文件和文件夹名都只能使用小写字母[a-z],数字[0-9],下划线_,连字符-,和点.

数据包中的不同符号是完全通过文件夹的结构管理的。每个文件都代表了一个独立的,“不可拆分”的对象。例如,你无法在一个文件里创建多个函数。反之,每个.mcfunction文件声明了一个新的,独立的函数。

和C++类似,数据包采用命名空间结构。来自不同命名空间的两个不同的start函数或JSON配置文件可以同时存在,被分别标识为namespace_1:startnamespace_2:start

数据包的大体文件结构如下:

  • pack.mcmeta:元数据文件
  • data:文件夹,包含所有mcfunctionjson文件。
    • <namespace>:命名空间文件夹。您的任意命名空间。本文件夹中的所有内容都会成为该命名空间的一部分。
      • function:函数文件夹。在里面创建任意函数
        • <your_function>.mcfunction:一个函数,可以嵌套更多文件夹。
      • <JSON配置项名>:一个游戏注册表的名称,例如advancement代表进度,recipe代表合成配方。
        • <your_config>.json:一个对该注册表的新增项,或覆盖一个已有项。

想要指代一个位于foo命名空间中的bar.mcfunction函数文件,直接使用foo:bar。若bar被额外放置于一个dir文件夹中,则为foo:dir/bar。对JSON文件同理。这种形式被称为命名空间ID。

您可能会发现,由于命名空间ID并不包含目标的类型,一个bar.mcfunction函数文件和任意注册表中的bar.json配置文件拥有相同的命名空间IDfoo:bar。这是正常的,因为任何需要命名空间ID的地方也知道自己需要的是什么目标。

您的第一个函数

现在,根据上方的文件结构,在根目录创建文件夹data,在其中创建您的命名空间文件夹,我们就使用tutorial

tutorial中创建文件夹function,在其中创建第一个文件hello_world.mcfunction

一个mcfunction文件由任意条命令组成,每条命令占据一行。当函数被执行时,游戏会从上到下依次执行每条命令。

如果某行的第一个非空白字符为#,则本行被视为注释。

它多少和汇编有些类似。Minecraft为您提供了一系列指令,每条指令只能完成一个简单?的操作,例如更改指定坐标的方块,在指定坐标生成生物,或操控变量进行简单的大小比较或四则运算等。

与大多数操作系统的终端命令行类似,每条命令都有一个命令头,然后是以空格相隔的数个参数。

例如say命令可以在聊天栏打印消息:

mcfunction
say <消息>

<消息>是贪婪字符串,可以为任意文本。

在你的函数中第一行写下:

mcfunction
say Hello, World!

保存,第一个函数完成了。

重载数据包并执行函数

回到游戏中,虽然您已经创建了第一个函数,但数据包并不是实时加载的。除了服务器重启(比如退出并重新进入世界)会自动加载一次数据包以外,每当数据包的内容发生变更,都需要手动重新加载数据。

想要重载,按/呼出聊天栏,输入

mcfunction
/reload

回车执行即可。reload本身也是一条命令,可以被写进函数中(虽然不建议)。事实上,你可以在聊天栏中执行任意函数中可以执行的命令,只需要在最前面加上/以区分正常的聊天消息即可(但这么做的效果不一定一致!)。

现在您的函数应该被加载了。想要执行任意函数,可以使用命令function

mcfunction
function <命名空间ID>

因此,执行你的函数试试看:/function tutorial:hello_world

当然,除了手动执行函数外,还有多种其他方式执行函数。比如由各种监听触发,或每刻,定时触发等,后面再详谈。

Mcfunction语言简述

本篇章将介绍mcfunction的语言逻辑,以为读者提供一些基础概念和参考。Wiki的命令界面有对所有命令的详细介绍,应该作为您的主要参考。此外,香草图书馆的参考页面包含了针对不同命令的详细教程。

语句控制流

return命令可以立刻结束当前函数的执行。它可以返回任意的整型;fail0的语法糖:

mcfunction
say 你好
return 1
# 不会执行
say 再见
mcfunction
# 以下两条命令等价
return 0
return fail

return run可以接任意命令,执行该命令并返回该命令的返回值。

mcfunction
# 执行本函数可以看到输出1和2,但是没有输出3
say 1
return run say 2
say 3
mcfunction
# 本函数会返回3,因为return run会返回被执行的命令的返回值。
return run return 3

很遗憾,mcfunction没有任何类似forwhilejumpgoto之类可以在函数内改变下一条被执行的命令的方式。函数永远只能按顺序从上到下执行命令。

因此,所有循环都只能通过递归实现。

同样很遗憾,虽然mcfunction可以做条件判断,但因为缺乏类似goto命令的原因,无法在函数内做分支。

execute是一个很复杂的,用于更改执行上下文的命令。它的一个功能是接在另一个命令前面,只有在某条件通过时才执行该命令,否则就不执行:

mcfunction
# 条件为真就执行<命令>
execute if <条件> run <命令>
# 条件为假就执行<命令>
execute unless <条件> run <命令>

它的条件判断仅能限制本条命令。无论通过与否都无法直接对后面的命令造成影响。若需要一个类似现代语言中的if-else if-else块,则需要额外创建3个函数,并搭配return命令:

mcfunction
# 你的主函数
say do something
# 整个条件块独立为一个函数:
function tutorial:condition
say do some more thing
mcfunction
# 函数tutorial:condition

# 若条件成立,只执行if函数(内容省略)
execute if <条件> run return run function tutorial:condition/if
# 若上方条件不成立但下方条件成立,只执行else_if函数(内容省略)
execute if <条件> run return run function tutorial:condition/else_if
# 否则,只执行下面的else块
say else部分

当然,在实际使用中很少像这样完全展开。很多时候有简便一些的方案。

变量

mcfunction中完全没有局部变量,也没有私有变量。所有变量都是全局且公开的,可以被任何人任意访问并修改。

同时,Minecraft的主逻辑是单线程的,数据包作为主逻辑的一部分也一样。因此,永远不需要担心多线程带来的数据竞争问题。

数据包常用的,保存数据的方式有两种:

记分板

记分板可以存储任意数量的32位带符号int。可以对两个记分板上的数据做基础运算,包括赋值,max,min,交换,加减乘除模。

所有的运算都只有两个运算目标;这意味着单独一条命令永远都是x += y的形式,而想要得到x = y + z的形式则需要多条命令。

记分板是一个双层结构。想要存储数值,需要先创建一个“记分项”。每个“目标”都可以在每个记分板上有一个不同的分数(想象字面意义的记分板!挂在体育场上那种)。一个目标和一个记分项共同定义了一个变量。

目标可以对应一个游戏中的实体,比如每个玩家都可以在每个记分项上有一个分数;目标也可以是完全虚拟的,不对应任何实体。

记分项在使用前必须先创建,但目标不需要声明就可以直接使用。目标在记分项上(未赋值前)的默认值为void,对它进行任何大于或小于的条件判断均不会通过,但在作为运算目标前会先被赋值0

此外,计分项的ID是数据包中少有的,没有命名空间概念的对象。若需要保证兼容性则只能使用前后缀区分。

管理记分板的命令为scoreboard。有关scoreboard命令的详细语法请见wiki,或查阅图书馆内教程

mcfunction
# 创建ID为tutorial的新计分项
scoreboard objectives add tutorial dummy
# 赋值 x@tutorial = 1
scoreboard players set x tutorial 1
# 创建ID为example的新计分项
scoreboard objectives add example dummy
# 赋值 x@example = 3
scoreboard players set x example 3
# 赋值 x@example += 5
scoreboard players add x example 5
# 赋值 y@tutorial = x@example
scoreboard players operation y tutorial = x example
# 计算 x@tutorial = max(x@tutorial, y@tutorial)
scoreboard players operation x tutorial > y tutorial
# 计算 x@exmaple *= x@tutorial
scoreboard players operation x example *= x tutorial
# return 0 if x@example >= 10
execute if score x example matches 10.. run return fail
# else return 1
return 1

Storage

Storage是一个类似JSON的字典结构的容器,以NBT格式存储(事实上,JSON几乎可以说是NBT的子集,null除外)。你可以以键值对的形式存储任意多的8位,16位,32位,或64位的带符号整数、单精度与双精度浮点数、和字符串,也可以任意嵌套字典和列表以组织它们。

听起来很美好?然而,Storage中存储的数值无法直接执行任何计算。想要计算,要么先将它们以int的形式复制进记分板,计算后再存储回来,要么使用利用其他特型的黑科技。

虽然Storage有计算限制,但它能够以复杂的结构存储复杂数据。此外,Minecraft中还有不少对象(即实体与方块实体)有属于自己的数据,用和Storage一样的格式存储。这些数据可以被复制进Storage中操作或存储……因此Storage实际上极为重要。

Storage使用命名空间格式,同样不需要声明就可以直接使用。它使用data命令操作,可以进行数据之间的读取,覆写,复制,字典合并,列表插入,删除,以及简单的字符串转换与切割。详见Wiki图书馆内教程

mcfunction
# 把tutorial:example的dict赋值为一个包含了各种数据类型的字典。数字后的字母后缀表示了它的数据类型。
data modify storage tutorial:example dict set value {int:1, short:1s, byte:1b, long:1L, float:1.0f, double:1.0d, string:"Hello World!", list:[1,2,3,4,5], child:{more_data:[1,2,3,4,5]}}
# 读一下int的数值(虽然既没有打印也没有存到其他地方)
data get storage tutorial:example dict.int
# 使用索引读一下child里面的列表的第3项:
data get storage tutorial:example dict.child.more_data[2]
# 自从1.21.5以后,列表均为异构列表,因此这里我们可以给list最后插入一个字典。
data modify storage tutorial:example dict.list append value {data:{answer:42}}
# 删掉double
data remove storage tutorial:example dict.double
# 把long复制一份存进copy:
data modify storage tutorial:example copy set from storage tutorial:example dict.long
# 把string截取前5个字符存进stem:
data modify storage tutorial:example stem set string storage tutorial:example dict.string 0 4
# 把list的所有元素都塞到more_data的最前面:
data modify storage tutorial:example dict.child.more_data prepend from storage tutorial:example dict.list[]
# 最后返回short的数值
return run data get storage tutorial:example dict.short

实体与选择器

Minecraft中的游戏对象除了方块,还有实体。实体并不像方块一样只能锁在网格里,它们都有自己的数据,且会被游戏每刻追踪更新。玩家,各种生物和怪物,乃至盔甲架,箭矢,火球之类都是实体。

有很多命令需要传入一个实体作为目标,比如kill命令,可以杀死一个实体:

mcfunction
kill <目标>

想要传入实体,如果不能在数据包内硬编码目标的ID,就只能使用选择器。选择器就是一个在运行时根据条件在世界中找到一个或多个实体的东西。

例如,选择器@a会选择所有玩家,@n会选择距离执行位置最近的实体,@r会选择随机玩家。因此kill @a命令会杀死所有玩家,kill @n会杀死最近的实体,而kill @r会杀死一个随机玩家。

除了@后跟一个字母的简单形式,还可以在后面添加各种条件以进行精细地筛选。

上面提到的大多数命令都可以额使用选择器,例如:

mcfunction
# 将所有玩家在tutorial上的分数设为1
scoreboard players set @a tutorial 1
# 给随机玩家在tutorial上的分数加1
scoreboard players add @r example 1
# 杀死所有tutorial上的分数小于等于1的玩家
kill @a[scores={tutorial=..1}]

详情请见Wiki和本图书馆中关于选择器的介绍。

实体的使用是数据包工程的核心之一。实体可以被summon命令被创建,kill命令删除,tp命令移动,data命令读取,修改,和存储于数据,它们也可以在记分板上有自己的分数,等等。使用实体可以轻松地长时间记录坐标,情况,追踪状态等。甚至Minecraft中提供一个专门的标记实体,可以在几乎不耗费性能的情况下完成上述工作。毫不夸张地说,实体的使用在数据包中和变量,控制流等一样核心。

函数宏

除非一个命令特别地允许,否则你没办法把变量传给命令,只能硬编码。1.20.2后出现的宏行以一定的性能代价解决了这个问题。它允许你在运行时使用宏“组装”出一条命令,游戏实时加载执行。这里不详细探讨,可阅读Wiki的函数界面。

错误处理

mcfunction完全没有内置的错误处理,当然也没有类似tryexceptcatch之类的语句。任何命令也永远不会报错。任何可能出现的错误都只会静默失败并继续执行下一条,最多在日志中留下警告信息。

对于可能失败的命令(比如尝试利用选择器寻找可能不存在的实体),如果需要处理失败的情况就必须由编写者显式地检查。

如果函数出现了出现了语法问题,或JSON配置文件的内容不合法,则加载时该文件就不会被加载。这种失败也只会在日志中留下信息。如果函数宏在运行时解析出错,那么整个函数都不会执行。

我之后应该做什么?

查阅Wiki和本图书馆了解更多命令!

命令就像是汇编中的指令,你应当逐渐地查阅,测试,和使用不同的命令,直到你对这个“数据包指令集”了如指掌!

Wiki的命令界面有对所有命令的详细介绍。此外,香草图书馆的参考页面包含了针对不同命令的详细教程。香草图书馆也有大量页面专精于命令系统的各个方面。

查阅注册表JSON文件!

本简述没有涉及这些文件,但它们非常重要!比如你可以注册谓词以在命令中调用来实现复杂的条件检测,把函数添加进minecraft:tick函数标签以让它每刻执行,又或是注册崭新的魔咒以服务于逻辑或是玩法,甚至注册独立于主世界,下界,和末地以外的崭新维度和新的生物群系!

Wiki的数据包界面会告诉你所有可以注册的东西。

玩玩资源包!

只靠数据包,表达能力多少受限。资源包可以添加自定义的纹理,模型,字体等,极大地拓展可能性。和数据包配合还能发展出各种各样的黑科技,估计Mojang都没想到可以这么干!

做个小项目!

最佳的成长方式不是硬读,而是自己尝试做个小小的玩意,然后查阅资料以尝试解决遇到的困难。

查看实例!

在本图书馆,月刊,和B站上找找其他人的项目或教程。他们是怎么用简单的命令组合出各种效果的?

寻求帮助!

不要闭门造车!由于中文互联网缺乏统一的大论坛,不少技术讨论都分散在一个个QQ群里。快去本页面找几个群加入。即使不发言,只看其他人发癫讨论也能收益匪浅。

最重要的……

享受乐趣!Minecraft是一款游戏,数据包当然也是。每位作者在是开发者之前首先是玩家。祝你玩的开心!

Powered by Vitepress and Github Pages