84.AudioManager 音頻播放功能

2021-02-20 涼鞋的筆記

在上一篇,我們完成了  GUIManager。
先列出 Manager Of Managers 如下:

(完成) MainManager: 作為入口管理器。

(已經有了) EventManager: 消息管理。

(完成) GUIManager: 圖形視圖管理。

AudioManager: 音效管理。

PoolManager: GameObject管理(減少動態開闢內存消耗,減少GC)。

LevelManager: 關卡管理。

GameManager: 遊戲管理。

SaveManager: 配置&存儲管理。 

MenuManager 菜單管理。

下一個,我們要做的就是 AudioManager 了。

播放音頻功能

AudioManager,字如其意就是音效管理。在 Unity 中我們需要用到的音效 API 有,AudioClip 、AudioSource、AudioListener。相信大家對它們已經很熟悉了。

我們還是以實際的問題去出發去完成這個 AudioManager。既然是音效管理,那麼它肯定要有音頻播放的功能。

我們呢就先馬上實現一個音頻播放的邏輯,先準備一個音效,放在我們的 Resources 目錄下,如下圖所示。

006tNc79gy1fzgf4x6dr8j306c01ya9y.jpg
然後直接實現我們的播放音頻邏輯,代碼如下:
QFramework/Example/13.AudioManager/AudioExample.cs

using UnityEngine;

namespace QFramework
{
    public class AudioExample : MonoBehaviour
    {

#if UNITY_EDITOR
        [UnityEditor.MenuItem("QFramework/Example/13.AudioManager", false, 13)]
        private static void MenuClicked()
        {
            UnityEditor.EditorApplication.isPlaying = true;

            new GameObject("AudioExample")
                .AddComponent<AudioExample>();
        }
#endif

        private void Start()
        {
            gameObject.AddComponent<AudioListener>();

            var audioSource = gameObject.AddComponent<AudioSource>();

            var coinSound = Resources.Load<AudioClip>("coin");

            audioSource.clip = coinSound;
            audioSource.Play();
        }
    }
}

執行之後,能聽到我們的音頻聲音,並且在場景中創建了一些東西,如下圖所示。

006tNc79gy1fzgf503ayyj30kf0bpq4l.jpg

這樣一個音頻播放的功能就做完了。我們把以上的邏輯,整理成方法,放到我們的 AudioManager 腳本了。
代碼如下所示:
Assets/QFramework/Framework/Manager/AudioManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace QFramework
{
    public class AudioManager : MonoBehaviour
    {
        public void PlaySound(string soundName)
        {
            gameObject.AddComponent<AudioListener>();

            var audioSource = gameObject.AddComponent<AudioSource>();

            var coinSound = Resources.Load<AudioClip>("coin");

            audioSource.clip = coinSound;
            audioSource.Play();
        }
    }
}

但是這樣寫了之後,我們的示例代碼就沒法訪問 PlaySound 這個方法了。所以呢,要把 AudioManager 做成一個單例。
代碼如下:
Assets/QFramework/Framework/Manager/AudioManager.cs

using UnityEngine;

namespace QFramework
{
    public class AudioManager : MonoBehaviour
    {
        private static AudioManager mInstance;

        public static AudioManager Instance
        {
            get
            {
                if (mInstance == null)
                {
                    mInstance = new GameObject("AudioManager").AddComponent<AudioManager>();

                    DontDestroyOnLoad(mInstance);
                }

                return mInstance;
            }
        }

        public void PlaySound(string soundName)
        {
            gameObject.AddComponent<AudioListener>();

            var audioSource = gameObject.AddComponent<AudioSource>();

            var coinSound = Resources.Load<AudioClip>("coin");

            audioSource.clip = coinSound;
            audioSource.Play();
        }
    }
}

單例的部分,我們用了一個 DontDestroyOnLoad 這樣的 API,它的意思是不管場景怎麼切換,這個 AudioManger 一直存在,不被銷毀。

這樣,我們的示例代碼就可以訪問 AudioManager 了。
示例代碼如下:
QFramework/Example/13.AudioManager/AudioExample.cs

using UnityEngine;

namespace QFramework
{
    public class AudioExample : MonoBehaviour
    {

#if UNITY_EDITOR
        [UnityEditor.MenuItem("QFramework/Example/13.AudioManager", false, 13)]
        private static void MenuClicked()
        {
            UnityEditor.EditorApplication.isPlaying = true;

            new GameObject("AudioExample")
                .AddComponent<AudioExample>();
        }
#endif

        private void Start()
        {
            AudioManager.Instance.PlaySound("coin");
        }
    }
}

運行之後,結果與原來的一致。

OK,第一個播放音頻功能就有了 ,雖然問題有很多……,但是有了……

完善播放功能

現在我們的示例中只播放了一次音頻,但是我們要播放兩次呢?
示例代碼改成如下:
QFramework/Example/13.AudioManager/AudioExample.cs

using UnityEngine;

namespace QFramework
{
    public class AudioExample : MonoBehaviour
    {

#if UNITY_EDITOR
        [UnityEditor.MenuItem("QFramework/Example/13.AudioManager", false, 13)]
        private static void MenuClicked()
        {
            UnityEditor.EditorApplication.isPlaying = true;

            new GameObject("AudioExample")
                .AddComponent<AudioExample>();
        }
#endif

        private void Start()
        {
            AudioManager.Instance.PlaySound("coin");
            AudioManager.Instance.PlaySound("coin");
        }
    }
}

運行之後結果如下:

006tNc79gy1fzgf55o3c8j30ml04f3z0.jpg
音頻播放成功了,但是報錯了,Unity 告訴你已經在 AudioManager 上掛過 AudioListener 腳本了。

我們先看看播放聲音的代碼

        public void PlaySound(string soundName)
        {
            gameObject.AddComponent<AudioListener>();

            var audioSource = gameObject.AddComponent<AudioSource>();

            var coinSound = Resources.Load<AudioClip>(soundName);

            audioSource.clip = coinSound;
            audioSource.Play();
        }

確實是,每次播放的時候,都會 AddComponet<AudioListener\>();
這部分解決起來比較簡單。
只要每次都判斷一次當前 GameObject 是否有 AudioListener 就好了。
代碼如下:

        private AudioListener mAudioListener;

        public void PlaySound(string soundName)
        {
            if (!mAudioListener)
            {
                mAudioListener = gameObject.AddComponent<AudioListener>();
            }

            var audioSource = gameObject.AddComponent<AudioSource>();

            var coinSound = Resources.Load<AudioClip>(soundName);

            audioSource.clip = coinSound;
            audioSource.Play();
        }

這樣就沒有邏輯上的問題了。
大家注意一下, audioListener 不是 bool 變量,但是它卻可以直接在 if 語句上當做表達式判斷。這是因為 Unity 對所有的 UnityEngine.Object 類型做了一個運算符重載。像我們的 MonoBehaviour、GameObject、Transform 全部都是可以這樣做的,因為它們都繼承了 UnityEngine.Object。

到這裡我們再運行一次示例。運行之後沒有任何問題。

006tNc79gy1fzgf58h58sj30ms07xdgz.jpg
我們看到運行之後創建了兩個 AudioSource。

不過這個問題不大。

今天的內容就這些,我們下一篇再見。


在上一篇,我們完成了  GUIManager。
先列出 Manager Of Managers 如下:

(完成) MainManager: 作為入口管理器。

(已經有了) EventManager: 消息管理。

(完成) GUIManager: 圖形視圖管理。

AudioManager: 音效管理。

PoolManager: GameObject管理(減少動態開闢內存消耗,減少GC)。

LevelManager: 關卡管理。

GameManager: 遊戲管理。

SaveManager: 配置&存儲管理。 

MenuManager 菜單管理。

下一個,我們要做的就是 AudioManager 了。

播放音頻功能

AudioManager,字如其意就是音效管理。在 Unity 中我們需要用到的音效 API 有,AudioClip 、AudioSource、AudioListener。相信大家對它們已經很熟悉了。

我們還是以實際的問題去出發去完成這個 AudioManager。既然是音效管理,那麼它肯定要有音頻播放的功能。

我們呢就先馬上實現一個音頻播放的邏輯,先準備一個音效,放在我們的 Resources 目錄下,如下圖所示。

006tNc79gy1fzgf4x6dr8j306c01ya9y.jpg
然後直接實現我們的播放音頻邏輯,代碼如下:
QFramework/Example/13.AudioManager/AudioExample.cs

using UnityEngine;

namespace QFramework
{
    public class AudioExample : MonoBehaviour
    {

#if UNITY_EDITOR
        [UnityEditor.MenuItem("QFramework/Example/13.AudioManager", false, 13)]
        private static void MenuClicked()
        {
            UnityEditor.EditorApplication.isPlaying = true;

            new GameObject("AudioExample")
                .AddComponent<AudioExample>();
        }
#endif

        private void Start()
        {
            gameObject.AddComponent<AudioListener>();

            var audioSource = gameObject.AddComponent<AudioSource>();

            var coinSound = Resources.Load<AudioClip>("coin");

            audioSource.clip = coinSound;
            audioSource.Play();
        }
    }
}

執行之後,能聽到我們的音頻聲音,並且在場景中創建了一些東西,如下圖所示。

006tNc79gy1fzgf503ayyj30kf0bpq4l.jpg

這樣一個音頻播放的功能就做完了。我們把以上的邏輯,整理成方法,放到我們的 AudioManager 腳本了。
代碼如下所示:
Assets/QFramework/Framework/Manager/AudioManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace QFramework
{
    public class AudioManager : MonoBehaviour
    {
        public void PlaySound(string soundName)
        {
            gameObject.AddComponent<AudioListener>();

            var audioSource = gameObject.AddComponent<AudioSource>();

            var coinSound = Resources.Load<AudioClip>("coin");

            audioSource.clip = coinSound;
            audioSource.Play();
        }
    }
}

但是這樣寫了之後,我們的示例代碼就沒法訪問 PlaySound 這個方法了。所以呢,要把 AudioManager 做成一個單例。
代碼如下:
Assets/QFramework/Framework/Manager/AudioManager.cs

using UnityEngine;

namespace QFramework
{
    public class AudioManager : MonoBehaviour
    {
        private static AudioManager mInstance;

        public static AudioManager Instance
        {
            get
            {
                if (mInstance == null)
                {
                    mInstance = new GameObject("AudioManager").AddComponent<AudioManager>();

                    DontDestroyOnLoad(mInstance);
                }

                return mInstance;
            }
        }

        public void PlaySound(string soundName)
        {
            gameObject.AddComponent<AudioListener>();

            var audioSource = gameObject.AddComponent<AudioSource>();

            var coinSound = Resources.Load<AudioClip>("coin");

            audioSource.clip = coinSound;
            audioSource.Play();
        }
    }
}

單例的部分,我們用了一個 DontDestroyOnLoad 這樣的 API,它的意思是不管場景怎麼切換,這個 AudioManger 一直存在,不被銷毀。

這樣,我們的示例代碼就可以訪問 AudioManager 了。
示例代碼如下:
QFramework/Example/13.AudioManager/AudioExample.cs

using UnityEngine;

namespace QFramework
{
    public class AudioExample : MonoBehaviour
    {

#if UNITY_EDITOR
        [UnityEditor.MenuItem("QFramework/Example/13.AudioManager", false, 13)]
        private static void MenuClicked()
        {
            UnityEditor.EditorApplication.isPlaying = true;

            new GameObject("AudioExample")
                .AddComponent<AudioExample>();
        }
#endif

        private void Start()
        {
            AudioManager.Instance.PlaySound("coin");
        }
    }
}

運行之後,結果與原來的一致。

OK,第一個播放音頻功能就有了 ,雖然問題有很多……,但是有了……

完善播放功能

現在我們的示例中只播放了一次音頻,但是我們要播放兩次呢?
示例代碼改成如下:
QFramework/Example/13.AudioManager/AudioExample.cs

using UnityEngine;

namespace QFramework
{
    public class AudioExample : MonoBehaviour
    {

#if UNITY_EDITOR
        [UnityEditor.MenuItem("QFramework/Example/13.AudioManager", false, 13)]
        private static void MenuClicked()
        {
            UnityEditor.EditorApplication.isPlaying = true;

            new GameObject("AudioExample")
                .AddComponent<AudioExample>();
        }
#endif

        private void Start()
        {
            AudioManager.Instance.PlaySound("coin");
            AudioManager.Instance.PlaySound("coin");
        }
    }
}

運行之後結果如下:

006tNc79gy1fzgf55o3c8j30ml04f3z0.jpg
音頻播放成功了,但是報錯了,Unity 告訴你已經在 AudioManager 上掛過 AudioListener 腳本了。

我們先看看播放聲音的代碼

        public void PlaySound(string soundName)
        {
            gameObject.AddComponent<AudioListener>();

            var audioSource = gameObject.AddComponent<AudioSource>();

            var coinSound = Resources.Load<AudioClip>(soundName);

            audioSource.clip = coinSound;
            audioSource.Play();
        }

確實是,每次播放的時候,都會 AddComponet<AudioListener\>();
這部分解決起來比較簡單。
只要每次都判斷一次當前 GameObject 是否有 AudioListener 就好了。
代碼如下:

        private AudioListener mAudioListener;

        public void PlaySound(string soundName)
        {
            if (!mAudioListener)
            {
                mAudioListener = gameObject.AddComponent<AudioListener>();
            }

            var audioSource = gameObject.AddComponent<AudioSource>();

            var coinSound = Resources.Load<AudioClip>(soundName);

            audioSource.clip = coinSound;
            audioSource.Play();
        }

這樣就沒有邏輯上的問題了。
大家注意一下, audioListener 不是 bool 變量,但是它卻可以直接在 if 語句上當做表達式判斷。這是因為 Unity 對所有的 UnityEngine.Object 類型做了一個運算符重載。像我們的 MonoBehaviour、GameObject、Transform 全部都是可以這樣做的,因為它們都繼承了 UnityEngine.Object。

到這裡我們再運行一次示例。運行之後沒有任何問題。

006tNc79gy1fzgf58h58sj30ms07xdgz.jpg
我們看到運行之後創建了兩個 AudioSource。

不過這個問題不大。

今天的內容就這些,我們下一篇再見。

轉載請註明地址:涼鞋的筆記:liangxiegame.com

訂閱全套專欄:liangxiegame.com

相關焦點

  • 85.AudioManager 背景音樂播放功能
    在上一篇,我們完成了音頻播放功能呢,並且修復了一個 AudioListener 重複的問題。我們今天再往下接著學習。
  • Android音頻播放AudioTrack詳解
    Android 中常用的播放音頻的接口有MediaPlayer、AudioTrack和SoundPool,音頻的渲染最常用的是AudioTrack和OpenSL ES ,下面將介紹下AudioTrack相關知識,主要內容如下:AudioTrack介紹AudioTrack用來點播放原始
  • ffmpeg解碼音頻,使用AudioQueue 播放
    了解ffmpeg解碼同時播放音頻與視頻的流程本節採用了解碼、播放音頻、渲染視頻各自己在單獨的線程中進行,共享數據緩衝區。解碼線程利用ffmpeg從數據流中讀取數據,並將音頻與視頻數據幀進行解碼轉碼後分別存儲到音頻與視頻緩衝區。
  • iOS Audio 手把手: 錄音、播放、音頻播放控制(音量採樣檢測等),Swift5,基於 AVFoundation
    iOS 設備中,每一個應用 app,都有一個音頻會話 Audio Session.app 調用音頻相關,自然會用到 iOS 的硬體功能。音頻會話 Audio Session ,就是來管理音頻操作的。iOS 使用音頻,管理粒度很細你覺得:後臺播放的音樂,要不要與你 app 的音頻,混雜在一起?
  • 音視頻開發之旅(三)AudioTrack播放PCM音頻
    API,常見的是MediaPlayer和AudioTrack其中AudioTrack管理、播放單一音頻資源。可以將PCM音頻數據傳輸到音頻接收器,以供播放,只能播放源碼流即PCM,wav封裝格式的音頻也可以用AudioTrack播放,但是wav頭部分在播放解析時會發出噪音。而MediaPlayer可以播放多種格式的音頻文件,比如 mp3 aac等,因為MediaPlayer會在framework層創建對應的音頻解碼器。
  • Android 音視頻開發-- 使用AudioRecord 錄製PCM(錄音),AudioTrack播放音頻
    今天要完成的功能如下;使用 AudioTrack 播放 pcm 格式音頻 (Stream 和 static 模式)由於聲音不好上動圖,只能來一張靜圖了,具體代碼看工程:https://github.com/LillteZheng/VideoDemo一、基礎知識
  • Android Audio音頻系統
    、AudioFlinger的openOutput()方法的調用流程分析十五、Audio系統為了能正常播放音頻數據,需要創建抽象的音頻輸出接口對象,打開音頻輸出過程十六、打開音頻輸入的流程十七、打開音頻輸出後,在AudioFlinger與AudioPolicyService中的表現形式十八、打開音頻輸入後,在AudioFlinger與AudioPolicyService
  • Android音頻管理之AudioManager
    A:獲取實例 由於音頻管理涉及到多媒體,因此這個 AudioManager 獲取實例的姿勢是這樣的:AudioManager audio = (AudioManager)Context.getSystemService(Context.AUDIO_SERVICE);B:豐富的API
  • 【第2019期】JS純前端實現audio音頻剪裁剪切複製播放與上傳
    下面,就以「截取用戶上傳音頻前3秒內容」的需求示意下如何藉助Web Audio API實現音頻的部分複製與播放功能。不嗶嗶,直接正題實現步驟如下。File對象轉ArrayBuffer在Web網頁中,用戶選擇的文件是個file對象,我們可以將這個文件對象轉換成Blob、ArrayBuffer或者Base64。
  • 音視頻開發之旅(35) -FFmpeg + AudioTrack 實現音頻解碼和播放
    這篇我們來實現下音頻的解碼器。解碼流程和視頻的基本一致。FFmpeg解碼的音頻裸數據是PCM格式,android上播放PCM音頻數據可以通過AudioTrack和OpenSL ES來實現。下面我們下來看下解碼的流程一、音頻解碼流程和上一篇的視頻解碼流程基本一致。
  • 新品 | 2018柏韻10周年紀念產品:Pureaudio AirDSD音頻流播放解碼器發布!
    就在2008年,柏韻音頻 Pureaudio 部門成立,公司成立之初以設計製作優質性價比高的膽機起步,而經歷了10年的發展,今年正式推出全新的Hi-Res音頻流播放解碼器,同時這也是柏韻音頻 Pureaudio 紀念成立10周年的心血之作。
  • Android Audio音頻架構
    Kernel - ASoC driverALSA 片上系統 (ASoC) 驅動程序將音頻系統分為四個組成部分Machine driver、Platform driver、CPU driver、Codec driver。
  • HTML5 音頻 API Web Audio
    方法AudioContext.createBufferSource()創建一個 AudioBufferSourceNode 對象, 他可以通過 AudioBuffer 對象來播放和處理包含在內的音頻數據。
  • Hi-Res High Resolution Audio,高解析音頻
    Hi-res是由JAS(日本音頻協會)和CEA(消費電子協會)制定的高品質音頻產品設計標準Hi-Res是High Resolution Audio的縮寫,而High Resolution就是高解析力、高解析度的意思,這個詞在圖像顯示領域運用得比較多,比如說4k解析度就可以High Resolution來形容,如此一來,High
  • Python 還能播放音頻,而且花樣多多?
    許您播放一系列音頻格式,包括MP3和NumPy數組。playsound:如果您只想播放WAV或MP3文件,可以使用最簡單的軟體包。它只提供簡單的回放功能。simpleaudio:允許您播放WAV文件和NumPy數組,並為您提供檢查文件是否仍在播放的選項。
  • Android 音頻系統:從 AudioTrack 到 AudioFlinger
    AudioTrack API 概述播放聲音可以使用 MediaPlayer 和 AudioTrack,兩者都提供 Java API 給應用開發者使用。兩者的差別在於:MediaPlayer 可以播放多種格式的音源,如 mp3、flac、wma、ogg、wav 等,而 AudioTrack 只能播放解碼後的 PCM 數據流。
  • Web Audio API介紹和web音頻應用案例分析
    ,它們之間相互連接且無迴路,類似一個有向圖。一次成功音頻播放必須有源節點和目的節點,即sourceNode ——> destinationNode。音頻從源節點到目的節點的中間可以有許多中間節點,這一點類似路由拓撲圖,節點間必須暢通才能實現音頻的播放。每個AudioContext對象可以一多個音頻源節點實例,但是只能有一個目的節點實例。AudioContext的中間節點實例可以對音頻進行處理,如音頻可視化、音效處理。
  • 第六節:用
    <audio>標籤:用於在文檔中表示音頻內容。利用它,你可以在你的個人網站上放一首你喜歡的歌。    <audio src="music.mp3"></audio>用法很簡單,跟<video>標籤一樣,屬性src指定音頻文件地址。
  • iOS音頻開發(錄音+播放+剪輯+合成+壓縮轉碼)
    AVAudioSessionCategoryPlayback :用於以語音為主的應用,不會隨著靜音鍵和屏幕關閉而靜音.可在後臺播放聲音AVAudioSessionCategoryRecord :用於需要錄音的應用,除了來電鈴聲,鬧鐘或日曆提醒之外的其它系統聲音都不會被播放,只提供單純錄音功能.
  • 音視頻開發之旅(二)AudioRecord錄製PCM音頻
    的使用(構造、開始錄製、停止錄製、其他細節點)ffplay播放pcmpcm轉為wav小結一、音頻採集API AudioRecord和MediaRecorderAndroidSDK提供了兩套音頻錄製的API,AudioRecord和MediaRecorder。