iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
Vibe Coding

從課堂半成品到完整發布:獨立遊戲的30天重生記系列 第 5

【DAY5】Unity 2D 像素風遊戲開發: AudioMixer 分貝轉化與音量設定持久化 (PlayerPrefs)】

  • 分享至 

  • xImage
  •  

一、前言:
昨天我們拉好了 Settings UI 的像素金幣 Slider 介面。今天彌補STD 測試報告中的遺憾之一 — UR-01 設定持久化。在原先的專題版本中,音量設定隨遊戲重啟就會重置,且線性 Slider 數值直接套用在聲音上非常不符合人耳的對數聽覺體驗。今天我們將透過 AudioMixer 的分貝(dB)轉化算式,結合 PlayerPrefs 實現真正的音量跨場景與重啟持久化!
今日開發目標:
1.AudioMixer 混音架構建置: 建立 Master、BGM 與 SFX 獨立軌道並暴露參數(Expose Parameters)。
2. 對數分貝轉化(Logarithmic dB Conversion): 解決 Slider 線性數值($0 \sim 1$)與人耳感知(dB)的落差,套用 $20 \times \log_{10}(\text{value})$ 公式。
3. 設定持久化(PlayerPrefs): 玩家調整滑桿時即時寫入本地,遊戲啟動時自動載入。
4. UI 與單例事件解耦: 防止 Slider 在初始化賦值時誤觸發 OnValueChanged 導致音量暴跌。

二、 AudioMixer 參數暴露與 2D 屬性設定
1. 開啟並建立 AudioMixer 視窗
從 Unity 頂部選單點選 Window -> Audio -> Audio Mixer 開啟混音器面板:
https://ithelp.ithome.com.tw/upload/images/20260911/20184073hqsHfAMxna.png
圖一、開啟 AudioMixer 視窗

2. 建立 Groups 軌道與參數暴露
建立 MainMixer 後,在 Groups 區塊底下新增 Master、BGM 與 SFX 軌道,並將 Volume 屬性 Expose 給腳本使用:
https://ithelp.ithome.com.tw/upload/images/20260911/20184073Zb2eY2LE5y.png
圖二、建立Groups軌道

3. AudioManager 單例物件配置
在 Hierarchy 中建立 AudioManager,掛載 AudioManager.cs 腳本,並將剛剛建立的 MainMixer 拖入欄位中作為總控制台:
https://ithelp.ithome.com.tw/upload/images/20260911/20184073hdPbqoYjq7.png
圖三、AudioManager 腳本掛載

三、 核心程式碼實作

  1. 音量總控制與持久化管理(為避免篇幅過長,以下擷取 AudioManager.cs 的單例初始化與核心音量轉換邏輯(BGM 與 SFX 採相同架構)):
using UnityEngine;
using UnityEngine.Audio;
public class AudioManager : MonoBehaviour {
    public static AudioManager Instance { get; private set; }
    [Header("Audio Mixer 設定")]
    [SerializeField] private AudioMixer mainMixer;
    private const string MASTER_KEY = "MasterVolume";
    
    private void Awake() {
        // 確保全域唯一並跨場景不銷毀
        if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); }
        else { Destroy(gameObject); }
    }
    private void Start() {
        // 載入儲存的音量設定(若無紀錄則預設 1.0f)
        SetMasterVolume(PlayerPrefs.GetFloat(MASTER_KEY, 1.0f));
        // (BGM 與 SFX 初始化邏輯以此類推...)
    }
    public void SetMasterVolume(float value)
    {
        // 關鍵:限制最小值避免 Log10(0) 產生負無限大 (-Inf)
        float clampedValue = Mathf.Clamp(value, 0.0001f, 1f);
        float dB = Mathf.Log10(clampedValue) * 20f;
        if (mainMixer != null)
            mainMixer.SetFloat("MasterVol", dB);
        PlayerPrefs.SetFloat(MASTER_KEY, value);
        PlayerPrefs.Save();
    }
    // SetBGMVolume、SetSFXVolume 與 Get 相關方法結構相同,此處省略以保持版面簡潔...
}
  1. UI 與事件解耦(為避免篇幅過長,以下擷取 SettingsPanelManager.cs 的核心防呆機制(Unregister/Register 避免誤觸發)與事件綁定邏輯進行講解):
using UnityEngine;
using UnityEngine.UI;
public class SettingsPanelManager : MonoBehaviour {
    [Header("面板與滑桿設定")]
    [SerializeField] private GameObject floatingPanel;
    [SerializeField] private Slider masterSlider;
    // [SerializeField] private Slider bgmSlider; ... (其餘滑桿以此類推)

    private void OnEnable() {
        // 每次面板開啟時,同步當前音量到 UI
        UpdateSliderValues();
    }

    private void UpdateSliderValues() {
        if (AudioManager.Instance == null) return;

        // 關鍵防呆:賦值前先解除監聽,防止 UI value 改變時誤觸發事件造成無限迴圈或不必要儲存
        UnregisterSliderEvents();
        if (masterSlider != null) 
            masterSlider.value = AudioManager.Instance.GetMasterVolume();
        RegisterSliderEvents();
    }
    private void RegisterSliderEvents() {
        UnregisterSliderEvents(); // 確保不重複註冊
        if (masterSlider != null) 
            masterSlider.onValueChanged.AddListener(OnMasterVolumeChanged);
    }
    private void UnregisterSliderEvents() {
        if (masterSlider != null) 
            masterSlider.onValueChanged.RemoveListener(OnMasterVolumeChanged);
    }
    public void OpenPanel() {
        if (floatingPanel != null) floatingPanel.SetActive(true);
    }
    public void ClosePanel() {
        if (floatingPanel != null) floatingPanel.SetActive(false);
    }
    public void OnMasterVolumeChanged(float value) {
        if (AudioManager.Instance != null)
            AudioManager.Instance.SetMasterVolume(value);
    }
    // OnBGMVolumeChanged / OnSFXVolumeChanged 邏輯相同...
}

四、 開發實戰踩雷紀錄
在今天的實作過程中,我們遇到了幾個非常典型的 Unity Audio 踩雷點:
1. Play 模式下調整 AudioMixer 無法儲存:

  • 問題:在 Play 模式下手動調整 Mixer 推桿,停止遊戲後數值跳回 -80 dB。
  • 解法:必須點擊 AudioMixer 視窗上的 Edit in Play Mode 按鈕,調整的數值才會在退出 Play 模式後被保留。

2. Slider 初始化導致音量暴跌至 -53.8 dB:

  • 問題:面板開啟瞬間,Slider 的 Value 觸發了 OnValueChanged,將剛載入的小數傳給 Mixer。
  • 解法:在 UpdateSliderValues() 中使用 RemoveListener / AddListener 阻斷初始化的事件傳遞。
    https://ithelp.ithome.com.tw/upload/images/20260911/20184073v04aflkVbP.png
    圖四、Edit in Play Mode

五、 今日成果與後續規劃

  • STD UR-01 設定持久化 宣告通過:重新開啟遊戲後,音量大小與 UI Slider 位置皆能 100% 恢復上次設定。
  • 聽覺平滑度優化:透過 20×log10(value) 公式,大幅提升拉動像素金幣 Slider 時的聲音變化自然度。

至此,我們遊戲的核心音效框架(BGM 管理、音量控制、設定持久化)已經正式告一段落! 基礎的聲音生態體系已完全建立完畢。不過,音效系統並不是就此落幕,在後續的開發中我們還會在以下環節回頭呼叫它:
(1)互動音效:按鈕點擊、選單切換時的 SFX 觸發。
(2)劇情與對話系統:打字機音效(Typewriter SFX)與劇情關鍵點的 BGM 切換。

最後,別忘了推送今天的進度:
1.檢查修改狀態
git status

2.加入暫存區並提交 Commit
git add .
git commit -m "feat: 完成 Audio System 音效架構重構與設定持久化"

3.推送到遠端開發分支
git push origin refactor/audio-system


上一篇
【DAY4】Unity 2D 像素風遊戲開發:Settings UI 面板重構與像素錢幣 Slider 音量控制整合
下一篇
【DAY6】Unity 2D 敘事遊戲開發:場景銜接與單向流轉機制 (SceneChanger)
系列文
從課堂半成品到完整發布:獨立遊戲的30天重生記17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言