Unity——音频管理器(附例子)

简介: Unity——音频管理器(附例子)

在实际游戏开发中,音效既是一个相对独立的部分,又与其他游戏逻辑密切关联。也就是说,与音效相关的代码会插入很多细节代码中。

而且在音效非常丰富的情况下,如果每一个游戏模块都单独播放音效,那么可能会带来一些问题。例如,Audio Source组件很多,但大部分限制,同时播放的音效太多会显得混乱。

成熟的技术开发思路是:如果音效不多、没有造成问题,则完全可以简单处理;如果音效已经引起了代码的混乱和性能问题,就有必要统一管理所有的音源和音效,也就是设计一个易用的音频管理器。有了音频管理器,所有的Audio Source组件都会统一创建,而所有音效播放的需求都要通过调用音频管理器的方法简介实现。

音频管理器有很多设计思路,其中一种比较简洁的思路是,事先指定游戏中最多同时播放多少个音频,然后创建若干个音源。例如,最多播放8个音频,那么就创建8个Audio Source组件,这8个组件可以看作8个频道。需要播放音频时,只要找到任意一个空闲的频道播放即可;而如果8个频道都正在播放,那么就可以用某种策略替换音频(如将播放时间最早的音频替换成新的音频)。用这种简单的思路创建音频管理器的代码如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//音频管理器
public class AudioManager : MonoBehaviour
{
    // 整个游戏中,总的音源数量
    private const int AUDIO_CHANNEL_NUM = 8;
    private struct CHANNEL
    {
        public AudioSource channel;
        public float keyOnTime; //记录最近一次播放音乐的时刻
    };
    private CHANNEL[] m_channels;
    void Awake()
    {
        m_channels = new CHANNEL[AUDIO_CHANNEL_NUM];
        for (int i = 0; i < AUDIO_CHANNEL_NUM; i++)
        {
            //每个频道对应一个音源
            m_channels[i].channel = gameObject.AddComponent<AudioSource>();
            m_channels[i].keyOnTime = 0;
        }
    }
    //公开方法:播放一次,参数为音频片段、音量、左右声道、速度
    //这个方法主要用于音效,因此考虑了音效顶替的逻辑
    public int PlayOneShot(AudioClip clip, float volume, float pan, float pitch = 1.0f)
    {
        for (int i = 0; i < m_channels.Length; i++)
        {
            //如果正在播放同一个片段,而且刚刚才开始,则直接退出函数
            if (m_channels[i].channel.isPlaying &&
                 m_channels[i].channel.clip == clip &&
                 m_channels[i].keyOnTime >= Time.time - 0.03f)
                return -1;
        }
        //遍历所有频道,如果有频道空闲直接播放新音频,并退出
        //如果没有空闲频道,先找到最开始播放的频道(oldest),稍后使用
        int oldest = -1;
        float time = 10000000.0f;
        for (int i = 0; i < m_channels.Length; i++)
        {
            if (m_channels[i].channel.loop==false &&
               m_channels[i].channel.isPlaying  &&
               m_channels[i].keyOnTime < time)
            {
                oldest = i;
                time=m_channels[i].keyOnTime;
            }
            if (!m_channels[i].channel.isPlaying)
            {
                m_channels[i].channel.clip=clip;
                m_channels[i].channel.volume=volume;
                m_channels[i].channel.pitch=pitch;
                m_channels[i].channel.panStereo=pan;
                m_channels[i].channel.loop = false;
                m_channels[i].channel.Play();
                m_channels[i].keyOnTime = Time.time;
                return i;
            }
        }
        //运行到这里说明没有空闲频道。让新的音频顶替最早播出的音频
        if(oldest>=0)
        {
            m_channels[oldest].channel.clip = clip;
            m_channels[oldest].channel.volume = volume;
            m_channels[oldest].channel.pitch = pitch;
            m_channels[oldest].channel.panStereo = pan;
            m_channels[oldest].channel.loop = false;
            m_channels[oldest].channel.Play();
            m_channels[oldest].keyOnTime = Time.time;
            return oldest;
        }
        return -1;
    }
    //公开方法:循环播放,用于播放长时间的背景音乐,处理方式相对简单一些
    public int PlayLoop(AudioClip clip, float volume, float pan, float pitch = 1.0f)
    {
        for(int i = 0; i < m_channels.Length; i++)
        {
            if (!m_channels[i].channel.isPlaying)
            {
                m_channels[i].channel.clip = clip;
                m_channels[i].channel.volume = volume;
                m_channels[i].channel.pitch = pitch;
                m_channels[i].channel.panStereo = pan;
                m_channels[i].channel.loop = true;
                m_channels[i].channel.Play();
                m_channels[i].keyOnTime = Time.time;
                return i;
            }
        }
        return -1;
    }
    //公开方法:停止所有音频
    public void StopAll()
    {
        foreach(CHANNEL channel in m_channels)
            channel.channel.Stop();
    }
    //公开方法:根据频道ID停止音频
    public void Stop(int id)
    {
        if (id>= 0&& id < m_channels.Length){
            m_channels[id].channel.Stop();
        }
    }
}

以上代码可以作为创建音频管理器的一种思路参考。

实际上,根据游戏类型的不同,音频管理器的创建思路也有区别。例如,在很多3D游戏中,需要考虑音效播放的空间位置(目的是营造真实感),这是统一创建音源就不是很合适了


相关文章
|
开发工具 图形学 git
Unity 之 加载工程卡在音频处不动(Unity识别不出音频文件)
在Git上克隆同事上传的工程,打开是卡在音频处不动解决方案分享。
654 0
Unity 之 加载工程卡在音频处不动(Unity识别不出音频文件)
|
4月前
|
图形学
Unity音频基础概念
Unity音频基础概念
|
存储 图形学 索引
Unity 编辑器开发实战【Custom Editor】- AudioDatabase Editor 音频库编辑器
Unity 编辑器开发实战【Custom Editor】- AudioDatabase Editor 音频库编辑器
196 1
Unity 编辑器开发实战【Custom Editor】- AudioDatabase Editor 音频库编辑器
|
算法 图形学 数据安全/隐私保护
Unity 之 音频类型和编码格式介绍
Inspector窗口显示多个导入设置。这些设置决定了:加载行为,压缩行为,质量,采样率,以及是否支持双声道音频。
376 0
Unity 之 音频类型和编码格式介绍
|
图形学
Unity组件:Audio Chorus Filter PRO only 音频合声滤波器
The Audio Chorus Filter takes an Audio Clip and processes it creating a chorus effect.音频合声滤波器(Audio Chorus Filter)采用一个音频剪辑(Audio Clip)并处理它,创建一个合声效果。
1468 0
|
4月前
|
C# 图形学
【Unity 3D】元宇宙案例之虚拟地球信息射线实战(附源码、演示视频和步骤 超详细)
【Unity 3D】元宇宙案例之虚拟地球信息射线实战(附源码、演示视频和步骤 超详细)
50 0
|
4月前
|
人工智能 自然语言处理 区块链
【Unity 3D】元宇宙概念、应用前景、价值链等概述
【Unity 3D】元宇宙概念、应用前景、价值链等概述
52 0
|
4月前
|
vr&ar C# 图形学
【Unity 3D】VR飞机拆装后零件说明功能案例实战(附源码和演示视频 超详细)
【Unity 3D】VR飞机拆装后零件说明功能案例实战(附源码和演示视频 超详细)
38 0
|
4月前
|
vr&ar C# 图形学
【Unity 3D】VR飞机动态拆装及引擎开关控制案例(附源码和演示视频 超详细)
【Unity 3D】VR飞机动态拆装及引擎开关控制案例(附源码和演示视频 超详细)
39 0
|
4月前
|
vr&ar 图形学
【Unity 3D】VR飞机起飞喷火游戏案例实战(附源码和演示视频 超详细)
【Unity 3D】VR飞机起飞喷火游戏案例实战(附源码和演示视频 超详细)
52 0