Skip to content

音频设计指南 ​

卡里做声音的几种常用做法:自己响起来的背景音乐、跟着场景换的音乐、关键词触发的音效、环境音循环、让 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.90.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 就全部停掉。它会丢掉播放进度;想接着放的话用 pauseAudio
  • api.pauseAudio(trackId) / api.resumeAudio(trackId):原地暂停、继续
  • api.onAudioEnded(cb):不循环的音轨放完时调用 cb(trackId),返回一个取消订阅的函数

playAudio 不会自己停掉别的音乐。在几首 BGM 之间切换时,先把其它的停掉。

在 音频 页复制每条音轨的 音轨 ID,然后在 面板 → 前端代码 → index.tsx 里,把默认的 return <Chat /> 换掉:

tsx
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"] 读回来
  • 点唱机只出现在最后一条消息下面

想按自己的播放列表一首首往下放,就监听每首放完的时候:

tsx
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 规则;想让剧情来挑,给音轨写一句说明;只有点唱机这类东西才需要代码。想更深入了解音频系统怎么决定放什么,看进阶:音频设计。