<本站文本内容除另有声明外,转载时均必须注明出处。(详情…中文Minecraft Wiki是完全公开的。请勇于扩充与修正内容!Minecraft中文Wiki微博正在更新!或许有兴趣去看看想与其他用户进行编辑上的沟通?社区专页正是为此创建的。翻译或创建页面之前,不妨看看译名标准化Wiki条例页面。需要管理员的协助?在管理员告示板留言也许可以帮到您。>

教程/制作数据包/实例:蜜蜂助手

来自Minecraft Wiki
跳转至: 导航搜索
Information icon.svg
此特性为Java版独有。

此教程介绍一个数据包制作的实例,阅读此教程前请先确保自己掌握教程/制作数据包教程/制作资源包的知识。了解命令(尤其是扁平化后的命令/execute命令/data)、记分板原始JSON文本等页面也会有所帮助。

目标[编辑 | 编辑源代码]

动手制作一个数据包前,你应该有一个明确的目标。也许你已经阅读过很多命令教程,但是却不知道如何有机地整合这些知识。在本实例中,我们将制作一个数据包,使得玩家能够得知他视线所指的蜂巢内有多少只成年蜜蜂,有多少只幼年蜜蜂。

本实例的GitHub地址:点击这里

开始[编辑 | 编辑源代码]

让我们来分解一下需求吧。

“使得玩家能够得知他视线所指蜂巢内有多少成年蜜蜂,有多少只幼年蜜蜂。”

“视线所指”[编辑 | 编辑源代码]

如果你已经知道如何使用命令制作“视线追踪”“视线投射”,你可以使用自己的方式实现。

首先是视线所指。在17w50a中,Minecraft加入了局部坐标这一概念。局部坐标(玩家坐标系,或者摄像机坐标系)是一个以玩家头部为原点,玩家的视线(注意,和世界坐标系并不一定对齐)为“前轴”(局部坐标的第三个参数),三维中两个垂直于前轴,正方向分别指向玩家头部的左方和上方的轴为“左轴”和“上轴”(局部坐标的第一、二个参数)的空间系。举例来说,^ ^ ^1就是玩家视线前方一个方块长度的位置。如果蜂巢就在玩家视线前方一个方块长度的位置,那么执行命令/data get block ^ ^ ^1就可以得到这个蜂巢的数据了。但由于我们并不知道蜂巢到玩家头部的距离,因此我们不能直接写一个固定的坐标,而是需要以某种方式得到蜂巢的位置。

图中三色羊毛所组成的坐标轴是世界坐标系,蓝绿红对应ZYX三个坐标轴的正半轴;颗粒所组成的坐标轴是玩家坐标系(摄像机坐标系),蓝绿红对应左上前三个坐标轴的正半轴。

“多少”[编辑 | 编辑源代码]

通常情况下,一只蜂巢中至多只有3只蜜蜂。因此,在这种小数量级的情况下,我们可以进行穷举。

我们先看看蜂巢的方块实体数据。

方块实体[编辑 | 编辑源代码]

参见:方块实体格式

蜂巢和蜂箱有与之关联的方块实体,用于存储附加信息。

  • 方块实体数据
    •  FlowerPos:储存花的位置,以便其他蜜蜂能够找到。
      •  X:花的X坐标。
      •  Y:花的Y坐标。
      •  Z:花的Z坐标。
    •  Bees:巢内目前存在的实体。
      • :巢内的单个实体。
        •  MinOccupationTicks:该实体会在巢内滞留的最短时间(刻)。
        •  TicksInHive:该实体在巢内已滞留的时间(刻)。
        •  EntityData:巢内该实体的NBT数据。

观察到,蜂巢内的蜜蜂数据都保存在一个 Bees列表里面。因此,结合NBT路径的知识,我们在判定时只需要读取Bees[0]Bees[1]Bees[2]就可以了。

“成年”“幼年”[编辑 | 编辑源代码]

我们再来看看蜜蜂的实体数据。由上,我们可以得知 Bees列表内的每一项都是一个蜜蜂的实体数据,因此直接在上述的NBT路径之后接上EntityData(即Bees[n].EntityData)再直接接上蜜蜂对应的NBT路径就可以了。

实体数据[编辑 | 编辑源代码]

参见:区块格式

蜜蜂有与之相联系的包含许多该生物属性的存档数据。

  • 实体数据
    • 实体共通标签 see Template:Nbt inherit/entity/template
    • 生物共通标签 see Template:Nbt inherit/mob/template
    • 可繁殖生物额外字段 see Template:Nbt inherit/breedable/template
    • 可愤怒生物额外字段 see Template:Nbt inherit/angerable/template
    •  HivePos:其蜂箱的坐标。
      •  X:X坐标。
      •  Y:Y坐标。
      •  Z:Z坐标。
    •  FlowerPos:储存其盘旋的花的坐标。
      •  X:X坐标。
      •  Y:Y坐标。
      •  Z:Z坐标。
    •  HasNectar:决定其是否携带花粉。
    •  HasStung:决定其是否蜇过玩家或生物。
    •  TicksSincePollination:其最后一次授粉后经过的刻数。
    •  CannotEnterHiveTicks:离其能再次进入蜂箱的刻数。当该蜜蜂被玩家激怒并从蜂箱中释放出来,但又被营火烟熏到时使用。
    •  CropsGrownSincePollination:其最后一次授粉前促进了多少作物的生长。用来限制蜜蜂促进生长作物的次数。
    •  HurtBy:攻击该蜜蜂的玩家的UUID

在“可繁殖生物额外字段”中,我们可以发现, Age标签决定了这只蜜蜂是成年还是幼年,大于等于0为成年,小于0为幼年。

分析与小结[编辑 | 编辑源代码]

编写与实现[编辑 | 编辑源代码]

初始化[编辑 | 编辑源代码]

以下文字中,#<命名空间>:<名字>表示<命名空间>下的<名字>标签,<命名空间>:<名字>函数表示<命名空间>下的<名字>函数,文件名对应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.mcfunctionray.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个方块长度,再次执行自己
    • 否则(else)
      • 执行完毕

将这个类流程图与实际的代码对比一下,看看哪些是因为命令的原因而被拆开的,可能有助于理解。

说了这么多,可能还是有人不理解这个函数在干什么,是吗?

想象一下,将玩家视线想象为一根实在的光滑杆子(杆子不与除下述小球以外的物体发生碰撞),将这根杆子截取为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.jsonen_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的物品上,让查看蜂巢的人进一步减少。同样的,上述谓词也得做出相应的改变来检测。

或者,你也可以不仅仅只读取蜜蜂个数,可以顺便展示一些其他数据,等等等等。

下一步[编辑 | 编辑源代码]

这个数据包甚至没有用到一个盔甲架效果区域云,也没有用到任何战利品表结构。试着在数据包中应用这些功能也许是个好选择。

你也可以学习其他已经做成的数据包,或者自己定好一个目标,并把它做出来。

另请参阅[编辑 | 编辑源代码]