表现与音频
客户端视觉特效
用目标、会话、更新模式和清理规则安全实现短期视觉反馈。
表现与音频
特效可以失败,游戏结果不能跟着改变
客户端特效用于飘字、高亮、抖动、遮罩和跟手提示。它读取已经结算的结果并增强反馈,不拥有属性、对象或剧情状态。
# 两层脚本
外层是普通 Worker 脚本,可使用 player、objects 和 tools;内层是传给客户端宿主的字符串,只使用注入的 api、payload、target、targets、session 和 pointer。
tools.runClientEffect(`
const current = api.getTarget();
if (!current) return;
current.setStyle({ filter: "brightness(1.15)" });
`, {
target: "confirm-control-id",
label: "confirm-highlight"
});
内层按原生 JavaScript 执行,不做关键字扫描或浏览器全局遮蔽。即使技术上能访问浏览器能力,也不要用它绕过 tools、读取私密数据或自行请求平台接口。需要的运行时值先在外层计算,再通过 payload 传入。
# 公开生命周期 API
| 方法 | 作用 |
|---|---|
tools.runClientEffect(script, options?) |
创建或更新特效会话 |
tools.disposeClientEffect(label) |
清理指定会话 |
tools.clearClientEffects() |
清理当前页面全部特效 |
options 的核心字段:
| 字段 | 作用 |
|---|---|
payload |
传入纯数据 |
target |
控件 ID 或 { itemId, objectId } |
targets |
多个目标 |
label |
可替换、更新和清理的稳定会话名 |
mode |
replace 或 update |
waitMs |
脚本完成后的额外保留时间 |
timeoutMs |
宿主中断脚本前的最大执行时间 |
pointer |
显式可序列化指针快照;通常从挂载点继承 |
目标不存在时应安静结束,不得因此改变规则结算。
# 完整内层 api 参考
| 方法 | 返回/责任 |
|---|---|
api.getTarget() |
第一个注入目标,缺失时为 null |
api.getTargets() |
全部注入目标句柄的副本 |
api.resolveTarget(input?) |
严格解析一个控件/对象目标 |
api.resolveTargets(inputs?) |
解析多个目标;省略参数时返回注入目标 |
api.createNode(options?) |
创建受跟踪 DOM 节点句柄 |
api.createText(text, options?) |
创建受跟踪文本节点句柄 |
api.upsertNode(key, options?) |
创建或更新稳定 key 节点 |
api.upsertText(key, text, options?) |
创建或更新稳定 key 文本节点 |
api.wait(ms?) |
受跟踪延迟;会话销毁时 reject |
api.animate(handle, keyframes, options?) |
受跟踪 Web Animation Promise |
api.remove(node) |
节点存在时移除 |
api.cleanup(reason?) |
销毁当前特效会话 |
api.getViewport() |
返回 { width, height, scrollX, scrollY } |
api.log(...args) |
带会话标签的客户端特效日志 |
target 是第一个注入目标,targets 是完整列表。runtime 提供 sessionId、label、now() 和 viewport()。session 是客户端特效/音频共用的页面前端脚本 session,不是 Worker 的 tools.session,也不会写入存档。
# 目标与节点句柄
目标句柄是不拥有 DOM 的已渲染控件引用:
| 目标方法/属性 | 用途 |
|---|---|
kind、id、source |
句柄身份和原始目标输入 |
exists() |
目标当前是否仍存在 |
getBounds() |
矩形以及 centerX / centerY |
animate(keyframes, options?) |
运行动画并跟踪恢复 |
setStyle(style) |
应用会话跟踪的临时内联样式 |
addClass(className) |
添加清理时移除的 class |
节点句柄拥有当前会话创建的特效 DOM:
| 节点方法/属性 | 用途 |
|---|---|
kind、id |
句柄身份 |
mount(parent?) / append(child) |
挂到根、目标或另一个节点 |
clearChildren() |
清理受跟踪子内容 |
setText(value) / setHTML(value) |
替换内容;HTML 必须由作者控制 |
setStyle(style) |
修改内联样式 |
addClass(name) / removeClass(name) |
修改 class |
setAttribute(name, value) |
设置允许的属性 |
place(placement) |
相对目标或视口坐标定位 |
animate(keyframes, options?) |
运行受跟踪动画 |
getBounds() |
读取当前矩形 |
remove() |
移除节点和跟踪器 |
节点 options 支持 tag、text、html、className、attrs、style、parent、mount、placement 和 motion。定位支持 target、x/y、偏移、目标锚点、自身锚点和 trackTarget。输入必须由作者控制:setHTML 不能用来渲染玩家、AI 或联网返回的 HTML。
# replace 与 update
| 模式 | 行为 | 用途 |
|---|---|---|
replace |
同 label 新请求销毁旧会话 | 飘字、闪烁、抖动、一次性动画 |
update |
复用会话和 keyed 节点 | 跟手提示、拖拽预览、连续尺寸/位置 |
update 必须有稳定 label,内层使用 api.upsertNode(key, options) 或 api.upsertText(key, text, options)。相同 label 与 key 会 patch 现有 DOM。
const point = tools.kwargs.pointer;
const label = `pointer-tip-` + tools.kwargs.itemID;
if (!point) return;
tools.runClientEffect(`
api.upsertText("tip", payload.text, {
style: {
padding: "4px 8px",
background: "rgba(15,23,42,.9)",
color: "#fff",
pointerEvents: "none"
},
placement: {
x: payload.x, y: payload.y,
offsetX: 12, offsetY: -10,
selfX: 0, selfY: 1
},
motion: { durationMs: 80, easing: "linear", position: true }
});
`, {
mode: "update",
label,
payload: { text: "查看详情", x: point.clientX, y: point.clientY }
});
对应的离开脚本调用 tools.disposeClientEffect(label)。高频更新中不要 await api.wait、等待动画或自己循环补帧。
# 目标与对象行
- 普通控件:
target: "control-id"; - 对象行:
target: { itemId: "control-id", objectId: obj.getID() }; - 多目标:
targets: ["a", "b"]; - 指针挂载点不传 target 时,运行时通常注入真实触发目标。
对象列表重排后仍按对象 ID 命中,不能使用数组下标。
# 纯 AI 提示词契约
说明触发挂载点、目标 ID、持续时间、是否跟随指针、稳定 label、页面切换与目标消失时的清理。要求 AI 先查询控件和对象上下文,再生成外层与内层脚本。
# 验收
测试目标存在/不存在、重复触发、快速移动、对象行重排、离开清理、页面切换、脚本异常和低性能设备。删除全部特效后,游戏状态仍应完全正确。
下一步:交互特效脚本。