Skip to content

AI指令与宏

AI通过指令改变游戏世界,宏让你的词条文本活起来——这是Yumina世界创作的两大核心机制。


简单版

指令是什么?

你可以把指令理解成AI给游戏引擎下的"命令"。当AI写完一段故事叙述之后,它会在回复的末尾悄悄塞上几条方括号包裹的指令,告诉引擎:"嘿,这个变量该变了。"

格式长这样:[变量名: 操作 值]

最常用的三种场景:

  • 扣血[health: -10] — 玩家被砍了一刀,生命值减10
  • 加金币[gold: +50] — 打败怪物,捡到50金
  • 切场景[location: set "森林"] — 角色走进了森林

你不需要写任何代码。只要你在世界编辑器里定义好变量(比如 healthgoldlocation),AI就会在合适的时机自动使用指令来更新它们。引擎会把指令从AI的回复里"摘掉",玩家看到的只有干净的故事文本,状态面板上的数字却已经悄悄变了。

宏是什么?

宏是你在词条里写的占位符,引擎在发给AI之前会自动替换成真实内容。最常用的两个:

  • {{char}} — 自动替换成当前角色的名字
  • {{user}} — 自动替换成玩家的名字

比如你在角色词条里写"{{char}}注意到{{user}}走了进来",如果角色叫小月、玩家叫阿凯,AI实际收到的就是"小月注意到阿凯走了进来"。

这样一来,你的词条就变成了"模板"——换个角色名字,整套词条自动适配,不用一个个改。


详细版

指令完全手册

基本语法

[variableId: operation value]

方括号里写三样东西:变量ID、操作、值。变量ID就是你在编辑器里定义的那个标识符。

所有操作一览表

操作完整写法简写示例说明
赋值[var: set value][var: value][location: set "城堡"][location: "城堡"]直接把变量设为某个值。省略 set 也行,引擎会自动理解为隐式赋值
加法[var: add N][var: +N][gold: add 50][gold: +50]给数值变量加上 N
减法[var: subtract N][var: -N][health: subtract 10][health: -10]给数值变量减去 N
乘法[var: multiply N][var: *N][damage: multiply 2][damage: *2]把数值变量乘以 N
开关[var: toggle][hasKey: toggle]布尔值翻转,true变false,false变true。不需要填值
追加[var: append "text"][log: append "找到了钥匙"]在字符串末尾追加文本
合并[var: merge {json}][stats: merge {"level": 2}]把一个JSON对象合并进变量(适合复杂状态)
推入[var: push value][inventory: push "magic_sword"]往数组变量里推入一个元素
删除[var: delete key][stats: delete "buff"]从对象变量里删除某个键

值的写法

  • 数字:直接写,比如 103.5
  • 字符串:用双引号包起来,比如 "森林""magic_sword"
  • 布尔值truefalse
  • JSON:直接写JSON对象或数组,比如 {"level": 2}["a", "b"]

嵌套路径

变量ID支持用点号表示嵌套路径,就像访问JavaScript对象属性一样:

[gameState.factions.fire.affinity: +5]

这条指令的意思是:在 gameState 这个变量里,找到 factions 下面的 fire 下面的 affinity,给它加5。很适合管理复杂的游戏状态树。

音频指令

除了状态变量,还有一套专门控制音频的指令语法:

[audio: trackId action]
动作示例说明
play[audio: battle_bgm play]播放音轨
stop[audio: tavern_ambient stop]停止音轨
crossfade[audio: forest_bgm crossfade 2.0]渐变切换,后面的数字是过渡秒数
volume[audio: rain volume 0.5]调节音量(0到1之间)

还支持chain语法,用来串联音轨:

[audio: intro_bgm play chain:loop_bgm]

意思是:播放 intro_bgm,播完之后自动接上 loop_bgm

JSON Patch 兼容格式

如果你的卡片是从SillyTavern迁移过来的,可能用的是XML包裹的JSON Patch格式。Yumina也能识别:

xml
<UpdateVariable>
<JSONPatch>
[
  {"op": "replace", "path": "/health/current", "value": 80},
  {"op": "delta", "path": "/gold", "value": 50},
  {"op": "insert", "path": "/inventory/newItem", "value": "圣剑"},
  {"op": "remove", "path": "/debuffs/poison"}
]
</JSONPatch>
</UpdateVariable>

支持四种JSON Patch操作:replace(赋值)、delta(加减,正数加负数减)、insert(新增键值)、remove(删除键值)。路径里的斜杠会被自动转换成点号。

解析流程

AI的回复从发出到玩家看到,中间经过了这么几步:

  1. 剥离思考标签 — 有些模型(比如Gemini)会输出 <thinking>...</thinking> 之类的内部推理,引擎先把它们去掉
  2. 提取JSON Patch — 扫描 <UpdateVariable> 块,转换成标准指令
  3. 提取音频指令 — 扫描 [audio: ...] 格式
  4. 提取JSON指令 — 扫描带JSON值的 [var: merge/push/set/delete {...}] 格式
  5. 提取标准指令 — 扫描 [var: op value] 格式
  6. 清理文本 — 把所有指令从回复里去掉,整理多余空行,返回干净文本 + 效果列表

玩家看到的是干净文本,引擎拿到效果列表去更新游戏状态。各司其职,互不干扰。


宏完全手册

完整宏列表

说明示例输出
{{char}}当前角色名小月
{{user}}玩家名阿凯
{{turnCount}}当前回合数42
{{random::a::b::c}}随机选一个(每次展开结果可能不同)b
{{pick::a::b::c}}稳定哈希选取(同一回合、同一位置结果始终不变)a
{{roll::NdS+M}}掷骰子,N个S面骰加修正值M{{roll::2d6+1}} 可能输出 8
{{time}}当前时间(HH:MM格式)14:30
{{date}}当前日期(本地格式)2026/3/23
{{weekday}}当前星期几(英文)Sunday
{{isodate}}ISO日期格式2026-03-23
{{isotime}}ISO时间格式14:30:00
{{idle}}玩家距上次发消息过了多久5 minutes
{{lastMessage}}上一条消息的内容(上一条消息的完整文本)
{{lastUserMessage}}玩家的上一条消息(玩家最后说的话)
{{lastCharMessage}}角色的上一条消息(角色最后说的话)
{{model}}当前使用的AI模型名claude-opus-4-6
{{// 注释内容}}注释,展开后消失(不会发送给AI)(空字符串)
{{trim}}吃掉前后空白(用来精确控制排版)(消除相邻空白)

randompick 的区别

这两个很容易混淆,打个比方:

  • {{random::猫::狗::兔子}} 就像每次都重新抽签,同一条词条被展开两次,可能一次是"猫"一次是"狗"
  • {{pick::猫::狗::兔子}} 就像在签上刻了你的名字,同一回合里无论展开多少次,结果都一样。但下一回合可能就变了

pick 背后用的是稳定哈希算法,输入是宏在模板里的位置序号和当前回合数。所以同一个词条里第一个 pick 和第二个 pick 的结果通常不同(位置不同),但同一个 pick 在同一回合里结果恒定。

变量回落

如果宏名不匹配任何内置宏,但恰好和某个游戏变量的ID一样,引擎会用那个变量的当前值来替换。比如你定义了一个叫 mood 的变量,当前值是"开心",那 {{mood}} 就会被替换成"开心"。

如果既不是内置宏、也不是变量名,引擎会原样保留 {{xxx}},不会报错也不会崩溃。


实用例子

例子1:战斗场景

假设你定义了三个变量:health(玩家血量,初始100)、enemy_hp(敌人血量,初始80)、location(当前地点)。

AI可能会这样回复:

你挥剑砍向哥布林,剑刃划过它的肩膀,绿色的血溅了出来。哥布林痛叫一声,举起木棒反击,狠狠敲在你的手臂上,一阵剧痛传来。

[health: -15] [enemy_hp: -30]

玩家看到的是纯粹的故事文本,但状态面板上,血量从100变成了85,哥布林的血量从80降到了50。引擎在后台默默完成了这一切。

例子2:物品获取与交易

变量:inventory(数组类型)、gold(数值类型,当前500)。

铁匠把一柄泛着蓝光的长剑递到你面前:"这是我的得意之作,附魔霜刃,一百金币。"你掏出钱袋付了钱,接过剑的瞬间感到一股寒意从掌心蔓延开来。

[inventory: push "frost_blade"] [gold: -100]

玩家的背包里多了一把霜刃剑,钱袋少了100金。

例子3:宏在词条中的应用

在角色人设词条里这样写:

{{char}}是一个性格冷淡的剑士,但对{{user}}有一种说不清的好感。
当回合数超过20时(当前回合:{{turnCount}}),{{char}}会逐渐敞开心扉。

如果角色叫"叶霜"、玩家叫"旅人"、当前第25回合,AI实际收到的就是:

叶霜是一个性格冷淡的剑士,但对旅人有一种说不清的好感。
当回合数超过20时(当前回合:25),叶霜会逐渐敞开心扉。

例子4:用 random 增加变化

在世界观词条里加点随机元素:

今天的天气是{{random::晴朗::阴沉::微雨::大雾}},街上{{random::行人稀少::熙熙攘攘::偶有行人经过}}。

每次这条词条被展开,天气和街景都可能不同,让故事更有变化感。

例子5:用 roll 做技能检定

{{char}}尝试开锁。掷骰结果:{{roll::1d20+2}}(需要12以上才能成功)。

AI收到的可能是"掷骰结果:17(需要12以上才能成功)",然后它就可以根据这个结果来叙述开锁成功还是失败了。把判定权从AI的"自由发挥"交给骰子,更公平。