音频设计指南
卡里做声音的几种常用做法:自己响起来的背景音乐、跟着场景换的音乐、关键词触发的音效、环境音循环、让 AI 按剧情挑音乐,还有写在界面代码里的点唱机。需要哪个用哪个,可以叠着用。
总览
控制声音的地方有好几个:
| 控制方式 | 在哪设 | 效果 |
|---|---|---|
| 默认播放列表 | 音频 页 → BGM 播放配置 | 玩家一进来音乐就响,按你的音轨轮着放 |
| 条件 BGM | 音频 页 → BGM 播放配置 → 条件 BGM | 变量、关键词、回合数或会话开始满足时换歌,不用行为 |
| 让 AI 挑 | 音轨上的 让 AI 自动放这首 / 让 AI 自动放这个音效 | 你写一句什么时候合适,剧情对上了 AI 就放 |
| 行为 | 播放音乐、播放音效、停止音频 这几个效果 | 声音只是其中一步,和别的效果一起(改变量、打开设定、放一段提示音) |
| 玩家界面的按钮 | 播放音频 / 停止音频 步骤 | 一个按钮放或停一首,不用代码 |
| 界面代码 | api.playAudio() / api.stopAudio() | 完全自己控制,比如做点唱机 |
音频 页从画布上的音频块打开(卡里还没有的话,点 还可以加 里的 + 音频),也可以从 面板 → 音频 打开。
做法 1:自己响起来的背景音乐
第 1 步:加音轨
在 音频 页点 添加音轨,填上:
| 字段 | 值 | 为什么 |
|---|---|---|
| 标题 | 主题曲 | 给你自己认的 |
| 音频源 | 上传 .mp3 或 .ogg,或者 浏览资源 | |
| 音轨类型 | BGM | 背景音乐 |
| 循环播放 | 开 | BGM 一般都循环 |
| 默认音量 | 0.7 | 给音效和环境音留点空间 |
| 淡入 (秒) | 2 | 慢慢进来,不会突然响 |
每条音轨都有一个改不了的 音轨 ID,改音轨名字它也不变。只有写界面代码时才用得到(旁边有 复制 ID)。
不止一首的话就多加几条:探索、战斗、城镇。
第 2 步:设好默认播放列表
点 BGM 播放配置(默认播放列表 + 触发规则):
| 字段 | 值 | 为什么 |
|---|---|---|
| 默认播放列表 | 选上主题曲(还有别的 BGM 音轨) | 列表里的音轨一首接一首放 |
| 播放模式 | 循环、随机 或 顺序 | 循环按顺序一遍遍放;随机打乱顺序 |
| 自动播放 | 开 | 玩家一进卡音乐就响 |
| 等待首条消息 | 关(或开) | 开的话,音乐等玩家发了第一条消息才响 |
| 曲间间隔(秒) | 0(或 2) | 0 就是一首放完直接接下一首 |
效果
玩家打开卡 → 音乐 2 秒内淡入 → 一首放完,下一首接上 → 最后一首放完,列表从头再来。
只有一首?
打开 循环播放,播放列表里只放它一首。
做法 2:跟着变量换的音乐
你要做的东西
玩家从村子走进地牢时,村子的音乐淡出,地牢的音乐淡入。Health 掉到 20 以下时,换成紧张的音乐;回血以后,正常的音乐回来。这些都不用行为。
怎么运作
一条 条件 BGM 规则,条件选 变量条件,每次变化后都会检查一遍。条件成立,就放它的音轨。条件不成立了,由 结束时 决定接下来放什么:返回默认播放列表、返回上一曲,或者指定一首。
一步一步来
先确保有一个文字变量 Location(初始值 village),还有村庄和地牢两条音轨。然后 BGM 播放配置 → 条件 BGM → 添加条件规则:
| 字段 | 值 | 为什么 |
|---|---|---|
| 条件 | 变量条件 | 看一个变量的值来决定 |
| 具体条件 | Location 等于 dungeon | |
| 播放 | 地牢 | |
| 优先级 | 10 | 好几条规则同时满足时,数字大的赢 |
| 淡入 (秒) | 2 | 新的那首淡入 |
| 淡出 (秒) | 2 | 旧的那首淡出 |
| 停止上一首 BGM | 开 | 村子的音乐停掉,不在底下接着放 |
| 结束时 | 返回默认播放列表 | 玩家离开地牢,回到村子的音乐 |
再加一条低血量的规则:
| 字段 | 值 |
|---|---|
| 条件 | 变量条件 |
| 具体条件 | Health 小于 20 |
| 播放 | 危机 |
| 优先级 | 20(比地点那条高,所以在地牢里也是它赢) |
| 停止上一首 BGM | 开 |
| 结束时 | 返回上一曲 |
玩家在探索,播放列表在放
→ Health 从 30 掉到 15
→ 危机规则满足;播放列表淡出,危机淡入
玩家喝了一瓶药,Health 回到 35
→ 规则不满足了;结束时把之前在放的那首放回来好几个条件
一条规则可以有好几个条件。选 满足全部条件,就是「Health 低于 20 而且 Location 等于 dungeon」;选 满足任一条件,就是满足一个就行。
Location 是谁改的?
能改这个变量的都行:AI(照着变量的行为规则)、玩家界面上的按钮(改变量),或者一条行为。音乐规则不管它是怎么变的。按钮的做法见地图与场景切换。
做法 3:关键词触发的音效
你要做的东西
AI 写到「爆炸」时响一声爆炸,玩家说「开门」时吱呀一声。在 音频 页上设好,不用行为。
第 1 步:加音效音轨
| 字段 | 爆炸 | 开门 |
|---|---|---|
| 标题 | 爆炸 | 开门 |
| 音轨类型 | 音效 | 音效 |
| 循环播放 | 关 | 关 |
| 默认音量 | 0.9 | 0.8 |
第 2 步:加条件 BGM 规则
别看名字叫条件 BGM,它能放任何类型的音轨,音效也行。
规则:AI 说了「爆炸」
| 字段 | 值 |
|---|---|
| 条件 | AI 关键词 |
| 关键词 | 爆炸、轰、引爆(每个打完按回车;出现任意一个就触发) |
| 播放 | 爆炸 |
| 停止上一首 BGM | 关,这样音乐在音效底下接着放 |
规则:玩家说了「开门」
| 字段 | 值 |
|---|---|
| 条件 | 玩家关键词 |
| 关键词 | 开门、推门 |
| 播放 | 开门 |
| 停止上一首 BGM | 关 |
仅匹配完整单词 能让 blast 不去匹配 blasted 里面的那一截;两个都想算上的话就别开。
做法 4:环境音循环
你要做的东西
雨声、风声或者酒馆里的人声,在音乐底下轻轻地放。
怎么运作
环境音 是第三种音轨类型。它和 BGM 一起放,所以同一时间可以有一首 BGM 加一条环境音。让它循环,音量调小。
第 1 步:加一条环境音
| 字段 | 值 |
|---|---|
| 标题 | 森林 |
| 音轨类型 | 环境音 |
| 循环播放 | 开 |
| 默认音量 | 0.3(比音乐小) |
| 淡入 (秒) | 3 |
| 淡出 (秒) | 3 |
第 2 步:在对的地方放它
用一条像做法 2 那样的条件 BGM 规则:变量条件 Location 等于 forest,播放森林,结束时 选 返回默认播放列表。
环境音的规则 停止上一首 BGM 一定要关。环境音是叠在音乐上面的;开着的话,雨声一响,音乐就停了。
也可以放在一条本来就管换场景的行为里:加一个 播放音效 或 播放音乐 放环境音,再加一个 停止音频 停掉旧的。
音量参考
BGM 0.5 到 0.7,环境音 0.2 到 0.4,音效 0.7 到 1.0。三层音量错开,就不会互相盖住。
做法 5:让 AI 挑音乐
你要做的东西
剧情走进酒馆时放热闹的酒馆音乐,打起来了放战斗音乐,有人拔剑时响一声刀剑相击,不用每种情况都写一条规则。
怎么运作
打开一条音轨,填 让 AI 自动放这首(BGM 音轨)或者 让 AI 自动放这个音效(音效音轨)。环境音不走这条路,用规则来放(做法 4)。
| 音轨 | 字段 | 写什么 |
|---|---|---|
| 酒馆(BGM) | 什么时候放 | 热闹欢快:酒馆、集市、节庆 |
| 战斗(BGM) | 什么时候放 | 打起来了,或者开始追逐 |
| 刀剑相击(音效) | 什么事发生时放一次 | 有人拔刀,或者刀剑相碰 |
音轨填了这一句以后,会显示 已开启:剧情对上的时候,AI 会自动放这首。每条回复之后,卡会拿剧情去对这些说明,然后换音乐或者放音效。这是 智能追踪(在 卡的设置 里)的一部分,所以不管玩家用哪个模型,效果都一样。不填的话,这条音轨只按播放列表和你的规则放。
只要有一条音轨填了这一句,BGM 播放配置 里就会问你几个问题,关于 AI 挑的和你的规则怎么相处。每个问题只在真的会碰上的时候才出现:
- AI 想换歌,但条件规则正在放它自己的曲子时:规则优先(规则的曲子照放,AI 等着)或 AI 优先
- AI 选的曲子放完以后:一直循环,直到 AI 换歌或交回;或者只放一遍,放完回到默认播放列表
- AI 放音效的时候,把背景音乐压低,放完恢复
不让 AI 碰某条音轨
在音轨上关掉 允许 AI 控制,AI 就不能放、停或者调这条音轨。你的播放列表、规则、行为和界面代码照样能控制它。适合菜单音乐,或者由你的代码管的音轨。
音频指令
写故事的 AI 也可以直接在回复里写音频指令。引擎会自己把它能控制的音轨列表(ID、类型、标题)和指令格式告诉 AI,所以你不用写一条设定把它们列出来。指令会在玩家看到之前从正文里拿掉:
你推开酒馆沉重的木门,一股暖气扑面而来。[audio: <酒馆音轨 ID> crossfade 2]| 指令 | 效果 |
|---|---|
[audio: trackId play] | 播放(用音轨自己的 淡入) |
[audio: trackId stop] | 停止(用音轨自己的 淡出) |
[audio: trackId crossfade 2] | 停掉 AI 控制的其它 BGM,这首用 2 秒淡入 |
[audio: trackId volume 0.5] | 改音量 |
[audio: trackId play chain:nextTrackId] | 这首放完,接着放下一首 |
chain 适合做开场:先响一声号角,放完接上战斗音乐。
AI 不一定每次都记得写指令,聊得久了尤其如此。上面的说明(什么时候放)和条件 BGM 规则才是靠得住的办法,指令只是锦上添花。
做法 6:按钮放声音
不用代码:玩家界面的按钮
在 玩家界面 里选中一个 按钮,在 按下时,依次做 里加 播放音频(选一条音轨)或者 停止音频(停一条,或者 全部停止)。行为的 播放音乐、播放音效、停止音频 这几个效果在画布上也能做到同样的事,按钮可以用 让一条行为生效 去跑那条行为。
写代码的做法:点唱机
useYumina() 给界面代码提供了这些音频调用(时长单位都是 秒):
api.playAudio(trackId, opts):放一条音轨。opts里可以有volume(0 到 1)、fadeDuration、chainTo、maxDuration、duckBgm和loop(这一次播放时覆盖音轨自己的循环设置)api.stopAudio(trackId?, fadeDuration?):停一条音轨,不写 ID 就全部停掉。它会丢掉播放进度;想接着放的话用pauseAudioapi.pauseAudio(trackId)/api.resumeAudio(trackId):原地暂停、继续api.onAudioEnded(cb):不循环的音轨放完时调用cb(trackId),返回一个取消订阅的函数
playAudio 不会自己停掉别的音乐。在几首 BGM 之间切换时,先把其它的停掉。
在 音频 页复制每条音轨的 音轨 ID,然后在 面板 → 前端代码 → index.tsx 里,把默认的 return <Chat /> 换掉:
export default function MyWorld() {
const api = useYumina();
const msgs = api.messages || [];
// 把音频页上的音轨 ID 粘贴进来
const tracks = [
{ id: "PASTE-JAZZ-TRACK-ID", label: "爵士", color: "#7c3aed" },
{ id: "PASTE-ROCK-TRACK-ID", label: "摇滚", color: "#dc2626" },
{ id: "PASTE-CLASSICAL-TRACK-ID", label: "古典", color: "#0891b2" },
];
const [nowPlaying, setNowPlaying] = React.useState("");
const play = (t) => {
// 先把点唱机里其它的曲子淡出,再把这首淡入
tracks.forEach((other) => {
if (other.id !== t.id) api.stopAudio(other.id, 1.5);
});
api.playAudio(t.id, { fadeDuration: 1.5 });
setNowPlaying(t.id);
};
const stop = () => {
api.stopAudio();
setNowPlaying("");
};
return (
<Chat renderBubble={(msg) => {
const isLastMsg = msg.messageIndex === msgs.length - 1;
return (
<div>
<div
style={{ color: "#e2e8f0", lineHeight: 1.7 }}
dangerouslySetInnerHTML={{ __html: msg.contentHtml }}
/>
{isLastMsg && (
<div style={{
marginTop: "12px",
padding: "12px",
background: "rgba(30,41,59,0.5)",
borderRadius: "8px",
border: "1px solid #334155",
}}>
<div style={{ fontSize: "12px", color: "#94a3b8", marginBottom: "8px" }}>
点唱机
</div>
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
{tracks.map((t) => (
<button
key={t.id}
onClick={() => play(t)}
style={{
padding: "8px 16px",
background: t.color,
border: "none",
borderRadius: "6px",
color: "#fff",
fontSize: "13px",
cursor: "pointer",
}}
>
{nowPlaying === t.id ? "♪ " + t.label : t.label}
</button>
))}
<button
onClick={stop}
style={{
padding: "8px 16px",
background: "#475569",
border: "none",
borderRadius: "6px",
color: "#e2e8f0",
fontSize: "13px",
cursor: "pointer",
}}
>
停止
</button>
</div>
</div>
)}
</div>
);
}} />
);
}代码做了什么:
<Chat renderBubble={...} />保留平台自带的消息列表、输入框和滚动,你只负责画每个气泡play()用 1.5 秒把点唱机里其它的曲子淡出,再把选中的那首淡入api.stopAudio()不带参数,就是把正在放的全部停掉nowPlaying只存在界面里,页面一刷新就没了。想把选择记进存档,就用api.setVariable("Now playing", t.id)存进一个变量,再从api.variables["Now playing"]读回来- 点唱机只出现在最后一条消息下面
想按自己的播放列表一首首往下放,就监听每首放完的时候:
React.useEffect(() => api.onAudioEnded((endedId) => {
if (endedId === nowPlaying) playNext();
}), [nowPlaying]);(只有不循环的音轨才会触发这个。)
速查
音轨类型
| 类型 | 用途 | 常用设置 |
|---|---|---|
| BGM | 背景音乐 | 循环开,音量 0.5 到 0.7 |
| 音效 | 放一次的音效 | 循环关,音量 0.7 到 1.0 |
| 环境音 | 垫在音乐底下的环境循环 | 循环开,音量 0.2 到 0.4 |
什么情况用什么
| 你想要 | 办法 | 在哪 |
|---|---|---|
| 玩家一进来就有音乐 | 默认播放列表 + 自动播放 | 音频 → BGM 播放配置 |
| 按变量换歌 | 条件 BGM,变量条件 | 音频 → 条件 BGM |
| AI 写到某个词时放音效 | 条件 BGM,AI 关键词 | 音频 → 条件 BGM |
| 玩家写到某个词时放音效 | 条件 BGM,玩家关键词 | 音频 → 条件 BGM |
| 到某个回合换歌 | 条件 BGM,回合计数(在第几回合 或 每 N 回合) | 音频 → 条件 BGM |
| 每局开始都放固定的一首 | 条件 BGM,会话开始 | 音频 → 条件 BGM |
| 让剧情来决定 | 音轨上的 什么时候放 / 什么事发生时放一次 | 音轨设置 |
| 不让 AI 碰某条音轨 | 关掉 允许 AI 控制 | 音轨设置 |
| 声音作为行为的一部分 | 播放音乐、播放音效、停止音频 | 行为效果 |
| 一个按钮放一首 | 播放音频 / 停止音频 步骤 | 玩家界面 |
| 用代码完全控制 | api.playAudio() / api.stopAudio() | 面板 → 前端代码 |
条件 BGM 的字段
| 字段 | 做什么 |
|---|---|
| 条件 | 变量条件、AI 关键词、玩家关键词、回合计数、会话开始 |
| 播放 | 要放的音轨 |
| 优先级 | 好几条规则同时满足时,大的赢 |
| 淡入 (秒) / 淡出 (秒) | 这条规则切换时的淡入淡出 |
| 停止上一首 BGM | 音乐换音乐时开;音效和环境音关 |
| 结束时 | 条件不成立后放什么:返回默认播放列表、返回上一曲,或者指定一首 |
界面代码的音频 API
| 方法 | 说明 |
|---|---|
api.playAudio(trackId, opts) | 放一条音轨。opts:volume、fadeDuration(秒)、chainTo、maxDuration、duckBgm、loop |
api.stopAudio(trackId?, fadeDuration?) | 停一条音轨,不写 ID 就全部停 |
api.pauseAudio(trackId) / api.resumeAudio(trackId) | 原地暂停、继续 |
api.onAudioEnded(cb) | 不循环的音轨放完时跑 cb(trackId);返回一个取消订阅的函数 |
api.setAudioVolume("bgm" | "sfx", v) / api.getAudioVolume(...) | 玩家自己的音乐或音效音量 |
常见问题
| 现象 | 可能的原因 | 怎么改 |
|---|---|---|
| 完全没声音 | 玩家和页面互动之前,浏览器不让放声音 | 打开 等待首条消息,或者让第一次点击来启动音乐 |
| 两首音乐同时在放 | 某条规则或行为开了一首 BGM,但没停掉旧的 | 音乐规则打开 停止上一首 BGM;行为里加一个 停止音频 停旧的;代码里在 playAudio 之前先停掉其它的 |
| 音效一响音乐就断 | 音效规则开着 停止上一首 BGM | 音效和环境音的规则把它关掉 |
| AI 从来不挑音轨 | 音轨没填那一句,或者 智能追踪 关着 | 填好 什么时候放,再到 卡的设置 里看看 智能追踪 |
| AI 放了不该放的音轨 | 允许 AI 控制 开着 | 把那条音轨的关掉 |
| 环境音太吵 | 音量太大 | 环境音 0.2 到 0.4,音乐 0.5 到 0.7 |
| 变量规则一直不触发 | 条件比较的值类型不对 | 数字要用数字的比较;文字要一字不差 |
| 规则之间打架 | 优先级一样 | 给规则设不同的 优先级,大的赢 |
这是食谱 #14:音频设计指南
先从播放列表做起。想让音乐跟着游戏走,加条件 BGM 规则;想让剧情来挑,给音轨写一句说明;只有点唱机这类东西才需要代码。想更深入了解音频系统怎么决定放什么,看进阶:音频设计。
