开启辅助访问     
收藏本站

站内搜索

搜索

Minecraft(我的世界)苦力怕论坛

[BE教程] 附加包教程:47.自定义组件(二)

 发表于 2024-8-21 07:00:59 来自手机|显示全部楼层|阅读模式 IP:山西省
本帖最后由 Cat_Anchor 于 2024-8-22 09:25 编辑

注意:此页面所述功能是方块和物品事件的新版本,也就是假日创作者实验移除后的正式特性。由于作者刚学 JavaScript,内容可能有误,我会标注代码的游戏内效果和漏洞。


前言
上期,我们学习了如何注册自定义组件。这期,我们将列举所有自定义组件的触发器,并写出一些例子。
触发器
以下是所有可用的方块和物品的触发器与它们的解释。
触发器解释
beforeOnPlayerPlace玩家放置方块前
onEntityFallOn实体掉落在方块上后 ①②
onPlace方块被放置后
onPlayerDestroy玩家破坏方块后 ③
onPlayerInteract玩家右键点击方块后
onRandomTick方块收到随机刻更新后
onStepOff实体走出方块后 ②
onStepOn实体走上方块后 ②
onTick方块收到计划刻更新后 ④
onBeforeDurabilityDamage物品受到耐久损耗前
onCompleteUse物品使用完成后
onConsume物品被食用后
onHitEntity物品使实体受伤后
onMineBlock物品挖掘方块后
onUse物品使用后
onUseOn物品在方块上使用后
①:此触发器需要 minecraft:entity_fall_on 组件。
②:此触发器需要方块在 Y 轴有至少 4 像素的碰撞箱。
③:玩家必须在生存模式下,而且必须完全破坏方块。
④:此触发器需要 minecraft:tick 组件。
每个触发器能访问的数据可能是不一样的,比如方块触发器能访问方块本身和方块所处的维度,涉及到玩家的触发器还能访问玩家,而物品触发器能访问物品本身和使用物品的玩家等。
示例
这是上期最后的代码。
  1. import { world, system, BlockComponentPlayerInteractEvent, BlockComponentTickEvent, EquipmentSlot, ItemStack, Player, EntityComponentTypes } from '@minecraft/server';

  2. const blockComponents = [{
  3.   id: "supplementary:invert_texture_shadow",
  4.   trigger: {
  5.     onPlayerInteract: (e) => { 我是代码 }
  6.   }
  7. },{
  8.   id: "supplementary:up_connected_detector",
  9.   trigger: {
  10.     onTick: (e) => { 我也是代码 }
  11.   }
  12. }];

  13. world.beforeEvents.worldInitialize.subscribe((i) => {
  14.   for (const c of blockComponents) {
  15.     i.blockComponentRegistry.registerCustomComponent(c.id, c.trigger);
  16.   }
  17. });
复制代码

这期,我们就来填充“我是代码”部分。我们先不看上面代码里的第二个自定义组件,而先看第一个自定义组件,它的作用是检测手中的物品并设置方块状态。具体来说,假如我手中的物品是木棍,那么将布尔方块状态 supplementary:texture_inverted_bit 设为它目前的相反值。所以只要稍微改造一下这个代码,就能变成骨粉催熟植物之类的功能,非常方便。

首先,我们来检测一下触发这个事件的实体是不是玩家,如果不是就直接不执行下面的代码,防止非预期的作用。有时候为了性能考虑,使用某些触发器后可以不检测玩家,也就是不用写这一行。
  1. if (!e.player) return;
复制代码

然后,我们关注于这个组件要实现的功能:检测物品和设置方块状态。那么首先,我们要获取需要检测的物品和目前的方块状态值,方便以后直接设为相反值。
所以我们定义两个常量,stateValue 和 mainhandItem。
  1. const stateValue = e.block.permutation.getState('supplementary:texture_inverted_bit');
  2. const mainhandItem = e.player.getComponent('equippable').getEquipment("Mainhand");
复制代码

我们首先来分析一下 stateValue。它被赋值为后面那一长串,我们来拆解一下:e 代表事件数据,e.block 就是被玩家点击的方块。那么 e.block.permutation 就是这个方块的附加数据——方块状态。这就是我们想要的!继续向后看,e.block.permutation.getState 中的 getState 就是获取方块状态的方法。它只有一个参数,那就是方块状态的名称,这里是 supplementary:texture_inverted_bit,所以 stateValue 被赋值为目前这个方块状态的值。
然后来看看 mainhandItem。与之前一样,e 代表事件数据,e.player 就是点击方块的玩家。玩家有很多组件,我们需要获取装备栏组件,也就是 getComponent('equippable')。获取了装备栏之后,我们需要获取装备栏中的主手栏,也就是 getEquipment("Mainhand"),它会返回一个物品堆栈。所以这一长串的意思就是将 mainhandItem 赋值为玩家手上的物品堆栈。
接下来,我们可以实现逻辑了。有很多情况导致执行失败,比如方块状态被改为其他类型而不是布尔型,或者方块状态还没有定义,或者手中没有物品等。还有一种情况,那就是手中物品不是木棍。这些情况下,我们需要停止执行接下来的代码,所以写:
  1. if (stateValue === undefined || typeof stateValue !== 'boolean' || !mainhandItem || mainhandItem.typeId !== 'minecraft:stick') return;
复制代码

多个条件之间是“或”的关系时,我们可以用 || 连接,这个符号表示逻辑上的“或”,只要有一个通过就通过。
如果 stateValue 返回了 undefined(也就是未定义),那么达成条件。这里的 === 是严格相等,只有类型相等才继续判断。
如果 mainhandItem 返回了 undefined(也就是手中没有物品),那么它会被转换为 false。前面加个 ! 会让它变成 true,达成条件。
而 typeof 关键字返回值的类型,这里它应该返回 boolean 字符串。这里的 !== 是严格不相等,只要类型不相等就不相等,如果返回了其他字符串,那么方块状态已经被修改了,最后返回 true,达成条件。
最后一个是主要的逻辑检测。mainhandItem 是一个物品堆栈,它有一个属性叫 typeId,它返回物品的 ID(字符串)。我们可以用 mainhandItem.typeId === 或 !== 'ID' 检测,这里就是检测手中物品是否不是木棍。这个就相当于以前的 q.is_item_name_any('slot.weapon.mainhand',0,'物品 ID') Molang 查询。不过更常见的可能是它的已弃用的前身——q.get_equipped_item_name == '物品 ID' Molang 查询。
总之,这串检测不仅保证了代码不会异常执行,还完成了主要逻辑检测。

接下来,为了保险,我们再写一个条件执行,如下:
  1. else if (mainhandItem.typeId === 'minecraft:stick') {}
复制代码

现在就可以设置方块状态了。
  1. e.block.setPermutation(e.block.permutation.withState('supplementary:texture_inverted_bit', !stateValue));
复制代码

又是一大串代码,我们来分解一下:e.block 代表事件涉及到的方块,setPermutation 是它的方法,用于设置方块状态。它只需要一个参数,那就是方块状态。e.block.permutation 里正好有一个 withState 方法,它的作用就是指定方块状态名称和值并返回方块状态。它有两个参数,方块状态的名称和它的值。名称是固定的,supplementary:texture_inverted_bit,而值就是之前获取的 stateValue 的相反值,所以在前面加 !。这一大串下来,就完成了把方块状态设为目前的相反值——也就是切换方块状态的功能。
设置完方块状态就返回,所以代码成了这样:
  1. else if (mainhandItem.typeId === 'minecraft:stick') {
  2.   e.block.setPermutation(e.block.permutation.withState('supplementary:texture_inverted_bit', !stateValue));
  3.   return;
  4. }
复制代码

现在我们实现了所有功能——检测手中物品并设置方块状态。不过它有个漏洞,那就是拿着方块点击时不会放置,我不知道怎么修复。

接下来我们看第二个自定义组件,它的作用是根据上方的方块设置方块状态。首先,它是根据计划刻触发事件的,所以需要一个 minecraft:tick 组件告诉引擎这个方块多久触发一次计划刻,以及是不是循环触发。这个组件的语法基本相当于以前的 minecraft:queued_ticking 组件去掉了 on_tick 字段,如下:
  1. "minecraft:tick": {
  2.   "looping": true,
  3.   "interval_range": [
  4.     5,
  5.     5
  6.   ]
  7. }
复制代码

以上组件的意思是每 5 刻(0.25 秒)触发一次,而且要循环触发。

现在可以专注于脚本了,我们来看看要实现的逻辑:检测上方一格的方块是否有指定标签并设置方块状态。
所以我们先获取上方的方块,定义一个常量 topBlock 并赋值,如下:
  1. const topBlock = e.block.above();
复制代码

这里的 e.block 是事件涉及到的方块,而后面的 above 方法会获取上方一格的方块。它还有个可选的参数,指定获取上方多少格的方块,不过默认为一,我们就省略了。
还有两种方法获取周围的方块,第一种是 block 类中的 offset 方法,获取相对坐标系中的方块。第二种是 dimension 类中的 getBlock 方法,获取绝对坐标系中的方块。这两个方法都需要定义一个向量数组,比如 const topBlockLocation = [{x:0,y:0,z:0}];,太麻烦了,这里用不到。

接下来,我们就能判断这个方块到底有没有指定标签了。
  1. if (topBlock && topBlock.hasTag('valley_tile_bracket')) {}
复制代码

block 类中有一个 hasTag 方法,用于判断这个方块有没有特定标签,它的唯一一个参数是标签名,我们正好可以用一下。
上面的第一个 topBlock 判断首先保证了这个方块不是空的,然后判断是否有 valley_tile_bracket 标签,两个条件都满足后才设置方块状态,如下:
  1. if (topBlock && topBlock.hasTag('valley_tile_bracket')) {
  2.   e.block.setPermutation(e.block.permutation.withState('supplementary:connected_bit', true));
  3.   return;
  4. }
复制代码

这里的 e.block.setPermutation 之前讲过了,不再赘述。
接下来,如果上面没有方块,或者没有指定标签,就要把这个方块的方块状态设为 false,如下:
  1. else if (!topBlock || !topBlock.hasTag('valley_tile_bracket')) {
  2.   e.block.setPermutation(e.block.permutation.withState('supplementary:connected_bit', false));
  3.   return;
  4. }
复制代码

第二个自定义组件也填写完成了,我们实现了所有功能。我暂时没有找到这个组件的漏洞,不过没有检测方块状态是一个潜在的漏洞。
以上所有代码综合起来,再放在数组里,是下面这样的:
  1. const blockComponents = [{
  2.   id: "supplementary:invert_texture_shadow",
  3.   trigger: {
  4.     onPlayerInteract: (e) => {
  5.       if (!e.player) return;
  6.       const stateValue = e.block.permutation.getState('supplementary:texture_inverted_bit');
  7.       const mainhandItem = e.player.getComponent('equippable').getEquipment("Mainhand");
  8.       if (stateValue === undefined || typeof stateValue !== 'boolean' || !mainhandItem || mainhandItem.typeId !== 'minecraft:stick') return;
  9.       else if (mainhandItem.typeId === 'minecraft:stick') {
  10.         e.block.setPermutation(e.block.permutation.withState('supplementary:texture_inverted_bit', !stateValue));
  11.         return;
  12.       }
  13.     }
  14.   }
  15. }, {
  16.   id: "supplementary:up_connected_detector",
  17.   trigger: {
  18.     onTick: (e) => {
  19.       const topBlock = e.block.above();
  20.       if (topBlock && topBlock.hasTag('valley_tile_bracket')) {
  21.         e.block.setPermutation(e.block.permutation.withState('supplementary:connected_bit', true));
  22.         return;
  23.       } else if (!topBlock || !topBlock.hasTag('valley_tile_bracket')) {
  24.         e.block.setPermutation(e.block.permutation.withState('supplementary:connected_bit', false));
  25.         return;
  26.       }
  27.     }
  28.   }
  29. }]
复制代码
总结
这一期,我解释了两个自定义组件。我的代码可能有误,或者不完善,或者可以优化,欢迎指出错误并提出建议。


苦力怕论坛,感谢有您~
 发表于 2024-8-21 12:23:32 来自手机|显示全部楼层 IP:山西省
本帖最后由 指令凋灵 于 2024-8-21 12:29 编辑

如果为了性能考虑一些事件不需要判断是否为player,matches的地方可以改成ItemStack.typeId === 物品英文id
还有一个建议,用不到的模块可以不导入(比如文中的Block、BlockEvent等)
2#2024-8-21 12:23:32收起回复
Cat_Anchor2024-8-21 12:46IP:山西省
回复举报
好的,感谢提醒!
Cat_Anchor2024-8-21 12:48IP:山西省
回复举报
我也是刚学 JavaScript 的新手,而且还是纯自学,难免有错误或不足,感谢指出
苦力怕论坛,感谢有您~
回复支持

使用道具举报

本版积分规则

本站
关于我们
联系我们
坛史纲要
官方
哔哩哔哩
技术博客
下载
网易版
安卓版
JAVA
反馈
意见建议
教程中心
更多
捐助本站
QQ群
QQ群

QQ群

访问手机版

访问手机版

手机版|小黑屋|系统状态|klpbbs.com

粤公网安备 44200002445329号 | 由 木韩网络 提供支持 | GMT+8, 2024-12-12 13:50

声明:本站与Mojang以及微软公司没有从属关系

Powered by Discuz! X3.4 粤ICP备2023071842号-3