脚本系统
tools 对象
脚本系统 用 tools 连接弹窗、场景、AI、地图、特效和音效 `tools` 负责把脚本和别的功能接起来。弹窗、输入、场景跳转、聊天消息、AI、地图、画面特效和前端音效,都会在这里用到。 # 最常见的用途 弹出提示或输入框 让玩家做选择 先把文案翻译成当前语言 跳转场景 发送聊天消息 读写本地数据 请求
脚本系统
用 tools 连接弹窗、场景、AI、地图、特效和音效
`tools` 负责把脚本和别的功能接起来。弹窗、输入、场景跳转、聊天消息、AI、地图、画面特效和前端音效,都会在这里用到。
# 最常见的用途
- 弹出提示或输入框
- 让玩家做选择
- 先把文案翻译成当前语言
- 跳转场景
- 发送聊天消息
- 读写本地数据
- 请求 AI 内容
- 控制地图移动
- 触发画面特效
- 触发前端音效
# 先记住这一点
很多 tools 方法不会立刻给结果。
脚本里常见写法是:
tools.inputText("输入名字").then((value) => {
if (!value) return;
player.setString("玩家名字", value);
});
# 还有一个现在很重要的约定
如果一段文本最后是给玩家看的,优先先过一遍 tools.tr(...)。
tools.showToast(tools.tr("购买成功"), "success");
tools.tr(text, params?) 的规则是:
- 直接把原文当成项目 i18n 的 key
- 返回当前语言下对应的文本
- 如果当前语言还没配这条翻译,就回退显示原文
带变量时可以这样写:
tools.showToast(
tools.tr("你获得了{count}个苹果", { count: 3 }),
"success"
);
这对下面这些内容尤其重要:
showToast/showNotifyshowDialogchoose/chooseObjectsinputText/inputNumberpostMessage动态字符串脚本的返回值
如果你现在先写死中文,以后想做多语言,通常就得回头逐个重改。直接从一开始写成 tools.tr(...) 会省很多事。
# 1. 提示和弹窗
# 中间提示
tools.showToast(tools.tr("购买成功"), "success");
# 顶部提示
tools.showNotify(tools.tr("新的任务已开启"));
# 确认弹窗
tools.showDialog(tools.tr("确认"), tools.tr("奖励已经发放"));
# 关闭当前控件弹窗
if (player.getPro("任务已完成") > 0) {
tools.closeCurrentDialog();
}
tools.closeCurrentDialog() 用于关闭当前控件所在的最上层 dialog/subdialog 弹窗。没有控件弹窗时不会报错,也不会影响脚本继续执行。
它不能关闭 tools.showDialog、tools.choose、tools.inputText 或 tools.inputNumber 创建的交互弹窗;这些弹窗仍通过确认、取消、点击遮罩或按 Esc 结束。
# 2. 选择和输入
# 选项选择
tools.choose(tools.tr("请选择奖励"), tools.tr("从下面选一个"), {
optionNames: [
tools.tr("金币"),
tools.tr("体力"),
tools.tr("经验")
],
optionValues: ["gold", "stamina", "exp"]
}).then((result) => {
if (!result?.value) return;
player.setString("本次选择", result.value);
});
# 文本输入
tools.inputText(tools.tr("输入昵称"), "", {
placeholder: tools.tr("请输入昵称")
}).then((value) => {
if (!value) return;
player.setString("玩家昵称", value);
});
# 数字输入
tools.inputNumber(tools.tr("输入数量"), "", {
min: 1,
max: 99,
defaultValue: 1
}).then((value) => {
if (value == null) return;
player.setString("购买数量", String(value));
});
# 对象选择
let objectList = objects.getObjects("NPC");
tools.chooseObjects(tools.tr("选择对象"), tools.tr("请选择一位角色"), {
objectList
}).then((selectedID) => {
if (!selectedID) return;
player.setString("当前伙伴", String(selectedID));
});
# 3. 场景和聊天消息
# 跳转场景
tools.gotoScene("主城");
tools.gotoScene("战斗场景");
# 发送消息
tools.postMessage(tools.tr("你获得了新的线索"));
tools.postMessage(tools.tr("服务器公告"), "server");
# 清空消息
tools.clearMessage("private");
tools.clearMessage("all");
# 4. 本地数据和临时数据
# 读取和写入本地数据
tools.setLocalData("last_name", "小明").then(() => {
tools.showToast(tools.tr("已保存"), "success");
});
tools.getLocalData("last_name").then((value) => {
if (!value) return;
player.setString("上次昵称", value);
});
这适合保存本机上的一些小数据。
# 当前运行中的临时数据
tools.session.lastSelectedNPC = "张三";
tools.setTempData("current_page", "商店");
let page = tools.getTempData("current_page");
这类数据只适合当前运行过程里的临时记录。
# 5. 延迟执行
脚本里如果要延迟执行,请用 tools.delay(...):
tools.delay(() => {
tools.showToast(tools.tr("时间到了"));
}, 5);
这里的第二个参数是游戏帧数,不是秒数。
运行时里不能用 setTimeout、setInterval、clearTimeout、clearInterval。
# 6. 联网限制
脚本不允许发起任意外部 HTTP 请求,也不提供直接读写云端 JSON 数据的通用工具。
# 7. AI 相关
# 请求一段 AI 文本
tools.requestAIText("总结玩家刚才的行动").then((text) => {
player.setString("战斗摘要", text);
});
# 让 AI 改属性或对象
tools.requestAIPropertyChanges("根据剧情给玩家增加奖励");
tools.requestAIObjectChanges("生成一位新的旅行商人");
# 控制 AI 智能体
tools.sendAIAgentInput("向导", "继续剧情");
tools.chooseAIAgentOption("向导", 0);
tools.undoAIAgentTurn("向导");
tools.resetAIAgentSession("向导");
# 8. 地图相关
tools.getMapGrid("主地图");
tools.jumpMapGrid("主地图", "城门");
tools.moveMapDirection("主地图", "right");
tools.canMoveMapGrid("主地图", "商店");
tools.resetMap("主地图");
这些方法适合做:
- 地图跳转
- 方向移动
- 检查某个格子能不能过去
# 9. 画面特效
tools.runClientEffect(`
api.createText(payload.text, {
style: {
color: "#fff",
fontSize: "18px"
}
}).place({
target: api.getTarget(),
offsetY: -20
});
`, {
payload: { text: "-10" },
target: "攻击按钮",
label: "damage-text"
});
配套清理方法:
tools.disposeClientEffect("damage-text");
tools.clearClientEffects();
这很适合做:
- 飘字
- 高亮
- 抖动
- 跟手提示
- 悬浮提示
# 10. 前端音效
tools.runClientAudio(`
await audio.resume();
const synth = audio.createSynth({
oscillator: { type: "triangle" },
envelope: { attack: 0.005, decay: 0.08, sustain: 0.02, release: 0.12 }
});
synth.setVolume(-10);
synth.triggerAttackRelease("C5", "16n");
`, {
label: "ui_click_confirm"
});
配套清理方法:
tools.disposeClientAudio("ui_click_confirm");
tools.clearClientAudio();
这很适合做:
- 点击音
- 命中音
- hover 音
- 程序化短旋律
- 循环背景音乐
使用时先记住两点:
- 音符要写完整音高,例如
C4、F#4、Bb3 - 同一时刻要发多个音时,优先用
createPolySynth(),不要把单音createSynth()当和弦器
更完整的写法可以继续看 客户端音效系统。
# 11. 当前脚本附带的信息
有些脚本里会直接带上当前上下文,可以这样拿:
let itemID = tools.kwargs.itemID;
let obj = tools.kwargs.object;
let objID = tools.kwargs.objID;
let effectTargetId = tools.kwargs.effectTargetId;
最常用的是:
tools.kwargs.object:当前对象行对应的对象tools.kwargs.objID:当前对象 ID
# 12. 一个常见例子
下面这个例子会先弹输入框,再把结果写到动态字符串里:
tools.inputText("输入留言").then((value) => {
if (!value) return;
player.setString("玩家留言", value);
tools.showToast("已保存", "success");
});
# 使用时要注意的几件事
- 很多
tools方法不会立刻返回结果,要用then(...)接结果。 - 延迟执行请用
tools.delay(...)。 setTimeout和setInterval在运行时里不能用。- 对象行脚本里,通常会直接用
tools.kwargs.object。 runClientEffect适合做临时视觉效果,不适合拿来保存长期状态。runClientAudio适合做前端声音表现;循环或背景音乐要重视label和清理时机。
# 下一步
如果你还没看过脚本挂载位置,可以继续看后面的脚本系统章节。