组件对象、布局公式与临时组件
component("组件ID") 会取得当前页面会话中的组件对象。它不是把某一段文字临时替换成数字,也不是直接暴露客户端内部对象;页面只能通过 ChaUI 提供的安全属性读取或修改这个组件。
中文写法完全相同:
组件("组件ID")取得组件对象后,可以继续读取公共属性、父组件、布局对象和当前组件类型自己的配置。例如文本组件可以读取 text.value,按钮可以读取 button.label,图片可以读取 image.path。
一个可以直接照着做的例子
下面这份页面片段包含面板、提示文字和检查按钮。点击按钮后会同时完成三件事:
- 把面板此刻算出的 X 保存到
vars.currentX; - 把面板原始 X 公式保存到
vars.originalFormula; - 让提示文字始终位于面板右侧 1 个 GUI 像素处。
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仍然保存公式文本,不会被计算结果覆盖。
中文等价写法
上面按钮的事件也可以完整写成中文:
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 只接受 x、y、width、height。中文也可以写 表达式("宽") 或 表达式("高")。
如果布局原本是固定数字 20,读取 layout.x 会得到 20,而 isFormula 为 false。如果布局原本是 window.width * 0.5,layout.x 会得到当前计算结果,source 会保留公式文本,isFormula 为 true。
普通等号是快照,formula 才会持续跟随
下面两段看起来相似,结果并不相同。
一次性快照:
events:
leftClick: |-
component("tip").layout.x = component("panel").layout.x + component("panel").layout.width + 1执行这一刻先算出数字,再把数字保存到 tip.layout.x。窗口改变后,tip 不会继续跟随。
响应式公式:
events:
leftClick: |-
component("tip").layout.x = formula(
component("panel").layout.x + component("panel").layout.width + 1
)这里保存的是表达式。面板位置、面板宽度、页面变量或窗口尺寸改变后,ChaUI 会按依赖顺序重新计算,不依赖组件在 yml 中的先后顺序。
用字符串保存公式
公式也可以写成带引号的字符串。字符串仍会交给同一个公式解析器检查,不会作为任意脚本执行。
events:
leftClick: |-
component("panel").layout.x = formula("window.width * 0.5")这种写法适合公式来自配置文本的场景。字符串内容必须是一条完整的数字公式;formula("1); cmd('执行某项业务')") 会被拒绝,因为它不是合法公式。
读取父组件
component("id").parent 返回该组件在当前页面会话中的父组件对象。因此不需要自己再写一次父布局 ID:
events:
create: |-
vars.parentX = component("child_title").parent.layout.x
vars.parentWidth = component("child_title").parent.layout.width根级组件没有父组件,读取它的 parent 会得到 null。不要继续对根组件写 .parent.layout.x;该条语句会被安全跳过。
设置组件父级
parent 也可以在脚本中修改。最直观的写法是把一个布局组件对象直接赋给它:
events:
leftClick: |-
component("d3").parent = component("panel")这表示把 d3 放进布局组件 panel。父级必须是 layout_absolute、layout_relative、layout_grid 或 layout_scroll,普通文本、按钮等组件不能作为父级。
如果希望 d3 和 test3 进入同一个父布局,可以直接复制 test3 的父组件对象:
events:
leftClick: |-
component("d3").parent = component("test3").parent下面几种写法也都有效:
component("d3").parent = component("test3").parent.id
component("d3").parent = "panel"
component("d3").parent = null
组件("d3").父级 = 组件("test3").父级.parent.id得到父级 ID 字符串;- 直接写
"panel"也是按父级 ID 设置; null表示取消父级,让组件回到页面根级;- 不存在的父级、非布局父级和循环嵌套会被拒绝。
换父级不会偷偷改写 d3 的 layout.x/y。如果新父级是相对布局,原来的 X、Y 会被当作新父级内的局部坐标,所以组件在屏幕上的位置可能发生变化。
可以读取哪些属性
组件对象可以安全读取:
- 公共字段:
id、type、visible、enabled、z、pointerEvents、scale、opacity、条件、提示和事件信息; - 父级:
parent; - 布局:
layout.x/y/width/height和layout.expression(...); - 对应组件类型的数据块,例如
text.value、button.label、image.path、input.value、video.volume、entity.displayName;物品槽还可以读取itemSlot.check。
读取结果可以用于变量赋值、条件、普通运算和 formula(...)。log 的花括号只直接读取 vars 与 vals,所以调试组件属性时先保存到变量:
events:
leftClick: |-
vars.currentPath = component("logo").image.path
vars.currentWidth = component("logo").layout.width
log("图片:{vars.currentPath},宽度:{vars.currentWidth}")读取范围比写入范围更广。写入仍只允许 ChaUI 明确开放的临时状态,例如显隐、启用、父级、四项布局、部分文字/图片/视频/输入框/实体属性;id、type、公式元数据和 itemSlot.check 仍是只读的。
itemSlot.check 是页面 yml 或编辑器“操作条件”保存的客户端发包前置条件。脚本可以读取它用于提示或调试,但不能在运行时改写:
events:
leftClick: |-
vars.slotRule = component("submit_slot").itemSlot.check
log("当前操作条件:{vars.slotRule}")条件没通过时只会阻止内置槽位操作包,当前 leftClick 脚本仍然执行。因此上面的日志在槽位被禁止操作时也能显示。
指针处理、缩放与透明度
组件对象可以读取这三个根级字段,也可以在当前页面会话中修改:
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(...):
events:
create: |-
component("dialog").scale = formula("vars.zoom")
component("dialog").opacity = formula("vars.fade / 100")以后修改 vars.zoom 或 vars.fade,组件会重新计算。component("dialog").scale 与 .opacity 读取的是当前算出的数字。运行期间变量暂时让公式无效时,组件会保留上一次合法结果;第一次就无效时使用 1。
布局组件不能设置 scale 或 opacity。实体支持根级 scale,但不支持根级 opacity。opacity 不会关闭命中;需要让组件不再遮挡时,应同时设置 pointerEvents = "pass" 或隐藏组件。
保存或重置拖动位置
启用了 dragMode 的组件可以让玩家调整位置。脚本还能把当前实际偏移保存到客户端本地,或删除记忆并立即回到页面原始布局位置:
component("movable_panel").savePosition()
component("movable_panel").resetPosition()savePosition() 适合“临时拖动,点击确认后再记住”的设计。即使组件使用 dragMode: temporary,也能在确认按钮脚本中主动保存当前位置:
- 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 只会禁止继续拖动,不会自动删除记忆。
中文写法为:
组件("movable_panel").保存位置()
组件("movable_panel").重置位置()脚本动作只处理指定组件。管理员要一次重置某位在线玩家的全部组件位置记忆时,请使用对应的服务端指令或 ChaUIAPI.resetRememberedComponentPositions。
常用写入
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 路径后,新素材会从第一帧重新开始显示。
读取和修改源图选区
图片、按钮、输入框、物品槽和物品展示的源图选区也属于组件对象。读取字段本身会得到当前渲染条件下算出的整数:
vars.currentSourceWidth = component("health_fill").image.sourceWidth读取保存的声明要在对应数据块上调用 expression("字段名"):
vars.currentSourceFormula = component("health_fill").image.expression("sourceWidth").source
vars.sourceIsFormula = component("health_fill").image.expression("sourceWidth").isFormula如果字段原本是固定数字,source 返回这个数字的文本形式,isFormula 为 false。如果字段保存的是响应式公式,source 保留公式原文,isFormula 为 true。
下面是一段完整的按钮事件。它先让血条源宽持续跟随玩家血量,再记录当前值和原公式,最后把结果显示在本地聊天栏:
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}")直接赋数字会清除旧公式,变成一次性固定值:
component("health_fill").image.sourceWidth = 50各组件可以使用的字段如下:
| 数据块 | 可读写的源图字段 |
|---|---|
image | sourceX/Y/Width/Height |
button | source*、hoverSource*、checkedSource* |
input | source*、focusSource* |
itemSlot | sourceX/Y/Width/Height |
itemDisplay | sourceX/Y/Width/Height |
所有源图公式都使用当前页面的 vars、vals、窗口、父级和自身上下文重新计算,小数向下取整。脚本修改只属于当前页面会话,不会保存到服务端 yml。完整的图集与无拉伸血条示例见图片裁剪与动态血条。
实体组件可以写入已开放的 entity.* 状态,例如:
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 = trueentity.orthographic 只能写入布尔值。上面的脚本会立即切换为正交投影;写回 false 会恢复默认的 30 度透视投影。也可以用 vars.mode = component("npc_preview").entity.orthographic 读取当前模式。脚本改动只作用于当前页面会话,不会改写服务端页面文件。
动态修改原版容器绑定
容器替换页面中的非布局组件可以读取或修改 containerBinding。下面脚本先读取菜单按钮当前绑定,再让详情物品跟随同一槽位:
vars.currentBinding = component("menu_button").containerBinding
component("detail_item").containerBinding = component("menu_button").containerBinding从页面变量设置固定语义槽位也可以:
vars.targetBinding = "container_13"
component("detail_item").containerBinding = vars.targetBinding解除绑定必须使用 null:
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 会复制已有组件:
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:
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") 的调试文字:
vars.parentObject = component("temporary_tip_2").parent
log("{vars.parentObject}")这些组件只存在于当前客户端页面会话,不会发起编辑器保存,也不会修改服务端页面文件。重新打开页面后,临时组件会消失。
跨事件引用动态组件
动态组件可以在一个事件中创建,在另一个事件、方法或计时器中使用。下面的页面打开时创建 d3,按钮点击时再修改它:
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 | 根组件的 parent 是 null。只对确实设置了父布局的子组件读取。 |
使用 layout.right | ChaUI 没有这个字段。右边位置请写 layout.x + layout.width。 |
formula("window.width *") | 公式不完整,保存或绑定会被拒绝。 |
| A 和 B 的公式互相引用 | 形成循环,本次新绑定被拒绝,旧的有效公式和值继续保留。 |
给 component("a").id 赋值 | id 是只读字段。需要新 ID 时使用复制或在编辑器中创建组件。 |
给 component("slot").itemSlot.check 赋值 | itemSlot.check 是只读配置。请在页面 yml 或编辑器“操作条件”中修改。 |
把普通组件赋给 .parent | 父级必须是布局组件。请改为目标布局组件对象、它的 ID,或使用 null 回到根级。 |
继续阅读变量与条件,了解组件对象如何与页面变量配合;需要页面跳转和服务端动作时阅读内置动作、日志与音效。
猹件开发组