此教程介绍一个数据包制作的实例,阅读此教程前请先确保自己掌握教程/制作数据包和教程/制作资源包的知识。了解命令(尤其是扁平化后的命令/execute、命令/data)、记分板、原始JSON文本等页面也会有所帮助。
目标[]
动手制作一个数据包前,你应该有一个明确的目标。也许你已经阅读过很多命令教程,但是却不知道如何有机地整合这些知识。在本实例中,我们将制作一个数据包,使得玩家能够得知他视线所指的蜂巢内有多少只成年蜜蜂,有多少只幼年蜜蜂。
本实例的GitHub地址:点击这里
开始[]
让我们来分解一下需求吧。
“使得玩家能够得知他视线所指的蜂巢内有多少只成年蜜蜂,有多少只幼年蜜蜂。”
“视线所指”[]
首先是视线所指。在17w50a中,Minecraft加入了局部坐标这一概念。局部坐标(玩家坐标系,或者摄像机坐标系)是一个以玩家头部为原点,玩家的视线(注意,和世界坐标系并不一定对齐)为“前轴”(局部坐标的第三个参数),三维中两个垂直于前轴,正方向分别指向玩家头部的左方和上方的轴为“左轴”和“上轴”(局部坐标的第一、二个参数)的空间系。举例来说,^ ^ ^1
就是玩家视线前方一个方块长度的位置。如果蜂巢就在玩家视线前方一个方块长度的位置,那么执行命令/data get block ^ ^ ^1
就可以得到这个蜂巢的数据了。但由于我们并不知道蜂巢到玩家头部的距离,因此我们不能直接写一个固定的坐标,而是需要以某种方式得到蜂巢的位置。
“多少”[]
通常情况下,一只蜂巢中至多只有3只蜜蜂。因此,在这种小数量级的情况下,我们可以进行穷举。
我们先看看蜂巢的方块实体数据。
方块实体[]
蜂巢和蜂箱有与之关联的方块实体,用于存储附加信息。
- 方块实体数据
- FlowerPos:(可选)储存花的位置,以便其他蜜蜂能够找到。
- X:花的X坐标。
- Y:花的Y坐标。
- Z:花的Z坐标。
- Bees:巢内目前存在的实体。
- :巢内的单个实体。
- MinOccupationTicks:该实体会在巢内滞留的最短时间(刻)。
- TicksInHive:该实体在巢内已滞留的时间(刻)。
- EntityData:巢内该实体的NBT数据。
- 见实体格式,不包含 UUID标签。
- :巢内的单个实体。
- FlowerPos:(可选)储存花的位置,以便其他蜜蜂能够找到。
基岩版:
- 方块实体数据
- 方块实体共通标签
- ShouldSpawnBees:生成新蜜蜂时值为true。
- Occupants:(可能不存在)蜂巢或蜂箱内的实体。
- :蜂巢或蜂箱内的具体实体。
- ActorIdentifier:蜂巢或蜂箱蜂箱内的具体单个实体。在原版游戏下总会是
minecraft:bee<>
。[需要更多信息] - TicksLeftToStay:实体离开蜂巢或蜂箱蜂箱前的时间(刻)。
- SaveData:蜂巢或蜂箱蜂箱内实体的NBT数据。
- 实体共通标签
- 此实体种类独有的标签。
- ActorIdentifier:蜂巢或蜂箱蜂箱内的具体单个实体。在原版游戏下总会是
- :蜂巢或蜂箱内的具体实体。
观察到,蜂巢内的蜜蜂数据都保存在一个 Bees列表里面。因此,结合NBT路径的知识,我们在判定时只需要读取Bees[0]
、Bees[1]
、Bees[2]
就可以了。
“成年”“幼年”[]
我们再来看看蜜蜂的实体数据。由上,我们可以得知 Bees列表内的每一项都是一个蜜蜂的实体数据,因此直接在上述的NBT路径之后接上EntityData
(即Bees[n].EntityData
)再直接接上蜜蜂对应的NBT路径就可以了。
实体数据[]
蜜蜂有与之相联系的包含许多该生物属性的存档数据。 Java版:
- 实体数据值
- 实体共通标签
- 活体共通标签
- 生物共通标签
- 可繁殖生物额外字段
- 可愤怒生物额外字段
- CannotEnterHiveTicks:离蜜蜂能再次进入蜂箱的刻数。
- CropsGrownSincePollination:蜜蜂一共促进了多少作物的生长,不超过10。此值用来限制蜜蜂促进生长作物的次数。
- FlowerPos:储存其盘旋的花的坐标。可能不存在。
- X:X坐标。
- Y:Y坐标。
- Z:Z坐标。
- HasNectar:表示蜜蜂是否携带花粉。
- HasStung:表示蜜蜂是否蜇过玩家或生物。
- HivePos:其蜂箱的坐标。可能不存在。
- X:X坐标。
- Y:Y坐标。
- Z:Z坐标。
- TicksSincePollination:蜜蜂离开蜂箱后未携带花粉的时间。如果 FlowerPos存在,当此值超过2400游戏刻(2分)时,蜜蜂会尝试飞向对应坐标。
基岩版:
在“可繁殖生物额外字段”中,我们可以发现, Age标签决定了这只蜜蜂是成年还是幼年,大于等于0为成年,小于0为幼年。
分析与小结[]
- 需要在数据包中实现视线投射。可以参考MCBBS上K_Bai的“超精准的射线追踪碰撞检测器”和MCBBS上Sethbling原版摄像机的解析。
- 由于蜂巢中最多三只蜜蜂,所以直接穷举能完成工作。对于制作数据包来说,穷举有时候也是唯一的方法。
- 要结合游戏内各个实体的NBT数据实现自己的功能。
编写与实现[]
初始化[]
#<命名空间>:<名字>
表示<命名空间>下的<名字>标签,<命名空间>:<名字>
函数表示<命名空间>下的<名字>函数,文件名对应data/<命名空间>/<名字>.mcfunction
。新建一个文件夹,填写好pack.mcmeta
,建立data
文件夹。在data
文件夹下建立beeutility
(本实例就以此为主要的命名空间)和minecraft/tags/functions
(一些函数需要被每刻执行和在地图加载时执行)文件夹。
类似于编程中变量的概念,我们需要记分项储存成年蜜蜂的数量、幼年蜜蜂的数量、总的蜜蜂的数量和蜜蜂的年龄,以便之后用命令处理和显示出来。
在命名记分项的时候,为了避免和其他数据包冲突,我们通常在命名一个记分项的时候在前面会一个前缀。通常这个前缀是该数据包英文名的简写,本实例则以bu_
为例。
建立data/beeutility/functions
,建立load.mcfunction
函数,内容如下:
scoreboard objectives add bu_beeadult dummy
scoreboard objectives add bu_beebaby dummy
scoreboard objectives add bu_beeamount dummy
scoreboard objectives add bu_beeage dummy
回顾一下/scoreboard
的语法。
scoreboard objectives add <记分项> <准则> [<显示名称>]
其中,这些记分项都不会直接以记分板的形式显示出来,所以没必要设置显示名称。准则设置为dummy
,使得该记分项只能被命令改变。
同样,你也可以建立一个unload.mcfunction
,在该函数中删除以上四个记分项。
建立data/minecraft/tags/functions/load.json
,内容如下:
{
"replace": false,
"values": [
"beeutility:load"
]
}
这样,将beeutility:load
函数设定为地图加载时执行。格式参见标签,下文不再赘述。
视线投射[]
现在,我们做“获取玩家所指的蜂巢”。由上,我们可以像下面这样一步一步尝试得出那个可能存在的蜂巢的坐标。
在beeutility
命名空间下,建立tick.mcfunction
和ray.mcfunction
函数。
tick.mcfunction
execute as @a at @s anchored eyes run function beeutility:ray
ray.mcfunction
execute if entity @s[distance=..4] if block ~ ~ ~ #minecraft:beehives run function beeutility:query
execute if entity @s[distance=..4] unless block ~ ~ ~ #minecraft:beehives positioned ^ ^ ^0.005 run function beeutility:ray
其中beeutility:query
为接下来要建立的查询函数。
可以发现,beeutility:ray
函数在特定条件下会反复执行自己,也就是递归。这样这个函数在被执行的1刻内就会执行完毕,不需要担心效率问题。
将beeutility:tick
函数加入#minecraft:tick
,使得其每刻执行。beeutility:tick
中,/execute
先指定执行者为所有活着的玩家(使用更多目标选择器语法来进一步筛选),将执行位置设定为选中的玩家,然后运用anchored
子命令,将执行位置对齐到玩家头部朝向,再运行beeutility:ray
函数。
在beeutility:ray
中,首先判断命令执行位置与执行实体(也就是一位选中的玩家)的距离是否小于4个方块长度。若是,则继续判断执行位置的方块是否在#minecraft:beehives
中,即这个方块是不是一个蜂巢。若是,那么执行beeutility:query
函数。以上过程有一个条件不满足就会跳过,执行下一条命令。假设这是游戏第一次执行这个函数,那么由于执行位置就是玩家的头部(通常不会与蜂巢重合),所以游戏不会执行查询。然后,依然判断命令执行位置与执行实体(还是那位选中的玩家)的距离是否小于4个方块长度。若是,那么判断执行位置的方块是否不满足在#minecraft:beehives
中(unless
可以理解为if not
)。若不满足,那么将执行位置向玩家视线方向移动0.005个方块长度,执行beeutility:ray
函数本身。
与一般编程语言不同的是,命令并没有提供非常方便的else
,因此我们需要重复验证很多条件。实际思路可能更类似于以下情况:
ray.mcfunction
- 如果执行位置到玩家头部的距离小于等于4
- 如果是(then)
- 如果执行位置所在的方块是一个蜂巢
- 如果是(then)
- 查询这个蜂巢
- 否则(else)
- 将执行位置向玩家视线方向移动0.005个方块长度,再次执行自己
- 如果是(then)
- 如果执行位置所在的方块是一个蜂巢
- 否则(else)
- 执行完毕
- 如果是(then)
将这个类流程图与实际的代码对比一下,看看哪些是因为命令的原因而被拆开的,可能有助于理解。
说了这么多,可能还是有人不理解这个函数在干什么,是吗?
想象一下,将玩家视线想象为一根实在的光滑杆子(杆子不与除下述小球以外的物体发生碰撞),将这根杆子截取为4个方块长度长,在杆子的末端有一个小挡板。在杆子的起点(玩家的眼睛)处套上一个小球,这个小球只会与蜂巢和杆子发生碰撞,然后让球沿杆向前运动。小球会不停往前运动,直到它在途中遇到了一个蜂巢(此时就会在这个位置执行beeutility:query
函数),或者它到达了杆子的最末端的挡板处(此时整个beeutility:ray
函数执行完毕)。
与一般的物理学运动不同的是,这个小球在移动时不是连续的而已(类似于以这个小球为执行实体,以玩家视线方向为小球视线方向,每次移动就是在执行/tp ^ ^ ^0.005
)。
这样,我们就实现了一个简单的“视线投射”,也就是获取玩家所看方块(本实例则是蜂巢)的坐标。
有人可能会问,如果碰不到蜂巢会怎么样呢?碰不到的意思就是玩家没有看着蜂巢,自然也就不存在要获取的蜂巢了。也许还会有人问0.005格是不是太慢了,见上,1刻内会把以上递归操作全部执行完毕,所以实际的平均速度非常快。
查询与显示[]
查询[]
在beeutility
命名空间下,建立beeutility:query
函数。
execute store result score @s bu_beeamount run data get block ~ ~ ~ Bees
这条命令将执行结果储存到玩家的记分项bu_beeamount
中,而执行的实际命令是获取执行位置方块数据中的 Bees列表。该命令的执行结果为该列表的长度,即总共有多少只蜜蜂。
# region query "Bee 1"'s age
execute store result score @s bu_beeage if data block ~ ~ ~ Bees[0].EntityData.Age run data get block ~ ~ ~ Bees[0].EntityData.Age
execute if data block ~ ~ ~ Bees[0].EntityData.Age if score @s bu_beeage matches 0.. run scoreboard players add @s bu_beeadult 1
execute if data block ~ ~ ~ Bees[0].EntityData.Age if score @s bu_beeage matches ..-1 run scoreboard players add @s bu_beebaby 1
# endregion
以上代码块重复三次,剩下两次分别将Bees[0]
替换为Bees[1]
和Bees[2]
即可。
以下将store
子命令放在每个分析过程的最后。
第一条命令,先判断执行位置的方块(蜂巢)数据中的第一只蜜蜂是否存在 Age标签,即判断是否存在第一只蜜蜂。若有,执行命令,获取第一只蜜蜂的 Age标签,该命令的执行结果即为这个标签的值,将结果储存到玩家的记分项bu_beeage
中。若无,则中断。
第二条命令,判断是否存在第一只蜜蜂,若有,判断玩家记分项bu_beeage
(也就是获取到的 Age)是否大于等于0,若是,则将玩家的记分项bu_beeadult
加1。
第三条命令,判断是否存在第一只蜜蜂,若有,判断玩家记分项bu_beeage
是否小于等于-1(即小于0),若是,则将玩家的记分项bu_beebaby
加1。
于是我们达成了目标——知道有多少只成年蜜蜂,有多少只幼年蜜蜂。现在要做的就是把结果显示出来了。
显示[]
这样一来,显示就简单很多了——我们有/title
,有/tellraw
,也许还可以用/bossbar
。此处以/title <选择器> actionbar
为例。
title @s actionbar ["",{"text":"[蜜蜂助手] ","color":"gold"},{"score":{"name":"*","objective":"bu_beeamount"}},"只蜜蜂,成年的",{"score":{"name":"*","objective":"bu_beeadult"}},"只,幼年的",{"score":{"name":"*","objective":"bu_beebaby"}},"只"]
此时,玩家的快捷栏上方就会显示蜂巢里有多少蜜蜂,有多少只成年蜜蜂,有多少只幼年蜜蜂了。
有关文本的格式,请见原始JSON文本。
清理[]
需要注意的是,以上四个记分项需要及时清理掉,否则会影响到下一次查询。
scoreboard players set @s bu_beeamount 0
scoreboard players set @s bu_beeadult 0
scoreboard players set @s bu_beebaby 0
scoreboard players set @s bu_beeage 0
润色与改进[]
目前这个数据包在加载地图后便会一直检测所有玩家有没有盯着蜂巢,有时这会导致极大的服务器计算资源浪费。而如果你在一个有多语需求的服务器使用这个数据包,也只有会中文的人能使用。下面介绍一下谓词和原始JSON文本。
筛选玩家[]
上面也提示了,说运用选择器参数可以筛选玩家。如果不是一直检测所有玩家的话,那么就只能检测有需要使用的人了。很容易想到,通常我们会使用玻璃瓶对蜂巢收集蜂蜜,因此可以只检测手(以副手为例)中拿着玻璃瓶的玩家。
建立data/beeutility/predicates/hold_glass_bottle.json
谓词文件,内容如下:
{
"condition": "minecraft:entity_properties",
"entity": "this",
"predicate": {
"equipment":{
"offhand":{
"item": "minecraft:glass_bottle"
}
}
}
}
此谓词会检测应用该谓词的实体是否在副手拿着玻璃瓶。通过目标选择器参数,我们可以应用该谓词,筛选出有需要使用该数据包的玩家。
将beeutility:tick
函数前面改为:
execute as @a[predicate=beeutility:hold_glass_bottle] at @s anchored eyes ...
就可以筛选了。
多语言[]
细心的你也许用/data
获取过灾厄旗帜的数据,这个物品的名字是一个原始JSON文本,里面含有 translate属性,值为"block.minecraft.ominous_banner"
。这样的值被称之为本地化键名(旧称翻译关键字或者翻译标识符)。如果你解压原版游戏的.jar
文件,你会发现上面的值就是assets/lang/en_us.json
里的键。游戏在解析 translate的时候先将值在资源包和外置资源文件的assets/lang/<当前语言的语言代码>.json
的键中寻找,再将对应的值(也就是实际的文本)实际显示出来。如果当前语言没有,就去en_us.json
里找。若都没有,就只能直接输出标识符。
原始JSON文本页面还提到了一个 with属性,就是和 translate搭配使用的。如果你自己翻过语言文件,你就会发现一些文本中含有%s
、%n$s
之类的变量。这个时候, with属性里面的文本就会按顺序替代这些变量。
新建一个资源包,填写好pack.mcmeta
,建立assets/beeutility/lang
文件夹,在文件夹下建立语言文件,以下以zh_cn.json
和en_us.json
为例。
en_us.json
{
"beeutility.actionbar": "%s %s",
"beeutility.beeutility": "[Bee utility]",
"beeutility.result": "%d bee(s), %d adult bee(s), %d baby bee(s)"
}
zh_cn.json
{
"beeutility.actionbar": "%s %s",
"beeutility.beeutility": "[蜜蜂助手]",
"beeutility.result": "%d 只蜜蜂,成年的 %d 只,幼年的 %d 只"
}
也许你会很迷惑,为什么会有一个"%s %s"
?这是因为有的语言可能需要调整显示顺序,对于这些语言我们可以将其设为%2$s %1$s
来改变顺序。
现在,将上述/title
命令的原始JSON文本写成这样:
{
"translate": "beeutility.actionbar",
"with": [
{
"translate": "beeutility.beeutility",
"color":"gold"
},
{
"translate": "beeutility.result",
"with": [
{"score":{"name":"*","objective":"bu_beeamount"}},
{"score":{"name":"*","objective":"bu_beeadult"}},
{"score":{"name":"*","objective":"bu_beebaby"}}
]
}
]
}
现在,将资源包通过服务器资源包分发出去就可以了。
更进一步?[]
既然已经用上资源包了,那么我们也可以另做一个调用CustomModelData
的物品模型并把CustomModelData
应用到一个不可用原版方法获得的自定义NBT的物品上,让查看蜂巢的人进一步减少。同样的,上述谓词也得做出相应的改变来检测。
或者,你也可以不仅仅只读取蜜蜂个数,可以顺便展示一些其他数据,等等等等。
下一步[]
这个数据包甚至没有用到一个盔甲架和区域效果云,也没有用到任何战利品表和结构。试着在数据包中应用这些功能也许是个好选择。
同样,你也可以尝试着和资源包结合,尝试把上文的玻璃瓶做成一个自定义物品。
你也可以学习其他已经做成的数据包,或者自己定好一个目标,并把它做出来。
另请参阅[]
- ruhuasiyu的数据包制作教程
- ruhuasiyu的《更多的合成》数据包,其中包含大量可学习借鉴的地方
组件 |
| ||
---|---|---|---|
数据包 | |||
教程 |
|