Files
2026-07-05 01:36:58 +08:00

139 lines
4.3 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace GameFramework
{
/// <summary>
/// 全局音频管理器(持久单例)。
/// 管理 BGM(背景音乐,循环)和 SFX(音效,一次性)。
/// 音量通过 PlayerPrefs 持久化。
/// </summary>
public class AudioManager : PersistentSingleton<AudioManager>
{
const string PREF_BGM_VOLUME = "AudioManager_BGMVolume";
const string PREF_SFX_VOLUME = "AudioManager_SFXVolume";
AudioSource bgmPlayer; // 背景音乐播放器
AudioSource sfxPlayer; // 音效播放器
float bgmVolume = 1f;
float sfxVolume = 1f;
public float BGMVolume => bgmVolume;
public float SFXVolume => sfxVolume;
protected override void Awake()
{
base.Awake();
// 如果是重复实例,base.Awake 已调用 Destroy,这里直接返回
if (Instance != this) return;
// 创建两个 AudioSource
bgmPlayer = gameObject.AddComponent<AudioSource>();
bgmPlayer.loop = true;
bgmPlayer.playOnAwake = false;
sfxPlayer = gameObject.AddComponent<AudioSource>();
sfxPlayer.loop = false;
sfxPlayer.playOnAwake = false;
// 读取持久化音量
bgmVolume = PlayerPrefs.GetFloat(PREF_BGM_VOLUME, 1f);
sfxVolume = PlayerPrefs.GetFloat(PREF_SFX_VOLUME, 1f);
bgmPlayer.volume = bgmVolume;
sfxPlayer.volume = sfxVolume;
}
#region BGM
/// <summary>
/// 播放背景音乐(循环)。
/// </summary>
public void PlayBGM(AudioClip clip, bool restart = false)
{
if (clip == null) return;
if (bgmPlayer.isPlaying && bgmPlayer.clip == clip && !restart)
return;
bgmPlayer.clip = clip;
bgmPlayer.volume = bgmVolume;
bgmPlayer.Play();
}
public void StopBGM()
{
if (bgmPlayer.isPlaying)
bgmPlayer.Stop();
}
public void PauseBGM() => bgmPlayer.Pause();
public void UnpauseBGM() => bgmPlayer.UnPause();
public void SetBGMVolume(float volume)
{
bgmVolume = Mathf.Clamp01(volume);
bgmPlayer.volume = bgmVolume;
PlayerPrefs.SetFloat(PREF_BGM_VOLUME, bgmVolume);
PlayerPrefs.Save();
}
#endregion
#region SFX
/// <summary>
/// 播放单个音效。
/// </summary>
public void PlaySFX(AudioData data)
{
if (data == null || data.clip == null) return;
// sfxPlayer.volume 已在 Awake 中设为 sfxVolume
// PlayOneShot 的 volumeScale 再乘以 data.volume
// 最终音量 = sfxVolume * data.volume
sfxPlayer.PlayOneShot(data.clip, data.volume);
}
/// <summary>
/// 从列表中随机播放一个音效。
/// </summary>
public void PlayRandomSFX(AudioData data, List<AudioData> variants = null)
{
if (variants != null && variants.Count > 0)
{
PlaySFX(variants[Random.Range(0, variants.Count)]);
return;
}
PlaySFX(data);
}
/// <summary>
/// 直接通过 AudioClip 播放音效。
/// </summary>
public void PlaySFX(AudioClip clip, float volume = 1f)
{
if (clip == null) return;
sfxPlayer.PlayOneShot(clip, volume);
}
/// <summary>
/// 通过 Resources 路径加载并播放音效。
/// 路径相对于 Assets/audios/Resources/,例如 "SFX/Bell"
/// </summary>
public void PlaySFXFromResources(string resourcePath, float volume = 1f)
{
var clip = Resources.Load<AudioClip>(resourcePath);
if (clip == null)
{
Debug.LogWarning($"[AudioManager] SFX not found: {resourcePath}");
return;
}
PlaySFX(clip, volume);
}
public void SetSFXVolume(float volume)
{
sfxVolume = Mathf.Clamp01(volume);
PlayerPrefs.SetFloat(PREF_SFX_VOLUME, sfxVolume);
PlayerPrefs.Save();
}
#endregion
}
}