一、前言:
昨天我們拉好了 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 開啟混音器面板:
圖一、開啟 AudioMixer 視窗
2. 建立 Groups 軌道與參數暴露
建立 MainMixer 後,在 Groups 區塊底下新增 Master、BGM 與 SFX 軌道,並將 Volume 屬性 Expose 給腳本使用:
圖二、建立Groups軌道
3. AudioManager 單例物件配置
在 Hierarchy 中建立 AudioManager,掛載 AudioManager.cs 腳本,並將剛剛建立的 MainMixer 拖入欄位中作為總控制台:
圖三、AudioManager 腳本掛載
三、 核心程式碼實作
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 相關方法結構相同,此處省略以保持版面簡潔...
}
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 無法儲存:
2. Slider 初始化導致音量暴跌至 -53.8 dB:

五、 今日成果與後續規劃
至此,我們遊戲的核心音效框架(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