玩游戏大师 | MC数据包开发攻略
数据包、指令什么的…它并不是区分普通玩家和开发者的东西,恰恰相反,
所有人都能参与到mc的开发中,毕竟mc很大一部分就是由玩家的创造性构建的。
怀着这样的目的,我想让我知道的,也让你们知道。 ——小业
基本信息
开发平台: Windows 10
MC启动器: PCL2
MC版本: Java版
参考资料
绪论
前期准备
- 下载并安装VS Code(推荐使用VS Code进行开发)
- 由于MC数据包的语法并不能被VS Code识别, 因此我们需要安装VS Code的插件: Data-pack Helper Plus(DHP大憨批!)和language-mcfunction
什么是数据包?
根据MCwiki的介绍, 数据包(Data Pack) 是MC官方为玩家进一步定制其Minecraft体验提供的一种新方式
数据包可用于在不进行任何代码修改的前提下, 覆盖或添加新的进度, 维度, 函数, 战利品表, 谓词, 配方, 结构, 标签, 伤害类型, 自定义世界生成和生物群系
数据包的内容组织是十分模块化的, 相同的内容必然出现在相同的地方, 因此, 制作数据包的过程可以理解为不断往一个新建的数据包里面添加新内容的过程
数据包的位置
与mod不同, 数据包并不和游戏绑定, 只和安装了该数据包的存档绑定, 在PCL2中打开某个版本MC的版本设置界面, 点击版本文件夹, 即可进入对应版本的MC的文件目录
在资源管理器中进入[version_name]/saves/[world_name]/datapacks/
, 即可进入某个存档的数据包储存目录:
这个目录即是我们使用数据包的目录.
那么对于游戏原版来说, 也有一个类似于"数据包"的文件, 储存着原版的配方, 战利品表等数据, 这个文件可以在[version_name]/[version_name].jar/data/minecraft(命名空间)
中找到, 这是我们初学数据包制作时需要参考的一个重要样本
数据包的结构
对于一个完整的数据包, 通常包含以下的结构.
(数据包名字): 数据包文件的名字, 是计算机区分不同文件的标识, 与游戏实现无关. 换言之, 数据包名字可以是任意的, 相对更重要的是下面的"命名空间"
pack.mcmeta: 数据包的识别文件, 一个数据包一定要有这个文件. 文件内容格式是固定的, 照抄即可
其中特定版本号对应的pack_format
的值可以在MC wiki中查询
PS: MC游戏在实现数据包时根本不会检查数据包的语法是否符合给定版本号, 因此即使随便填一个数字也不影响数据包的实际表现
{ |
pack.png: 数据包的图标, 缺省为圆石. 图片大小要求为64*64像素
data: 命名空间总文件夹, 存放各命名空间文件夹
(命名空间): 命名空间ID(Namespaced identifier)是用来指明和识别游戏中特定对象而又能避免潜在的歧义和冲突的一种方式
命名空间的概念不仅仅在MC里面出现, 在很多编程语言中也占有一席之地, 比如C++就有标准命名空间std等等, 用处是区分不同主体对于同一个标识符产生的歧义
例如MC原版和某数据包都用到了标识符apple, 假如没有命名空间, 两个apple标识符就会产生歧义, 但是假如该数据包的命名空间是class, 两个标识符就会被游戏解释为minecraft:apple和class:apple, 从而消除了歧义
实际上, 标识符命名空间的缺省值为minecraft. 例如在上述例子中, 我们在游戏中输入指令/give @s apple 1
, 会给自己minecraft:apple, 而不是class:apple.
假如你的数据包并不想对原版的游戏机制产生覆盖, 请使用自己的而不是原版的命名空间.
- 命名空间的名字即是该文件夹的名字
- 命名空间文件夹内所有标识符的命名空间都是该文件夹的名字
- 一个data文件夹下可以有多个命名空间文件夹, 他们彼此之间是独立的
- 命名空间文件夹不允许嵌套
- 命名空间只能是英文小写字母, 数字, 下划线, 短横线和点号的组合
命名空间内的文件夹: 存放特定内容, 例如recipes, advancements等等
与命名空间不同, 存放特定内容的文件夹是允许嵌套的. 例如以下的写法是合法的./(命名空间)/functions/function_a/main.mcfunction
, 这样的写法大大提高了内容的可读性
与其他文件夹不同的是, tags文件夹内的结构是保留的, 例如blocks, items等等. 我们自定义的标签应当遵循这样的分类方法, 将标签归类到特定的子文件夹中. 类似的, 子文件夹允许嵌套, 例如以下的写法是合法的./(命名空间)/tags/blocks/blocks_b/fragile_block.json
命名空间冲突处理
假如两个数据包各自的data文件夹中含有名字相同的命名空间文件夹时, 游戏会按照以下机制处理冲突:
- 按照一定次序依次加载数据包
- 如果两个文件的路径完全相同(包括路径和文件名), 后加载的会覆盖先加载的
利用上述机制, 我们可以用自己的数据包来覆盖原版数据包(因为加载时, 原版数据包一定是先于其他所有数据包记载的)
数据包指令
/reload 指令
/reload
会重新读取./datapacks
内的全部数据包, 一般用于调试或增删数据包后更新数据包状态
/datapack 指令
/datapack
控制数据包的加载和卸载
/datapack disable <name>
卸载某个数据包(并不是删除)/datapack list [available|enabled]
列出[生效的|失效的]数据包/datapack enable <name>
加载某个数据包
JSON文件格式与NBT格式
JSON文件格式
JSON是存储和交换文本信息的语法, 被广泛的运用在MC的数据包编写中
JSON的结构
JSON包含对象(由大括号{}
保存)和数组(由中括号[]
保存)两种结构, 其中对象是无序的, 数组是有序的. 对象的元素只能是空null
或键值对key: value
的有限组合, 数组的元素只能是空null
或字符串(由双引号""
括起来), 数值, 布尔值(true
或是false
), 对象和数组的有限组合. 例如下列都是合法的JSON文件(注意: 一个合法的JSON文件一定是数组或是对象)
{ |
{ |
[ |
NBT简介
NBT(二进制命名标签, Named Binary Tags)格式是一种由众多的标签所组成的树状数据结构. 在Minecraft中, 其被广泛用于向存档文件中存储数据
NBT本质上是按照一定的结构存储的数据。通常NBT本身是一个复合标签(compound), 它类似于一个Lua的表(table)、JSON的对象(object)或Python的字典(dict). 一个复合标签有多个标签(tag, 或者称为字段), 每个标签都包含键(key, 也成为标签名称)和值(value)
NBT的值
- 数字: 一般用后缀表示其储存类型, 一般可以省略
类型 | 后缀 | 字节数 | 举例 |
---|---|---|---|
字节型byte | b | 1 | 1b, 0b(对应true和false) |
短整型short | s | 2 | 32767s |
整型integer/int | - | 4 | 2147483647 |
长整型long | - | 8 | (少见) |
单精度浮点型float | f | 4 | 3.5f |
双精度浮点型double | d | 8 | 3.5d |
- 字符串: 可以用
""
或''
括起来, 如果字符串不包括标点符号, 可以省略引号. 但是为了和JSON保持一致, 建议统一用""
括起来 - 列表
- 复合标签
复合标签
类似于JSON的对象, 用大括号{}
括起来, 内部包含零个或若干个键值对, 键通常是字符串, 值可以是数字, 字符串, 列表或另一个复合标签
列表
类似于JSON的数组, 用中括号[]
括起来, 内部包含零个或若干个值, 值可以是数字, 字符串, 复合标签或另一个列表
实体的NBT标签格式可以在wiki中查询得到, 以僵尸为例, 我们可以写出其NBT:
{ |
上述NBT代表的就是一个可以破坏木门的小僵尸, 我们可以使用指令召唤一个这样的僵尸:
/summon minecraft:zombie ~ ~ ~ {CanBreakDoors: 1b, DrownedConversionTime: -1,InWaterTime: -1, IsBaby: true} |
要注意的是, 输入指令的时候是不允许换行的, 因此我们只能把NBT写成一坨()
文件路径和标签文件
路径引用格式
- 指明<命名空间>
- 写出路径(用
/
分隔路径层级) - 末尾是文件名字, 不包含后缀
- 路径省略
- 如果是标签, 在最前面加上
#
例如以下引用是合法的
#引用了原版铁砧anvil的配方anvil.json |
以下引用是非法的
#未指明命名空间, 默认会引用minecraft:stone |
标签文件类型
标签文件包含方块标签, 物品标签, 实体类型标签和函数标签
不同类型的标签文件表示不同类型的游戏元素
在函数标签.../(命名空间)/tags/functions/
内, 有两个特殊的标签
tick
标签内的函数会在每个tick执行一次load
标签内的函数会在数据包加载的时候执行一次
标签文件格式
标签文件格式可以表示为
{ |
函数
函数(Function) 是一个可包含多行命令的数据包文件. 作为一个文本文件, 函数可以很容易地进行修改, 并且在执行大量命令时更不容易像命令方块一样造成延迟.
函数的语法和调用
函数与指令很相似, 但是函数文件不以/
开头, 可以直接书写. 函数的注释以#
开头, 并且仅允许单行注释. 在函数行末使用\
会使下一行承接到该行函数后, 以达到分行书写的目的.
以<命名空间>:函数名
和#<命名空间>:函数标签名
调用一个和一系列函数
- 命令调用:
/function
命令可以调用函数 - 进度调用: 达成某个进度时, 可以调用函数作为奖励. 此时函数的执行者是玩家
- 函数调用: 函数可以嵌套调用其他函数, 也可以递归调用自身(但是一个tick最多执行32767条命令)
函数会在一个tick内执行所有命令, 执行命令的执行者会继承父函数的执行者, 例如:
- 在聊天栏输入指令, 此时执行者是玩家
- 其他情况, 执行者是游戏核心
- 用
/execute
命令可以更改该命令的执行者
实例
load
标签
load
标签会在数据包加载的时候执行一次(注意: 此时玩家并未加入世界, 因此无法通过命令寻找到玩家), 适用于数据包的前置操作
例如写一个躲猫猫数据包, 规则要求永远都是黑夜, 我们可以这样书写:
load.json
{ |
set_night.mcfunction
#停止昼夜更替 |
函数的执行者
当数据包直接调用函数时, 执行者是游戏核心
- 位置: 世界出生点(可以用
/setworldspawn
更改) - 实体: 无
- 维度: 主世界
- 朝向: +X
当玩家调用函数时, 执行者是玩家
- 位置/维度/朝向: 同玩家
- 实体: 玩家
用/execute
命令可以更改该命令的执行者(未更改的属性会继承)
例如玩家想要在自身上面10格调用函数: /execute positioned ~ ~10 ~ run function <函数>
设置函数计划
/schedule function <函数> <时间> [append|replace]
时间: 正整数+单位(t: tick, s: 秒, d: 游戏日)
append: 独立计划
replace: 覆盖之前的计划
/schedule clear <函数>
清除函数的计划
注意: 计划函数的执行者是游戏核心, 与创建计划者无关
例: 编写可以开关的函数, 每隔50tick给予所有玩家一个金苹果
give_golden_apple.mcfunction
give @a minecraft:golden_apple 1 |
循环结构和选择结构的实现
例1: 执行函数class:test
1000次
test_loop.mcfunction
#利用计分板把虚拟实体的分数设置为1000 |
test.mcfunction
#函数体 |
例2: 编写函数, 玩家调用时, 实现: 把玩家传送至当前XZ坐标, 海拔Y最高的位置(循环次数未知)
tp.mcfunction
#从最高处开始寻找 |
tp_find.mcfunction
#假如当前方块不是空气, 则把玩家传送至当前方块上方 |
例3: 编写函数, 实现: 如果当前位置是空气时, 放置一个钻石矿, 否则放置一个远古残骸
set_ore.mcfunction
scoreboard players set #is_air ctrl 0 |
配方
配方(Recipes)是一种引导新玩家游玩Minecraft的方式,通过帮助玩家了解合成、烧炼以及其他的方块和物品转化方式来使玩家熟悉游戏
配方由数据包配置,从其中读取数据. 所有的合成、烧炼、冶炼、营火烹饪、烟熏、锻造和切石配方都使用这个系统
常规配方无法更改NBT, 同理所有涉及修改/保留NBT的配方都无法被数据包修改
高炉配方
营火配方
工作台有序配方
工作台无序配方
熔炉配方
锻造台配方
烟熏炉配方
切石机配方
战利品表
战利品表(Loot table)是一种技术性JSON表,用于决定各个情况下生成什么物品,比如自然生成的容器的内容物、破坏方块时的掉落物、杀死实体时的掉落物、钓鱼时可以钓上的物品、猪灵的以物易物
战利品表可以控制什么?
- 实体死亡后的掉落物(不包括经验球)
- 方块破坏后的掉落物
- 自然生成的容器(箱子, 箱子矿车等等)内的物品
- 钓鱼钓到的物品
- 猫, 村民(村庄英雄)的赠予
- 和猪灵的以物易物
- 达成进度的奖励
战利品表函数/物品修饰器
战利品表函数与之前提到的函数完全不同. 战利品表函数也称物品修饰器, 用于控制物品的个数, 附魔, NBT等等属性
存在多个战利品表函数时, 游戏会从上到下依次使用战利品表函数
战利品表谓词
战利品表谓词是战利品表函数执行的前提条件. 当且仅当条件被满足时, 物品才会依照战利品表函数掉落或生成
例如燃烧状态的羊会掉落熟羊肉, 反之则不会
战利品表的格式
{ |
type: 战利品表类型表示该战利品表的适用范围. 加载数据包时, 游戏会检查该战利品表的语法是否与类型对应
functions: 对该战利品表或随机池内所有物品应用的战利品表函数列表
conditions: 对以下战利品表函数或者是该随机池应用的谓词列表, 当且仅当所有谓词均通过, 才应用这个战利品表函数或者是该随机池
pools: 随机池列表, 里面每一个对象都是一个独立的随机池. 例如骷髅死亡会掉落骨头和箭, 两者掉落与否是彼此独立的, 因此骨头和箭会分别写在两个随机池里面(即两个对象)
rolls: 该随机池的抽奖次数
bonus_rolls: 该随机池, 每1点幸运值能额外提供的抽奖次数
entries: 该随机池能产生的物品的列表. 当该随机池被通过时, 从该列表中按照权重随机抽出最多一项物品作为战利品. 例如挖掘钻石矿, 会根据使用工具的品质分别掉落null, 钻石和钻石矿, 此时后两个物品应当位于同一个entries
中
weight: 该项的权重, 被抽到的概率为该项的权重/总权重
quality: 每1点幸运值能额外提供的权重
type: 项的类型, 根据项的类型的不同, 接下来的参数会有不一样
战利品表的结构组织与掉落物的可能性的关系
假设挖掘方块会掉落A, B或什么都不掉落
- A与B没有关系, 放在不同的随机池内
- A与B有关系, 放在同一个随机池内
- 没有先后逻辑顺序, 放在同一个
entries
里面 - 存在先后逻辑顺序, 利用
alternative
嵌套
- 没有先后逻辑顺序, 放在同一个
除此之外
- 在
alternative
内,weight
没有意义 - 生成
empty
也算成功 - 允许有限次嵌套
- 可以利用
group
分组使用战利品表函数
实例
例1: 写一个战利品表, 实现: 产生1个钻石或1个金锭或1个绿宝石, 概率比为1:1:2
{ |
{ |
例2: 写一个战利品表: 如果玩家主手手中拿的不是钻石镐, 则生成1个钻石镐, 否则不生产任何物品
{ |
例3: 写一个僵尸死亡时的实体战利品表, 满足以下需求:
- 僵尸被玩家杀死时:
- 如果玩家主手拿着僵尸头颅, 则固定掉落1个钻石
- 当
1
失败, 有0.05的概率掉落1个钻石, 每多1级抢夺, 增加0.01掉落概率和1掉落数量, 掉落数量最多不超过3 - 如果没有掉落钻石, 掉落1个腐肉
- 如果不是玩家杀死的, 则掉落1~3个鸡蛋, 概率均匀分布
- 此外, 还有独立的0.5概率掉落1个土豆
- 如果掉落了土豆, 并且僵尸处于燃烧状态, 则改为掉落1个烤土豆
{ |
进度
进度(Advancements)是一种用来逐步引导新手玩家融入Minecraft,并给玩家提供挑战的系统
在数据包领域, 进度常常用来代替配方等无法自定义NBT的手段获得NBT物品, 同时也可以对一些函数难以判断的事件做出判定
进度的检测
进度的检测依赖于进度触发器, 不同于函数的检测, 函数只能检测某个tick或某段tick的状态, 而进度可以检测任意时刻的状态
下面给出了一些函数和进度的常见检测范围
事件 | 函数 | 进度 |
---|---|---|
玩家记分板的分数 | T | T |
玩家杀死僵尸 | T | T |
玩家杀死一只小僵尸 | F | T |
玩家被玩家(可以是自己)打了一下 | F | T |
玩家捡起草方块 | T | T |
玩家捡起村民扔出来的任意物品 | F | T |
僵尸击杀村民 | F | F |
进度的文件格式
{ |
关于进度的补充
- 完成进度时, 只有玩家可以作为奖励函数的执行者, 而其他进度相关的的其他实体不可以
- 实现重复完成进度, 可以在奖励函数中用
/advancemants
命令剥夺该进度
实例
例1: 编写一系列进度A, B和C, 满足:
- 新建选项卡, 层次关系为
A->B, A->C
- 选项卡背景是绿宝石矿贴图, 路径为
.../assets/minecraft/textures/block/emerald_ore.png
A, B, C满足:
进度 | 图标 | 边框 | 标题 | 描述 | 获得进度的方法 | 前提 |
---|---|---|---|---|---|---|
A | 绿宝石物品 | 普通 | 掠夺的开始 | 发现一个村庄 | 玩家进入村庄 | - |
B | 南瓜灯 | 上下圆边 | 拿去吧你 | 给予村民食物 | 玩家向村民丢食物 | - |
C | 附魔了抢夺III的木剑 | 爆炸边框 | 你…你干嘛? | 拿来吧你 | 当村民之间相互丢物品时, 玩家抢走物品 | 同时完成A和B |
a.json
{ |
b.json
{ |
c.json
{ |
例2: 利用进度和函数实现玩家吃下金苹果时, 返还1个苹果, 这个效果有10s的内置冷却时间
a.json
{ |
a.mcfunction
give @p minecraft:apple 1 |
a_1.mcfunction
advancement revoke @p only class:a |
数据包开发实战
前期准备
创建数据包加载成功提示函数
创建新物品
汉堡
配方: 2面包+1牛排
效果: 食用后恢复24饥饿值(熟鳕鱼: 5), 获得力量I效果, 持续60s
战利品表创建
...\mfood\loot_tables\hamburger.json
{ |
纹理创建(资源包部分)
...\minecraft\models\item\cooked_cod.json
{ |
...\mfood\models\item\hamburger.json
{ |
...\mfood\textures\item\hamburger.png
食用效果
...\mfood\advancements\hamburger.json
{ |
...\mfood\functions\hamburger.mcfunction
effect give @s strength 60 0 |
地板合成
...\minecraft\tags\functions\tick.json
{ |
...\mfood\functions\tick.mcfunction
... |
...\mfood\functions\recipe\hamburger.mcfunction
kill @e[type=item,limit=1,distance=..1,nbt={Item:{id:"minecraft:bread",Count:2b}}] |