第 15 课
播放背景音乐
本课从声音输出链路开始,依次完成音乐资源导入、Audio Source 设置、Singleton 单例基类、AudioManager 音乐管理器和 VideoController 联动。完成后,场景启动会播放背景音乐,视频播放期间只保留视频声音,视频停止、自然结束或失败后音乐都会恢复。
让背景音乐与视频声音自然衔接
配置 Unity 背景音乐播放链路,编写单例音乐管理器,并让视频开始时暂停音乐、结束后从原位置继续。
学完你将能够
学习目标
- 区分 Audio Clip、Audio Source 和 Audio Listener 的职责。
- 把背景音乐导入 Unity,并创建清楚命名的声音播放器对象。
- 正确设置自动播放、循环、音量和二维声音。
- 解释背景音乐为什么不应随学生的位置和朝向变化。
- 说明场景中为什么通常只启用一个 Audio Listener。
- 读懂 Singleton<T> 的泛型、静态实例和 Instance 属性。
- 说明 DontDestroyOnLoad 与重复实例处理的作用。
- 编写 AudioManager,并正确取得 Audio Source 引用。
- 区分 Play、Pause、UnPause 和 Stop 对播放位置的影响。
- 在 VideoController 的开始、停止和错误路径中控制背景音乐。
- 根据无声、无法循环、不能恢复和重复播放等现象定位设置。
- 使用精简提示词让人工智能编程助手完成本课脚本。
开始操作前
准备工作
- 确认第 13 课的视频画面和声音能够正常播放。
- 确认第 14 课的 VideoController.cs 可以控制视频开始与停止。
- 确认 XR Origin 的 Main Camera 上保留了 Audio Listener。
- 准备耳机或把设备音量调到较低位置,避免首次运行时声音过大。
- 打开 Project、Hierarchy、Inspector、Game 和 Console 窗口。
- 保存 MainScene,并等待 Unity 完成现有脚本编译。
本课学习路线
按照学习模块逐步完成
每个模块都包含理解、跟随操作和独立实践。先分清声音资源、播放器和接收者
理解 Unity 的声音输出链路
音频文件不会直接把声音送到耳机。Unity 需要先把文件导入为音频片段,再由声音播放器播放,最后由代表学生听声位置的组件接收。声音链路中的三个核心对象
第一次出现的英文名称都是 Unity 中的资源或组件类型。
左右滑动表格,可查看完整内容
| Unity 名称 | 中文理解 | 主要职责 |
|---|---|---|
| Audio Clip | 音频片段 | 保存导入 Unity 的声音内容 |
| Audio Source | 声音播放器 | 决定播放、暂停、循环、音量和空间效果 |
| Audio Listener | 声音接收者 | 模拟学生的耳朵,接收场景中的声音 |
先阅读上半部分的声音输出链路,再阅读下半部分的视频联动:视频开始时暂停背景音乐,视频停止后从暂停位置继续。
手机上可左右滑动;点击图片可以查看完整图Audio Source 像音箱,Audio Listener 像耳朵
一个场景可以有多个 Audio Source,例如背景音乐、环境声音、按钮提示音和角色声音都可以使用不同的声音播放器。
一个场景通常只启用一个 Audio Listener,因为系统需要一个明确的听声位置。在虚拟现实场景中,它通常位于 XR Origin 的 Main Camera 上,随头显一起移动。
二维声音与三维声音
这里的二维和三维描述声音是否受空间位置影响,不表示音频有几个声道。
左右滑动表格,可查看完整内容
| 声音类型 | 距离和方向影响 | 适合内容 |
|---|---|---|
| 二维声音 | 不随声源距离和朝向改变 | 背景音乐、界面提示音 |
| 三维声音 | 受位置、距离和方向影响 | 脚步声、机器声、空间讲解声 |
找到学生的听声位置
在 Hierarchy 中展开 XR Origin,找到 Main Camera 及其 Audio Listener。
- 能够指出 Audio Listener 位于哪一个相机对象上。
- 确认场景中没有第二个启用的 Audio Listener。
- 能够完整说出“BGM → Audio Clip → Audio Source → Audio Listener → 听到声音”。
- 能够解释背景音乐为什么适合二维声音。
从音乐文件建立场景对象
导入背景音乐并创建声音播放器
把音乐拖进 Project 只完成资源导入;把它继续拖到 Hierarchy,Unity 才会创建带有 Audio Source 的场景对象。下载本课背景音乐
下载后把 BGM.mp3 放入 Assets/_Audio。导入前先降低设备音量,导入后可在 Inspector 底部试听。
- MP3 音频文件
- 双声道背景音乐
- 用于本课自动循环播放练习
创建 _Audio 并试听 BGM
- 01
在 Assets 下新建文件夹,并命名为 _Audio。
- 02
把下载的 BGM.mp3 拖入 _Audio。
- 03
等待 Unity 完成导入后,单击 BGM 资源。
- 04
在 Inspector 底部找到音频预览区域并试听。
- 05
确认左右声道都有声音,音量适中且没有明显破音。
把 BGM 拖到场景根层级
- 01
从 Project 中把 BGM 拖到 Hierarchy 的空白区域。
- 02
选中 Unity 自动创建的新对象。
- 03
确认对象上已经出现 Audio Source。
- 04
确认 Audio Clip 已经指定为 BGM。
- 05
把游戏对象重命名为 BGAudioSource。

重点观察三个位置:Project 中的 _Audio/BGM、Hierarchy 中新建的声音对象,以及 Inspector 中自动指定的 Audio Clip。
点击图片可以查看完整截图一次拖拽完成了两个动作
Unity 新建了一个游戏对象,又为它添加 Audio Source,并把 BGM 指定给 Audio Clip。这与手动创建空对象、添加组件和指定音频的结果相同。
BGAudioSource 比 BGM 更适合作为场景对象名称,因为它直接说明这个对象负责播放背景音乐。
建立背景音乐对象
独立完成音乐导入、试听、拖入场景和对象重命名。
- Assets/_Audio 中存在 BGM.mp3。
- Hierarchy 根层级存在 BGAudioSource。
- BGAudioSource 上具有 Audio Source。
- Audio Clip 指向 BGM。
让背景音乐稳定覆盖整个体验过程
设置自动、循环和二维播放
背景音乐应该在进入场景后自动开始,播放结束后继续循环,并且不随学生移动或转头发生明显变化。Audio Source 的关键设置
选中 BGAudioSource,在 Audio Source 中完成下列设置。
左右滑动表格,可查看完整内容
| 属性 | 本课设置 | 作用 |
|---|---|---|
| Audio Clip | BGM | 指定播放的音乐资源 |
| Mute | 关闭 | 保持声音正常输出 |
| Play On Awake | 开启 | 对象启用后自动开始播放 |
| Loop | 开启 | 播放结束后从头继续 |
| Priority | 128 | 保持本课默认声音优先级 |
| Volume | 0.8 | 使用适中的相对音量 |
| Pitch | 1 | 保持正常音高与速度 |
| Stereo Pan | 0 | 左右声道保持居中 |
| Spatial Blend | 0(2D) | 不受位置、距离和方向影响 |

重点查看 Play On Awake、Loop、Volume 0.8 和 Spatial Blend 0。背景音乐不需要使用下面的三维距离设置。
点击图片可以查看完整截图四个设置分别解决什么问题
运行现象异常时,可以根据现象返回对应属性。
左右滑动表格,可查看完整内容
| 设置 | 开启或设定后 | 没有正确设置时 |
|---|---|---|
| Play On Awake | 进入场景后自动播放 | 必须由脚本或按钮主动开始 |
| Loop | 播放结束后继续 | 音乐只播放一次 |
| Volume 0.8 | 保持适中的背景音量 | 可能过响或过轻 |
| Spatial Blend 0 | 移动和转头时音乐稳定 | 音乐可能随距离变小或左右偏移 |
确认背景音乐的基础效果
- 01
保存 MainScene 并进入运行状态。
- 02
不点击任何按钮,确认背景音乐自动开始。
- 03
持续聆听,确认音乐结束后会继续循环。
- 04
在模拟器中移动相机或转动视角。
- 05
确认音乐的音量和左右方向保持稳定。
- 06
退出运行状态,并确认刚才的设置仍然保留。
完成背景音乐播放设置
把 BGAudioSource 设置为自动、循环、适中音量的二维声音。
- Play On Awake 和 Loop 已开启。
- Volume 为 0.8,Pitch 为 1。
- Stereo Pan 与 Spatial Blend 都为 0。
- 运行后无需点击就能听到音乐。
- 移动和转头不会明显改变音乐。
让多个脚本都能找到同一个音乐管理器
用 Singleton 建立唯一管理入口
Audio Source 已经能独立播放,但视频开始、停止、自然结束和播放失败都需要通知音乐。Singleton 单例基类负责提供一个统一入口,并避免切换场景后出现多个背景音乐对象。下载 Singleton 单例基类
把 Singleton.cs 放入 Assets/_Scripts。下面三段代码按顺序连接后,与下载文件完全一致。
- 通用泛型单例基类
- 提供静态 Instance 访问入口
- 保留第一个实例并移除重复对象
单例只适合具有全局职责的对象
单例表示运行过程中只保留一个主要实例,并提供统一访问入口。本课可以通过 AudioManager.Instance 找到音乐管理器,不必让每个控制脚本都保存一份引用。
背景音乐、场景切换和总体设置具有全局性质,适合使用单例;只属于某个文物或按钮的局部组件仍然应使用直接引用。
先读懂继承、重写与父类调用
Singleton 是 AudioManager 的父类。子类可以复用父类能力,也可以在规定位置加入自己的初始化。
左右滑动表格,可查看完整内容
| 写法 | 含义 | 本课中的作用 |
|---|---|---|
| : Singleton<AudioManager> | AudioManager 继承这个父类 | 直接获得 Instance 和唯一实例处理能力 |
| protected | 只允许当前类和继承它的子类使用 | AudioManager 可以重写 Awake,其他普通脚本不能随意调用它 |
| virtual | 父类先提供一个可运行的方法,并允许子类重写 | Singleton 允许管理器扩展 Awake |
| override | 子类明确重写父类的 virtual 方法 | AudioManager 在 Awake 中补充音乐源初始化 |
| base.Awake() | base 表示父类,先调用父类版本的方法 | 先登记单例,再执行 AudioManager 自己的初始化 |
| this / this as T | this 是当前组件;as T 尝试把它看作 T 类型,失败时得到空值 | 把当前 Singleton 子类登记为对应管理器的正式实例 |
using UnityEngine;
public abstract class Singleton<T> : MonoBehaviour where T : Component
{
// 保存当前类型唯一的正式实例。
private static T instance;
T 是类型占位符,where T : Component 限定它必须是 Unity 组件。static instance 属于类型本身,用来保存唯一实例。
泛型类声明中的关键词
当 AudioManager 继承 Singleton<AudioManager> 时,T 就代表 AudioManager。
左右滑动表格,可查看完整内容
| 代码 | 在本课中的含义 |
|---|---|
| abstract | 这是供其他管理器继承的模板,不能直接创建普通 Singleton 组件 |
| T | 类型占位符;写成 Singleton<AudioManager> 时,T 就是 AudioManager |
| MonoBehaviour | 最终能够作为 Unity 组件工作 |
| where T : Component | T 必须是 Unity 组件或其子类 |
| static instance | 字段属于管理器类型本身,不属于某一个对象;同一种管理器共用这一份实例记录 |
| public static T Instance | 不先取得场景对象,也能用 AudioManager.Instance 进入 get 取回正式实例 |
/// <summary>取得当前类型的唯一实例。</summary>
public static T Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<T>();
if (instance == null)
{
GameObject obj = new GameObject();
obj.name = typeof(T).Name;
instance = obj.AddComponent<T>();
}
}
return instance;
}
}
读取 Instance 时先返回已有实例;没有实例就先从场景寻找,仍然找不到才创建新对象和组件。
读取属性时会执行 get 中的代码
AudioManager.Instance 看起来像读取一个值,实际会进入 Instance 属性的 get 大括号。程序先寻找可用实例,最后通过 return instance 把结果交还给调用它的代码。
这里的 return 与第 14 课用于提前结束方法的 return 使用同一个关键词,但作用取决于方法是否需要返回结果:Instance 必须返回一个 T 类型的组件引用。
new GameObject() 表示创建一个新的场景对象;typeof(T).Name 取得 T 的类型名称,用它给新对象命名;AddComponent<T>() 再把对应管理器组件添加到对象上。这条保护路径只在场景完全找不到管理器时执行。
读取 Instance 时的执行顺序
以 AudioManager.Instance.ToggleBGMusic(false) 为例,必须先取得实例,才能继续调用音乐方法。
左右滑动表格,可查看完整内容
| 顺序 | 执行动作 | 得到的结果 |
|---|---|---|
| 1 | 读取静态属性 AudioManager.Instance | 程序进入 Instance 的 get |
| 2 | 判断 instance 是否为空 | 已有值就跳过寻找和创建 |
| 3 | 为空时执行 FindObjectOfType<T>() | 优先取得场景中已经配置好的 AudioManager |
| 4 | 场景仍找不到时执行 new GameObject() | 创建一个保护性的空对象 |
| 5 | 用 typeof(T).Name 命名,再执行 AddComponent<T>() | 把 AudioManager 组件添加到新对象并保存引用 |
| 6 | 执行 return instance | 把正式实例交还给调用处 |
| 7 | 继续执行 .ToggleBGMusic(false) | 进入音乐管理器的方法并选择暂停分支 |
/// <summary>保留第一个实例,并移除后来出现的重复对象。</summary>
protected virtual void Awake()
{
if (instance == null)
{
instance = this as T;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}第一个实例通过 DontDestroyOnLoad 跨场景保留,后来出现的重复对象会被移除。
Awake 中的三个关键动作
BGAudioSource 应放在场景根层级,让管理器与声音播放器一起保留。
左右滑动表格,可查看完整内容
| 代码 | 作用 |
|---|---|
| this as T | 把当前组件转换为 T 类型后保存;本课的 T 是 AudioManager |
| DontDestroyOnLoad | 切换场景时保留当前根对象 |
| Destroy(gameObject) | 移除后来出现的重复音乐对象 |
读懂 Singleton 的两条职责
在完整脚本中分别标出 Instance 属性和 Awake 方法。
- 能够说明 Instance 负责寻找或取得实例。
- 能够说明 Awake 负责确认唯一实例。
- 能够解释为什么 BGAudioSource 应位于根层级。
- 能够解释自动创建为什么不能替代 Audio Clip 和播放参数设置。
集中处理背景音乐的暂停与继续
编写 AudioManager 音乐管理器
AudioManager 只负责一类工作:保存背景音乐的 Audio Source,并接收 true 或 false 指令,让音乐继续或暂停。下载完整 AudioManager 脚本
把 AudioManager.cs 放入 Assets/_Scripts。下面四段主线代码按顺序连接后,与下载文件完全一致。
- 继承 Singleton<AudioManager>
- 自动取得同对象上的 Audio Source
- 使用 Pause 与 UnPause 保留播放位置
建立 AudioManager.cs
- 01
打开 Assets/_Scripts。
- 02
新建 C# Script,并命名为 AudioManager。
- 03
确认文件名与类名都为 AudioManager。
- 04
打开脚本,删除默认的空 Update。
using UnityEngine;
[DisallowMultipleComponent]
[RequireComponent(typeof(AudioSource))]
public class AudioManager : Singleton<AudioManager>
{
[SerializeField, Tooltip("用于播放背景音乐的 Audio Source 组件。")]
private AudioSource musicSource;
DisallowMultipleComponent 避免同对象重复添加管理器;RequireComponent 保证同对象具有 Audio Source;musicSource 保存实际声音播放器。
类声明与字段的职责
RequireComponent 只能保证存在 Audio Source,不能替你指定 BGM 或设置循环和音量。
左右滑动表格,可查看完整内容
| 代码 | 作用 |
|---|---|
| DisallowMultipleComponent | 同一游戏对象上只允许一个 AudioManager |
| RequireComponent(AudioSource) | 缺少 Audio Source 时由 Unity 自动添加 |
| Singleton<AudioManager> | 获得统一访问和重复实例处理能力 |
| SerializeField | 让私有 musicSource 显示在 Inspector 中 |
| Tooltip | 鼠标停留时说明字段用途 |
protected override void Awake()
{
base.Awake();
// 如果当前对象是重复实例,基类会将它销毁,此处不再继续初始化。
if (Instance != this)
{
return;
}
// AudioManager 与 Audio Source 位于同一对象时,可以自动取得引用。
if (musicSource == null)
{
musicSource = GetComponent<AudioSource>();
}
}
先调用 base.Awake 执行单例规则;只有当前对象是正式实例时,才继续从同对象取得 Audio Source。
为什么 base.Awake 必须在前面
父类 Awake 先判断当前对象是正式实例还是重复实例。重复对象已经不应继续初始化,因此随后使用 Instance != this 提前结束。
musicSource 为空时才调用 GetComponent,这样既支持在 Inspector 中手动指定,也保留自动寻找作为保护。
private void Start()
{
if (musicSource == null)
{
Debug.LogError(
"AudioManager:没有找到用于播放背景音乐的 Audio Source。",
this);
}
}
如果手动指定和自动寻找都没有得到 Audio Source,Console 会给出可以直接理解的信息。
/// <summary>
/// 控制背景音乐的暂停与继续。
/// true 表示继续,false 表示暂停。
/// </summary>
public void ToggleBGMusic(bool isOn)
{
if (musicSource == null)
{
return;
}
if (isOn)
{
// UnPause 只负责继续曾经被 Pause 的音乐。
if (!musicSource.isPlaying && musicSource.clip != null)
{
musicSource.UnPause();
}
}
else
{
if (musicSource.isPlaying)
{
musicSource.Pause();
}
}
}
}false 进入暂停分支,true 进入继续分支。继续前同时确认当前没有播放并且已经指定 Audio Clip。
Audio Source 四个播放方法的区别
本课使用 Pause 与 UnPause,是为了在视频停止后从原位置继续音乐。
左右滑动表格,可查看完整内容
| 方法 | 播放位置 | 本课用途 |
|---|---|---|
| Play() | 开始或重新播放 | Play On Awake 已负责首次播放 |
| Pause() | 保留当前位置 | 视频开始时暂停背景音乐 |
| UnPause() | 从保留位置继续 | 视频停止后恢复背景音乐 |
| Stop() | 回到开始位置 | 不用于本课音乐联动 |
完成音乐管理器
按顺序连接四段代码,保存后返回 Unity 等待编译。
- AudioManager.cs 与类名一致。
- 脚本继承 Singleton<AudioManager>。
- Awake 先调用 base.Awake。
- Music Source 可以手动指定,也能从同对象自动取得。
- ToggleBGMusic(false) 使用 Pause。
- ToggleBGMusic(true) 使用 UnPause。
在正确的状态路径发出音乐指令
连接 VideoController 与背景音乐
VideoController 最清楚视频何时开始、停止和失败,因此由它通知 AudioManager;AudioManager 只负责执行背景音乐的暂停与继续。两个脚本各自负责什么
职责分开后,视频脚本不需要直接保存 BGM,也不需要管理背景音乐参数。
左右滑动表格,可查看完整内容
| 脚本 | 负责内容 | 不负责内容 |
|---|---|---|
| VideoController | 视频准备、播放、停止、幕布和按钮状态 | 不直接控制 Audio Source 细节 |
| AudioManager | 背景音乐暂停、继续和音乐源引用 | 不处理视频画面和按钮 |
AudioManager.Instance.ToggleBGMusic(false);在现有 PlayVideo() 方法的大括号内,把这一行插入为第一条可执行语句。它应位于视频准备、继续播放或幕布展开之前;不要复制新的方法声明,也不要覆盖原有代码。
读懂 AudioManager.Instance.ToggleBGMusic(false)
点号表示继续访问前一个对象的成员。整行代码会立即进入音乐管理器的方法,执行完后再回到 PlayVideo。
左右滑动表格,可查看完整内容
| 代码片段 | 发生的事情 |
|---|---|
| AudioManager | 指定要寻找的管理器类型 |
| .Instance | 执行单例属性的 get,取得当前正式实例 |
| .ToggleBGMusic(...) | 调用该实例的背景音乐控制方法 |
| false | 作为参数传给 isOn,选择暂停分支 |
| 方法执行完毕 | 返回 PlayVideo,继续后面的视频准备与幕布逻辑 |
AudioManager.Instance.ToggleBGMusic(true);在现有 StopVideo() 方法的大括号内,把这一行插入为第一条可执行语句。StopVideo 同时服务于手动停止和自然播放结束,因此两条路径都能恢复音乐;其余停止与幕布代码保持不变。
AudioManager.Instance.ToggleBGMusic(true);在现有 OnVideoError(...) 方法的大括号内,把这一行插入为第一条可执行语句。视频已经让 BGM 暂停却无法播放时,错误路径必须恢复音乐;原有错误处理代码继续保留在它后面。
VideoController 中的音乐指令
false 只用于视频开始,true 用于所有返回普通场景声音的路径。
左右滑动表格,可查看完整内容
| 方法 | 音乐指令 | 最终结果 |
|---|---|---|
| PlayVideo() | ToggleBGMusic(false) | 暂停 BGM,突出视频声音 |
| StopVideo() | ToggleBGMusic(true) | 从暂停位置继续 BGM |
| OnVideoFinished() | 调用 StopVideo() | 自然结束后复用恢复路径 |
| OnVideoError() | ToggleBGMusic(true) | 播放失败后恢复场景声音 |
PauseVideo 暂时不恢复背景音乐
第 14 课的 PauseVideo 用于应用进入后台或头显暂停等情况,此时整个应用暂时不在正常交互状态,因此不需要立即恢复背景音乐。
如果以后增加学生可以主动点击的视频暂停按钮,再根据交互目标决定临时暂停视频时是否需要播放 BGM。
完成三个联动入口
只把三行音乐调用分别插入现有方法的指定位置,不复制示例中的方法外壳,不删除或替换原有视频逻辑。
- PlayVideo 开头调用 ToggleBGMusic(false)。
- StopVideo 开头调用 ToggleBGMusic(true)。
- OnVideoError 调用 ToggleBGMusic(true)。
- OnVideoFinished 仍然通过 StopVideo 统一结束。
- 保存后 Unity 能正常编译。
把管理器与声音播放器放在同一个根对象
完成场景配置与声音联动
代码完成后,还需要把 AudioManager 挂到已经配置好 BGM 的声音对象上,并确认场景中只有一个代表学生视角的 Audio Listener。把 AudioManager 挂到 BGAudioSource
- 01
返回 Unity,等待脚本编译完成。
- 02
选中根对象 BGAudioSource。
- 03
确认同一个对象上已经存在配置好 BGM 的 Audio Source。
- 04
点击 Add Component,添加 AudioManager。
- 05
把 BGAudioSource 对象拖到 Music Source 字段。
- 06
确认 Unity 自动选中了这个对象上的 Audio Source。

重点观察左侧根对象 BGAudioSource,以及右侧同一个对象上的 Audio Source、Audio Manager 和 Music Source 引用。
点击图片可以查看完整截图为什么管理器与播放器放在一起
这个结构让手动指定、自动取得和跨场景保留都指向同一个对象。
左右滑动表格,可查看完整内容
| 结构选择 | 带来的结果 |
|---|---|
| 同对象具有 AudioManager 与 Audio Source | GetComponent<AudioSource>() 可以直接取得播放器 |
| BGAudioSource 位于根层级 | DontDestroyOnLoad 可以可靠保留整个声音对象 |
| Music Source 指向同对象 Audio Source | Inspector 中能直接看见管理关系 |
| Main Camera 保留唯一 Audio Listener | 声音从正确的学生视角输出 |
按声音状态依次观察
- 01
进入运行状态,不点击按钮,确认 BGM 自动播放。
- 02
点击视频播放按钮,确认 BGM 暂停,视频声音正常。
- 03
再次点击停止视频,确认 BGM 从暂停位置继续。
- 04
再次播放视频并等待自然结束,确认视频关闭后 BGM 恢复。
- 05
连续完成多轮播放与停止,确认没有两段 BGM 同时播放。
- 06
在 Hierarchy 中搜索 Audio Listener,确认只启用 Main Camera 上的一个。
- 07
确认 Console 没有新增错误。
完成声音联动演示
从进入场景到视频结束,完整演示一次声音的自动切换。
- BGM 自动播放并循环。
- 视频开始后只保留视频声音。
- 视频停止后音乐从暂停位置继续。
- 视频自然结束后音乐能够恢复。
- 重复操作不会产生两个音乐实例。
- 场景中只启用一个 Audio Listener。
根据听到的现象返回对应设置或代码
综合操作并定位声音问题
声音问题通常来自资源、Audio Source、Audio Listener、管理器引用或视频联动中的某一层。一次只处理最早出现异常的环节。常见现象与处理方法
先根据现象找到对应层,再修改设置,避免同时改变多个参数。
左右滑动表格,可查看完整内容
| 现象 | 优先查看 | 处理方法 |
|---|---|---|
| 进入场景没有声音 | Audio Clip、Mute、Volume、Play On Awake、Audio Listener | 确认 BGM 已指定、未静音、音量大于 0,并保留一个 Listener |
| 音乐只播放一次 | Loop | 开启 Audio Source 的 Loop |
| 走远或转头后音乐变化 | Spatial Blend 与 Stereo Pan | 两项都设为 0 |
| Console 提示多个 Listener | 场景中的摄像机 | 只保留 XR Main Camera 上启用的 Audio Listener |
| AudioManager 无法挂载 | 文件名、类名、Singleton.cs 和首条编译信息 | 保持文件名与类名一致,并先解决最前面的编译问题 |
| 视频开始后 BGM 仍播放 | PlayVideo | 确认开头调用 ToggleBGMusic(false) |
| 停止视频后 BGM 不恢复 | StopVideo、Music Source、Pause 与 UnPause | 确认恢复调用与音乐源引用正确 |
| 视频失败后一直无声 | OnVideoError | 在错误路径调用 ToggleBGMusic(true) |
| 音乐每次从头开始 | 是否使用 Stop 与 Play | 改用 Pause 与 UnPause |
| 切换场景后两段音乐重叠 | 单例继承、base.Awake 和重复场景对象 | 确认第一个实例保留、后续重复对象被移除 |
isPlaying 为 false 不一定表示暂停
Audio Source 被 Pause 后,isPlaying 会返回 false;但从未播放、已经播放结束、调用 Stop 或没有 Audio Clip 时,也可能得到 false。
因此 !isPlaying 只能说明此刻没有播放,不能单独证明音乐一定处于暂停状态。本课能够使用简洁逻辑,是因为 Play On Awake、Pause 和 UnPause 的顺序明确。
本课成果清单
逐项完成后,背景音乐播放、全局管理和视频联动已经形成完整功能。
- Assets/_Audio 中存在并能试听 BGM.mp3。
- 根对象 BGAudioSource 上的 Audio Source 已指定 BGM。
- Play On Awake、Loop 已开启,Volume 为 0.8,Spatial Blend 为 0。
- Main Camera 上保留唯一启用的 Audio Listener。
- Singleton.cs 和 AudioManager.cs 能够正常编译。
- AudioManager 与 Audio Source 位于同一个根对象。
- Music Source 指向正确的 Audio Source。
- 视频开始会暂停背景音乐。
- 手动停止、自然结束和播放失败都会恢复背景音乐。
- 背景音乐从暂停位置继续,不会每次回到开头。
- 连续多次操作后不会出现重复音乐。
- MainScene 已保存,Console 没有新增错误。
用一段简洁任务说明完成三个文件
用人工智能编程助手完成音乐控制代码
这段提示词只保留人工智能编程助手完成代码所必需的信息。场景中的 BGM、Audio Source 和 Music Source 仍需要你在 Unity 中亲自配置。准备项目文件
- 01
确认 Assets/_Scripts/VideoController.cs 已经是第 14 课的完整版本。
- 02
如果 Singleton.cs 和 AudioManager.cs 尚未创建,可以让编程助手直接新建。
- 03
在人工智能编程助手中打开 Unity 项目根目录。
- 04
复制下面的提示词并发送。
- 05
完成后返回 Unity,等待编译并查看 Console。
请在这个 Unity 2022.3 项目中完成背景音乐控制代码:
1. 新建 Assets/_Scripts/Singleton.cs:实现泛型单例 Singleton<T>,提供静态 Instance;第一个实例使用 DontDestroyOnLoad 保留,重复实例销毁。
2. 新建 Assets/_Scripts/AudioManager.cs:继承 Singleton<AudioManager>,要求同对象具有 AudioSource;支持 Inspector 指定或 GetComponent 自动取得音乐源;提供 ToggleBGMusic(bool isOn),false 使用 Pause,true 使用 UnPause。
3. 修改 Assets/_Scripts/VideoController.cs:PlayVideo 开始时暂停背景音乐;StopVideo 和 OnVideoError 中恢复背景音乐;不要改动原有视频、幕布和按钮逻辑。
只修改以上三个文件,不安装第三方插件。请直接写入完整、可编译的代码,并保持文件名与类名一致。提示词只要求创建单例、音乐管理器并修改三个视频状态入口,不会让编程助手改动场景或重写原有视频功能。
比较人工智能生成结果
生成完成后,不要只看“是否编译”,还要对照本课代码确认职责与状态方向。
- Singleton 提供 Instance、跨场景保留和重复实例处理。
- AudioManager 使用 Pause 与 UnPause,不使用 Stop 与 Play 替代。
- PlayVideo 传入 false。
- StopVideo 与 OnVideoError 传入 true。
- VideoController 原有视频和幕布逻辑仍然完整。
- 三个文件名与类名一致,Unity 能够正常编译。
把知识变成作品
本课练习
请按顺序完成以下任务,并保存自己的学习成果。- 01
不用查看属性表,独立创建 BGAudioSource 并完成自动、循环、音量和二维声音设置。
- 02
画出 AudioManager.Instance 到 AudioSource.Pause 或 UnPause 的调用链路。
- 03
故意关闭 Loop 或改变 Spatial Blend,根据听到的现象定位对应属性,再恢复正确设置。
- 04
连续完成多轮视频播放、停止和自然结束,确认背景音乐始终从正确位置恢复。
- 05
使用本课精简提示词生成三个脚本的修改,再与主线代码比较职责和状态方向。
把本课进度保存下来
登录后可以保存浏览内容、有效学习时间、完成状态和答题参与记录。