首页 > 代码库 > 天下性能 唯池不破

天下性能 唯池不破

文章名字是我杜撰的,之前一直在做服务器开发,上周有机会接触了客户端,发现很多资源没有有效管理起来,甚至有资源泄露的发生,我就先针对特效做了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管理高效多了。