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上克隆同事上传的工程,打开是卡在音频处不动解决方案分享。
744 0
Unity 之 加载工程卡在音频处不动(Unity识别不出音频文件)
|
5月前
|
C# 图形学
【unity小技巧】Unity音乐和音效管理器
【unity小技巧】Unity音乐和音效管理器
145 0
|
3月前
|
开发者 图形学 C#
揭秘游戏沉浸感的秘密武器:深度解析Unity中的音频设计技巧,从背景音乐到动态音效,全面提升你的游戏氛围艺术——附实战代码示例与应用场景指导
【8月更文挑战第31天】音频设计在游戏开发中至关重要,不仅能增强沉浸感,还能传递信息,构建氛围。Unity作为跨平台游戏引擎,提供了丰富的音频处理功能,助力开发者轻松实现复杂音效。本文将探讨如何利用Unity的音频设计提升游戏氛围,并通过具体示例代码展示实现过程。例如,在恐怖游戏中,阴森的背景音乐和突然的脚步声能增加紧张感;在休闲游戏中,轻快的旋律则让玩家感到愉悦。
87 0
|
11月前
|
图形学
Unity音频基础概念
Unity音频基础概念
|
存储 图形学 索引
Unity 编辑器开发实战【Custom Editor】- AudioDatabase Editor 音频库编辑器
Unity 编辑器开发实战【Custom Editor】- AudioDatabase Editor 音频库编辑器
245 1
Unity 编辑器开发实战【Custom Editor】- AudioDatabase Editor 音频库编辑器
|
人工智能 算法 测试技术
Unity & FACEGOOD Audio2Face 通过音频驱动面部BlendShape
Unity & FACEGOOD Audio2Face 通过音频驱动面部BlendShape
2090 0
Unity & FACEGOOD Audio2Face 通过音频驱动面部BlendShape
|
算法 图形学 数据安全/隐私保护
Unity 之 音频类型和编码格式介绍
Inspector窗口显示多个导入设置。这些设置决定了:加载行为,压缩行为,质量,采样率,以及是否支持双声道音频。
464 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)并处理它,创建一个合声效果。
1502 0
|
3月前
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
176 6
|
2月前
|
测试技术 C# 图形学
掌握Unity调试与测试的终极指南:从内置调试工具到自动化测试框架,全方位保障游戏品质不踩坑,打造流畅游戏体验的必备技能大揭秘!
【9月更文挑战第1天】在开发游戏时,Unity 引擎让创意变为现实。但软件开发中难免遇到 Bug,若不解决,将严重影响用户体验。调试与测试成为确保游戏质量的最后一道防线。本文介绍如何利用 Unity 的调试工具高效排查问题,并通过 Profiler 分析性能瓶颈。此外,Unity Test Framework 支持自动化测试,提高开发效率。结合单元测试与集成测试,确保游戏逻辑正确无误。对于在线游戏,还需进行压力测试以验证服务器稳定性。总之,调试与测试贯穿游戏开发全流程,确保最终作品既好玩又稳定。
105 4