Skip to content
On this page

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

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

中文写法完全相同:

text
组件("组件ID")

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

在组件事件中使用 self、parent 和 child

组件自己的事件经常需要操作“我自己”或“我所在的面板”。这时不必重复填写组件 ID:

  • self / 自身:触发当前事件的组件;
  • parent / 父级:当前组件的父组件,等同于 self.parent
  • component("id") / 组件("id"):按 ID 取得当前页面中的指定组件。

三种写法得到的是同一种安全组件对象,因此都能继续读取属性、调用动作或访问子组件。组件对象还可以直接读取当前算出的 xywidthheight,中文宽高可写成 。例如 self.widthself.layout.width 都会得到当前宽度。

下面是一份可以直接保存的完整页面。点击按钮后,脚本会取得按钮自己、父面板和父面板的第一个子组件,并把结果显示到页面上:

yaml
id: component_reference_demo
version: 1
title: "组件引用示例"
size:
  width: 240
  height: 120
display:
  mode: screen
vars:
  clickedId: ""
  panelId: ""
  firstChildId: ""
events: {}
methods: {}
elements:
  - id: panel
    type: layout_relative
    layout:
      x: 20
      y: 20
      width: 200
      height: 80

  - id: result
    type: text
    parent: panel
    layout:
      x: 10
      y: 10
      width: 180
      height: 20
    text:
      value: "按钮={vars.clickedId},面板={vars.panelId},首项={vars.firstChildId}"

  - id: inspect_button
    type: button
    parent: panel
    layout:
      x: 10
      y: 45
      width: 100
      height: 20
    button:
      label: "读取组件"
    events:
      leftClick: |-
        vars.clickedId = self.id
        vars.panelId = parent.id
        vars.firstChildId = parent.child(0).id

child(index) / 子组件(index) 只读取这一层的直接子组件,索引从 0 开始。上例的 panel.child(0)resultpanel.child(1)inspect_button。它不会继续向下搜索孙组件。

子组件顺序是稳定的:页面原有组件按 yml 的 elements 顺序排列;脚本动态新增、复制后放入父级,或把已有组件重新挂到该父级时,会追加到当前子列表末尾。z、坐标和组件 ID 不会改变 child() 的顺序。

可以连续链式访问:

js
self.parent.child(0).id
component("panel").child(1).button.label
父级.子组件(0).文本.value

没有父级、没有对应子组件或索引越界时返回 null。例如 component("panel").child(99) 会得到 null;若继续写 .id,只会让当前语句产生运行时错误,页面其他脚本仍可继续。负数、小数、字符串、布尔值、缺少参数或传入多个参数都会被拒绝。

页面级事件没有触发组件,所以页面的 openclose、键鼠等根级事件中,selfparent 都是 null。根组件事件中的 self 正常指向该根组件,但它的 parent 仍是 null

组件事件调用的自定义方法、sleep 恢复链、timeoutintervaltimerdebouncethrottle 回调都会保留触发组件上下文,不需要把组件 ID 作为额外参数传入:

yaml
methods:
  report: |-
    log("自身={self.id},父级={parent.id},首项={parent.child(0).id}")
elements:
  - id: inspect_button
    type: button
    parent: panel
    events:
      leftClick: methods.report()

这里的 selfparent 和从 0 开始的 child(0) 与普通组件脚本表达式完全相同,并非 log 的特殊替换规则。

旧布局公式保持原样

布局和其他数值公式中的 self.width/heightparent.x/y/width/height 仍表示公式矩形,不是这里的可空组件对象。根级 parent 继续使用窗口矩形作为回退,因此旧页面不需要修改。child() 属于事件脚本,不能写进数值公式。

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

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

  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 会被当作新父级内的局部坐标,所以组件在屏幕上的位置可能发生变化。

可以读取哪些属性

组件对象可以安全读取:

  • 公共字段:idtypevisibleenabledzpointerEventsscalerotationopacity、条件、提示和事件信息;
  • 父级: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").rotation = formula("vars.angle")
    component("dialog").opacity = formula("vars.fade / 100")

以后修改 vars.zoomvars.anglevars.fade,组件会重新计算。component("dialog").scale.rotation.opacity 读取的是当前算出的数字。运行期间变量暂时让公式无效时,组件会保留上一次合法结果;首次失效时 scaleopacity 使用 1rotation 使用 0。中文属性名可写为 组件("dialog").旋转

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

销毁当前组件

组件自己的事件可以调用 self.destroy() / 自身.销毁(),立即从当前页面会话中销毁触发事件的组件。早期资料中误拼写的 self.destory() 继续兼容,新脚本建议统一使用 destroy

yaml
- id: one_time_tip
  type: button
  layout:
    x: 20
    y: 20
    width: 100
    height: 20
  button:
    label: "点击后移除"
  events:
    leftClick: |-
      self.destroy()

点击后,one_time_tip 会从这次页面会话中移除,并释放它的提示、拖拽、滚动、视频、实体和视觉状态。它的直接子组件不会一起销毁,而是解除父级后继续保留;需要移除整组组件时,应让各组件分别处理销毁,或在页面设计中控制整组显隐。

页面级事件没有 self,不能调用此方法。组件销毁后,同一条后续脚本或延时回调再次访问旧引用时,只会让当前失败语句停止,不会影响页面其他脚本。

保存或重置拖动位置

启用了 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 用法见槽位、按钮与物品信息

动态修改物品槽绑定与模式

普通虚拟 item_slot 可以在当前页面会话中修改 itemSlot.binditemSlot.mode。切换到持久槽时,先写入已注册的 bind,再写 mode:

js
component("动态槽").itemSlot.bind = "宝石槽_1"
component("动态槽").itemSlot.mode = "persistent"

中文写法等价:

js
组件("动态槽").物品槽.绑定 = "宝石槽_1"
组件("动态槽").物品槽.模式 = "persistent"

顺序很重要。若先切换为 persistent,ChaUI 会用旧 bind 检查注册表;旧值未注册时,本次赋值会被拒绝并保留原状态。目标 ID 没有注册、模式与绑定不匹配或组件不是物品槽时,也只拒绝当前赋值。

修改不会写回页面 yml。客户端会先用当前服务器同步的注册表预检,实际槽位操作仍由服务端重新验证并裁决。中文 ID、acceptWhen 和 reload 规则见槽位注册表

公式依赖和循环

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

中文函数为 新增组件复制组件addComponent 的类型只接受当前支持的 17 种组件:imagevideotextbuttonrectinputdropdowntogglesliderprogressitem_slotitem_displayentitylayout_absolutelayout_relativelayout_gridlayout_scroll。自定义类型或拼错的类型不能创建。

两个动作的新 ID 都只能包含 Unicode 字母、Unicode 数字、_-,并且不能和当前页面已有 ID 重复。例如 任务提示_2card-3 可以使用,含空格、点号或斜杠的 ID 不可以。新增或复制成功后,新对象会立即执行自己的 events.create

copyComponent(source, target) 只复制来源组件本身,不会把源组件的子组件一起复制。源组件处于某个布局容器中时,复制体会保留相同父级;源组件本来就在根级时,复制体也在根级。同一段脚本可以紧接着使用新 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}")

保存页面时,copyComponent 的源组件可以暂时不存在,便于先写好跨事件或计时器脚本;这不会自动创建源组件。真正执行复制语句时,源组件必须已经存在,否则只终止当前复制语句,后续语句仍会继续执行。

连同全部子组件一起复制

需要复制一整张卡片、列表项或组合面板时,使用 copyComponentTree(source, suffix);中文写法是 复制组件树(source, suffix)。第一个参数是来源根组件的固定 ID,第二个参数是追加到每个来源 ID 后面的字符串,也可以使用字符串表达式。

假设原结构为:

来源 ID父级用途
card根级卡片布局
card_iconcard卡片图标
card_titlecard卡片标题

执行:

text
copyComponentTree("card", "_副本")
component("card_副本").layout.x = 180

会一次生成 card_副本card_icon_副本card_title_副本。两个子组件的父级会分别改为 card_副本,自身的局部 X、Y 不变,因此整张卡片会保持原来的内部排布。编辑器里的“复制”按钮同样会复制完整子树,但会为副本自动分配新 ID,并只给副本根节点增加位置偏移。

后缀不能为空。最终生成的每个 ID 只能包含 Unicode 字母、数字、_-,并且最多为 64 个 Unicode 字符。执行前会先检查整棵子树:只要任意新 ID 已存在、格式非法或过长,整次复制失败,不会留下只复制了一半的组件。

脚本正文不会重写

复制会重建组件定义和副本内部的父级关系,但不会自动改写事件或自定义方法正文中手写的组件 ID。上例若 card_title 的点击脚本写了 component("card"),复制后仍然指向原来的 card;需要按业务需求改为通过 selfparent 访问,或主动设置副本状态。

copyComponentcopyComponentTree 的区别很明确:前者的第二个参数是完整目标 ID,并且只复制一个组件;后者的第二个参数是统一后缀,并递归复制来源根组件和全部后代。

如果把父级对象本身存入变量,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

普通脚本允许用字符串表达式计算组件 ID,适合配合 repeat 的索引变量批量处理同一组组件:

js
repeat(4, vars.i) {
    component("entry_" + vars.i).enabled = vars.i != vars.disabledIndex
    component("entry_text_" + vars.i).text.value = "条目 " + (vars.i + 1)
}

保存时仍会检查参数是否可能返回字符串,并继续检查字段名是否属于 ChaUI Script 的公开组件属性。执行时,计算结果必须是当前页面中真实存在的完整组件 ID,目标组件也必须支持所访问的类型数据块;否则只终止当前失败语句。

动态组件 ID 不能写进 formula(...)。响应式公式必须使用固定组件 ID,以便保存时建立确定的组件依赖图:

js
// 支持:普通脚本在执行时写入固定值
component("entry_" + vars.i).layout.x = vars.i * 24

// 支持:formula 使用固定组件依赖
component("tip").layout.x = formula(component("panel").layout.x + 8)

// 不支持:formula 内动态选择依赖组件
component("tip").layout.x = formula(component("entry_" + vars.i).layout.x)

先写尚未创建的组件动作

有时你会先完成按钮逻辑,之后才在编辑器里补上目标组件;也可能由另一段脚本在运行期间创建目标组件。这种情况下,可以直接保存尚不存在的组件引用:

text
component("future_tip").visible = true
component("future_tip").text.value = "操作完成"
vars.actionFinished = true

编辑器不会只因为 future_tip 当前不在组件列表中而拒绝整张页面。语法、字段名、动作参数和已经存在组件的类型仍会正常检查;例如已经存在的文本组件不能调用视频动作,拼错的字段也不能保存。

真正执行到引用语句时,目标组件必须已经存在,并且类型要和所用的数据块或动作相符。若目标仍不存在或类型不符,只终止当前这一条语句,后面的 vars.actionFinished = true 仍会继续执行。客户端不会在聊天栏刷提示;同一页面会话中的同类错误只会在 latest.log 记录一次 warning,便于管理员排查组件 ID 和执行顺序。

下面的写法先创建组件,再使用预先写好的动作:

text
addComponent("text", "future_tip")
component("future_tip").text.value = "现在已经可以使用"
component("future_tip").visible = true

重新打开页面或执行 reload 后会建立新的页面会话;如果同类错误仍然存在,新会话会再次记录一次,避免旧日志状态掩盖当前配置问题。

常见错误

错误结果与处理方式
组件 ID 写错或组件不存在页面仍可保存,但执行时当前语句会停止并在 latest.log 留下一条去重 warning;先检查组件 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 回到根级。

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