Skip to content
On this page

组件对象、布局公式与临时组件

component("组件ID") 会取得当前页面会话中的组件对象。它不是把某一段文字临时替换成数字,也不是直接暴露客户端内部对象;页面只能通过 ChaUI 提供的安全属性读取或修改这个组件。

中文写法完全相同:

text
组件("组件ID")

取得组件对象后,可以继续读取公共属性、父组件、布局对象和当前组件类型自己的配置。例如文本组件可以读取 text.value,按钮可以读取 button.label,图片可以读取 image.path

一个可以直接照着做的例子

下面这份页面片段包含面板、提示文字和检查按钮。点击按钮后会同时完成三件事:

  1. 把面板此刻算出的 X 保存到 vars.currentX
  2. 把面板原始 X 公式保存到 vars.originalFormula
  3. 让提示文字始终位于面板右侧 1 个 GUI 像素处。
yaml
vars:
  currentX: 0
  originalFormula: ""

elements:
  - id: panel
    type: rect
    pointerEvents: pass
    opacity: 0.8
    layout:
      x: window.width * 0.25
      y: 40
      width: 120
      height: 40
    rect:
      color: "#203040"

  - id: tip
    type: text
    layout:
      x: 0
      y: 50
      width: 120
      height: 20
    text:
      value: "面板 X:{vars.currentX}\n原公式:{vars.originalFormula}"
      textSize: 1

  - id: inspect_button
    type: button
    layout:
      x: 20
      y: 100
      width: 100
      height: 20
    button:
      label: "读取并绑定"
    events:
      leftClick: |-
        vars.currentX = component("panel").layout.x
        vars.originalFormula = component("panel").layout.expression("x").source
        component("tip").layout.x = formula(
          component("panel").layout.x + component("panel").layout.width + 1
        )

假设点击时窗口宽度是 400

  • panel.layout.x 的当前计算结果是 100,所以 vars.currentX 得到固定值 100
  • layout.expression("x").source 得到原始公式文本 window.width * 0.25
  • tip.layout.x 计算为 100 + 120 + 1 = 221

之后把窗口宽度改为 600

  • 面板 X 会重新计算为 150
  • 提示文字因为使用了 formula(...),会自动移动到 271
  • vars.currentX 仍然是点击时保存的 100,除非再次执行读取脚本;
  • vars.originalFormula 仍然保存公式文本,不会被计算结果覆盖。

中文等价写法

上面按钮的事件也可以完整写成中文:

yaml
events:
  leftClick: |-
    变量.currentX = 组件("panel").布局.x
    变量.originalFormula = 组件("panel").布局.表达式("x").source
    组件("tip").布局.x = 公式(
      组件("panel").布局.x + 组件("panel").布局.宽 + 1
    )

中英文可以混用,但同一份页面建议选一种主要风格,方便以后维护。

当前值和原始公式是两回事

每个布局属性都会同时维护声明和当前计算结果:

写法得到什么
component("panel").layout.x当前窗口和变量条件下计算出的数字
component("panel").layout.expression("x").source保存时使用的原始公式文本
component("panel").layout.expression("x").isFormula该属性是否保存为响应式公式

expression 只接受 xywidthheight。中文也可以写 表达式("宽")表达式("高")

如果布局原本是固定数字 20,读取 layout.x 会得到 20,而 isFormulafalse。如果布局原本是 window.width * 0.5layout.x 会得到当前计算结果,source 会保留公式文本,isFormulatrue

普通等号是快照,formula 才会持续跟随

下面两段看起来相似,结果并不相同。

一次性快照:

yaml
events:
  leftClick: |-
    component("tip").layout.x = component("panel").layout.x + component("panel").layout.width + 1

执行这一刻先算出数字,再把数字保存到 tip.layout.x。窗口改变后,tip 不会继续跟随。

响应式公式:

yaml
events:
  leftClick: |-
    component("tip").layout.x = formula(
      component("panel").layout.x + component("panel").layout.width + 1
    )

这里保存的是表达式。面板位置、面板宽度、页面变量或窗口尺寸改变后,ChaUI 会按依赖顺序重新计算,不依赖组件在 yml 中的先后顺序。

用字符串保存公式

公式也可以写成带引号的字符串。字符串仍会交给同一个公式解析器检查,不会作为任意脚本执行。

yaml
events:
  leftClick: |-
    component("panel").layout.x = formula("window.width * 0.5")

这种写法适合公式来自配置文本的场景。字符串内容必须是一条完整的数字公式;formula("1); cmd('执行某项业务')") 会被拒绝,因为它不是合法公式。

读取父组件

component("id").parent 返回该组件在当前页面会话中的父组件对象。因此不需要自己再写一次父布局 ID:

yaml
events:
  create: |-
    vars.parentX = component("child_title").parent.layout.x
    vars.parentWidth = component("child_title").parent.layout.width

根级组件没有父组件,读取它的 parent 会得到 null。不要继续对根组件写 .parent.layout.x;该条语句会被安全跳过。

设置组件父级

parent 也可以在脚本中修改。最直观的写法是把一个布局组件对象直接赋给它:

yaml
events:
  leftClick: |-
    component("d3").parent = component("panel")

这表示把 d3 放进布局组件 panel。父级必须是 layout_absolutelayout_relativelayout_gridlayout_scroll,普通文本、按钮等组件不能作为父级。

如果希望 d3test3 进入同一个父布局,可以直接复制 test3 的父组件对象:

yaml
events:
  leftClick: |-
    component("d3").parent = component("test3").parent

下面几种写法也都有效:

text
component("d3").parent = component("test3").parent.id
component("d3").parent = "panel"
component("d3").parent = null
组件("d3").父级 = 组件("test3").父级
  • .parent.id 得到父级 ID 字符串;
  • 直接写 "panel" 也是按父级 ID 设置;
  • null 表示取消父级,让组件回到页面根级;
  • 不存在的父级、非布局父级和循环嵌套会被拒绝。

换父级不会偷偷改写 d3layout.x/y。如果新父级是相对布局,原来的 X、Y 会被当作新父级内的局部坐标,所以组件在屏幕上的位置可能发生变化。

可以读取哪些属性

组件对象可以安全读取:

  • 公共字段:idtypevisibleenabledzpointerEventsscaleopacity、条件、提示和事件信息;
  • 父级:parent
  • 布局:layout.x/y/width/heightlayout.expression(...)
  • 对应组件类型的数据块,例如 text.valuebutton.labelimage.pathinput.valuevideo.volumeentity.displayName;物品槽还可以读取 itemSlot.check

读取结果可以用于变量赋值、条件、普通运算和 formula(...)log 的花括号只直接读取 varsvals,所以调试组件属性时先保存到变量:

yaml
events:
  leftClick: |-
    vars.currentPath = component("logo").image.path
    vars.currentWidth = component("logo").layout.width
    log("图片:{vars.currentPath},宽度:{vars.currentWidth}")

读取范围比写入范围更广。写入仍只允许 ChaUI 明确开放的临时状态,例如显隐、启用、父级、四项布局、部分文字/图片/视频/输入框/实体属性;idtype、公式元数据和 itemSlot.check 仍是只读的。

itemSlot.check 是页面 yml 或编辑器“操作条件”保存的客户端发包前置条件。脚本可以读取它用于提示或调试,但不能在运行时改写:

yaml
events:
  leftClick: |-
    vars.slotRule = component("submit_slot").itemSlot.check
    log("当前操作条件:{vars.slotRule}")

条件没通过时只会阻止内置槽位操作包,当前 leftClick 脚本仍然执行。因此上面的日志在槽位被禁止操作时也能显示。

指针处理、缩放与透明度

组件对象可以读取这三个根级字段,也可以在当前页面会话中修改:

yaml
events:
  leftPress: |-
    component("confirm_button").scale = 0.96
  leftRelease: |-
    component("confirm_button").scale = 1
  leftClick: |-
    component("dialog_mask").pointerEvents = "pass"
    component("dialog_mask").opacity = 0

直接赋数字是一次性值。需要持续跟随变量时使用 formula(...)

yaml
events:
  create: |-
    component("dialog").scale = formula("vars.zoom")
    component("dialog").opacity = formula("vars.fade / 100")

以后修改 vars.zoomvars.fade,组件会重新计算。component("dialog").scale.opacity 读取的是当前算出的数字。运行期间变量暂时让公式无效时,组件会保留上一次合法结果;第一次就无效时使用 1

布局组件不能设置 scaleopacity。实体支持根级 scale,但不支持根级 opacityopacity 不会关闭命中;需要让组件不再遮挡时,应同时设置 pointerEvents = "pass" 或隐藏组件。

保存或重置拖动位置

启用了 dragMode 的组件可以让玩家调整位置。脚本还能把当前实际偏移保存到客户端本地,或删除记忆并立即回到页面原始布局位置:

js
component("movable_panel").savePosition()
component("movable_panel").resetPosition()

savePosition() 适合“临时拖动,点击确认后再记住”的设计。即使组件使用 dragMode: temporary,也能在确认按钮脚本中主动保存当前位置:

yaml
- id: movable_panel
  type: layout_relative
  dragMode: temporary
  layout:
    x: 40
    y: 30
    width: 180
    height: 100

- id: save_layout
  type: button
  layout:
    x: 40
    y: 140
    width: 80
    height: 20
  button:
    label: "保存位置"
  events:
    leftClick: component("movable_panel").savePosition()

- id: reset_layout
  type: button
  layout:
    x: 130
    y: 140
    width: 80
    height: 20
  button:
    label: "恢复默认"
  events:
    leftClick: component("movable_panel").resetPosition()

resetPosition() 会删除这个组件在当前服务器、当前玩家和当前页面下的记忆,取消它正在进行的拖动,并让当前页面实例立刻回到原始布局结果。已有记忆与当前 dragMode 无关:把模式改为 off 只会禁止继续拖动,不会自动删除记忆。

中文写法为:

js
组件("movable_panel").保存位置()
组件("movable_panel").重置位置()

脚本动作只处理指定组件。管理员要一次重置某位在线玩家的全部组件位置记忆时,请使用对应的服务端指令或 ChaUIAPI.resetRememberedComponentPositions

常用写入

yaml
events:
  leftClick: |-
    component("detail_panel").visible = true
    component("confirm_button").enabled = false
    component("confirm_button").pointerEvents = "block"
    component("confirm_button").scale = formula("vars.buttonScale")
    component("confirm_button").opacity = 0.85
    component("title").text.value = "新的标题"
    component("confirm_button").button.path = "gui/button_active.png"
    component("preview").image.path = "gui/result.gif"
    component("name_input").input.value = "默认名称"

修改图片、按钮图片或 GIF 路径后,新素材会从第一帧重新开始显示。

读取和修改源图选区

图片、按钮、输入框、物品槽和物品展示的源图选区也属于组件对象。读取字段本身会得到当前渲染条件下算出的整数:

js
vars.currentSourceWidth = component("health_fill").image.sourceWidth

读取保存的声明要在对应数据块上调用 expression("字段名")

js
vars.currentSourceFormula = component("health_fill").image.expression("sourceWidth").source
vars.sourceIsFormula = component("health_fill").image.expression("sourceWidth").isFormula

如果字段原本是固定数字,source 返回这个数字的文本形式,isFormulafalse。如果字段保存的是响应式公式,source 保留公式原文,isFormulatrue

下面是一段完整的按钮事件。它先让血条源宽持续跟随玩家血量,再记录当前值和原公式,最后把结果显示在本地聊天栏:

yaml
events:
  leftClick: |-
    component("health_fill").image.sourceWidth = formula(
      clamp(vals.player.health / vals.player.maxHealth, 0, 1) * 100
    )
    vars.currentSourceWidth = component("health_fill").image.sourceWidth
    vars.currentSourceFormula = component("health_fill").image.expression("sourceWidth").source
    log("当前源宽:{vars.currentSourceWidth}")
    log("源宽公式:{vars.currentSourceFormula}")

直接赋数字会清除旧公式,变成一次性固定值:

js
component("health_fill").image.sourceWidth = 50

各组件可以使用的字段如下:

数据块可读写的源图字段
imagesourceX/Y/Width/Height
buttonsource*hoverSource*checkedSource*
inputsource*focusSource*
itemSlotsourceX/Y/Width/Height
itemDisplaysourceX/Y/Width/Height

所有源图公式都使用当前页面的 varsvals、窗口、父级和自身上下文重新计算,小数向下取整。脚本修改只属于当前页面会话,不会保存到服务端 yml。完整的图集与无拉伸血条示例见图片裁剪与动态血条

实体组件可以写入已开放的 entity.* 状态,例如:

yaml
events:
  leftClick: |-
    component("npc_preview").entity.displayName = "任务向导"
    component("npc_preview").entity.showName = true
    component("npc_preview").entity.yaw = 180
    component("npc_preview").entity.orthographic = true
    component("npc_preview").entity.trackMouse = true

entity.orthographic 只能写入布尔值。上面的脚本会立即切换为正交投影;写回 false 会恢复默认的 30 度透视投影。也可以用 vars.mode = component("npc_preview").entity.orthographic 读取当前模式。脚本改动只作用于当前页面会话,不会改写服务端页面文件。

动态修改原版容器绑定

容器替换页面中的非布局组件可以读取或修改 containerBinding。下面脚本先读取菜单按钮当前绑定,再让详情物品跟随同一槽位:

javascript
vars.currentBinding = component("menu_button").containerBinding
component("detail_item").containerBinding = component("menu_button").containerBinding

从页面变量设置固定语义槽位也可以:

javascript
vars.targetBinding = "container_13"
component("detail_item").containerBinding = vars.targetBinding

解除绑定必须使用 null

javascript
component("detail_item").containerBinding = null

修改只属于当前打开的容器会话,不写回页面配置。非法或超出当前容器范围的绑定会跳过当前赋值并保留旧值。完整槽位编号、组件点击和 Lore 用法见槽位、按钮与物品信息

公式依赖和循环

ChaUI 会根据“组件 ID + 布局字段”建立依赖关系。例如 B 的 X 引用 A 的 X,C 的 X 又引用 B 的 X,ChaUI 会先计算 A,再计算 B,最后计算 C。

如果新公式形成循环,例如 A 依赖 B,同时 B 又依赖 A,本次公式绑定会被拒绝,组件会保留之前仍然有效的声明和值,不会把整个页面算崩。

新增和复制临时组件

addComponent 会在当前页面会话中新增默认组件,copyComponent 会复制已有组件:

yaml
events:
  leftClick: |-
    addComponent("text", "temporary_tip")
    component("temporary_tip").text.value = "这是临时生成的提示"
    component("temporary_tip").layout.x = 20
    component("temporary_tip").layout.y = 80

    copyComponent("temporary_tip", "temporary_tip_2")
    component("temporary_tip_2").layout.x = 140

中文函数为 新增组件复制组件。新 ID 不能和当前页面已有 ID 重复。新增或复制成功后,新对象会立即执行自己的 events.create

copyComponent 只复制源组件本身,不会把源组件的子组件一起复制。源组件处于某个布局容器中时,复制体会保留相同父级;源组件本来就在根级时,复制体也在根级。同一段脚本可以紧接着使用新 ID:

js
copyComponent("temporary_tip", "temporary_tip_2")
component("temporary_tip_2").layout.x = 120

// parent 是组件对象;加上 .id 才得到纯文本 ID
vars.parentId = component("temporary_tip_2").parent.id
log("复制体父级={vars.parentId}")

如果把父级对象本身存入变量,log("{vars.parentObject}") 会显示类似 component("panel") 的调试文字:

js
vars.parentObject = component("temporary_tip_2").parent
log("{vars.parentObject}")

这些组件只存在于当前客户端页面会话,不会发起编辑器保存,也不会修改服务端页面文件。重新打开页面后,临时组件会消失。

跨事件引用动态组件

动态组件可以在一个事件中创建,在另一个事件、方法或计时器中使用。下面的页面打开时创建 d3,按钮点击时再修改它:

yaml
events:
  open: |-
    addComponent("text", "d3")

elements:
  - id: update_button
    type: button
    layout:
      x: 20
      y: 20
      width: 100
      height: 20
    button:
      label: "更新动态文字"
    events:
      leftClick: |-
        component("d3").text.value = "按钮已经点击"

编辑器保存时会检查整张页面的事件、方法和计时器脚本,因此不会再把这里的 d3 当成未知组件。但是运行顺序仍然重要:如果某条引用语句实际执行时 d3 还没创建,ChaUI 只会跳过那一条语句,不能提前生成组件。

常见错误

错误结果与处理方式
组件 ID 写错或组件不存在component(...) 无法取得对象;当前语句安全跳过。先检查页面组件列表中的 ID。
根组件继续读取 .parent.layout.x根组件的 parentnull。只对确实设置了父布局的子组件读取。
使用 layout.rightChaUI 没有这个字段。右边位置请写 layout.x + layout.width
formula("window.width *")公式不完整,保存或绑定会被拒绝。
A 和 B 的公式互相引用形成循环,本次新绑定被拒绝,旧的有效公式和值继续保留。
component("a").id 赋值id 是只读字段。需要新 ID 时使用复制或在编辑器中创建组件。
component("slot").itemSlot.check 赋值itemSlot.check 是只读配置。请在页面 yml 或编辑器“操作条件”中修改。
把普通组件赋给 .parent父级必须是布局组件。请改为目标布局组件对象、它的 ID,或使用 null 回到根级。

继续阅读变量与条件,了解组件对象如何与页面变量配合;需要页面跳转和服务端动作时阅读内置动作、日志与音效