星空晶体 发表于 2024-10-27 11:58:03

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

本帖最后由 星空晶体 于 2024-11-16 08:30 编辑


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


由于本人对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 这样写:

"dependencies": [
      {
            "module_name": "@minecraft/server",
            "version": "xxx"
      },
      {
            "module_name": "@minecraft/server-ui",
            "version": "xxx"
      }
    ]
   
module_name 是库的名字,version 是库的版本,@minecraft/server-ui 就是 Server UI 的库名。截止到目前,@minecraft/server 的最新版本是1.15.0,@minecraft/server-ui 的最新版本是 1.3.0。若想要查看这两个库名的最新版本,可以去
https://www.npmjs.com/package/@minecraft/server
https://www.npmjs.com/package/@minecraft/server-ui
查看

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

{
    "format_version": 2,
    "header": {
      "name": "名称",
      "description": "介绍",
      "uuid": "自行填写",
      "version": [
            0,
            0,
            1
      ],
      "min_engine_version": [
            x,
            x,
            x
      ]
    },
    "modules": [
      {
            "type": "script",
            "language": "javascript",
            "uuid": "自行填写",
            "entry": "scripts/xxx.js",//自行选择xxx部分
            "version": [
                0,
                0,
                1
            ]
      }
    ],
    "dependencies": [
      {
            "module_name": "@minecraft/server",
            "version": "1.15.0"
      },
      {
            "module_name": "@minecraft/server-ui",
            "version": "1.3.0"
      }
    ]
}

在行为包的 script 文件夹,在JS文件中写入

import { ActionFormData, MessageFormData, ModalFormData } from "@minecraft/server-ui"

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

ActionFormData

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

菜单按钮2


菜单按钮3

...



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

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

.show(player)
.then((Test) => {
      
    }
展现给玩家的语句,在里面括号里写入当点击按钮时会发生什么。这里使用IF语句
if (Test.selection == 0) {
    ...
}
里面的其他函数以及其他的这是当点击按钮1时会执行的,如果 Test.selection 为1,就是第2个按钮点击后发生的。以此类推。
可以执行另一个 UI 函数,也可以执行命令。使用 player.runCommand,比如:
player.runCommand(`effect ${player.nameTag} instant_health 99999 255 true`)

ModalFormData

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


...

提交




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

写法:
function Functionname(player) {
    const Test = new ModalFormData()
    .title(``)
    .textField(``,``)
    .toggle(``,true)
    .dropdown(``,[``,``,``,``],0)
    .slider(``,0,64,1,32)//不要在意这些数字
    .show(player)
    .then((Test) => {
      if (xxx.formValues == xxx) {
            ...
      } else {
            ...
      }
    })
}
代码讲解
▪.textField(``,``) 输入框,前者反引号为输入框上面的输入文字,后者为输入框里面的提醒文字。
▪.toggle(``,true) 前者反引号是拉杆选项是文字,后者布尔值是默认为开还是关。true 是默认为打开,false 是默认不打开。
▪.dropdown(``,[``,``,``,``],0) 下拉栏,前者反引号是下拉栏说明,中间的数组下拉栏内容,也是用反引号为一个,内容直接用英文逗号隔开。最后的数字是下拉栏默认的内容,0 是 第一个,1 是 第二个,以此类推。
▪.slider(``,,,,) 滑动条,这样写:.slider(`滑动条名字`,最小值,最大值,分度值,默认值) 分度值是每次滑动一次时会滑动多少。比如最大值为10,分度值为2,每次滑动数值都会滑动±2,从开头/结尾滑动5次就会拉到头。默认值是设置滑动条的默认数值。
如何获取表单内容进行判断:如果有的表单内容需要判断,要获取一个部分的值并判断,写为以下代码
if (Test.formValues == xxx) {
            ...
      } else {
            ...
          }
Test.formValues == xxx 是索引值为某一数值、字符串或布尔值的判断部分。当点击提交,就会安装IF语句里执行其他内容。

MessageFormData

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






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

写法
function Functionname(player) {
    const Test = new MessageFormData()
    .title(`标题`)
    .body(``)
    .button(``)
    .button(``)
    .show(player)
    .then((Test) => {
      if (Test.selection == 0) {
            ...
      }
      if (Test.selection == 1) {
            ...
      }
    })
}

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

实例

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

import { world, system } from "@minecraft/server"
import { ActionFormData, MessageFormData, ModalFormData } from "@minecraft/server-ui"

world.beforeEvents.itemUseOn.subscribe((event) => {
    const huchu = event.itemStack
    const player = event.source
    if (huchu.typeId == `minecraft:snowball`) system.run(() => Action(player))
})
function Action(player) {
    const Action = new ActionFormData()
    .title(`SAPI测试2|作者KLPBBS星空晶体`)
    .body(`下方测试`)
    .button(`时间设置`)
    .button(`效果设置`)
    .button(`清除设置`)
    .button(`矿物给予`)
    .button(`当前位置设置重生点`)
    .button(`游戏模式`)
    .show(player)
    .then((Action) => {
      if (Action.selection == 0) {
            timeshzh(player)
      }
      if (Action.selection == 1) {
            effectshzh(player)
      }
      if (Action.selection == 2) {
            killshzh(player)
      }
      if (Action.selection == 3) {
            oregiving(player)
      }
      if (Action.selection == 4) {
            player.runCommand(`setworldspawn`)
            player.onScreenDisplay.setActionBar({"rawtext":[{"text":`已设置重生点`}]})
      }
      if (Action.selection == 5) {
            mode(player)
      }
    })
}

function timeshzh(player) {
    const timeshzh = new ActionFormData()
    .title(`时间设置`)
    .body(`下方设置`)
    .button(`日出`)
    .button(`白天`)
    .button(`正午`)
    .button(`傍晚`)
    .button(`夜晚`)
    .button(`午夜`)
    .show(player)
    .then((timeshzh) => {
      if (timeshzh.selection == 0) {
            player.runCommand(`time set sunrise`)
            player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间: 日出`}]})
      }
      if (timeshzh.selection == 1) {
            player.runCommand(`time set day`)
            player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间:白日`}]})
      }
      if (timeshzh.selection == 2) {
            player.runCommand(`time set noon`)
            player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间:中午`}]})
      }
      if (timeshzh.selection == 3) {
            player.runCommand(`time set sunset`)
            player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间: 傍晚`}]})
      }
      if (timeshzh.selection == 4) {
            player.runCommand(`time set night`)
            player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间: 夜晚`}]})
      }
      if (timeshzh.selection == 5) {
            player.runCommand(`time set midnight`)
            player.onScreenDisplay.setActionBar({"rawtext":[{"text":`时间: 正夜`}]})
      }
    })
}

function effectshzh(player) {
    const effectshzh = new ModalFormData()
    .title(`药水效果`)
    .toggle(`瞬间治疗`,false)
    .toggle(`夜视`,false)
    .toggle(`饱和`,false)
    .toggle(`抗性`,false)
    .toggle(`生命恢复`,false)
    .toggle(`速度`,false)
    .toggle(`水下呼吸`,false)
    .toggle(`力量`,false)
    .toggle(`隐身`,false)
    .toggle(`跳跃增强`,false)
    .toggle(`急迫`,false)
    .toggle(`抗火`,false)
    .show(player)
    .then((effectshzh) => {
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} instant_health 99999 255 true`)
      }
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} night_vision 99999 255 true`)
      }
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} saturation 99999 255 true`)
      }
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} resistance 99999 255 true`)
      }
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} regeneration 99999 255 true`)
      }
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} speed 99999 10 true`)
      }
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} water_breathing 99999 255 true`)
      }
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} strength 99999 255 true`)
      }
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} invisibility 99999 255 true`)
      }
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} fire_resistance 99999 255 true`)
      }
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} jump_boost 99999 10 true`)
      }
      if (effectshzh.formValues == true) {
            player.runCommand(`effect ${player.nameTag} haste 99999 10 true`)
      }
    })
}

function killshzh(player) {
    const killshzh = new ActionFormData()
    .title(`清除目标选择`)
    .button(`清除随机玩家(需多人)`)
    .button(`清除所有玩家`)
    .button(`清除所有实体`)
    .button(`清除命令执行者`)
    .button(`清除所有掉落物`)
    .button(`清除X半径范围中的所有实体`)
    .button(`清除特定实体`)
    .button(`自定义清除(需有选择器基础)`)
    .show(player)
    .then((killshzh) => {
      if (killshzh.selection == 0) {
            player.runCommand(`kill @r`)
      }
      if (killshzh.selection == 1) {
            player.runCommand(`kill @a`)
      }
      if (killshzh.selection == 2) {
            player.runCommand(`kill @e`)
      }
      if (killshzh.selection == 3) {
            reallykill(player)
      }
      if (killshzh.selection == 4) {
            player.runCommand(`kill @e`)
      }
      if (killshzh.selection == 5) {
            mkill1(player)
      }
      if (killshzh.selection == 6) {
            mkill2(player)
      }
      if (killshzh.selection == 7) {
            mkill3(player)
      }
    })
}

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

function mkill1(player) {
    const mkill1 = new ModalFormData()
    .title(`清除特定半径范围中的实体`)
    .textField(`请输入半径范围`,`输入一个整数`)
    .toggle(`不包括玩家`,true)
    .show(player)
    .then((mkill1) => {
      if (mkill1.formValues == true) {
            player.runCommand(`kill @e}]`)
      } else {
            player.runCommand(`kill @e}]`)
      }
    })
}

function mkill2(player) {
    const mkill2 = new ModalFormData()
    .title(`清除特定实体`)
    .textField(`请输入半径范围`,`若不想设置范围,请填0`)
    .textField(`请输入实体ID`,`可省略minecraft前缀`)
    .show(player)
    .then((mkill2) => {
      if (mkill2.formValues == 0) {
            player.runCommand(`kill @e}]`)
      } else {
            player.runCommand(`kill @e},r=${mkill2.formValues}]`)
      }
    })
}

function mkill3(player) {
    const mkill3 = new ModalFormData()
    .title(`自定义清除实体`)
    .textField(`请输入`,`注:开头必须有那个斜杠`)
    .toggle(`这个是没有用的`,false)
    .show(player)
    .then((mkill3) => {
      if (mkill3.formValues == false) {
            player.runCommand(`${mkill3.formValues}`)
      } else {
            player.runCommand(`${mkill3.formValues}`)
      }
    })
}

function oregiving(player) {
    const oregiving = new ModalFormData()
    .title(`矿物给予`)
    .slider(`铁矿石`,0,64,1,32)// .slider(`滑动条`,最小值,最大值,分度值,默认值)
    .slider(`金矿石`,0,64,1,32)
    .slider(`煤矿石`,0,64,1,32)
    .slider(`青金石矿石`,0,64,1,32)
    .slider(`绿宝石矿石`,0,64,1,32)
    .slider(`钻石矿石`,0,64,1,32)
    .slider(`红石矿石`,0,64,1,32)
    .slider(`铜矿石`,0,64,1,32)
    .show(player)
    .then((oregiving) => {
      player.runCommand(`give ${player.nameTag} iron_ore ${oregiving.formValues}`)
      player.runCommand(`give ${player.nameTag} gold_ore ${oregiving.formValues}`)
      player.runCommand(`give ${player.nameTag} coal_ore ${oregiving.formValues}`)
      player.runCommand(`give ${player.nameTag} lapis_ore ${oregiving.formValues}`)
      player.runCommand(`give ${player.nameTag} emerald_ore ${oregiving.formValues}`)
      player.runCommand(`give ${player.nameTag} diamond_ore ${oregiving.formValues}`)
      player.runCommand(`give ${player.nameTag} redstone_ore ${oregiving.formValues}`)
      player.runCommand(`give ${player.nameTag} copper_ore ${oregiving.formValues}`)
    })
}

function mode(player) {
    const mode = new ModalFormData()
    .title(`游戏模式`)
    .dropdown(`模式选择`,[`生存`,`创造`,`冒险`,`旁观`],0)
    .show(player)
    .then((mode) => {
      if (mode.formValues == `生存`) {
            player.runCommand(`gamemode survival`)
      } else if (mode.formValues == `创造`){
            player.runCommand(`gamemode creative`)
      } else if (mode.formValues == `冒险`) {
            player.runCommand(`gamemode adventure`)
      } else {
            player.runCommand(`gamemode spectator`)
      }
    })
}

以上内容包括本教程帖的所有内容,可进行参考。
可去原帖找到资源进行改编。本人表示欢迎。

结语

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

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



星空抚吾梦,晶体映吾心。青年应有梦,破浪乘舟行!

FlashDragon 发表于 2024-10-27 12:04:54

看不懂,但是感谢分享。[哔哩_脱单]

翠娥 发表于 2024-10-27 13:10:23

感谢分享

Sakarwei 发表于 2024-10-27 13:22:16

感谢分享!
不过,个人建议尽量避免使用版本号带 alpha beta 的测试版组建——这些组建每次更新 SAPI 都要更新,相当麻烦,还要打开“测试版API”才能用。
(当然,用到测试版才有的内容的话那就没办法了)

花冈柚子 发表于 2024-10-27 18:25:45

那能否使用Script API检测玩家发送的消息然后做出特定的行为吗🤔
就和自定义指令一样

YanRan233 发表于 2024-10-27 23:36:23

感谢楼主教程[哔哩_脱单]

yi个NPC 发表于 7 天前

ActionForm:按钮表单

MessageForm:消息表单(也叫双按钮表单)

ModalForm:自定义表单

我一般不用MessageForm

liyuzuo 发表于 4 小时前

感谢大佬让我弄懂了怎么写UI
页: [1]
查看完整版本: [原创][SAPI]Server UI 教程 —— 用Script API在游戏里创造UI!