Cat_Anchor 发表于 2024-8-20 11:07:59

附加包教程:46.自定义组件(一)

本帖最后由 Cat_Anchor 于 2024-8-21 07:10 编辑

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




前言

实验性玩法“假日创作者功能”的移除可谓是一次巨变。然而,它只是官方正常的开发周期,因为假日创作者功能的功能已经走出了实验性玩法,而一部分内容使用了脚本实现而已。相对于复杂的方块和物品事件语法,脚本是实现复杂逻辑的更优选择。
这次更新会使开发附加包的门槛提高一些,附加包的上限也会提高一些,但这个幅度不会很大,只有方块和物品的技术会提升。不过这次更新会削弱一些只会 JSON 而不会 JavaScript 的开发者,比如曾经的我。那么这是否意味着低质的附加包就完全不会出现,或者很少了呢?答案是不会,因为假日创作者功能的移除只是意味着方块和物品事件系统要被写为脚本,而基础的方块和物品仍然可以用 JSON 定义。
还有一个影响,那就是大部分旧版附加包都会失效,不能正常工作。除非它们的作者继续维护这些附加包,部分无人维护的优质附加包将永远停留在 1.21.2 这个正式版,有点像 Java 版 1.12.2。有些附加包体量过于巨大,靠社区维护可能需要很久。


https://klpbbs.com/static/image/hrline/line5.png


脚本模块

为了使用自定义组件,我们首先需要定义附加包的脚本模块,在 manifest.json 的 modules 字段下,如以下数据。
{
"type": "script",
"uuid": "20240810-e7b7-bbe8-a1a5-e8849ae69cac", //请替换为其他 UUID
"version": [
    1,
    0,
    0
], //模块的版本
"language": "javascript",
"entry": "scripts/main.js" //脚本的接入点文件
}
现在我们需要绑定我们要用的脚本模块和它的版本。比如大部分服务端逻辑都在 @minecraft/server 模块,而现在它的最新版本是 2.0.0-alpha。这个操作使用 dependencies 字段,如以下数据。
"dependencies": [
{
    "module_name": "@minecraft/server", //模块名称
    "version": "2.0.0-alpha" //模块版本
}
]
为了使用脚本的额外功能,还可以在 capabilities 数组中添加 script_eval 字符串。

以下是所有可用的模块和它们的最新版本。名称版本@minecraft/common1.2.0@minecraft/debug-utilities1.0.0-beta@minecraft/math1.4.0@minecraft/server1.15.0-beta@minecraft/server-admin1.0.0-beta@minecraft/server-editor0.1.0-beta@minecraft/server-gametest1.0.0-beta@minecraft/server-net1.0.0-beta@minecraft/server-ui1.4.0-beta@minecraft/vanilla-data1.21.30-preview.23(此版本号随游戏更新而变)整个 manifest.json 最后可以是这样的。{
"format_version": 2,
"header": {
    "description": " ",
    "name": " ",
    "uuid": "20240428-e59b-a0e4-b8ba-e783ade788b1", //请替换为其他 UUID
    "version": [
      1,
      0,
      0
    ],
    "min_engine_version": [
      1,
      21,
      30
    ]
},
"modules": [
    {
      "type": "data",
      "uuid": "20240428-e689-80e4-bba5-e5889be980a0", //请替换为其他 UUID
      "version": [
      1,
      0,
      0
      ]
    },
    {
      "type": "script",
      "uuid": "20240810-e7b7-bbe8-a1a5-e8849ae69cac",
      "version": [
      1,
      0,
      0
      ],
      "language": "javascript",
      "entry": "scripts/main.js"
    }
],
"dependencies": [
    {
      "module_name": "@minecraft/server",
      "version": "2.0.0-alpha"
    }
],
"capabilities": [
    "script_eval"
]
}


https://klpbbs.com/static/image/hrline/line3.png


脚本文件

在 scripts 文件夹中创建一个名为 main.js 的文件,现在可以在这个文件里写 JavaScript 了。首先导入模块,可以导入模块的一部分,例如:
import { world, system } from '@minecraft/server';
此时我们可以直接以 world,system 为开头写代码。
或者整个模块,例如:
import * as mcs from "@minecraft/server";其中 mcs 可以自定义。此时使用模块中的类时需要添加 mcs. 前缀,比如 mcs.world。

注册自定义组件的过程是在世界初始化前完成的,所以我们首先订阅世界初始化之前事件:
world.beforeEvents.worldInitialize.subscribe();
括号里的写法是 (参数名) => {代码},这里的参数名可以是任意字符串,比如 event。这里我使用 i,现在代码成了这样:
world.beforeEvents.worldInitialize.subscribe((i) => {});

而我们知道,自定义组件由组件名和触发器两部分组成。所以我们可以定义一个数组,遍历这个数组来注册数组里的所有自定义组件,这样我们就不用每次注册自定义组件都要写一遍 world.beforeEvents.worldInitialize.subscribe 里面的代码了。

为了实现这一点,我们首先定义一个常量数组,就叫 blockComponents:
const blockComponents = [];
现在给数组里加入一个对象:
const blockComponents = [{}];
接着,我们可以定义组件名叫 id,而触发器叫 trigger:
const blockComponents = [{
id: "supplementary:invert_texture_shadow", //这里填写自定义组件名,我填写了 supplementary:invert_texture_shadow 作为组件名
trigger: {}
}];
接下来,我们就可以定义它到底拥有怎样的 trigger(触发器),也可以定义触发事件之后会发生什么事了。比如定义一个玩家与方块互动的触发器:
const blockComponents = [{
id: "supplementary:invert_texture_shadow", //这里填写自定义组件名,我填写了 supplementary:invert_texture_shadow 作为组件名
trigger: {
    onPlayerInteract: (e) => {}
}
}];
这里的 onPlayerInteract 就相当于以前的 minecraft:on_interact 触发器组件。等等,是不是有点眼熟?(e) => {},这不就是上文提到的 (参数名) => {代码} 吗?没错,就是这样,现在我们又可以自定义一个参数名,这里是 e,和它的代码了。

回到 blockComponents 数组,现在我们可以定义很多个自定义组件了,如下:
const blockComponents = [{
id: "supplementary:invert_texture_shadow",
trigger: {
    onPlayerInteract: (e) => { 我是代码 }
}
},{
id: "supplementary:up_connected_detector",
trigger: {
    onTick: (e) => { 我也是代码 }
}
}];

现在还有一个问题,如何注册这些自定义组件?为了注册这些自定义组件,我们要回到这行代码:
world.beforeEvents.worldInitialize.subscribe((i) => {});
现在是时候填充 {} 了。我们可以用 for ... of 循环遍历刚才的数组,如下:
world.beforeEvents.worldInitialize.subscribe((i) => {
for (const c of blockComponents) {}
});
这行代码的意思是遍历 blockComponents 数组,其中 c 是从 blockComponents 中接收到的值,而 {} 是每次迭代要执行的语句。

所以我们要执行什么语句?当然是注册自定义组件的语句。
world.beforeEvents.worldInitialize.subscribe((i) => {
for (const c of blockComponents) {
    i.blockComponentRegistry.registerCustomComponent();
}
});
这里的 blockComponentRegistry 是注册方块组件的代码,如果要注册物品组件,请使用 itemComponentRegistry。注意,这两个方法必须在 world.beforeEvents.worldInitialize.subscribe 中执行。
blockComponentRegistry 需要两个参数,一个是组件名,另一个是触发器,正好与我们的数组对应。因为每次迭代都接收到了数组中的一个对象,我们可以用 c.id 和 c.trigger 指代组件名和触发器,于是代码成了这样:
world.beforeEvents.worldInitialize.subscribe((i) => {
for (const c of blockComponents) {
    i.blockComponentRegistry.registerCustomComponent(c.id, c.trigger);
}
});
现在我们可以通过修改数组来定义新的自定义组件了!至于如何将自定义组件应用于方块,请看附加包教程第 11 期的“与脚本相关的组件”部分。全部代码如下:
import { world, system, Block, BlockEvent, 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);
}
});以上代码,经过我的测试,是可以运行的,没有任何问题。现在,我们只需要定义触发器对应的事件就行了,这部分内容放在下期。


https://klpbbs.com/static/image/hrline/line7.png


总结

这一期,我们知道了如何注册自定义组件。我的代码可能有误,或者不完善,或者可以优化,欢迎指出错误并提出建议。




第四十五期 第四十六期 第四十七期

呼啸泰坦 发表于 2024-8-31 22:32:58

实体和物品怎么搞呢,如果要适配无假日的版本的话

指令凋灵 发表于 2024-8-20 23:05:32

onTick这个api需要在block.json里定义时间间隔,否则会报错并失效

Cat_Anchor 发表于 2024-8-20 14:47:34

我已经写好了自定义组件的第二期(也就是附加包教程的第 47 期),但是就像发布按钮过热一样,我得让它冷却一下,明天想起来再发。https://pic.imgdb.cn/item/66c43bdfd9c307b7e953de32.jpg

所以说有些时候不是我没写好教程,而是那个“按钮”还没冷却好。

星空晶体 发表于 2024-8-20 12:46:39

大佬,请问一下,那些以前需要开实验性内容的物品/方块组件,大部分组件都可以用脚本复原出来吗
页: [1]
查看完整版本: 附加包教程:46.自定义组件(一)