循环与计时器
ChaUI Script 可以用有界循环一次处理多项页面状态,也可以按游戏 tick 或真实毫秒安排后续动作。配合响应式文字、布局和显隐条件,页面只需要修改变量,界面就会自动显示最新结果。
自定义方法不会改变循环安全边界。从 repeat、timer 或周期 interval 中调用的方法同样不能绕过对应限制,也不能通过方法间接创建嵌套延时任务。页面关闭、reload 或退出预览时,当前作用域的全部延时任务都会立即停止。
重复执行
repeat 会立即执行指定次数。次数可以是数字,也可以是包含页面变量、只读常量、四则运算、括号和数学函数的表达式。
repeat(vars.rewardCount, vars.i) {
vars.total = vars.total + vars.i
}第二个参数是可选的索引变量。上例会在每一轮把 0、1、2……写入 vars.i;循环结束后,索引变量保留最后一次的值。次数为 0 时不会执行循环,也不会修改索引变量。
不需要索引时可以省略第二个参数:
repeat(vars.flashCount) {
vars.total = vars.total + 1
}中文别名为 重复:
重复(变量.rewardCount, 变量.i) {
变量.total = 变量.total + 变量.i
}循环次数只在开始时计算一次。即使循环体修改了 vars.rewardCount,已经开始的这次循环也不会临时增减轮数。
命名计时器
timer 会按固定 tick 间隔重复执行一段脚本。计时器必须填写名称,便于后续停止、重启和管理生命周期。
timer("countdown", 20) {
vars.remaining = vars.remaining - 1
if vars.remaining <= 0 {
stopTimer("countdown")
}
}Minecraft 正常运行时每秒通常有 20 个游戏 tick,因此 20 通常约为 1 秒。计时器启动后会先等待完整间隔,再执行第一次;之后继续按同样的间隔运行。
间隔也可以使用表达式:
timer("status_refresh", vars.refreshTicks) {
vars.refreshCount = vars.refreshCount + 1
}间隔表达式只在启动时计算一次。若要使用新的间隔,再次启动同名计时器即可;同一页面作用域内的新计时器会重启并替换旧计时器,不会同时保留两份回调。
中文别名为 计时器 与 停止计时器:
计时器("倒计时", 20) {
变量.remaining = 变量.remaining - 1
if 变量.remaining <= 0 {
停止计时器("倒计时")
}
}毫秒延时任务
需要比游戏 tick 更直观的等待时间时,可以使用 sleep、timeout 和 interval。时间单位都是毫秒,允许 1 至 3600000,也就是最短 1 毫秒、最长 1 小时。实际动作只会在客户端主线程的安全更新点执行,不会早于填写的时间;客户端短暂卡顿时,周期任务也不会补跑已经错过的次数。
sleep:暂停当前脚本链
sleep 会暂停当前这一次脚本,等待时间到达后从下一条语句继续。它必须有名称,方便其他事件取消这段等待。
log("准备显示提示")
sleep("show_tip", 500)
component("tip").visible = true
log("提示已经显示")上例先输出第一条消息,等待至少 500 毫秒,再显示 tip 并输出第二条消息。sleep 不会阻塞游戏画面、输入或其他页面脚本。
中文写法完全等价:
日志("准备显示提示")
等待("显示提示", 500)
组件("tip").visible = true如果等待期间执行 cancel("show_tip"),这条 sleep 会被取消,后面的显示与日志也不会继续执行。
timeout:稍后执行一次
timeout 不暂停当前脚本,而是登记一段只执行一次的回调。适合自动隐藏提示、延后刷新状态或在动画结束后做一次收尾。
component("tip").visible = true
timeout("hide_tip", 1500) {
component("tip").visible = false
}
vars.requestRegistered = truevars.requestRegistered 会立即更新,隐藏动作在至少 1500 毫秒后执行一次。中文别名是 延时:
延时("隐藏提示", 1500) {
组件("tip").visible = false
}interval:按毫秒周期执行
interval 会先等待完整间隔,再周期执行回调。它适合需要毫秒单位的轮播、闪烁和客户端状态刷新。
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("名称") 可以取消 sleep、timeout、interval,也可以取消旧的 tick timer。旧写法 stopTimer("名称") / 停止计时器("名称") 仍然可用,但只用于 tick timer;新页面推荐使用更通用的 cancel。
cancel("hide_tip")
取消("自动刷新")同一页面作用域中再次用相同名称启动任务时,新任务会重启并替换旧任务。不同页面会话、HUD 和世界页面实例仍然相互隔离。
用本地日志检查计时器
遇到“计时器似乎没有运行”时,可以先用 log 在当前客户端聊天栏输出回调结果。下面的脚本每隔 20 tick 重新读取文本组件当前的局部 Y,向下移动 10 个 GUI 像素,再显示写回后的最新值:
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,再用普通 = 保存新的数字,所以适合做逐步移动。如果希望组件在计时器结束后仍然持续跟随另一个组件,应绑定公式:
component("tip").layout.x = formula(component("panel").layout.x + component("panel").layout.width + 1)关闭页面、关闭 HUD 或世界页面实例、退出预览以及 reload 更换页面时,这个计时器仍会按既有规则立即停止并释放。毫秒任务也遵循相同生命周期。
页面关闭时自动停止
计时器属于启动它的当前页面作用域。以下情况都会自动停止并释放对应的全部计时器:
- 关闭普通页面或编辑器;
- 关闭 HUD 页面;
- 关闭某个世界页面实例;
- 替换同一个页面实例;
- 玩家退出当前客户端会话。
不同页面会话、HUD 和世界实例相互独立。它们即使使用相同的计时器名称,也不会互相停止或覆盖。
使用限制
| 限制 | 数值 |
|---|---|
单次 repeat 最大次数 | 1000 |
| 脚本结构最大嵌套 | 16 层 |
| 单次事件或延时回调最大执行量 | 4096 个脚本节点 |
| tick 计时器间隔 | 1 至 72000 tick |
| 毫秒任务时间 | 1 至 3600000 毫秒 |
| 每个页面作用域的活动延时任务 | tick timer 与全部毫秒任务合计最多 32 个 |
repeat 可以嵌套,也可以写在计时器中;但不能在 repeat 或另一个延时回调内创建新的延时任务。需要多个任务时,应在事件的顶层分别启动并命名。
循环、tick timer 与周期 interval 内可以修改变量、组件状态、播放本地音效、控制视频或发送自定义包,但不能使用以下服务端高权限动作:
close/关闭open/打开cmd/指令opcmd/OP指令consolecmd/控制台指令
这些限制会在保存页面时由服务端检查,避免脚本循环批量触发页面跳转或高权限命令。
与响应式界面配合
推荐让计时器只负责更新变量,让文本组件自己显示变量:
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 自动更新,不需要在每次回调中手动拼接文字。继续阅读响应式变量与实时文字,可以把同样的方式应用到布局、按钮文字和显隐条件。
猹件开发组