开启辅助访问     
收藏本站

站内搜索

搜索

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

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

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

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


前言
实验性玩法“假日创作者功能”的移除可谓是一次巨变。然而,它只是官方正常的开发周期,因为假日创作者功能的功能已经走出了实验性玩法,而一部分内容使用了脚本实现而已。相对于复杂的方块和物品事件语法,脚本是实现复杂逻辑的更优选择。
这次更新会使开发附加包的门槛提高一些,附加包的上限也会提高一些,但这个幅度不会很大,只有方块和物品的技术会提升。不过这次更新会削弱一些只会 JSON 而不会 JavaScript 的开发者,比如曾经的我。那么这是否意味着低质的附加包就完全不会出现,或者很少了呢?答案是不会,因为假日创作者功能的移除只是意味着方块和物品事件系统要被写为脚本,而基础的方块和物品仍然可以用 JSON 定义。
还有一个影响,那就是大部分旧版附加包都会失效,不能正常工作。除非它们的作者继续维护这些附加包,部分无人维护的优质附加包将永远停留在 1.21.2 这个正式版,有点像 Java 版 1.12.2。有些附加包体量过于巨大,靠社区维护可能需要很久。
脚本模块
为了使用自定义组件,我们首先需要定义附加包的脚本模块,在 manifest.json 的 modules 字段下,如以下数据。
  1. {
  2.   "type": "script",
  3.   "uuid": "20240810-e7b7-bbe8-a1a5-e8849ae69cac", //请替换为其他 UUID
  4.   "version": [
  5.     1,
  6.     0,
  7.     0
  8.   ], //模块的版本
  9.   "language": "javascript",
  10.   "entry": "scripts/main.js" //脚本的接入点文件
  11. }
复制代码

现在我们需要绑定我们要用的脚本模块和它的版本。比如大部分服务端逻辑都在 @minecraft/server 模块,而现在它的最新版本是 2.0.0-alpha。这个操作使用 dependencies 字段,如以下数据。
  1. "dependencies": [
  2.   {
  3.     "module_name": "@minecraft/server", //模块名称
  4.     "version": "2.0.0-alpha" //模块版本
  5.   }
  6. ]
复制代码

为了使用脚本的额外功能,还可以在 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 最后可以是这样的。
  1. {
  2.   "format_version": 2,
  3.   "header": {
  4.     "description": " ",
  5.     "name": " ",
  6.     "uuid": "20240428-e59b-a0e4-b8ba-e783ade788b1", //请替换为其他 UUID
  7.     "version": [
  8.       1,
  9.       0,
  10.       0
  11.     ],
  12.     "min_engine_version": [
  13.       1,
  14.       21,
  15.       30
  16.     ]
  17.   },
  18.   "modules": [
  19.     {
  20.       "type": "data",
  21.       "uuid": "20240428-e689-80e4-bba5-e5889be980a0", //请替换为其他 UUID
  22.       "version": [
  23.         1,
  24.         0,
  25.         0
  26.       ]
  27.     },
  28.     {
  29.       "type": "script",
  30.       "uuid": "20240810-e7b7-bbe8-a1a5-e8849ae69cac",
  31.       "version": [
  32.         1,
  33.         0,
  34.         0
  35.       ],
  36.       "language": "javascript",
  37.       "entry": "scripts/main.js"
  38.     }
  39.   ],
  40.   "dependencies": [
  41.     {
  42.       "module_name": "@minecraft/server",
  43.       "version": "2.0.0-alpha"
  44.     }
  45.   ],
  46.   "capabilities": [
  47.     "script_eval"
  48.   ]
  49. }
复制代码
脚本文件
在 scripts 文件夹中创建一个名为 main.js 的文件,现在可以在这个文件里写 JavaScript 了。首先导入模块,可以导入模块的一部分,例如:
  1. import { world, system } from '@minecraft/server';
复制代码

此时我们可以直接以 world,system 为开头写代码。
或者整个模块,例如:
  1. import * as mcs from "@minecraft/server";
复制代码
其中 mcs 可以自定义。此时使用模块中的类时需要添加 mcs. 前缀,比如 mcs.world。

注册自定义组件的过程是在世界初始化前完成的,所以我们首先订阅世界初始化之前事件:
  1. world.beforeEvents.worldInitialize.subscribe();
复制代码

括号里的写法是 (参数名) => {代码},这里的参数名可以是任意字符串,比如 event。这里我使用 i,现在代码成了这样:
  1. world.beforeEvents.worldInitialize.subscribe((i) => {});
复制代码


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

为了实现这一点,我们首先定义一个常量数组,就叫 blockComponents:
  1. const blockComponents = [];
复制代码

现在给数组里加入一个对象:
  1. const blockComponents = [{}];
复制代码

接着,我们可以定义组件名叫 id,而触发器叫 trigger:
  1. const blockComponents = [{
  2.   id: "supplementary:invert_texture_shadow", //这里填写自定义组件名,我填写了 supplementary:invert_texture_shadow 作为组件名
  3.   trigger: {}
  4. }];
复制代码

接下来,我们就可以定义它到底拥有怎样的 trigger(触发器),也可以定义触发事件之后会发生什么事了。比如定义一个玩家与方块互动的触发器:
  1. const blockComponents = [{
  2.   id: "supplementary:invert_texture_shadow", //这里填写自定义组件名,我填写了 supplementary:invert_texture_shadow 作为组件名
  3.   trigger: {
  4.     onPlayerInteract: (e) => {}
  5.   }
  6. }];
复制代码

这里的 onPlayerInteract 就相当于以前的 minecraft:on_interact 触发器组件。等等,是不是有点眼熟?(e) => {},这不就是上文提到的 (参数名) => {代码} 吗?没错,就是这样,现在我们又可以自定义一个参数名,这里是 e,和它的代码了。

回到 blockComponents 数组,现在我们可以定义很多个自定义组件了,如下:
  1. const blockComponents = [{
  2.   id: "supplementary:invert_texture_shadow",
  3.   trigger: {
  4.     onPlayerInteract: (e) => { 我是代码 }
  5.   }
  6. },{
  7.   id: "supplementary:up_connected_detector",
  8.   trigger: {
  9.     onTick: (e) => { 我也是代码 }
  10.   }
  11. }];
复制代码


现在还有一个问题,如何注册这些自定义组件?为了注册这些自定义组件,我们要回到这行代码:
  1. world.beforeEvents.worldInitialize.subscribe((i) => {});
复制代码

现在是时候填充 {} 了。我们可以用 for ... of 循环遍历刚才的数组,如下:
  1. world.beforeEvents.worldInitialize.subscribe((i) => {
  2.   for (const c of blockComponents) {}
  3. });
复制代码

这行代码的意思是遍历 blockComponents 数组,其中 c 是从 blockComponents 中接收到的值,而 {} 是每次迭代要执行的语句。

所以我们要执行什么语句?当然是注册自定义组件的语句。
  1. world.beforeEvents.worldInitialize.subscribe((i) => {
  2.   for (const c of blockComponents) {
  3.     i.blockComponentRegistry.registerCustomComponent();
  4.   }
  5. });
复制代码

这里的 blockComponentRegistry 是注册方块组件的代码,如果要注册物品组件,请使用 itemComponentRegistry。注意,这两个方法必须在 world.beforeEvents.worldInitialize.subscribe 中执行。
blockComponentRegistry 需要两个参数,一个是组件名,另一个是触发器,正好与我们的数组对应。因为每次迭代都接收到了数组中的一个对象,我们可以用 c.id 和 c.trigger 指代组件名和触发器,于是代码成了这样:
  1. world.beforeEvents.worldInitialize.subscribe((i) => {
  2.   for (const c of blockComponents) {
  3.     i.blockComponentRegistry.registerCustomComponent(c.id, c.trigger);
  4.   }
  5. });
复制代码

现在我们可以通过修改数组来定义新的自定义组件了!至于如何将自定义组件应用于方块,请看附加包教程第 11 期的“与脚本相关的组件”部分。全部代码如下:
  1. import { world, system, Block, BlockEvent, 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. });
复制代码
以上代码,经过我的测试,是可以运行的,没有任何问题。现在,我们只需要定义触发器对应的事件就行了,这部分内容放在下期。
总结
这一期,我们知道了如何注册自定义组件。我的代码可能有误,或者不完善,或者可以优化,欢迎指出错误并提出建议。


评分

参与人数 1铁粒 +100收起理由
 YesN*** + 100期待更多教程!

查看全部评分

苦力怕论坛,感谢有您~
 发表于 2024-8-31 22:32:58 来自手机|显示全部楼层 IP:贵州省
实体和物品怎么搞呢,如果要适配无假日的版本的话
5#2024-8-31 22:32:58收起回复
Cat_Anchor2024-9-1 06:12IP:天津
回复举报
实体事件没有移除,物品和方块差不多
呼啸泰坦2024-9-20 12:38IP:贵州省
回复举报
实际上实体事件不能执行指令了,不过dataDrivenEntityTrigger即可
苦力怕论坛,感谢有您~
回复支持

使用道具举报

 发表于 2024-8-20 23:05:32 来自手机|显示全部楼层 IP:山西省
onTick这个api需要在block.json里定义时间间隔,否则会报错并失效
4#2024-8-20 23:05:32收起回复
Cat_Anchor2024-8-21 06:57IP:山西省
回复举报
是的,这个在下一期提到了
苦力怕论坛,感谢有您~
回复支持

使用道具举报

 楼主|  发表于 2024-8-20 14:47:34 来自手机|显示全部楼层 IP:山西省
我已经写好了自定义组件的第二期(也就是附加包教程的第 47 期),但是就像发布按钮过热一样,我得让它冷却一下,明天想起来再发。

所以说有些时候不是我没写好教程,而是那个“按钮”还没冷却好。
3#2024-8-20 14:47:34回复收起回复
苦力怕论坛,感谢有您~
回复支持

使用道具举报

 发表于 2024-8-20 12:46:39 来自手机|显示全部楼层 IP:山东省
大佬,请问一下,那些以前需要开实验性内容的物品/方块组件,大部分组件都可以用脚本复原出来吗
2#2024-8-20 12:46:39收起回复
Cat_Anchor2024-8-20 14:11IP:山西省
回复举报
那些触发器组件(用来触发事件的组件)可以用脚本复原,其他的组件有少部分可以,大部分很难很难
苦力怕论坛,感谢有您~
回复支持

使用道具举报

本版积分规则

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

QQ群

访问手机版

访问手机版

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

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

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

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