开启辅助访问     
收藏本站

站内搜索

搜索

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

[BE教程] 【26.30+】ScriptAPI中CustomForm表单的使用

 发表于 昨天 11:20|显示全部楼层|阅读模式 IP:江西省
本帖最后由 dyodyo 于 2026-7-14 11:22 编辑
提示
建议先看看这篇教程。请注意,测试版的API在正式版可能已经不适用。

开始之前,你需要知道……

  • 基本的JavaScript/TypeScript知识
  • 基岩版ScriptAPI的简单使用。





ScriptAPI为附加包的开发提供了极大的便利,其中表单(Form)是较为常用的功能之一。它的重要功能是用来收集数据,不过各大开发者们都把它玩出了花。你在过去看到的这些东西都是表单的一种:

1.jpg
基岩版创世神的笔刷编辑表单
2.jpg
结构管理器(来自于这篇帖子)的表单
不过,无论是ActionFormData、MessageFormData,还是ModalFormData,它们都是静态的。这意味着我们无法方便地用这些表单做出丰富的交互效果,直到Mojang在基岩版26.30中把@minecraft/server-ui模块的2.1.0正式版给端了上来!
在本次更新中,Mojang为我们带来了一种全新的表单:CustomForm。与之前的表单不同,它的组件支持动态更新。有了动态的表单,我们就可以做出一些过去做不到的交互效果。
5.gif



这到底是怎么实现的?

Mojang除了更新CustomForm这个表单之外,还新加入了一大堆为它服务的可观测(Observable)数据类型,有字符串ObservableString、数字ObservableNumber、布尔值ObservableBoolean,以及文本组件ObservableUIRawMessage。
所以这有什么用呢?请看下列示例。
第一步当然是更新一下我们的清单文件(manifest.json),不然没法用新的API:
  1. ……
  2. "dependencies": [
  3.     {
  4.         "module_name": "@minecraft/server",
  5.         "version": "2.8.0"
  6.     },
  7.     {
  8.         "module_name": "@minecraft/server-ui",
  9.         "version": "2.1.0"
  10.     }
  11. ]
复制代码
之后,创建并打开脚本文件,新实例化一个CustomForm:
  1. import { system, world } from "@minecraft/server";
  2. import { CustomForm } from "@minecraft/server-ui";

  3. const customForm = new CustomForm(
  4.     world.getPlayers()[0], // 玩家
  5.     "一个标题" // 窗口的标题
  6. );
复制代码
添加一个组件,比如说按钮:
  1. customForm.button( /* …… */ );
复制代码
我们注意到,添加一个按钮需要2个参数:显示的文本和按下按钮后触发的回调函数。在显示的文本这一参数中,不仅可以填字符串string和文本组件UIRawMessage,还可以填我们刚刚说的可观测字符串ObservableString和可观测文本组件ObservableUIRawMessage。让我们看看它有什么用吧。
新实例化一个可观测字符串ObservableString:
  1. const observableString = new ObservableString("我是一个字符串");
复制代码
用它作为按钮的文本:
  1. customForm.button(observableString, /* …… */);
复制代码
还差一个回调函数,写些什么呢……这个observableString有一个方法setData,还能设置数据?让我们来试一试:
  1. customForm.button(observableString, () => {
  2.     observableString.setData("更改了按钮的文本,耶");
  3. });
复制代码
根据官方文档,如果要对可观测对象设置数据,在声明它时还要传入一个额外的clientWritable属性。所以,让我们把之前的代码改一下:
  1. const observableString = new ObservableString("我是一个字符串", { clientWritable: true });
复制代码
这是最后一步了,显示表单:
  1. customForm.show();
复制代码
大功告成!
4.gif





让表单变得丰富起来

除了最基本的按钮以外,我们还有下拉栏(dropdown)、输入框(textField)、开关(toggle)、滑块(slider)等诸多组件可以使用,下面让我们来逐一认识它们。

下拉栏(dropdown)
让我们在表单中添加一个下拉栏:
  1. customForm.dropdown( /* …… */ );
复制代码
设置一个标题,这是当然的:
  1. customForm.dropdown(
  2.     "下拉栏",
  3.     /* …… */
  4. );
复制代码
根据语法提示,我们必须要给它设置一个值。比如说,来个0怎么样:
  1. customForm.dropdown(
  2.     "下拉栏",
  3.     0,
  4.     /* …… */
  5. );
复制代码
最后是下拉栏的选项们,这需要一个数组……
  1. customForm.dropdown(
  2.     "下拉栏",
  3.     0,
  4.     [
  5.         {
  6.             label: "选项1"
  7.         },
  8.         {
  9.             label: "选项2"
  10.         },
  11.         {
  12.             label: "选项3"
  13.         }
  14.     ]
  15. );
复制代码
别忘了给每个选项分配一个独一无二的值!
  1. customForm.dropdown(
  2.     "下拉栏",
  3.     0,
  4.     [
  5.         {
  6.             label: "选项1",
  7.             value: 0 // 这个选项是默认选中的
  8.         },
  9.         {
  10.             label: "选项2",
  11.             value: 1
  12.         },
  13.         {
  14.             label: "选项3",
  15.             value: 114514 // 随便什么值都是可以的,只要你能记住它们……
  16.         }
  17.     ]
  18. );
复制代码
把表单显示出来:
  1. customForm.show();
复制代码
干得漂亮!
9.jpg




为了使这个表单变得更高级一点,我们还可以让标题动起来。下拉栏的值也是可以使用可观测数字ObservableNumber的,让我们先新实例化一个ObservableNumber和一个ObservableString,后者用来作下拉栏的标题:
  1. const dropdownIndex = new ObservableNumber(0);
  2. const dropdownLabel = new ObservableString("下拉栏", { clientWritable: true });
复制代码
使用它们:
  1. customForm.dropdown(
  2.     dropdownLabel,
  3.     dropdownIndex,
  4.     [{ label: "选项1", value: 0 }, { label: "选项2", value: 1 }, { label: "选项3", value: 114514 }]
  5. );
复制代码
当下拉栏选中的选项发生变化时,我们希望改变下拉栏的标题,看来这需要使用一点新的方法来监听它的变化了。正巧可观测对象都有一个subscribe方法,这可以让我们像订阅事件一样——当下拉栏的值发生变化时(即它选中的选项发生了变化),我们订阅的回调函数就会被调用。
  1. dropdownIndex.subscribe(currentIndex => {
  2.     if (dropdownIndex.getData() === 114514) {
  3.         dropdownLabel.setData("你选中了一个十分特别的选项");
  4.     } else {
  5.         dropdownLabel.setData(你选中的值为:${currentIndex});
  6.     }
  7. });
复制代码
这很酷,不是吗?别着急,还有更酷的。经过前面的了解,我们发现,只要是能填可观测对象的地方都是可以动态变化的。所以,表单的标题也能变成ObservableString!
  1. const customFormTitle = new ObservableString("一个标题", { clientWritable: true });
  2. const customForm = new CustomForm(world.getPlayers()[0], customFormTitle);
复制代码
把选项的文字也变成ObservableString!
  1. const myOptionLabel = new ObservableString("选项2", { clientWritable: true });
  2. customForm.dropdown(
  3.     dropdownLabel,
  4.     dropdownIndex,
  5.     [
  6.         {
  7.             label: "选项1",
  8.             value: 0
  9.         },
  10.         {
  11.             label: myOptionLabel, // 注意这里!
  12.             value: 1
  13.         },
  14.         {
  15.             label: "选项3",
  16.             value: 114514
  17.         }
  18.     ]
  19. );
复制代码
在回调中处理它们!
  1. dropdownIndex.subscribe(currentIndex => {
  2.     if (currentIndex === 114514) {
  3.         dropdownLabel.setData("你选中了一个十分特别的选项");
  4.         customFormTitle.setData("恶臭的表单");
  5.         myOptionLabel.setData("你触发了彩蛋!");
  6.     } else {
  7.         dropdownLabel.setData(你选中的值为:${currentIndex});
  8.     }
  9. });
复制代码
11.gif




输入框(textField)
其实剩下的几个组件用起来基本上和下拉栏一样,让我们简单地过一遍。
  1. customForm.textField("我是标题", "我是输入的文本");
复制代码
如果要监听输入的文本变化,就可以用一个ObservableString代替原来的值。
  1. const text = new ObservableString("0");
  2. const message = new ObservableString("", { clientWritable: true });
  3. customForm.textField("请输入数字", text, {
  4.     description: message
  5. });
复制代码
然后通过订阅添加一个回调。
  1. text.subscribe(currentText => {
  2.     if (!isFinite(Number(currentText))) {
  3.         message.setData("非法的数字!");
  4.     } else {
  5.         message.setData("");
  6.     }
  7. });
复制代码
12.gif


开关(toggle)
  1. customForm.toggle("我是标题", new ObservableBoolean(true) /* 值 */);
复制代码
所有的操作依旧适用,只不过要使用布尔类型的可观测对象(ObservableNumber)。

滑块(slider)
这个就有一点不同了,不仅有值,还要有最大值和最小值。
  1. customForm.slider("我是标题", new ObservableNumber(0) /* 值 */, 0 /* 最小值 */, 100 /* 最大值 */);
复制代码
甚至还有可选的步长:
  1. customForm.slider("我是标题", new ObservableNumber(0), 0, 100, {
  2.     step: 1 // 步长
  3. });
复制代码
你注意到了吗?这里的所有参数都可以使用可观测对象,所以能干什么就不必多说了。


除了这些组件外还有……?

手搓动画
10.gif


让我们看看这是如何实现的。首先,向表单中添加一个label:
  1. customForm.label( /* …… */ );
复制代码
考虑到文本是动态的,所以这里我们使用ObservableString:
  1. const label = new ObservableString("", { clientWritable: true });
  2. customForm.label(label);
复制代码
……以及要显示的文本:
  1. const text = "打字机入场效果测试";
复制代码
编写一个辅助函数用来实现打字机入场效果:
  1. function showAnimation() {
  2.     // 如果动画完成了就停止
  3.     if (label.getData().length >= text.length) return;
  4.     // 增加一个字符
  5.     label.setData(text.substring(0, label.getData().length + 1));
  6. }
复制代码
别忘了这要循环多次!
  1. function showAnimation() {
  2.     if (label.getData().length >= text.length) return;
  3.     label.setData(text.substring(0, label.getData().length + 1));
  4.     // 如果表单还在显示的话,在 5 游戏刻之后再次调用自己
  5.     if (customForm.isShowing()) {
  6.         system.runTimeout(showAnimation, 5);
  7.     }
  8. }
复制代码
回到表单上来。先显示表单:
  1. customForm.show();
复制代码
再显示动画:
  1. // 为了防止玩家来不及看到动画开头,延迟 10 游戏刻后再调用
  2. system.runTimeout(showAnimation, 10);
复制代码
太棒了,我们的表单又变得高级了一点。


从外部更新数据
数据不仅仅止于表单内部,还可以从外部获取!比如说做一个可以实时监视掉落物数量的表单。
13.gif



让我们先添加一个label。
  1. const label = new ObservableString("", { clientWritable: true });
  2. customForm.label(label);
复制代码
编写更新文本的函数:
  1. function update() {
  2.     // 获取玩家所在维度中掉落物的数量
  3.     const itemCount = world.getPlayers()[0].dimension.getEntities({ type: "minecraft:item" }).length;
  4.     // 更新文本
  5.     label.setData(当前维度共有 ${itemCount} 个掉落物);
  6.     // 每 20 游戏刻更新一次
  7.     if (customForm.isShowing()) system.runTimeout(update, 20);
  8. }
复制代码
显示表单并更新文本:
  1. customForm.show();
  2. update();
复制代码
这些简单的例子让我们的表单拥有了高级的交互,但CustomForm能实现的功能可远不止这些。


附:一个可用的完整实例

我使用CustomForm做了一个简单的传送器,附加包可以在这里下载:
 transporter.mcpack(7.69 KB, 下载次数: 1)
它主要是为了展示表单的动态效果,支持自定义位置、朝向、维度,还有一些附加的选项。
这个行为包使用TypeScript编写,源代码可以在这里(  scripts.zip(6.82 KB, 下载次数: 1) )下载到。
由于我嫌创建可观测对象太麻烦了,所以所有的组件都被我被封装成了对象,并附带了一些方便的功能。这个包里的代码的画风大概是这样的:
  1. export function test() {
  2.     // 窗口实例
  3.     const dialog = new Dialog(world.getPlayers()[0], "标题");

  4.     // 一个输入框
  5.     const textField = new DialogTextField("输入内容");

  6.     // 一个按钮
  7.     const button = new DialogButton("清空内容", () => {
  8.         textField.text = "";
  9.     });

  10.     // 一个文本
  11.     const label = new DialogLabel("");

  12.     // 输入框的回调
  13.     textField.onChange = (text) => {
  14.         label.label = 你输入了:${text};
  15.     };

  16.     // 一个开关
  17.     const toggle = new DialogToggle("隐藏所有组件");

  18.     // 开关的回调
  19.     toggle.onChange = (toggled) => {
  20.         textField.visible = !toggled;
  21.         button.visible = !toggled;
  22.         label.visible = !toggled;
  23.     };

  24.     // 添加所有组件并显示
  25.     dialog.add(textField, button, label, toggle).show();
  26. }
复制代码
如果你想的话,可以去看看包里的源代码,可能可以让你对CustomForm的理解更加深刻。





你知道吗

  • @minecraft/server-ui模块的最新测试版本2.2.0-beta.1.26.40-preview.29中,CustomForm已经支持显示图像了。
  • dropdown组件的选项通过数组定义,所以你不能动态添加或减少选项。(~~什么时候Mojang能出个ObservableArray~~)
  • 表单最下面的close(关闭)按钮是Mojang的小巧思,目前没办法去除也没办法更改按钮上的文字。
  • 附:微软的官方文档(英文)



苦力怕论坛,感谢有您~

本版积分规则

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

QQ群

访问手机版

访问手机版

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

| 由 木韩网络 提供支持 | GMT+8, 2026-7-15 04:37

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

Powered by Discuz! X3.4