开启辅助访问     
收藏本站

站内搜索

搜索

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

[BE教程] 附加包教程:60.SAPI:表单

 发表于 2026-2-8 21:02:14 来自手机|显示全部楼层|阅读模式 IP:山西省
前言
从这期开始,我们就开始介绍一些基本的 SAPI 知识。我们首先来看目前最新的表单实现方法:数据驱动 UI(DDUI)。

数据驱动 UI 是 26.10.21 加入的新内容,在这之前,我们通常使用 ModalFormData 来创建比较复杂的自定义表单。但是这种旧表单限制较多,功能较少。因此,出现了新的表单,即 DDUI 式表单。
准备
由于数据驱动 UI 使用 @minecraft/server-ui 模块,我们首先需要在 manifest.json 中导入这个模块的最新版本。
  1. {
  2.   "module_name": "@minecraft/server-ui",
  3.   "version": "2.1.0-beta"
  4. }
复制代码


之后,我们在脚本文件中导入 DDUI 式表单需要用到的类,CustomForm 和 Observable。
  1. import { CustomForm, Observable } from "@minecraft/server-ui";
复制代码


CustomForm 是一个用于创建最基础的表单的类,而 Observable 是 DDUI 独有的新功能:可观察对象,它属于一种包装器。

那么可观察对象是什么?其实就是一个代表着 UI 中的值的对象,比如要在表单里添加“目前人数:2”的文本,那么这个文本就是一个可观察对象。我们需要用可观察对象,而不是直接指定值,这样,我们可以修改可观察对象的值,然后 UI 就会自动更新。

总之,如果要在 UI 里定义不变的量,比如固定文本的提示,或者滑块的最大最小值,就直接用对应的字面量;但是如果要显示“滑块的值为:40”“选择的时间是 22:17”“附近有 12 个实体”这样的文本,并且动态更新其中的值,或者要提供表单的默认值(实际上,也是获取对于这个值的引用)那么应该使用可观察对象。




我们首先来做一个最基本的表单。什么都没有,只有一个标题。
  1. CustomForm.create(player, "我是标题").show()
复制代码


这段代码就创建了一个最基本的表单,其中,player 是表单要显示给的玩家,后面的字符串自然是标题。现在它看起来是这样的。



目前为止,相当简单!

然后……也许给它添加一条分割线?

  1. CustomForm
  2. .create(player, "我是标题")
  3. .divider()
  4. .show()
复制代码


这段代码与上面那段相比,只是增加了 .divider(),这就是添加分割线的关键。

现在它是这样的!






在添加更多控件之前,我们首先来看看上面的代码到底有什么含义。首先,既然我们要创建自定义表单,那么当然要写出 CustomForm,它代表自定义表单这个类。这个类下面有一些方法(函数),既然要“创建”表单,那么肯定要调用 create 方法。这个方法接受两个参数,第一个是显示的玩家,第二个就是表单的标题。所以说,对 CustomForm 调用 create 方法,并传入两个参数——

  1. CustomForm
  2. .create(player, "我是标题")
复制代码


之后,怎么添加更多控件?其实 CustomForm 的 create 方法有返回值。也就是说,调用了一个方法之后,它总是会返回点什么东西,有时候是 undefined,而对于这个 create 方法,它返回 CustomForm。所以说,create 之后,我们会得到一个 CustomForm 对象。这又属于 CustomForm 类,这样,我们就可以在返回的对象上调用其他属于 CustomForm 的方法了。

大多数给表单添加控件的方法都会返回修改后的 CustomForm,返回了 CustomForm,就可以继续添加控件。divider 方法也是如此,它也返回了 CustomForm,这样我们就可以继续添加控件。不过我们没有继续添加,而是让表单结束,调用 show 方法,把表单显示给玩家。show 方法不会返回 CustomForm,毕竟表单都已经显示给玩家了。




既然了解了代码的原理,我们就可以加入更多控件了。让我们调用 label 和 spacer,给表单添加一点文本和空行……

  1. CustomForm
  2.   .create(e.sourceEntity, '我是标题')
  3.   .divider()
  4.   .label('我是测试文本!')
  5.   .spacer()
  6.   .label('§a丰§b富§c多§d彩§e的§g文§u本')
  7.   .show()
复制代码




是的!DDUI 的 label 是支持 § 格式化代码的,这一点与 Ore UI(蜂鸟 UI)不同。

对了,有一件事需要注意,那就是两个 label 文本之间一定要加 spacer!否则……文字都挤到一起了……




好了,我们已经学会了最基本的表单控件。接下来,我们即将创建一个按钮。我们暂时还不会遇到可观察对象,不过很快就会见到它的!

那么,按钮怎么定义?其实相当简单。

  1. .button('一个神秘的按钮', _ => {})
复制代码


这段代码的后边怎么那么多奇怪符号?别急,它们其实只有一个作用。不过我们先看 button 方法的第一个参数,它很简单,就是指定按钮上的文本。这里,文本是“一个神秘的按钮”。

而接下来,既然它是一个按钮,那么按下之后肯定要有点反应。如何实现这个“反应”?当然是调用函数。这就是代码里 _ => {} 的作用,定义一个函数!

当然,这只是一个什么内容都没有的函数,按钮也暂时还是没有任何作用。不过既然有了函数,向里面添加东西就简单多了。

如果你不认识这种函数定义,打开这个折叠块:


以上就是按钮的基本特性了。我们还可以给按钮加一个提示,就像这样:

  1. .button('一个神秘的按钮', _ => {}, {
  2.   tooltip: '但是目前没什么用 ^_^'
  3. })
复制代码


这里,我们传入了第三个参数,它是一个对象,其中有 tooltip 属性,表示它的提示文本。鼠标悬停在按钮上的时候,就可以看到提示文本。对于触屏,暂时未知如何看到此文本。




好了,隆重向你介绍,可观察对象!

Observable


我们来创建一个可观察对象吧。

  1. const bool = Observable.create(true);
复制代码


相当简单。Observable 有一个静态方法 create,可以用它创建可观察对象。可观察对象可以是布尔值,true 和 false;可以是数值,0 和 70 之类;也可以是字符串。

创建了可观察对象之后,我们就可以用它代表一个开关的值,创建那个开关了。

  1. .toggle('扳弄一下我!', bool)
复制代码


开关要用 toggle 方法创建。它接受两个参数,第一个是开关的名称,第二个就是刚才说的可观察对象,表示开关的值。因为刚才我们定义可观察对象时,传入的是 true,所以开关默认是开的。

类似地,我们可以定义更多可观察对象,把文本框、下拉菜单和滑块全都整出来。

  1. const inputText = Observable.create('我好饿!');
  2. const dropdownGuest = Observable.create(0);
  3. const percentageValue = Observable.create(70);
  4. .textField('请用文本投喂我', inputText, {
  5.   description: '文本是什么?好好吃的样子'
  6. })
  7. .spacer()
  8. .dropdown('来都来了拉一下我嘛 *_*\n\n你猜我最喜欢哪个数字?', dropdownGuest, [{
  9.     label: '11 看起来不错',
  10.     value: 0
  11.   },
  12.   {
  13.     label: '17 也很好',
  14.     value: 1
  15.   }
  16. ])
  17. .spacer()
  18. .slider('诶?我是百分比嘛?', percentageValue, 0, 100, {
  19.   description: '我的分度值是 10,好像不是诶……',
  20.   step: 10
  21. })
复制代码


这段代码有点长,其实只是因为我们一口气定义了三个控件,而且它们接受的参数又很多。我们一个一个看,首先是 textField 方法,它定义了文本框。

textField 的第一个参数是文本框的名称,会显示在框上面。第二个参数就是可观察对象,表示文本框里的内容。第三个参数是个对象(其实应该说是接口),里面的 description 属性表示这个文本框的解释性文字,会以灰色小字的形式出现在框下方。

dropdown 的第一个参数是下拉菜单的名称,显示在下拉菜单上面。第二个参数是可观察对象,表示选中了下拉菜单的哪个选项。第三个参数是个包含着对象的数组,表示下拉菜单的选项。每个选项都由 label 和 value 定义,label 是选项名称,value 是用来标记选项的内部值。以后可以通过代码判断 value 值,来判断到底选择了哪个选项。可观察对象的值,也是这个 value 值。

slider 的第一个参数是滑块的名称,显示在滑块上面。与名称同一行的最右侧,还会显示滑块目前的值。接下来,第二个参数是可观察对象,指定了滑块的值;第三个和第四个参数分别是滑块的最小值与最大值。最后是一个对象,其中的 description 与 textField 的 description 相同,都是解释性文字。不过 slider 这边还多出了一个 step 属性,表示这个滑块的分度值,或者说步进值。

接下来,我们把所有东西合并到一起(省略了可观察对象的声明):



效果就是这样的:




接下来,我们即将发挥可观察对象的优势,创建一个不一样的表单。我们会创建一个带有“高级设置”的表单,其中有一个叫“显示高级设置”的开关,它打开时,可以在表单下方看到高级设置;关闭时,则看不到。还有一个叫“启用高级设置”的开关,它打开时,高级设置会启用;关闭时,高级设置会禁用,无法交互。

那么,我们先把这两个开关,和一个代表高级设置的按钮创建出来吧。

  1. const isAdvancedMode = Observable.create(false);
  2. const showAdvancedOptions = Observable.create(false);
  3. .toggle('启用高级设置', isAdvancedMode)
  4. .toggle('显示高级设置', showAdvancedOptions)
  5. .button('哇,好高端啊', () => {})
复制代码


这时候,showAdvancedOptions 可观察对象的值需要根据“显示高级设置”的变化而变化。而这就涉及到了客户端修改可观察对象的值的问题,我们需要允许客户端的修改。允许的方法很简单,在创建的时候,再传入一个对象,把其中的 clientWritable 字段设为 true 即可。

  1. const isAdvancedMode = Observable.create(false, {
  2.   clientWritable: true
  3. });
  4. const showAdvancedOptions = Observable.create(false, {
  5.   clientWritable: true
  6. });
复制代码


接下来,我们想办法让这些可观察对象影响按钮这个控件。

  1. .button('哇,好高端啊', () => {}, {
  2.   visible: showAdvancedOptions,
  3.   disabled: isNotAdvancedMode
  4. })
复制代码


没错,方法就是在传入的第三个参数中,设置 visible 和 disabled 字段,它们控制一个控件的可见性与可用性。

但是这时,我们发现了一个问题:如果把 disabled 设为 true,那就是禁用这个按钮,而这时候 isAdvancedMode 的值应该是 false。所以说,这里需要的可观察对象的值与 isAdvancedMode 的值是反着的,我们需要新的 isNotAdvancedMode 可观察对象,把 isAdvancedMode 的值反过来。

那么这时候为什么不用 ! 来反转值?因为我们操作的不是布尔值,而是可观察对象。

定义 isNotAdvancedMode:
  1. const isNotAdvancedMode = Observable.create(true, {
  2.   clientWritable: true
  3. });
复制代码


之后,我们就要根据 isAdvancedMode 设置 isNotAdvancedMode 的值了。这时候,我们就迎来了可观察对象的一个非常重要的方法,subscribe。

  1. isAdvancedMode.subscribe(newValue => isNotAdvancedMode.setData(!newValue));
复制代码


可观察对象的 subscribe 方法可以让我们订阅 这个可观察对象的值的变化。它的值变化的时候,就会执行后面的回调函数。这里我们也使用了箭头表达式来定义这个函数。

subscribe 方法为我们提供了相关的参数,我们只需要关心,它提供的第一个参数是可观察对象改变后的新的值。这里,我们使用 newValue 来命名这个参数。而可观察对象还有 setData 方法用来设置它的值,因此,isNotAdvancedMode.setData(!newValue) 的意思就是把 isNotAdvancedMode 可观察对象的值设为 newValue 的相反值。setData 方法返回了什么不重要,重要的是它设置了值,这就够了。

这样,我们就完成了全部代码。把它们放到一起:

  1. const isAdvancedMode = Observable.create(false, {
  2.   clientWritable: true
  3. });
  4. const isNotAdvancedMode = Observable.create(true, {
  5.   clientWritable: true
  6. });
  7. const showAdvancedOptions = Observable.create(false, {
  8.   clientWritable: true
  9. });
  10. isAdvancedMode.subscribe(newValue => isNotAdvancedMode.setData(!newValue));

  11. function ddui(e) {
  12. CustomForm
  13.   .create(e.sourceEntity, '我是标题')
  14.   .divider()
  15.   .toggle('启用高级设置', isAdvancedMode)
  16.   .toggle('显示高级设置', showAdvancedOptions)
  17.   .button('哇,好高端啊', () => {}, {
  18.     visible: showAdvancedOptions,
  19.     disabled: isNotAdvancedMode
  20.   })
  21.   .divider()
  22.   .show()
  23. }
复制代码


效果就是这样的:




最后,表单的 show 方法返回的是一个 Promise,会在玩家关闭表单时变为 Resolved 状态。这时候我们可以用 then 方法执行一些代码,还可以继续用 catch 方法捕获错误。也可以用 close 方法关闭表单。

  1. const playerName = Observable.create('默认名称', {
  2.   clientWritable: true
  3. });
  4. function ddui(e) {
  5. CustomForm.create(e.sourceEntity, '设置')
  6.   .spacer()
  7.   .divider()
  8.   .spacer()
  9.   .textField('玩家名称', playerName, {
  10.     description: '您在游戏中的显示名称'
  11.   })
  12.   .show()
  13.   .then(() => {
  14.     console.log(`填写的玩家名称是 ${playerName.getData()}`);
  15.   })
  16.   .catch(e => {
  17.     console.error(e);
  18.   });
  19. }
复制代码


效果是这样的:




以上就是 CustomForm 的大部分内容了。还有一个 MessageBox,它用于创建简单的双按钮对话框。它也有 create 方法,不过这次它只接受要显示给的玩家,而标题通过 title 方法指定。

  1. import { MessageBox } from "@minecraft/server-ui";
  2. MessageBox.create(e.sourceEntity)
  3. .title('确认操作')
  4. .body('确定要删除此物品吗?此操作无法撤消。')
  5. .button1('删除')
  6. .button2('取消', '保留物品并关闭对话框')
  7. .show()
复制代码


之后,可以通过 body 方法,指定一段显示在标题下方的说明性文字。之后就是 button1 和 button2 方法了,定义两个按钮上面的文字。它们还接受另一个参数,用于显示提示。鼠标悬停在按钮上的时候,就可以看到提示。最后,可以通过 show 方法显示表单,然后用 then 和 catch 处理结果。还可以用 close 方法关闭表单。
总结
这一期,我们讲解了最新的 DDUI 技术。


苦力怕论坛,感谢有您~
 发表于 2026-2-8 21:55:03|显示全部楼层 IP:黑龙江省
本帖最后由 牢冥王 于 2026-2-22 20:04 编辑

以后服务器表单也变成Ore UI了
触屏是不是可以长按控件(即选中而不触发)就可以看到tooltip了
2#2026-2-8 21:55:03回复收起回复
苦力怕论坛,感谢有您~
回复支持

使用道具举报

 发表于 2026-2-18 16:44:48|显示全部楼层 IP:天津
本帖最后由 grieogfhroi 于 2026-2-26 15:15 编辑

怎么用菜单和可观察对象来显示实体的数量?
3#2026-2-18 16:44:48回复收起回复
苦力怕论坛,感谢有您~
回复支持

使用道具举报

 发表于 2026-4-21 10:10:59|显示全部楼层 IP:江苏省
写的太棒了
4#2026-4-21 10:10:59回复收起回复
苦力怕论坛,感谢有您~
回复支持

使用道具举报

 发表于 6 天前|显示全部楼层 IP:马萨诸塞
膜拜神贴,后面的请保持队形~
5#6 天前回复收起回复
苦力怕论坛,感谢有您~
回复支持

使用道具举报

本版积分规则

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

QQ群

访问手机版

访问手机版

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

| 由 木韩网络 提供支持 | GMT+8, 2026-6-23 08:24

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

Powered by Discuz! X3.4