Skip to content
On this page

循环与计时器

ChaUI Script 可以用有界循环一次处理多项页面状态,也可以按游戏 tick 或真实毫秒安排后续动作。配合响应式文字、布局和显隐条件,页面只需要修改变量,界面就会自动显示最新结果。

自定义方法不会改变循环安全边界。从 repeattimer 或周期 interval 中调用的方法同样不能绕过对应限制,也不能通过方法间接创建嵌套延时任务。页面关闭、reload 或退出预览时,当前作用域的全部延时任务都会立即停止。

重复执行

repeat 会立即执行指定次数。次数可以是数字,也可以是包含页面变量、只读常量、四则运算、括号和数学函数的表达式。

js
repeat(vars.rewardCount, vars.i) {
    vars.total = vars.total + vars.i
}

第二个参数是可选的索引变量。上例会在每一轮把 012……写入 vars.i;循环结束后,索引变量保留最后一次的值。次数为 0 时不会执行循环,也不会修改索引变量。

不需要索引时可以省略第二个参数:

js
repeat(vars.flashCount) {
    vars.total = vars.total + 1
}

中文别名为 重复

js
重复(变量.rewardCount, 变量.i) {
    变量.total = 变量.total + 变量.i
}

循环次数只在开始时计算一次。即使循环体修改了 vars.rewardCount,已经开始的这次循环也不会临时增减轮数。

命名计时器

timer 会按固定 tick 间隔重复执行一段脚本。计时器必须填写名称,便于后续停止、重启和管理生命周期。

js
timer("countdown", 20) {
    vars.remaining = vars.remaining - 1
    if vars.remaining <= 0 {
        stopTimer("countdown")
    }
}

Minecraft 正常运行时每秒通常有 20 个游戏 tick,因此 20 通常约为 1 秒。计时器启动后会先等待完整间隔,再执行第一次;之后继续按同样的间隔运行。

间隔也可以使用表达式:

js
timer("status_refresh", vars.refreshTicks) {
    vars.refreshCount = vars.refreshCount + 1
}

间隔表达式只在启动时计算一次。若要使用新的间隔,再次启动同名计时器即可;同一页面作用域内的新计时器会重启并替换旧计时器,不会同时保留两份回调。

中文别名为 计时器停止计时器

js
计时器("倒计时", 20) {
    变量.remaining = 变量.remaining - 1
    if 变量.remaining <= 0 {
        停止计时器("倒计时")
    }
}

毫秒延时任务

需要比游戏 tick 更直观的等待时间时,可以使用 sleeptimeoutinterval。时间单位都是毫秒,允许 13600000,也就是最短 1 毫秒、最长 1 小时。实际动作只会在客户端主线程的安全更新点执行,不会早于填写的时间;客户端短暂卡顿时,周期任务也不会补跑已经错过的次数。

sleep:暂停当前脚本链

sleep 会暂停当前这一次脚本,等待时间到达后从下一条语句继续。它必须有名称,方便其他事件取消这段等待。

js
log("准备显示提示")
sleep("show_tip", 500)
component("tip").visible = true
log("提示已经显示")

上例先输出第一条消息,等待至少 500 毫秒,再显示 tip 并输出第二条消息。sleep 不会阻塞游戏画面、输入或其他页面脚本。

中文写法完全等价:

js
日志("准备显示提示")
等待("显示提示", 500)
组件("tip").visible = true

如果等待期间执行 cancel("show_tip"),这条 sleep 会被取消,后面的显示与日志也不会继续执行。

timeout:稍后执行一次

timeout 不暂停当前脚本,而是登记一段只执行一次的回调。适合自动隐藏提示、延后刷新状态或在动画结束后做一次收尾。

js
component("tip").visible = true
timeout("hide_tip", 1500) {
    component("tip").visible = false
}
vars.requestRegistered = true

vars.requestRegistered 会立即更新,隐藏动作在至少 1500 毫秒后执行一次。中文别名是 延时

js
延时("隐藏提示", 1500) {
    组件("tip").visible = false
}

interval:按毫秒周期执行

interval 会先等待完整间隔,再周期执行回调。它适合需要毫秒单位的轮播、闪烁和客户端状态刷新。

js
vars.flashCount = 0
interval("flash", 250) {
    vars.flashCount = vars.flashCount + 1
    if component("tip").visible {
        component("tip").visible = false
    } else {
        component("tip").visible = true
    }
    if vars.flashCount >= 8 {
        cancel("flash")
        component("tip").visible = true
    }
}

这个示例每隔至少 250 毫秒切换一次显示状态,执行 8 次后停止。中文别名是 间隔取消

cancel:统一取消命名任务

cancel("名称") 可以取消 sleeptimeoutinterval,也可以取消旧的 tick timer。旧写法 stopTimer("名称") / 停止计时器("名称") 仍然可用,但只用于 tick timer;新页面推荐使用更通用的 cancel

js
cancel("hide_tip")
取消("自动刷新")

同一页面作用域中再次用相同名称启动任务时,新任务会重启并替换旧任务。不同页面会话、HUD 和世界页面实例仍然相互隔离。

用本地日志检查计时器

遇到“计时器似乎没有运行”时,可以先用 log 在当前客户端聊天栏输出回调结果。下面的脚本每隔 20 tick 重新读取文本组件当前的局部 Y,向下移动 10 个 GUI 像素,再显示写回后的最新值:

text
timer("t2d", 20) {
  component("player_name_value").layout.y = component("player_name_value").layout.y + 10
  vars.currentY = component("player_name_value").layout.y
  log("当前 Y:{vars.currentY}")
}

如果聊天栏持续出现递增的 Y,说明计时器回调和组件赋值都已经执行。组件不存在、布局值无法计算或右侧公式无效时,该次赋值会跳过;可以继续增加变量日志缩小排查范围。log 只显示给当前客户端玩家,不会把调试内容发送到服务端。

上例每次计时器回调都重新读取当前 Y,再用普通 = 保存新的数字,所以适合做逐步移动。如果希望组件在计时器结束后仍然持续跟随另一个组件,应绑定公式:

text
component("tip").layout.x = formula(component("panel").layout.x + component("panel").layout.width + 1)

关闭页面、关闭 HUD 或世界页面实例、退出预览以及 reload 更换页面时,这个计时器仍会按既有规则立即停止并释放。毫秒任务也遵循相同生命周期。

页面关闭时自动停止

计时器属于启动它的当前页面作用域。以下情况都会自动停止并释放对应的全部计时器:

  • 关闭普通页面或编辑器;
  • 关闭 HUD 页面;
  • 关闭某个世界页面实例;
  • 替换同一个页面实例;
  • 玩家退出当前客户端会话。

不同页面会话、HUD 和世界实例相互独立。它们即使使用相同的计时器名称,也不会互相停止或覆盖。

使用限制

限制数值
单次 repeat 最大次数1000
脚本结构最大嵌套16
单次事件或延时回调最大执行量4096 个脚本节点
tick 计时器间隔172000 tick
毫秒任务时间13600000 毫秒
每个页面作用域的活动延时任务tick timer 与全部毫秒任务合计最多 32

repeat 可以嵌套,也可以写在计时器中;但不能在 repeat 或另一个延时回调内创建新的延时任务。需要多个任务时,应在事件的顶层分别启动并命名。

循环、tick timer 与周期 interval 内可以修改变量、组件状态、播放本地音效、控制视频或发送自定义包,但不能使用以下服务端高权限动作:

  • close / 关闭
  • open / 打开
  • cmd / 指令
  • opcmd / OP指令
  • consolecmd / 控制台指令

这些限制会在保存页面时由服务端检查,避免脚本循环批量触发页面跳转或高权限命令。

与响应式界面配合

推荐让计时器只负责更新变量,让文本组件自己显示变量:

yaml
vars:
  remaining: 10

elements:
  - id: countdown_text
    type: text
    layout:
      x: 20
      y: 20
      width: 160
      height: 20
    text:
      value: "剩余 {vars.remaining} 秒"

启动倒计时后,text.value 会随着 vars.remaining 自动更新,不需要在每次回调中手动拼接文字。继续阅读响应式变量与实时文字,可以把同样的方式应用到布局、按钮文字和显隐条件。