文章名字是我杜撰的,之前一直在做服务器开发,上周有机会接触了客户端,发现很多资源没有有效管理起来,甚至有资源泄露的发生,我就先针对特效做了pool,结果在一定程度上纠正之前一直很难解决的位置同步问题。
总结上来客户端的资源有:模型、特效、音频、动画等。
音频怎么管理起来呢,http://answers.unity3d.com/questions/482218/best-practices-for-playing-a-lot-of-audio.html这个链接的撸主也提出同样问题:大概是项目有大量音频,如果都是运行时加载,开销太大,怎么解决呢?
这个是下周我的一个私人课题,哈哈,毕竟是业余客户端,首先了解Audio的相关API,如果播放音频,停止播放,暂停,重新播放等,如果在某个地点或者某个对象上播放;基本上控制好这几个操作就可以写一个简单的AudioPool了。
网上也找到一个简单的AudioMgr:
// ///////////////////////////////////////////////////////////////////////////////////////////////////////// // // Audio Manager. // // This code is release under the MIT licence. It is provided as-is and without any warranty. // // Developed by Daniel Rodríguez (Seth Illgard) in April 2010 // http://www.silentkraken.com // // ///////////////////////////////////////////////////////////////////////////////////////////////////////// using UnityEngine; using System.Collections; public class AudioManager : MonoBehaviour { public AudioSource Play(AudioClip clip, Transform emitter) { return Play(clip, emitter, 1f, 1f); } public AudioSource Play(AudioClip clip, Transform emitter, float volume) { return Play(clip, emitter, volume, 1f); } /// <summary> /// Plays a sound by creating an empty game object with an AudioSource /// and attaching it to the given transform (so it moves with the transform). Destroys it after it finished playing. /// </summary> /// <param name="clip"></param> /// <param name="emitter"></param> /// <param name="volume"></param> /// <param name="pitch"></param> /// <returns></returns> public AudioSource Play(AudioClip clip, Transform emitter, float volume, float pitch) { //Create an empty game object GameObject go = new GameObject ("Audio: " + clip.name); go.transform.position = emitter.position; go.transform.parent = emitter; //Create the source AudioSource source = go.AddComponent<AudioSource>(); source.clip = clip; source.volume = volume; source.pitch = pitch; source.Play (); Destroy (go, clip.length); return source; } public AudioSource Play(AudioClip clip, Vector3 point) { return Play(clip, point, 1f, 1f); } public AudioSource Play(AudioClip clip, Vector3 point, float volume) { return Play(clip, point, volume, 1f); } /// <summary> /// Plays a sound at the given point in space by creating an empty game object with an AudioSource /// in that place and destroys it after it finished playing. /// </summary> /// <param name="clip"></param> /// <param name="point"></param> /// <param name="volume"></param> /// <param name="pitch"></param> /// <returns></returns> public AudioSource Play(AudioClip clip, Vector3 point, float volume, float pitch) { //Create an empty game object GameObject go = new GameObject("Audio: " + clip.name); go.transform.position = point; //Create the source AudioSource source = go.AddComponent<AudioSource>(); source.clip = clip; source.volume = volume; source.pitch = pitch; source.Play(); Destroy(go, clip.length); return source; } }
就是对AudioClip,正是我需要补的基础知识。
然后这里还有一些Pool的知识 http://forum.unity3d.com/threads/simple-reusable-object-pool-help-limit-your-instantiations.76851/:
其中还有代码,针对特效的管理:
using UnityEngine; using System.Collections; public class Effect : MonoBehaviour { /// <summary> /// The array of emitters to fire when the effect starts. /// </summary> public ParticleEmitter[] emitters; /// <summary> /// The length of the effect in seconds. After which the effect will be reset and pooled if needed. /// </summary> public float effectLength = 1f; /// <summary> /// Should the effect be added to the effects pool after completion. /// </summary> public bool poolAfterComplete = true; /// <summary> /// Resets the effect. /// </summary> public virtual void ResetEffect () { if(poolAfterComplete) { ObjectPool.instance.PoolObject(gameObject); } else { Destroy(gameObject); } } /// <summary> /// Starts the effect. /// </summary> public virtual void StartEffect () { foreach ( ParticleEmitter emitter in emitters ) { emitter.Emit(); } StartCoroutine(WaitForCompletion()); } public IEnumerator WaitForCompletion () { //Wait for the effect to complete itself yield return new WaitForSeconds(effectLength); //Reset the now completed effect ResetEffect(); } }
比如同一设置特效长度,启动特效后启动所以离子,并启动一个协程,时间一到就ResetEffect,这时候回放到Pool里。
using UnityEngine; using System.Collections; public class SoundEffect : MonoBehaviour { /// <summary> /// The sound source that will be played when the effect is started. /// </summary> public AudioSource soundSource; /// <summary> /// The sound clips that will randomly be played if there is more than 1. /// </summary> public AudioClip[] soundClips; /// <summary> /// The length of the effectin seconds. /// </summary> public float effectLength = 1f; /// <summary> /// Should the effect be pooled after its completed. /// </summary> public bool poolAfterComplete = true; /// <summary> /// Resets the effect. /// </summary> public virtual void ResetEffect () { if(poolAfterComplete) { ObjectPool.instance.PoolObject(gameObject); } else { Destroy(gameObject); } } /// <summary> /// Starts the effect. /// </summary> public virtual void StartEffect () { soundSource.PlayOneShot(soundClips[Random.Range(0,soundClips.Length)]); StartCoroutine(WaitForCompletion()); } public IEnumerator WaitForCompletion () { //Wait for the effect to complete itself yield return new WaitForSeconds(effectLength); //Reset the now completed effect ResetEffect(); } }
音频的管理跟特效管理类似,就是播放的API不一样,操作完成后一样进行回收。
那这个池子是怎么写的呢?
using UnityEngine; using System.Collections; using System.Collections.Generic; public class ObjectPool : MonoBehaviour { public static ObjectPool instance; /// <summary> /// The object prefabs which the pool can handle. /// </summary> public GameObject[] objectPrefabs; /// <summary> /// The pooled objects currently available. /// </summary> public List<GameObject>[] pooledObjects; /// <summary> /// The amount of objects of each type to buffer. /// </summary> public int[] amountToBuffer; public int defaultBufferAmount = 3; /// <summary> /// The container object that we will keep unused pooled objects so we dont clog up the editor with objects. /// </summary> protected GameObject containerObject; void Awake () { instance = this; } // Use this for initialization void Start () { containerObject = new GameObject("ObjectPool"); //Loop through the object prefabs and make a new list for each one. //We do this because the pool can only support prefabs set to it in the editor, //so we can assume the lists of pooled objects are in the same order as object prefabs in the array pooledObjects = new List<GameObject>[objectPrefabs.Length]; int i = 0; foreach ( GameObject objectPrefab in objectPrefabs ) { pooledObjects[i] = new List<GameObject>(); int bufferAmount; if(i < amountToBuffer.Length) bufferAmount = amountToBuffer[i]; else bufferAmount = defaultBufferAmount; for ( int n=0; n<bufferAmount; n++) { GameObject newObj = Instantiate(objectPrefab) as GameObject; newObj.name = objectPrefab.name; PoolObject(newObj); } i++; } } /// <summary> /// Gets a new object for the name type provided. If no object type exists or if onlypooled is true and there is no objects of that type in the pool /// then null will be returned. /// </summary> /// <returns> /// The object for type. /// </returns> /// <param name='objectType'> /// Object type. /// </param> /// <param name='onlyPooled'> /// If true, it will only return an object if there is one currently pooled. /// </param> public GameObject GetObjectForType ( string objectType , bool onlyPooled ) { for(int i=0; i<objectPrefabs.Length; i++) { GameObject prefab = objectPrefabs[i]; if(prefab.name == objectType) { if(pooledObjects[i].Count > 0) { GameObject pooledObject = pooledObjects[i][0]; pooledObjects[i].RemoveAt(0); pooledObject.transform.parent = null; pooledObject.SetActiveRecursively(true); return pooledObject; } else if(!onlyPooled) { return Instantiate(objectPrefabs[i]) as GameObject; } break; } } //If we have gotten here either there was no object of the specified type or non were left in the pool with onlyPooled set to true return null; } /// <summary> /// Pools the object specified. Will not be pooled if there is no prefab of that type. /// </summary> /// <param name='obj'> /// Object to be pooled. /// </param> public void PoolObject ( GameObject obj ) { for ( int i=0; i<objectPrefabs.Length; i++) { if(objectPrefabs[i].name == obj.name) { obj.SetActiveRecursively(false); obj.transform.parent = containerObject.transform; pooledObjects[i].Add(obj); return; } } } }
其实这个Pool有多重写法,我在项目里的写法就是写成单例类。以资源的文件位置为Key,比如一个特效名字是“hit/release/bloat”就存进去。
这个帖子的启发点是播放一个时间段的特效用协程来完成,比现有项目用一个Mgr管理高效多了。