在Unity制作游戏的使用过程中.发现在游戏中激活大量GameObject的时候会卡顿.还有偶尔没有任何瓶颈的时候也会莫名的卡顿一下.好在有Profiler可以方便的查看到卡顿的点.
帧率稍慢不是问题.但在某一时间点上的卡住就是个很不好的体验.查看发现主要卡顿点在两处地方.
一是Instantiate.尤其是类似Rigdbody的组件.在激活时会更新一些全局的状态.
二是GC.Collect.Unity的自动GC发生的时间是无法控制的.可能发生在任何时间点.
对于一来说可以通过分批激活的方式来解决.比如使用coroutine来挨个激活就可以避免卡顿.
但对于二来说只有避免GC.那就要避免Destory.
综上.如果想避免游戏中的卡顿.首先要使用对象池.重复使用对象.避免对象的销毁.二是在合适的时候手动GC.比如Loading界面.
ObjectPool是很容易实现的.但Unity的Particles和Trail初始化会有一些问题.
简单的ObjectPool :
public class ObjectPool<T>
{
private List<T> _objects = new List<T>();
public T GetObject()
{
T item;
if( _objects.Count > 0 )
{
item = _objects[0];
_objects.RemoveAt( 0 );
return item;
}
return default(T);
}
public void PutObject(T item)
{
_objects.Add(item);
}
public int GetObjectCount()
{
return _objects.Count;
}
}
public class ObjectManager
{
Dictionary< int, Dictionary<int, ObjectPool<GameObject>> > m_BulletsPool; //子弹的对象池
public GameObject GetBullet( int iBulletId, int iBulletDisplayId )
{
if( m_BulletsPool.ContainsKey( iBulletId ) == false )
return null;
if( m_BulletsPool[iBulletId].ContainsKey(iBulletDisplayId) == false )
return null;
return m_BulletsPool[iBulletId][iBulletDisplayId].GetObject();
}
public void RecycleBullet( int iBulletId, int iBulletDisplayId, GameObject objBullet )
{
if( m_bUseBulletPool == false )
return;
if( m_BulletsPool.ContainsKey( iBulletId ) == false )
{
m_BulletsPool.Add( iBulletId, new Dictionary<int, ObjectPool<GameObject>>() );
}
if( m_BulletsPool[iBulletId].ContainsKey(iBulletDisplayId) == false )
{
m_BulletsPool[iBulletId].Add( iBulletDisplayId, new ObjectPool<GameObject>() );
}
objBullet.SetActive( false );
m_BulletsPool[iBulletId][iBulletDisplayId].PutObject( objBullet );
}
}
这样在使用过程中会发现.带Trail的GameObject在重用后.由于没有重置.会在旧的位置和新的位置之间产生拖尾.而带有Particle System的GameObject再重用过程中参数会不断积累.还有其他一些组件.也都需要初始化.
对于Trail.关键是要在回收时将TrailRenderer.time 置为-1.
对于Particle System.应该是要重置所有的属性.
针对以上情况.写了一个组件.挂在每个要初始化的GameObject上
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
public class ObjectPoolComponent : MonoBehaviour {
Dictionary<Component, Dictionary<PropertyInfo,object>> m_FieldMap = new Dictionary<Component, Dictionary<PropertyInfo, object>>();
// T CopyComponent<T>(T original, GameObject destination) where T : Component
// {
// System.Type type = original.GetType();
// Component copy = destination.AddComponent(type);
// System.Reflection.FieldInfo[] fields = type.GetFields();
// foreach (System.Reflection.FieldInfo field in fields)
// {
// field.SetValue(copy, field.GetValue(original));
// }
// return copy as T;
// }
//
// void CopyField<T>( T original, T destination ) where T : Component
// {
// System.Type type = original.GetType();
// System.Reflection.FieldInfo[] fields = type.GetFields();
// foreach (System.Reflection.FieldInfo field in fields)
// {
// field.SetValue(destination, field.GetValue(original));
// }
// }
NcCurveAnimation[] m_CurveAnms = null;
ParticleSystem[] m_Particles = null;
SpriteSheet[] m_SpriteSheets = null;
public TrailRenderer[] m_tcs = null;
//public float[] m_tcTimes;
void Awake()
{
m_CurveAnms = GetComponentsInChildren<NcCurveAnimation>();
m_Particles = GetComponentsInChildren<ParticleSystem>();
m_SpriteSheets = GetComponentsInChildren<SpriteSheet>();
if( m_Particles != null )
{
for( int i = 0; i < m_Particles.Length; ++i )
{
Dictionary<PropertyInfo,object> mapPro = new Dictionary<PropertyInfo, object>();
PropertyInfo[] propertyInfos = m_Particles[i].GetType().GetProperties();
for( int j = 0; j < propertyInfos.Length; ++j )
{
if( propertyInfos[j].CanWrite && propertyInfos[j].PropertyType.IsPublic )
{
mapPro.Add( propertyInfos[j], propertyInfos[j].GetValue( m_Particles[i], null ) );
propertyInfos[0].GetValue( m_Particles[i], null );
}
}
if( mapPro.Count > 0 )
{
m_FieldMap.Add( m_Particles[i], mapPro );
}
}
}
m_tcs = GetComponentsInChildren<TrailRenderer>();
if( m_tcs != null )
{
for( int i = 0; i < m_tcs.Length; ++i )
{
Dictionary<PropertyInfo,object> mapPro = new Dictionary<PropertyInfo, object>();
PropertyInfo[] propertyInfos = m_tcs[i].GetType().GetProperties();
for( int j = 0; j < propertyInfos.Length; ++j )
{
if( propertyInfos[j].CanWrite && propertyInfos[j].PropertyType.IsPublic )
{
mapPro.Add( propertyInfos[j], propertyInfos[j].GetValue( m_tcs[i], null ) );
propertyInfos[0].GetValue( m_tcs[i], null );
}
}
if( mapPro.Count > 0 )
{
m_FieldMap.Add( m_tcs[i], mapPro );
}
}
// m_tcTimes = new float[m_tcs.Length];
//
// for( int i = 0; i < m_tcs.Length; ++i )
// {
// m_tcTimes[i] = m_tcs[i].time;
// }
}
}
void ResetTrails()
{
if( m_tcs != null )
{
for( int i = 0; i < m_tcs.Length; ++i )
{
if( m_FieldMap.ContainsKey( m_tcs[i] ) == false )
{
continue;
}
PropertyInfo[] propertyInfos = m_tcs[i].GetType().GetProperties();
for( int j = 0; j < propertyInfos.Length; ++j )
{
if( m_FieldMap[m_tcs[i]].ContainsKey( propertyInfos[j] ) )
{
propertyInfos[j].SetValue( m_tcs[i], m_FieldMap[m_tcs[i]][propertyInfos[j]], null );
}
}
}
// for( int i = 0; i < m_tcs.Length; ++i )
// m_tcs[i].time = m_tcTimes[i];
}
}
void RestoreParticles()
{
if( m_Particles != null )
{
for( int i = 0; i < m_Particles.Length; ++i )
{
if( m_FieldMap.ContainsKey( m_Particles[i] ) == false )
{
continue;
}
PropertyInfo[] propertyInfos = m_Particles[i].GetType().GetProperties();
for( int j = 0; j < propertyInfos.Length; ++j )
{
if( m_FieldMap[m_Particles[i]].ContainsKey( propertyInfos[j] ) )
{
propertyInfos[j].SetValue( m_Particles[i], m_FieldMap[m_Particles[i]][propertyInfos[j]], null );
}
}
m_Particles[i].renderer.enabled = true;
m_Particles[i].time = 0;
m_Particles[i].Clear( true );
m_Particles[i].Play( true );
}
}
}
void OnEnable()
{
if( m_CurveAnms != null )
{
for( int i = 0; i < m_CurveAnms.Length; ++i )
{
m_CurveAnms[i].ResetAnimation();
}
}
if( m_SpriteSheets != null )
{
for( int i = 0; i < m_SpriteSheets.Length; ++i )
{
m_SpriteSheets[i].ResetPlay();
}
}
//在Disable时将粒子发射器和Trail关闭.但unenable时并未进行刷新.所以需要等一帧再重置回来
//ResetTrails();
//RestoreParticles();
Invoke( "ResetTrails", 0.001f );
Invoke( "RestoreParticles", 0.001f );
}
void OnDisable()
{
if( m_Particles != null )
{
for( int i = 0; i < m_Particles.Length; ++i )
{
m_Particles[i].Stop();
m_Particles[i].time = 0;
m_Particles[i].Clear( true );
m_Particles[i].renderer.enabled = false;
}
}
if( m_tcs != null )
{
for( int i = 0; i < m_tcs.Length; ++i )
{
if( m_tcs[i] != null )
{
m_tcs[i].time = -1;
}
}
}
}
}