UFL6-2——音效管理模块音效部分实现

实现音效管理模块 音效 相关内容

主要实现内容

注意:

  1. 音效和背景音乐不同

    音效存在多个,并且音效需要管理是否结束,因此需要用容器记录音效组件

  2. 音效分为循环和非循环

    非循环的需要我们检测它播放结束,循环的需要让外部进行管理

  3. 播放音效

    如果不考虑3D音效的话,可以将所有的音效源组件添加到一个单独的对象上,方便管理
    每次播放音效时,都需要将播放音效的音效源组件添加到列表内,方便管理,同时还需要一个回调函数参数,将音效源组件传递出去

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    //用于音效组件依附的对象
    private GameObject soundObj = null;
    //管理正在播放的音效
    private List<AudioSource> soundList = new List<AudioSource>();
    //音效音量大小
    private float soundVolume = 0.1f;

    // 播放音效
    public void PlaySound(string name, bool isLoop = false, bool isSync = false, UnityAction<AudioSource> callBack = null)
    {
    if (soundObj == null)
    {
    //音效依附的对象,一般过场景音效都需要停止,所以我们可以不处理,它过场景不移除
    soundObj = new GameObject("soundObj");
    }
    //加载音效资源,进行播放
    ABResManager.Instance.LoadResAsync<AudioClip>("sound", name, (clip) =>
    {
    AudioSource source = soundObj.AddComponent<AudioSource>();
    source.clip = clip;
    source.loop = isLoop;
    source.volume = soundVolume;
    source.Play();
    //存储容器,用于记录,方便之后判断是否停止
    soundList.Add(source);
    callBack?.Invoke(source);
    }, isSync);
    }
  4. 自动移除播放完成的音效

    由于音效源没有播放完毕就执行的回调委托,我们必须每帧或者每隔一小段时间遍历检测音效是否播放完毕,将播放完毕的音效销毁并移除出列表
    这里就会使用公共Mono模块的添加帧更新方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //管理正在播放的音效
    private List<AudioSource> soundList = new List<AudioSource>();

    private MusicManager()
    {
    //固定每一小段时间检测是否有音效播放完毕
    MonoManager.Instance.AddFixedUpdateListener(Update);
    }

    private void Update()
    {
    //逆向遍历容器,检测是否有音效播放完毕了,如果播放完毕就销毁该组件并移除
    for (int i = soundList.Count - 1; i >= 0; i--)
    {
    if (!soundList[i].isPlaying)
    {
    GameObject.Destroy(soundList[i]);
    soundList.RemoveAt(i);
    }
    }
    }
  5. 停止播放指定音效

    能够停止的音效必须是存在于列表内的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //管理正在播放的音效
    private List<AudioSource> soundList = new List<AudioSource>();

    // 停止播放音效
    public void StopSound(AudioSource source)
    {
    if (soundList.Contains(source))
    {
    source.Stop();
    soundList.Remove(source);
    GameObject.Destroy(source);
    }
    }
  6. 设置音效声音大小

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //管理正在播放的音效
    private List<AudioSource> soundList = new List<AudioSource>();
    //音效音量大小
    private float soundVolume = 0.1f;

    public void ChangeSoundValue(float value)
    {
    soundVolume = value;
    for (int i = 0; i < soundList.Count; i++)
    {
    soundList[i].volume = soundVolume;
    }
    }
  7. 暂停或继续播放所有音效

    注意,为我们在帧更新方法里使用音效源的isPlaying​属性来判断是否需要销毁音效源,而暂停也会让isPlaying == false
    因此,我们需要额外声明一个变量soundIsPlay​,用于控制帧更新销毁音效源的逻辑是否执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    //管理正在播放的音效
    private List<AudioSource> soundList = new List<AudioSource>();
    //音效是否在播放,如果false说明在暂停
    private bool soundIsPlay = true;

    // 继续播放或者暂停所有音效
    public void PlayOrPauseSound(bool isPlay)
    {
    soundIsPlay = isPlay;
    if (isPlay)
    {
    for (int i = 0; i < soundList.Count; i++)
    {
    soundList[i].Play();
    }
    }
    else
    {
    for (int i = 0; i < soundList.Count; i++)
    {
    soundList[i].Pause();
    }
    }
    }

    private MusicManager()
    {
    //固定每一小段时间检测是否有音效播放完毕
    MonoManager.Instance.AddFixedUpdateListener(Update);
    }

    private void Update()
    {
    if (!soundIsPlay)
    return;
    //逆向遍历容器,检测是否有音效播放完毕了,如果播放完毕就销毁该组件并移除
    for (int i = soundList.Count - 1; i >= 0; i--)
    {
    if (!soundList[i].isPlaying)
    {
    GameObject.Destroy(soundList[i]);
    soundList.RemoveAt(i);
    }
    }
    }

使用示例

准备几个音效文件,放到Assets/Editor/sound​文件夹下,以便于读取和打包

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private float v = 0.1f;
private float oldV = 0.1f;
private AudioSource source;

private void OnGUI()
{
v = GUILayout.HorizontalSlider(v, 0, 1);
//当值改变时,才执行改变音量方法
if (oldV != v)
{
MusicManager.Instance.ChangeBGMVolume(v);
MusicManager.Instance.ChangeSoundValue(v);
oldV = v;
}

if (GUILayout.Button("播放音效"))
{
MusicManager.Instance.PlaySound("sound_1");
}

//需要停止的音效往往是循环音效
if (GUILayout.Button("播放循环音效"))
{
if (this.source == null)
{
MusicManager.Instance.PlaySound("sound_2", true, false, (source) =>
{
this.source = source;
});
}
}

if (GUILayout.Button("停止循环音效"))
{
MusicManager.Instance.StopSound(this.source);
this.source = null;
}

if (GUILayout.Button("暂停音效播放"))
{
MusicManager.Instance.PlayOrPauseSound(false);
}

if (GUILayout.Button("恢复音效播放"))
{
MusicManager.Instance.PlayOrPauseSound(true);
}
}

通过点击屏幕上的绘制的按钮和滑动条,观察音效源控件的变化,监听音效变化

抛出问题

  1. 频繁的删除创建音效组件,比较浪费性能,应该如何优化?
  2. 3D音效需要让音效对象位置产生变化,我们应该如何修改?

具体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 音乐音效管理器
/// </summary>
public class MusicManager : BaseManager<MusicManager>
{
//背景音乐播放组件
private AudioSource bgmSource = null;
//背景音乐大小
private float bgmVolume = 0.1f;
//用于音效组件依附的对象
private GameObject soundObj = null;
//管理正在播放的音效
private List<AudioSource> soundList = new List<AudioSource>();
//音效音量大小
private float soundVolume = 0.1f;
//音效是否在播放,如果false说明在暂停
private bool soundIsPlay = true;

private MusicManager()
{
//固定每一小段时间检测是否有音效播放完毕
MonoManager.Instance.AddFixedUpdateListener(Update);
}

private void Update()
{
if (!soundIsPlay)
return;
//逆向遍历容器,检测是否有音效播放完毕了,如果播放完毕就销毁该组件并移除
for (int i = soundList.Count - 1; i >= 0; i--)
{
if (!soundList[i].isPlaying)
{
GameObject.Destroy(soundList[i]);
soundList.RemoveAt(i);
}
}
}

public void PlayBGM(string name)
{
//动态创建播放背景音乐的组件,并且不会因为过场景而移除
//保证背景音乐在过场景时也能播放
if (bgmSource == null)
{
GameObject obj = new GameObject("BGM");
GameObject.DontDestroyOnLoad(obj);
bgmSource = obj.AddComponent<AudioSource>();
}
//根据传入的背景音乐名字,来播放背景音乐
ABResManager.Instance.LoadResAsync<AudioClip>("music", name, (clip) =>
{
bgmSource.clip = clip;
bgmSource.loop = true;
bgmSource.volume = bgmVolume;
bgmSource.Play();
});
}

public void StopBGM()
{
if (bgmSource == null)
return;
bgmSource.Stop();
}

public void PauseBGM()
{
if (bgmSource == null)
return;
bgmSource.Pause();
}

public void ChangeBGMVolume(float value)
{
bgmVolume = value;
if (bgmSource == null)
return;
bgmSource.volume = bgmVolume;
}

/// <summary>
/// 播放音效
/// </summary>
/// <param name="name">音效名字</param>
/// <param name="isLoop">是否循环</param>
/// <param name="isSync">是否同步加载</param>
/// <param name="callBack">音效加载结束后的回调</param>
public void PlaySound(string name, bool isLoop = false, bool isSync = false, UnityAction<AudioSource> callBack = null)
{
if (soundObj == null)
{
//音效依附的对象,一般过场景音效都需要停止,所以我们可以不处理,它过场景不移除
soundObj = new GameObject("soundObj");
}
//加载音效资源,进行播放
ABResManager.Instance.LoadResAsync<AudioClip>("sound", name, (clip) =>
{
AudioSource source = soundObj.AddComponent<AudioSource>();
source.clip = clip;
source.loop = isLoop;
source.volume = soundVolume;
source.Play();
//存储容器,用于记录,方便之后判断是否停止
soundList.Add(source);
callBack?.Invoke(source);
}, isSync);
}

/// <summary>
/// 停止播放音效
/// </summary>
/// <param name="source">要停止播放音效的组件对象</param>
public void StopSound(AudioSource source)
{
if (soundList.Contains(source))
{
source.Stop();
soundList.Remove(source);
GameObject.Destroy(source);
}
}

/// <summary>
/// 改变音效大小
/// </summary>
/// <param name="value"></param>
public void ChangeSoundValue(float value)
{
soundVolume = value;
for (int i = 0; i < soundList.Count; i++)
{
soundList[i].volume = soundVolume;
}
}

/// <summary>
/// 继续播放或者暂停所有音效
/// </summary>
/// <param name="isPlay">是否继续播放,true为播放,false为暂停</param>
public void PlayOrPauseSound(bool isPlay)
{
soundIsPlay = isPlay;
if (isPlay)
{
for (int i = 0; i < soundList.Count; i++)
{
soundList[i].Play();
}
}
else
{
for (int i = 0; i < soundList.Count; i++)
{
soundList[i].Pause();
}
}
}
}