[基岩版教程] [原创][SAPI]Server UI 教程 —— 用Script API在游戏里创造UI!

[复制链接]
查看80 | 回复0 | 2024-10-27 12:30:47 来自手机 | 显示全部楼层 |阅读模式 IP:意大利
原帖根据CC BY-NC-SA 4.0授权转载

原帖作者:星空晶体(原作者本人转载到MCNeko
原帖地址:https://klpbbs.com/thread-150351-1-1.html

回帖前请务必观看坛规与版规,本帖不适合灌水

由于本人对Script API不是非常了解,所以本教程可能会出现遗漏或出错,欢迎进行反馈。授权协议请查看帖子最后


Server UI 教程
做一个由 SAPI 制作的游戏内UI!


前言



Server UI 是 Script API 中的一个库,可以在游戏里创建菜单、表单等 UI。它的出现使 Script API 变得更加有创造性,你可以将此功能用到 Script API 的各种地方,例如雪球(时钟)菜单、触发事件的自定义组件或其他。本教程将告诉大家什么是 ActionFormData、MessageFormData 与 ModalFormData 以及它们的使用方法。本人对 Script API 并不过于全知,所以可能会有遗漏,欢迎进行提醒。

如何使用 Server UI



第一个问题来了,我们怎么来使用 Script API 以及 Server UI库呢?

打开行为包文件夹,打开附加包清单文件 manifest.json ,在 modules 数组以下,添加 dependencies 数组,dependencies 这样写:

  1. "dependencies": [
  2.         {
  3.             "module_name": "@minecraft/server",
  4.             "version": "xxx"
  5.         },
  6.         {
  7.             "module_name": "@minecraft/server-ui",
  8.             "version": "xxx"
  9.         }
  10.     ]
复制代码

   
module_name 是库的名字,version 是库的版本,@minecraft/server-ui 就是 Server UI 的库名。截止到正式版 1.21.3X,@minecraft/server 的最新版本是1.15.0-beta,@minecraft/server-ui 的最新版本是 1.4.0-beta。

完整的清单文件是这样的:

  1. {
  2.     "format_version": 2,
  3.     "header": {
  4.         "name": "名称",
  5.         "description": "介绍",
  6.         "uuid": "自行填写",
  7.         "version": [
  8.             0,
  9.             0,
  10.             1
  11.         ],
  12.         "min_engine_version": [
  13.             x,
  14.             x,
  15.             x
  16.         ]
  17.     },
  18.     "modules": [
  19.         {
  20.             "type": "script",
  21.             "language": "javascript",
  22.             "uuid": "自行填写",
  23.             "entry": "scripts/xxx.js",//自行选择xxx部分
  24.             "version": [
  25.                 0,
  26.                 0,
  27.                 1
  28.             ]
  29.         }
  30.     ],
  31.     "dependencies": [
  32.         {
  33.             "module_name": "@minecraft/server",
  34.             "version": "1.15.0-beta"
  35.         },
  36.         {
  37.             "module_name": "@minecraft/server-ui",
  38.             "version": "1.4.0-beta"
  39.         }
  40.     ]
  41. }
复制代码


在行为包的 Script API,在JS文件中写入

  1. import { ActionFormData, MessageFormData, ModalFormData } from "@minecraft/server-ui"
复制代码


接下来将讲解三个 UI 是什么,不再赘述 Script API 其他内容。

ActionFormData



此 UI 相当于一个菜单,在游戏里显示为以下:
名称
简介
菜单按钮1

菜单按钮2

菜单按钮3

...

BBCode 表格无法完全清楚显示 ActionFormData UI ,请参考游戏内

写法:
  1. function Functionname(player) {
  2.     const Test = new ActionFormData()
  3.     .title(``)
  4.     .body(``)
  5.     .button(``)
  6.     .button(``)
  7.     .button(``)
  8.     .show(player)
  9.     .then((Test) => {
  10.         
  11.     })
  12. }
复制代码

代码讲解
const Test = new ActionFormData() 此代码是创建 ActionFormData UI 的语句,Test 是这个 ActionFormData 对象的名字,可替换为其他名字。
.title(``) 菜单最上面的标题,可见上方图例。
.body(``) 菜单中靠上的简介部分,可见上方图例。
.button(``) 菜单的按钮部分,可见上方图例。还可以写为 .button(``, `textures/...`),后者资源包的 textures 图片路径指定按钮左旁的图片。可多个添加。

.show(player)
.then((Test) => {
        
    }

展现给玩家的语句,在里面括号里写入当点击按钮时会发生什么。这里使用IF语句
  1. if (Test.selection == 0) {
  2.     ...
  3. }
复制代码

里面的其他函数以及其他的这是当点击按钮1时会执行的,如果 Test.selection 为1,就是第2个按钮点击后发生的。以此类推。
可以执行另一个 UI 函数,也可以执行命令。使用 player.runCommand,比如:
  1. player.runCommand(`effect ${player.nameTag} instant_health 99999 255 true`)
复制代码


ModalFormData



此 UI 相当于一个表单,里面内含输入框、下拉栏、滑动栏、拉杆。
在游戏里显示如下:
名称
输入框
下滑栏

▪▪▪



...
提交


BBCode 表格无法完全清楚显示 ModalFormData UI ,请参考游戏内

写法:
  1. function Functionname(player) {
  2.     const Test = new ModalFormData()
  3.     .title(``)
  4.     .textField(``,``)
  5.     .toggle(``,true)
  6.     .dropdown(``,[``,``,``,``],0)
  7.     .slider(``,0,64,1,32)//不要在意这些数字
  8.     .show(player)
  9.     .then((Test) => {
  10.         if (xxx.formValues[0] == xxx) {
  11.             ...
  12.         } else {
  13.             ...
  14.         }
  15.     })
  16. }
复制代码

代码讲解
.textField(``,``) 输入框,前者反引号为输入框上面的输入文字,后者为输入框里面的提醒文字。
.toggle(``,true) 前者反引号是拉杆选项是文字,后者布尔值是默认为开还是关。true 是默认为打开,false 是默认不打开。
.dropdown(``,[``,``,``,``],0) 下拉栏,前者反引号是下拉栏说明,中间的数组下拉栏内容,也是用反引号为一个,内容直接用英文逗号隔开。最后的数字是下拉栏默认的内容,0 是 第一个,1 是 第二个,以此类推。
.slider(``,,,,) 滑动条,这样写:
  1. .slider(`滑动条名字`,最小值,最大值,分度值,默认值)
复制代码
分度值是每次滑动一次时会滑动多少。比如最大值为10,分度值为2,每次滑动数值都会滑动±2,从开头/结尾滑动5次就会拉到头。默认值是设置滑动条的默认数值。
如何获取表单内容进行判断:如果有的表单内容需要判断,要获取一个部分的值并判断,写为以下代码
  1. if (Test.formValues[0] == xxx) {
  2.             ...
  3.         } else {
  4.             ...
  5.           }
复制代码

Test.formValues[0] == xxx 是索引值为某一数值、字符串或布尔值的判断部分。当点击提交,就会安装IF语句里执行其他内容。

MessageFormData



此 UI 相当于一个弹窗,在游戏里显示如下:
名称
弹窗内容


BBCode 表格无法完全清楚显示 MessageFormData UI ,请参考游戏内

写法
  1. function Functionname(player) {
  2.     const Test = new MessageFormData()
  3.     .title(`标题`)
  4.     .body(``)
  5.     .button(``)
  6.     .button(``)
  7.     .show(player)
  8.     .then((Test) => {
  9.         if (Test.selection == 0) {
  10.             ...
  11.         }
  12.         if (Test.selection == 1) {
  13.             ...
  14.         }
  15.     })
  16. }
复制代码


弹窗有两个按钮,当点击第一个按钮后执行第一个IF语句内容,第二个同理。
.body(``) 弹窗内容

实例


下面内容来自我的资源帖子[原创][1.21.2+][Script API][测试插件]雪球游戏菜单-2

[spoiler]
  1. import { world, system } from "@minecraft/server"
  2. import { ActionFormData, MessageFormData, ModalFormData } from "@minecraft/server-ui"

  3. world.beforeEvents.itemUseOn.subscribe((event) => {
  4.     const huchu = event.itemStack
  5.     const player = event.source
  6.     if (huchu.typeId == `minecraft:snowball`) system.run(() => Action(player))
  7. })
  8. function Action(player) {
  9.     const Action = new ActionFormData()
  10.     .title(`SAPI测试2|作者KLPBBS星空晶体`)
  11.     .body(`下方测试`)
  12.     .button(`时间设置`)
  13.     .button(`效果设置`)
  14.     .button(`清除设置`)
  15.     .button(`矿物给予`)
  16.     .button(`当前位置设置重生点`)
  17.     .button(`游戏模式`)
  18.     .show(player)
  19.     .then((Action) => {
  20.         if (Action.selection == 0) {
  21.             timeshzh(player)
  22.         }
  23.         if (Action.selection == 1) {
  24.             effectshzh(player)
  25.         }
  26.         if (Action.selection == 2) {
  27.             killshzh(player)
  28.         }
  29.         if (Action.selection == 3) {
  30.             oregiving(player)
  31.         }
  32.         if (Action.selection == 4) {
  33.             player.runCommand(`setworldspawn`)
  34.             player.onScreenDisplay.setActionBar({"rawtext":[{"text":`已设置重生点`}]})
  35.         }
  36.         if (Action.selection == 5) {
  37.             mode(player)
  38.         }
  39.     })
  40. }

  41. function timeshzh(player) {
  42.     const timeshzh = new ActionFormData()
  43.     .title(`时间设置`)
  44.     .body(`下方设置`)
  45.     .button(`日出`)
  46.     .button(`白天`)
  47.     .button(`正午`)
  48.     .button(`傍晚`)
  49.     .button(`夜晚`)
  50.     .button(`午夜`)
  51.     .show(player)
  52.     .then((timeshzh) => {
  53.         if (timeshzh.selection == 0) {
  54.             player.runCommand(`time set sunrise`)
  55.             player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间: 日出`}]})
  56.         }
  57.         if (timeshzh.selection == 1) {
  58.             player.runCommand(`time set day`)
  59.             player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间:白日`}]})
  60.         }
  61.         if (timeshzh.selection == 2) {
  62.             player.runCommand(`time set noon`)
  63.             player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间:中午`}]})
  64.         }
  65.         if (timeshzh.selection == 3) {
  66.             player.runCommand(`time set sunset`)
  67.             player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间: 傍晚`}]})
  68.         }
  69.         if (timeshzh.selection == 4) {
  70.             player.runCommand(`time set night`)
  71.             player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间: 夜晚`}]})
  72.         }
  73.         if (timeshzh.selection == 5) {
  74.             player.runCommand(`time set midnight`)
  75.             player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间: 正夜`}]})
  76.         }
  77.     })
  78. }

  79. function effectshzh(player) {
  80.     const effectshzh = new ModalFormData()
  81.     .title(`药水效果`)
  82.     .toggle(`瞬间治疗`,false)
  83.     .toggle(`夜视`,false)
  84.     .toggle(`饱和`,false)
  85.     .toggle(`抗性`,false)
  86.     .toggle(`生命恢复`,false)
  87.     .toggle(`速度`,false)
  88.     .toggle(`水下呼吸`,false)
  89.     .toggle(`力量`,false)
  90.     .toggle(`隐身`,false)
  91.     .toggle(`跳跃增强`,false)
  92.     .toggle(`急迫`,false)
  93.     .toggle(`抗火`,false)
  94.     .show(player)
  95.     .then((effectshzh) => {
  96.         if (effectshzh.formValues[0] == true) {
  97.             player.runCommand(`effect ${player.nameTag} instant_health 99999 255 true`)
  98.         }
  99.         if (effectshzh.formValues[1] == true) {
  100.             player.runCommand(`effect ${player.nameTag} night_vision 99999 255 true`)
  101.         }
  102.         if (effectshzh.formValues[2] == true) {
  103.             player.runCommand(`effect ${player.nameTag} saturation 99999 255 true`)
  104.         }
  105.         if (effectshzh.formValues[3] == true) {
  106.             player.runCommand(`effect ${player.nameTag} resistance 99999 255 true`)
  107.         }
  108.         if (effectshzh.formValues[4] == true) {
  109.             player.runCommand(`effect ${player.nameTag} regeneration 99999 255 true`)
  110.         }
  111.         if (effectshzh.formValues[5] == true) {
  112.             player.runCommand(`effect ${player.nameTag} speed 99999 10 true`)
  113.         }
  114.         if (effectshzh.formValues[6] == true) {
  115.             player.runCommand(`effect ${player.nameTag} water_breathing 99999 255 true`)
  116.         }
  117.         if (effectshzh.formValues[7] == true) {
  118.             player.runCommand(`effect ${player.nameTag} strength 99999 255 true`)
  119.         }
  120.         if (effectshzh.formValues[8] == true) {
  121.             player.runCommand(`effect ${player.nameTag} invisibility 99999 255 true`)
  122.         }
  123.         if (effectshzh.formValues[9] == true) {
  124.             player.runCommand(`effect ${player.nameTag} fire_resistance 99999 255 true`)
  125.         }
  126.         if (effectshzh.formValues[10] == true) {
  127.             player.runCommand(`effect ${player.nameTag} jump_boost 99999 10 true`)
  128.         }
  129.         if (effectshzh.formValues[11] == true) {
  130.             player.runCommand(`effect ${player.nameTag} haste 99999 10 true`)
  131.         }
  132.     })
  133. }

  134. function killshzh(player) {
  135.     const killshzh = new ActionFormData()
  136.     .title(`清除目标选择`)
  137.     .button(`清除随机玩家(需多人)`)
  138.     .button(`清除所有玩家`)
  139.     .button(`清除所有实体`)
  140.     .button(`清除命令执行者`)
  141.     .button(`清除所有掉落物`)
  142.     .button(`清除X半径范围中的所有实体`)
  143.     .button(`清除特定实体`)
  144.     .button(`自定义清除(需有选择器基础)`)
  145.     .show(player)
  146.     .then((killshzh) => {
  147.         if (killshzh.selection == 0) {
  148.             player.runCommand(`kill @r`)
  149.         }
  150.         if (killshzh.selection == 1) {
  151.             player.runCommand(`kill @a`)
  152.         }
  153.         if (killshzh.selection == 2) {
  154.             player.runCommand(`kill @e`)
  155.         }
  156.         if (killshzh.selection == 3) {
  157.             reallykill(player)
  158.         }
  159.         if (killshzh.selection == 4) {
  160.             player.runCommand(`kill @e[type=item]`)
  161.         }
  162.         if (killshzh.selection == 5) {
  163.             mkill1(player)
  164.         }
  165.         if (killshzh.selection == 6) {
  166.             mkill2(player)
  167.         }
  168.         if (killshzh.selection == 7) {
  169.             mkill3(player)
  170.         }
  171.     })
  172. }

  173. function reallykill(player) {
  174.     const reallykill = new MessageFormData()
  175.     .title(`确认操作`)
  176.     .body(`[谨慎选择]是否清除命令执行者本身\n如果您是玩家,请确保开了保留物品栏以不会丢失雪球`)
  177.     .button(`是的`)
  178.     .button(`不是[我误点/我反悔]`)
  179.     .show(player)
  180.     .then((reallykill) => {
  181.         if (reallykill.selection == 0) {
  182.             player.runCommand(`kill @e`)
  183.         }
  184.         if (reallykill.selection == 1) {
  185.             killshzh(player)
  186.         }
  187.     })
  188. }

  189. function mkill1(player) {
  190.     const mkill1 = new ModalFormData()
  191.     .title(`清除特定半径范围中的实体`)
  192.     .textField(`请输入半径范围`,`输入一个整数`)
  193.     .toggle(`不包括玩家`,true)
  194.     .show(player)
  195.     .then((mkill1) => {
  196.         if (mkill1.formValues[1] == true) {
  197.             player.runCommand(`kill @e[type=!player,r=${mkill1.formValues[0]}]`)
  198.         } else {
  199.             player.runCommand(`kill @e[r=${mkill1.formValues[0]}]`)
  200.         }
  201.     })
  202. }

  203. function mkill2(player) {
  204.     const mkill2 = new ModalFormData()
  205.     .title(`清除特定实体`)
  206.     .textField(`请输入半径范围`,`若不想设置范围,请填0`)
  207.     .textField(`请输入实体ID`,`可省略minecraft前缀`)
  208.     .show(player)
  209.     .then((mkill2) => {
  210.         if (mkill2.formValues[0] == 0) {
  211.             player.runCommand(`kill @e[type=${mkill2.formValues[1]}]`)
  212.         } else {
  213.             player.runCommand(`kill @e[type=${mkill2.formValues[1]},r=${mkill2.formValues[0]}]`)
  214.         }
  215.     })
  216. }

  217. function mkill3(player) {
  218.     const mkill3 = new ModalFormData()
  219.     .title(`自定义清除实体`)
  220.     .textField(`请输入`,`注:开头必须有那个斜杠`)
  221.     .toggle(`这个是没有用的`,false)
  222.     .show(player)
  223.     .then((mkill3) => {
  224.         if (mkill3.formValues[1] == false) {
  225.             player.runCommand(`${mkill3.formValues[0]}`)
  226.         } else {
  227.             player.runCommand(`${mkill3.formValues[0]}`)
  228.         }
  229.     })
  230. }

  231. function oregiving(player) {
  232.     const oregiving = new ModalFormData()
  233.     .title(`矿物给予`)
  234.     .slider(`铁矿石`,0,64,1,32)// .slider(`滑动条`,最小值,最大值,分度值,默认值)
  235.     .slider(`金矿石`,0,64,1,32)
  236.     .slider(`煤矿石`,0,64,1,32)
  237.     .slider(`青金石矿石`,0,64,1,32)
  238.     .slider(`绿宝石矿石`,0,64,1,32)
  239.     .slider(`钻石矿石`,0,64,1,32)
  240.     .slider(`红石矿石`,0,64,1,32)
  241.     .slider(`铜矿石`,0,64,1,32)
  242.     .show(player)
  243.     .then((oregiving) => {
  244.         player.runCommand(`give ${player.nameTag} iron_ore ${oregiving.formValues[0]}`)
  245.         player.runCommand(`give ${player.nameTag} gold_ore ${oregiving.formValues[1]}`)
  246.         player.runCommand(`give ${player.nameTag} coal_ore ${oregiving.formValues[2]}`)
  247.         player.runCommand(`give ${player.nameTag} lapis_ore ${oregiving.formValues[3]}`)
  248.         player.runCommand(`give ${player.nameTag} emerald_ore ${oregiving.formValues[4]}`)
  249.         player.runCommand(`give ${player.nameTag} diamond_ore ${oregiving.formValues[5]}`)
  250.         player.runCommand(`give ${player.nameTag} redstone_ore ${oregiving.formValues[6]}`)
  251.         player.runCommand(`give ${player.nameTag} copper_ore ${oregiving.formValues[7]}`)
  252.     })
  253. }

  254. function mode(player) {
  255.     const mode = new ModalFormData()
  256.     .title(`游戏模式`)
  257.     .dropdown(`模式选择`,[`生存`,`创造`,`冒险`,`旁观`],0)
  258.     .show(player)
  259.     .then((mode) => {
  260.         if (mode.formValues[0] == `生存`) {
  261.             player.runCommand(`gamemode survival`)
  262.         } else if (mode.formValues[0] == `创造`){
  263.             player.runCommand(`gamemode creative`)
  264.         } else if (mode.formValues[0] == `冒险`) {
  265.             player.runCommand(`gamemode adventure`)
  266.         } else {
  267.             player.runCommand(`gamemode spectator`)
  268.         }
  269.     })
  270. }
复制代码
[/spoiler]

结语



感谢您观看本教程,本教程共花费了本人几个小时的编写。包括查阅资料、文字编写已经BBCode排版。这也是本人第一次写这样体量的教程,若有遗失欢迎补充。感谢支持,愿大家制作自己的作品,让Minecraft论坛社区变得更好!

帖子信息
[原创][SAPI]Server UI 教程 —— 用Script API在游戏里创造UI!
字数(包括BBCode):大于12000字
用时:大于4小时
完毕时间:11:30
转载协议:本教程根据 CC BY-NC-SA 4.0 进行授权,转载请标注原作者以及原帖子地址
本教程作者:星空晶体
帖子地址:https://klpbbs.com/thread-150351-1-1.html

评分

参与人数 1喵币 +500 收起 理由
MeowcoQAQ + 500

查看全部评分

MCNeko 我的世界论坛免责声明

1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关

2、本站所有主题由该帖子作者发表,该帖子作者享有帖子相关版权

3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者的同意

4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任

5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责

6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意

7、Minecraft(我的世界)MCNeko 中文论坛管理员和版主有权不事先通知发贴者而删除本文

MCNeko 中文论坛,感谢有您~
文明发言,和谐互动
文明发言,和谐互动
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则