基于ET框架的Unity游戏音效管理组件(转)
介绍
本组件是基于强大的ET框架之上的,此处附上et框架链接:https://github.com/egametang/ET
当然如果不使用et的话,也可自行修改为单例模式进行使用,非常简单易用
本音效组件主要分三个文件:
SoundComponent.cs——游戏音效管理组件
SoundData.cs——每个音效单元类
EditorCreateAudio.cs——创建音效预设(Editor类)
特点:支持控制全局音量变化,音乐静音及音效静音
流程:.mp3等资源–> . prefab预制体并附上SoundData单元类–> SoundComponent中通过ab加载. prefab并实例化–> SoundComponent调用SoundData去控制每一个音效
组件部分思路源于海马哥,谢谢!
SoundData(音效资源类)
请看代码,一目了然,简单明了
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Model
{
[RequireComponent(typeof(AudioSource))]
public class SoundData : MonoBehaviour
{
//音频源控件
public new AudioSource audio;
/// <summary>
/// 是否强制重新播放
/// </summary>
//[HideInInspector]
public bool isForceReplay = false;
/// <summary>
/// 是否循环播放
/// </summary>
//[HideInInspector]
public bool isLoop = false;
/// <summary>
/// 音量
/// </summary>
//[HideInInspector]
public float volume = 1;
/// <summary>
/// 延迟
/// </summary>
//[HideInInspector]
public ulong delay = 0;
public AudioSource GetAudio()
{
return audio;
}
public bool IsPlaying
{
get
{
return audio != null && audio.isPlaying;
}
}
public bool IsPause
{
get;
set;
}
public void Dispose()
{
Destroy(gameObject);
}
/// <summary>
/// 音效类型
/// </summary>
public SoundType Sound { get; set; }
public bool Mute
{
get { return audio.mute; }
set { audio.mute = value; }
}
public float Volume
{
get { return audio.volume; }
set { audio.volume = value; }
}
}
}
EditorCreateAudio(工具类)
自动将目录下的音效资源文件创建成Prefab预制体,方便加载和管理
PS:务必给Prefab加上ab标识,打进需要的ab包,也可以自行在类中拓展标上ab标识的代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
namespace Model
{
public class EditorCreateAudio : MonoBehaviour
{
//音效资源路径
private static string audiosDir = “assets/art/audios”;
//导出预制体路径
private static string prefabDir = “assets/ETAB/sounds”;
[MenuItem(“ZjjManager/创建音效预设”, priority = 1004)]
static void CreateAudioPrefab()
{
string[] _patterns = new string[] { “*.mp3”,“*.wav”, “*.ogg” };//识别不同的后缀名
List<string> _allFilePaths = new List<string>();
foreach (var item in _patterns)
{
string[] _temp = Directory.GetFiles(audiosDir, item,SearchOption.AllDirectories);
_allFilePaths.AddRange(_temp);
}
foreach (var item in _allFilePaths)
{
System.IO.FileInfo _fi = new System.IO.FileInfo(item);
var _tempName = _fi.Name.Replace(_fi.Extension, “”).ToLower();
AudioClip _clip = AssetDatabase.LoadAssetAtPath<AudioClip>(item);
if(null != _clip)
{
GameObject _go = new GameObject();
_go.name = _tempName;
AudioSource _as = _go.AddComponent<AudioSource>();
_as.playOnAwake = false;
SoundData _data = _go.AddComponent<SoundData>();
_data.audio = _as;
_data.audio.clip = _clip;
var temp = PrefabUtility.CreatePrefab(prefabDir + _tempName + “.prefab”, _go);
GameObject.DestroyImmediate(_go);
EditorUtility.SetDirty(temp);
Resources.UnloadAsset(_clip);
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
}
}
SoundComponent(音效管理组件)
各部分我都有注释,各位可以自行理解,简易上手
PS:我用的是et2.0,各位用新版et的请自行修改事件系统的调用等
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GGLib;
using UnityEngine;
namespace Model
{
/// <summary>
/// 音效类型
/// </summary>
public enum SoundType
{
Music,//长音乐
Sound,//短音乐
}
[ObjectEvent]
public class SoundComponentEvent : ObjectEvent<SoundComponent>, IAwake
{
public void Awake()
{
this.Get().Awake();
}
}
/// <summary>
/// 游戏音效管理组件
/// </summary>
public class SoundComponent:Component
{
public static SoundComponent Instance;
/// <summary>
/// 控制游戏全局音量
/// </summary>
public float SoundVolume
{
get
{
return _soundVolume;
}
set
{
_soundVolume = Mathf.Clamp(value, 0, 1);
foreach (SoundData clip in m_clips.Values)
{
clip.Volume = _soundVolume * clip.volume;
}
}
}
private float _soundVolume = 0.8f;
//所有音效
private Dictionary<string, SoundData> m_clips = new Dictionary<string, SoundData>();
//根据类型分类所有音效
private Dictionary<SoundType, Dictionary<string, SoundData>> _allClips = new Dictionary<SoundType, Dictionary<string, SoundData>>()
{ { SoundType.Music, new Dictionary<string, SoundData>() }, { SoundType.Sound, new Dictionary<string, SoundData>() } };
//catch ab资源
private static Dictionary<string, SoundData> abSounds = new Dictionary<string, SoundData>();
//根物体
private Transform root;
/// <summary>
/// 音乐静音
/// </summary>
public bool MusicMute
{
get { return _musicMute; }
set
{
_musicMute = value;
foreach (var soundData in _allClips[SoundType.Music].Values)
{
soundData.Mute = _musicMute;
}
PlayerPrefs.SetInt(“MusicMute”, value ? 1 : 0);
}
}
private bool _musicMute = false;
/// <summary>
/// 音效静音
/// </summary>
public bool SoundMute
{
get { return _soundMute; }
set
{
_soundMute = value;
foreach (var soundData in _allClips[SoundType.Sound].Values)
{
soundData.Mute = _soundMute;
}
PlayerPrefs.SetInt(“SoundMute”, value ? 1 : 0);
}
}
private bool _soundMute = false;
public void Awake()
{
Instance = this;
_musicMute = PlayerPrefs.GetInt(“MusicMute”,0) == 1;
_soundMute = PlayerPrefs.GetInt(“SoundMute”, 0) == 1;
root = new GameObject(“SoundDatas”).transform;
GameObject.DontDestroyOnLoad(root.gameObject);
}
private bool IsContainClip(string clipName)
{
lock (m_clips)
{
if (m_clips.ContainsKey(clipName))
return true;
return false;
}
}
private SoundData GetAudioSource(string clipName)
{
if (IsContainClip(clipName))
return m_clips[clipName];
return null;
}
private void AddClip(string clipName, SoundData data, SoundType type)
{
lock (m_clips)
{
data.IsPause = false;
data.transform.transform.SetParent(root);
data.Sound = type;
if (IsContainClip(clipName))
{
m_clips[clipName] = data;
_allClips[type][clipName] = data;
}
else
{
m_clips.Add(clipName, data);
_allClips[type].Add(clipName, data);
}
}
}
/// <summary>
/// 短暂的声音和特效
/// 无法暂停
/// 异步加载音效
/// </summary>
public async void PlayClip(string clipName, float volume = 1)
{
SoundData sd = await LoadSound(clipName);
if (sd != null)
{
sd.volume = Mathf.Clamp(volume, 0, 1);
sd.Mute = SoundMute;
if (!IsContainClip(clipName))
{
AddClip(clipName, sd, SoundType.Sound);
}
PlayMusic(clipName, sd);
}
else
{
Log.Error($“没有此音效={clipName}”);
}
}
/// <summary>
/// 播放长音乐 背景音乐等
/// 可以暂停 继续播放
/// 异步加载音效
/// </summary>
/// <param name=”clipName”>声音的预设名字(不包括前缀路径名)</param>
/// <param name=”delay”>延迟播放 单位秒</param>
/// <param name=”volume”>音量</param>
/// <param name=”isloop”>是否循环播放</param>
/// /// <param name=”forceReplay”>是否强制重头播放</param>
public async void PlayMusic(string clipName, ulong delay = 0, float volume = 1, bool isloop = false, bool forceReplay = false)
{
SoundData sd = await LoadSound(clipName);
if (sd != null)
{
sd.isForceReplay = forceReplay;
sd.isLoop = isloop;
sd.delay = delay;
sd.volume = Mathf.Clamp(volume,0,1);
sd.Mute = MusicMute;
if (!IsContainClip(clipName))
{
AddClip(clipName, sd, SoundType.Music);
}
PlayMusic(clipName, sd);
}
else
{
Log.Error($“没有此音效={clipName}”);
}
}
//加载声音
private async Task<SoundData> LoadSound(string soundName)
{
ResourcesComponent resourcesComponent = Game.Scene.GetComponent<ResourcesComponent>();
if (!abSounds.ContainsKey(soundName)||abSounds[soundName]==null)
{
await resourcesComponent.LoadBundleAsync(ResourcesLoadHelper.Sound);
abSounds.Add(soundName, GameObject.Instantiate(resourcesComponent.GetAsset<GameObject>(ResourcesLoadHelper.Sound, soundName)).GetComponent<SoundData>());
resourcesComponent.UnloadBundle(ResourcesLoadHelper.Sound);
}
return abSounds[soundName];
}
//播放SoundData
private void PlayMusic(string clipName, SoundData asource)
{
if (null == asource)
return;
bool forceReplay = asource.isForceReplay;
asource.audio.volume = asource.volume * SoundVolume;
asource.audio.loop = asource.isLoop;
if (!forceReplay)
{
if (!asource.IsPlaying)
{
if (!asource.IsPause)
asource.audio.Play(asource.delay);
else
Resume(clipName);
}
}
else
{
asource.audio.PlayDelayed(asource.delay);
asource.audio.PlayScheduled(0);
}
}
/// <summary>
/// 停止并销毁声音
/// </summary>
/// <param name=”clipName”></param>
public void Stop(string clipName)
{
SoundData data = GetAudioSource(clipName);
if (null != data)
{
if (_allClips[data.Sound].ContainsKey(clipName))
{
_allClips[data.Sound].Remove(clipName);
}
m_clips.Remove(clipName);
abSounds.Remove(clipName);
data.Dispose();
}
}
/// <summary>
/// 暂停声音
/// </summary>
/// <param name=”clipName”></param>
public void Pause(string clipName)
{
SoundData data = GetAudioSource(clipName);
if (null != data)
{
data.IsPause = true;
data.audio.Pause();
}
}
/// <summary>
/// 继续播放
/// </summary>
/// <param name=”clipName”></param>
public void Resume(string clipName)
{
SoundData data = GetAudioSource(clipName);
if (null != data)
{
data.IsPause = false;
data.audio.UnPause();
}
}
/// <summary>
/// 销毁所有声音
/// </summary>
public void DisposeAll()
{
foreach (var allClip in _allClips.Values)
{
allClip.Clear();
}
foreach (var item in m_clips)
{
item.Value.Dispose();
}
m_clips.Clear();
}
}
}
下载
下载:
GitHub(国外):
ET模组仓库: https://github.com/egametang/ET-Modules.git
本人工具仓库:https://github.com/HealthyChina/HealthyResource.git
Gitee(国内):
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)