UID82897性别保密经验 EP铁粒 粒回帖0主题精华在线时间 小时注册时间2021-7-23最后登录1970-1-1
| 本帖最后由 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 组件。 |
每个触发器能访问的数据可能是不一样的,比如方块触发器能访问方块本身和方块所处的维度,涉及到玩家的触发器还能访问玩家,而物品触发器能访问物品本身和使用物品的玩家等。
| | 示例 | 这是上期最后的代码。
- import { world, system, BlockComponentPlayerInteractEvent, BlockComponentTickEvent, EquipmentSlot, ItemStack, Player, EntityComponentTypes } from '@minecraft/server';
- const blockComponents = [{
- id: "supplementary:invert_texture_shadow",
- trigger: {
- onPlayerInteract: (e) => { 我是代码 }
- }
- },{
- id: "supplementary:up_connected_detector",
- trigger: {
- onTick: (e) => { 我也是代码 }
- }
- }];
- world.beforeEvents.worldInitialize.subscribe((i) => {
- for (const c of blockComponents) {
- i.blockComponentRegistry.registerCustomComponent(c.id, c.trigger);
- }
- });
复制代码
这期,我们就来填充“我是代码”部分。我们先不看上面代码里的第二个自定义组件,而先看第一个自定义组件,它的作用是检测手中的物品并设置方块状态。具体来说,假如我手中的物品是木棍,那么将布尔方块状态 supplementary:texture_inverted_bit 设为它目前的相反值。所以只要稍微改造一下这个代码,就能变成骨粉催熟植物之类的功能,非常方便。
首先,我们来检测一下触发这个事件的实体是不是玩家,如果不是就直接不执行下面的代码,防止非预期的作用。有时候为了性能考虑,使用某些触发器后可以不检测玩家,也就是不用写这一行。
然后,我们关注于这个组件要实现的功能:检测物品和设置方块状态。那么首先,我们要获取需要检测的物品和目前的方块状态值,方便以后直接设为相反值。
所以我们定义两个常量,stateValue 和 mainhandItem。
- const stateValue = e.block.permutation.getState('supplementary:texture_inverted_bit');
- 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 赋值为玩家手上的物品堆栈。
接下来,我们可以实现逻辑了。有很多情况导致执行失败,比如方块状态被改为其他类型而不是布尔型,或者方块状态还没有定义,或者手中没有物品等。还有一种情况,那就是手中物品不是木棍。这些情况下,我们需要停止执行接下来的代码,所以写:
- 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 查询。
总之,这串检测不仅保证了代码不会异常执行,还完成了主要逻辑检测。
接下来,为了保险,我们再写一个条件执行,如下:
- else if (mainhandItem.typeId === 'minecraft:stick') {}
复制代码
现在就可以设置方块状态了。
- e.block.setPermutation(e.block.permutation.withState('supplementary:texture_inverted_bit', !stateValue));
复制代码
又是一大串代码,我们来分解一下:e.block 代表事件涉及到的方块,setPermutation 是它的方法,用于设置方块状态。它只需要一个参数,那就是方块状态。e.block.permutation 里正好有一个 withState 方法,它的作用就是指定方块状态名称和值并返回方块状态。它有两个参数,方块状态的名称和它的值。名称是固定的,supplementary:texture_inverted_bit,而值就是之前获取的 stateValue 的相反值,所以在前面加 !。这一大串下来,就完成了把方块状态设为目前的相反值——也就是切换方块状态的功能。
设置完方块状态就返回,所以代码成了这样:
- else if (mainhandItem.typeId === 'minecraft:stick') {
- e.block.setPermutation(e.block.permutation.withState('supplementary:texture_inverted_bit', !stateValue));
- return;
- }
复制代码
现在我们实现了所有功能——检测手中物品并设置方块状态。不过它有个漏洞,那就是拿着方块点击时不会放置,我不知道怎么修复。
接下来我们看第二个自定义组件,它的作用是根据上方的方块设置方块状态。首先,它是根据计划刻触发事件的,所以需要一个 minecraft:tick 组件告诉引擎这个方块多久触发一次计划刻,以及是不是循环触发。这个组件的语法基本相当于以前的 minecraft:queued_ticking 组件去掉了 on_tick 字段,如下:
- "minecraft:tick": {
- "looping": true,
- "interval_range": [
- 5,
- 5
- ]
- }
复制代码
以上组件的意思是每 5 刻(0.25 秒)触发一次,而且要循环触发。
现在可以专注于脚本了,我们来看看要实现的逻辑:检测上方一格的方块是否有指定标签并设置方块状态。
所以我们先获取上方的方块,定义一个常量 topBlock 并赋值,如下:
- const topBlock = e.block.above();
复制代码
这里的 e.block 是事件涉及到的方块,而后面的 above 方法会获取上方一格的方块。它还有个可选的参数,指定获取上方多少格的方块,不过默认为一,我们就省略了。
还有两种方法获取周围的方块,第一种是 block 类中的 offset 方法,获取相对坐标系中的方块。第二种是 dimension 类中的 getBlock 方法,获取绝对坐标系中的方块。这两个方法都需要定义一个向量数组,比如 const topBlockLocation = [{x:0,y:0,z:0}];,太麻烦了,这里用不到。
接下来,我们就能判断这个方块到底有没有指定标签了。
- if (topBlock && topBlock.hasTag('valley_tile_bracket')) {}
复制代码
block 类中有一个 hasTag 方法,用于判断这个方块有没有特定标签,它的唯一一个参数是标签名,我们正好可以用一下。
上面的第一个 topBlock 判断首先保证了这个方块不是空的,然后判断是否有 valley_tile_bracket 标签,两个条件都满足后才设置方块状态,如下:
- if (topBlock && topBlock.hasTag('valley_tile_bracket')) {
- e.block.setPermutation(e.block.permutation.withState('supplementary:connected_bit', true));
- return;
- }
复制代码
这里的 e.block.setPermutation 之前讲过了,不再赘述。
接下来,如果上面没有方块,或者没有指定标签,就要把这个方块的方块状态设为 false,如下:
- else if (!topBlock || !topBlock.hasTag('valley_tile_bracket')) {
- e.block.setPermutation(e.block.permutation.withState('supplementary:connected_bit', false));
- return;
- }
复制代码
第二个自定义组件也填写完成了,我们实现了所有功能。我暂时没有找到这个组件的漏洞,不过没有检测方块状态是一个潜在的漏洞。
以上所有代码综合起来,再放在数组里,是下面这样的:
- const blockComponents = [{
- id: "supplementary:invert_texture_shadow",
- trigger: {
- onPlayerInteract: (e) => {
- if (!e.player) return;
- const stateValue = e.block.permutation.getState('supplementary:texture_inverted_bit');
- const mainhandItem = e.player.getComponent('equippable').getEquipment("Mainhand");
- if (stateValue === undefined || typeof stateValue !== 'boolean' || !mainhandItem || mainhandItem.typeId !== 'minecraft:stick') return;
- else if (mainhandItem.typeId === 'minecraft:stick') {
- e.block.setPermutation(e.block.permutation.withState('supplementary:texture_inverted_bit', !stateValue));
- return;
- }
- }
- }
- }, {
- id: "supplementary:up_connected_detector",
- trigger: {
- onTick: (e) => {
- const topBlock = e.block.above();
- if (topBlock && topBlock.hasTag('valley_tile_bracket')) {
- e.block.setPermutation(e.block.permutation.withState('supplementary:connected_bit', true));
- return;
- } else if (!topBlock || !topBlock.hasTag('valley_tile_bracket')) {
- e.block.setPermutation(e.block.permutation.withState('supplementary:connected_bit', false));
- return;
- }
- }
- }
- }]
复制代码 | | 总结 | 这一期,我解释了两个自定义组件。我的代码可能有误,或者不完善,或者可以优化,欢迎指出错误并提出建议。 |
|
|