在游戏开发中,我们经常需要频繁创建一些物体,诸如射击游戏的子弹、频繁的脚步声,如果频繁创建和销毁,势必会造成大量性能的开销。于是就有了缓存池,以及在缓存池基础上发展出的音效系统。
缓存池脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 using System.Collections.Generic; using UnityEngine; public class GamePoolManager : SingleMono<GamePoolManager> { [System.Serializable] private class PoolItem { public string itemName; public GameObject item; public int itemCount; } [Header("对象池配置")] [SerializeField] private List<PoolItem> poolList = new List<PoolItem>(); private Dictionary<string, Queue<GameObject>> poolDic = new Dictionary<string, Queue<GameObject>>(); private GameObject _poolParent; protected override void Awake() { _poolParent = new GameObject("对象池父对象"); _poolParent.transform.SetParent(transform); InitPool(); } private void InitPool() { foreach (var poolItem in poolList) { if (string.IsNullOrEmpty(poolItem.itemName) || poolItem.item == null || poolItem.itemCount <= 0) { Debug.LogWarning("无效的对象池配置项"); continue; } if (!poolDic.ContainsKey(poolItem.itemName)) { poolDic.Add(poolItem.itemName, new Queue<GameObject>()); } for (int j = 0; j < poolItem.itemCount; j++) { var itemObj = Instantiate(poolItem.item); itemObj.transform.SetParent(_poolParent.transform); itemObj.SetActive(false); poolDic[poolItem.itemName].Enqueue(itemObj); } } } public void TryGetPoolItem(string name, Vector3 position, Quaternion rotation) { if (poolDic.TryGetValue(name, out var queue) && queue.Count > 0) { GameObject item = queue.Dequeue(); item.transform.position = position; item.transform.rotation = rotation; item.SetActive(true); queue.Enqueue(item); } else { Debug.LogWarning($"对象池中找不到: {name}"); } } public GameObject TryGetPoolItem(string name) { if (poolDic.TryGetValue(name, out var queue) && queue.Count > 0) { GameObject item = queue.Dequeue(); item.SetActive(true); queue.Enqueue(item); return item; } Debug.LogWarning($"对象池中找不到: {name}"); return null; } }
音效系统脚本 首先是SoundType脚本
1 2 3 4 5 6 7 8 public enum SoundType { ATK, HIT, BLOCK, Run, Walk }
其次是SoundData脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 using UnityEngine; using System.Collections.Generic; [CreateAssetMenu(fileName = "SoundData", menuName = "Scriptable Objects/SoundData")] public class SoundData : ScriptableObject { [SerializeField] private List<Sound_Data> _data = new List<Sound_Data>(); [System.Serializable] private class Sound_Data { public SoundType soundType; public AudioClip[] audioClip; } public AudioClip GetAudioClip(SoundType type) { foreach (var data in _data) { if (data.soundType == type && data.audioClip != null && data.audioClip.Length > 0) { return data.audioClip[Random.Range(0, data.audioClip.Length)]; } } Debug.LogWarning($"找不到音效类型: {type}"); return null; } }
最后是Sound脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 using UnityEngine; public class Sound : MonoBehaviour { private AudioSource audioSource; [Header("音效设置")] [SerializeField] private SoundType soundType; [SerializeField] private SoundData soundData; private void Awake() { audioSource = GetComponent<AudioSource>(); if (audioSource == null) { audioSource = gameObject.AddComponent<AudioSource>(); audioSource.playOnAwake = false; } } private void OnEnable() { PlaySound(soundType); } private void PlaySound(SoundType type) { if (soundData == null) { Debug.LogError("SoundData未配置!"); return; } AudioClip clip = soundData.GetAudioClip(type); if (clip == null) { gameObject.SetActive(false); Debug.Log("获取不到音频类型,请检查SoundType脚本和预制体是否配置了"); return; } audioSource.clip = clip; audioSource.Play(); StartRecycle(clip.length); } private void StartRecycle(float clipLength) { float delay = Mathf.Max(0.1f, clipLength); Invoke(nameof(DisableSelf), delay); } private void DisableSelf() { if (audioSource != null && audioSource.isPlaying) audioSource.Stop(); gameObject.SetActive(false); } }
原理分析 GamePoolManager提供了InitPool方法和TryGetPoolItem方法,InitPool方法会创建出多个对象并默认失活,TryGetPoolItem方法既可以处理音频创建、也可以处理物体创建,会激活一个对象。 而音效系统的代码逻辑,SoundData用来存储所有的音频类型和数据,Sound预制体在被激活时会自动识别我们配置的音频类型、并随机播放音频。最后加入对象池中,形成这样的顺序:对象池创建并激活预制体——识别预制体的音频类型、随机播放对应音频。
## 使用方法
音效系统使用 一,更改SoundType脚本,里面的枚举是你的音效类型,按需修改。 二,右键Scriptable Object,创建一个SoundData,配置好你所需要的音频类型、以及对应的音频。 三,游戏里创建Sound空物体,拖入Sound.cs脚本,为每一个空物体配置好音频类型、并拖入创建好的SoundData物体,保存作为预制体。有多少个类型的音频创建多少个音频预制体。 四,游戏再创建空物体,命名GamePoolManager,拖入GamePoolManager.cs脚本。Item Name输入缓存池里物体的名字、Item拖入你上一步创建好的各个音频预制体,Item Count则是缓存池存的物体的数量。 五,配置好动画事件,在Player_Model脚本写入动画事件函数:
1 2 3 4 5 public void FootStep() { //括号里传入Item Name的名字 GamePoolManager.MainInstance.TryGetPoolItem("Walk"); }