LPAI AI 游戏编辑器文档

特效系统

交互特效脚本

特效系统 让控件在移入、移出、移动、长按时出现视觉反馈 交互特效脚本对应 4 个位置:`js_enter`、`js_leave`、`js_move`、`js_long`。它们最适合做移入高亮、悬浮提示、跟手提示、长按反馈这类交互表现。 # 先记住这 4 个入口 脚本 什么时候触发 js_enter 鼠标移入,

特效系统

让控件在移入、移出、移动、长按时出现视觉反馈

交互特效脚本对应 4 个位置:`js_enter`、`js_leave`、`js_move`、`js_long`。它们最适合做移入高亮、悬浮提示、跟手提示、长按反馈这类交互表现。

# 先记住这 4 个入口

脚本 什么时候触发
js_enter 鼠标移入,或手机手指按下时
js_leave 鼠标移出,或手机手指松开 / 取消时
js_move 鼠标移动,或手指按住移动时
js_long 鼠标主键按住,或手机手指持续按住,达到长按阈值时

# 1. 这类脚本最适合做什么

# js_enter

  • 鼠标移入高亮
  • 显示悬浮提示
  • 按钮轻微放大

# js_leave

  • 关闭悬浮提示
  • 恢复默认样式
  • 清理 hover 特效

# js_move

  • 跟手提示
  • 光标附近文字
  • 局部聚光

# js_long

  • 长按出现菜单
  • 长按预览详情
  • 长按触发一段强调特效或提示

# 2. 交互脚本本身更适合只做“特效入口”

交互脚本更常见的用法是:

  • 读当前上下文
  • tools.runClientEffect(...)
  • tools.disposeClientEffect(...)
  • tools.clearClientEffects()

js_enterjs_leavejs_move 在指针事件到达运行时后会立即执行,并在脚本启动后同步一次状态。异步后续 Promise 完成时不会再自动同步一次。因此 js_move 应保持轻量,只负责提交最新视觉状态。

真正的结算和状态变化,通常会放在别的脚本位置:

  • 扣金币
  • 发奖励
  • 写剧情日志
  • 创建对象
  • 发 AI 请求

# 3. 这里最容易搞混的一点

在手机上,这 4 个入口不是互斥关系:

  • 鼠标移入通常会先触发 js_enter
  • 手机按下也会先触发 js_enter
  • 按住移动时会触发 js_move
  • 持续按住达到阈值后再触发 js_long
  • 最后松开或取消时触发 js_leave

在交互脚本本身里,能直接用的是:

  • player
  • objects
  • tools

而这些变量:

  • api
  • target
  • targets
  • pointer

只在 tools.runClientEffect("...") 里面那段特效脚本里可用。

所以这是错的:

const target = api.getTarget();

可以这样写:

tools.runClientEffect(`
  const target = api.getTarget();
  if (!target) return;
`);

# 4. 当前脚本附带的信息

交互脚本里,常见会直接用这些值:

  • tools.kwargs.itemID
  • tools.kwargs.object
  • tools.kwargs.objID
  • tools.kwargs.effectTargetId

例如:

let obj = tools.kwargs.object;
let itemID = tools.kwargs.itemID;

# 5. 常见写法:进入创建,离开销毁

# js_enter

tools.runClientEffect(`
  const current = api.getTarget();
  if (!current) return;

  current.setStyle({
    outline: "2px solid rgba(255,255,255,0.65)",
    filter: "brightness(1.08)"
  });
`, {
  label: `hover-outline-${tools.kwargs.itemID}`
});

# js_leave

tools.disposeClientEffect(`hover-outline-${tools.kwargs.itemID}`);

这套写法很常见,因为:

  • 进入时只负责开效果
  • 离开时只负责收效果
  • label 固定,不容易堆积

# 6. js_move 使用保留式 update 模式

js_move 触发频率很高。如果每次都 replace 整个特效,就会重复创建 DOM,移动效果也会受事件频率影响。跟手、拖拽预览、位置或尺寸连续变化时,需要同时使用新模式的四个部分:

  • mode: "update"
  • 稳定 label,用于复用同一个特效会话
  • 稳定的 api.upsertNodeapi.upsertText key,用于复用同一个 DOM
  • motion,让浏览器在最新位置或尺寸目标之间自动插值

# 跟手提示例子

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",
      borderRadius: "6px",
      background: "rgba(15,23,42,0.88)",
      color: "#fff",
      fontSize: "12px",
      pointerEvents: "none"
    },
    placement: {
      x: payload.x,
      y: payload.y,
      offsetX: 14,
      offsetY: -10,
      selfX: 0,
      selfY: 1
    },
    motion: {
      durationMs: 80,
      easing: "linear",
      position: true,
      size: false
    }
  });
`, {
  mode: "update",
  label,
  payload: { text: "查看详情", x: point.clientX, y: point.clientY }
});

每次调用只提交最新目标,不要在这段 update 脚本里 await api.wait(...)await node.animate(...) 或自行写动画循环。浏览器会重定向 transition 并自动补齐中间画面,和游戏速度无关。

# js_leave

const label = `pointer-tip-${tools.kwargs.itemID}`;
tools.disposeClientEffect(label);

# 7. js_long 更适合做“延迟确认”型反馈

js_longjs_enter 的区别是:

  • js_enter 更适合立即亮起
  • js_long 更适合“按住一会儿才出现”的反馈

例如长按后出现一个环形描边:

tools.runClientEffect(`
  const current = api.getTarget();
  if (!current) return;

  current.setStyle({
    outline: "2px solid rgba(250,204,21,0.95)",
    boxShadow: "0 0 0 6px rgba(250,204,21,0.18)"
  });
`, {
  label: `long-press-highlight-${tools.kwargs.itemID}`
});

结束长按态时,通常还是在 js_leave 里统一收掉:

tools.disposeClientEffect(`long-press-highlight-${tools.kwargs.itemID}`);

# 8. 什么时候不用手动传 target

在交互脚本里,如果你不传 target,运行时通常会自动绑定当前真实触发的那个目标。

所以很多移入高亮、悬浮提示,直接省略 target 就够了。

# 9. 什么时候适合自己传 target

如果你想高亮的不是当前控件,而是别的控件,可以自己传:

tools.runClientEffect(`
  const current = api.getTarget();
  if (!current) return;
  current.setStyle({ filter: "brightness(1.08)" });
`, {
  target: "主按钮",
  label: "main-btn-highlight"
});

如果要一次命中多个目标,可以用 targets

# 10. 对象控件里更适合直接用当前对象

如果这个控件本身绑定了对象类,交互脚本里通常会直接用:

let obj = tools.kwargs.object;
if (!obj) return;

tools.runClientEffect(`
  const current = api.getTarget();
  if (!current) return;
  current.setStyle({ filter: "brightness(1.08)" });
`, {
  label: `hover-${obj.getID()}`
});

# 使用时要注意的几件事

  • 交互脚本本身更像特效入口。
  • api / target / pointer 只在内层特效脚本里可用。
  • 高频 js_move 视觉更新使用 mode: "update"、稳定 label、稳定 upsert key 和 motion,并在 js_leave 清理该 label。
  • js_long 更适合放延迟出现的反馈,不要和 js_enter 做成完全重复的一套效果。
  • js_enter 创建,js_leave 销毁,是最常见的组合。

# 下一步

如果你还没看过基础特效入口,可以先看 客户端特效系统