Все знают что операции создания и удаления объектов не дешевые. Например создавать каждый раз пулю и уничтожать, довольно накладно для тех же мобильных устройств. Может стоит не уничтожать пулю, а скрывать ее. Вот решил поделится своей реализацией Pool Manager. Который использую в разных проектах, в том числе и на photon server.
Структуры
Для начала, нужно создать интерфейс:
public interface IPoolObject<T>
{
T Group { get; }
void Create();
void OnPush();
void FailedPush();
}
Где метод Create() будет играть роль псевдо-конструктора. Ведь когда вы достанете объект из пула, его состояние будет не определено, что может пагубно отразится на дальнейшем его использовании.
Теперь сам Pool Manager
using System.Collections.Generic;
using System;
public class PoolManager<K, V> where V :IPoolObject<K>
{
public virtual int MaxInstances { get; protected set; }
public virtual int InctanceCount { get { return objects.Count; } }
public virtual int CacheCount { get { return cache.Count; } }
public delegate bool Compare<T>(T value) where T: V;
protected Dictionary<K, List<V>> objects;
protected Dictionary<Type, List<V>> cache;
public PoolManager(int maxInstance)
{
MaxInstances = maxInstance;
objects = new Dictionary<K, List<V>>();
cache = new Dictionary<Type, List<V>>();
}
public virtual bool CanPush()
{
return InctanceCount + 1 < MaxInstances;
}
public virtual bool Push(K groupKey, V value)
{
bool result = false;
if (CanPush())
{
value.OnPush();
if (!objects.ContainsKey(groupKey))
{
objects.Add(groupKey, new List<V>());
}
objects[groupKey].Add(value);
Type type = value.GetType();
if (!cache.ContainsKey(type))
{
cache.Add(type, new List<V>());
}
cache[type].Add(value);
}
else
{
value.FailedPush();
}
return result;
}
public virtual T Pop<T>(K groupKey) where T : V
{
T result = default(T);
if (Contains(groupKey) && objects[groupKey].Count > 0)
{
for (int i = 0; i < objects[groupKey].Count; i++)
{
if (objects[groupKey][i] is T)
{
result = (T)objects[groupKey][i];
Type type = result.GetType();
RemoveObject(groupKey, i);
RemoveFromCache(result, type);
result.Create();
break;
}
}
}
return result;
}
public virtual T Pop<T>() where T: V
{
T result = default(T);
Type type = typeof(T);
if (ValidateForPop(type))
{
for (int i = 0; i < cache[type].Count; i++)
{
result = (T)cache[type][i];
if (result != null && objects.ContainsKey(result.Group))
{
objects[result.Group].Remove(result);
RemoveFromCache(result, type);
result.Create();
break;
}
}
}
return result;
}
public virtual T Pop<T>(Compare<T> comparer) where T : V
{
T result = default(T);
Type type = typeof(T);
if (ValidateForPop(type))
{
for(int i = 0; i < cache[type].Count; i++)
{
T value = (T)cache[type][i];
if (comparer(value))
{
objects[value.Group].Remove(value);
RemoveFromCache(result, type);
result = value;
result.Create();
break;
}
}
}
return result;
}
public virtual bool Contains(K groupKey)
{
return objects.ContainsKey(groupKey);
}
public virtual void Clear()
{
objects.Clear();
}
protected virtual bool ValidateForPop(Type type)
{
return cache.ContainsKey(type) && cache[type].Count > 0;
}
protected virtual void RemoveObject(K groupKey, int idx)
{
if (idx >= 0 && idx < objects[groupKey].Count)
{
objects[groupKey].RemoveAt(idx);
if (objects[groupKey].Count == 0)
{
objects.Remove(groupKey);
}
}
}
protected void RemoveFromCache(V value, Type type)
{
if (cache.ContainsKey(type))
{
cache[type].Remove(value);
if (cache[type].Count == 0)
{
cache.Remove(type);
}
}
}
}
На что стоит обратить внимание.
MaxInstances — поле максимального количества pool объектов. В случае, если не возможно поместить в пул очередной объект, сам объект вывозит метод FailedPush();
OnPush() — непосредственно перед попаданием в пул.
Create() — перед тем, как пул вернет нам экземпляр.
Общий Pool готов. Теперь нужно сделать вариацию для Unity3d. Приступим
using UnityEngine;
using System.Collections;
public class UnityPoolManager : MonoBehaviour
{
public static UnityPoolManager Instance {get; protected set;}
public int maxInstanceCount = 128;
protected PoolManager<string, UnityPoolObject> poolManager;
protected virtual void Awake()
{
Instance = this;
poolManager = new PoolManager<string, UnityPoolObject>(maxInstanceCount);
}
public virtual bool CanPush()
{
return poolManager.CanPush();
}
public virtual bool Push(string groupKey, UnityPoolObject poolObject)
{
return poolManager.Push(groupKey, poolObject);
}
public virtual T PopOrCreate<T>(T prefab) where T : UnityPoolObject
{
return PopOrCreate(prefab, Vector3.zero, Quaternion.identity);
}
public virtual T PopOrCreate<T>(T prefab, Vector3 position, Quaternion rotation) where T : UnityPoolObject
{
T result = poolManager.Pop<T>(prefab.Group);
if (result == null)
{
result = CreateObject<T>(prefab, position, rotation);
}
else
{
result.SetTransform(position, rotation);
}
return result;
}
public virtual UnityPoolObject Pop(string groupKey)
{
return poolManager.Pop<UnityPoolObject>(groupKey);
}
public virtual T Pop<T>() where T : UnityPoolObject
{
return poolManager.Pop<T>();
}
public virtual T Pop<T>(PoolManager<string, UnityPoolObject>.Compare<T> comparer) where T : UnityPoolObject
{
return poolManager.Pop<T>(comparer);
}
public virtual T Pop<T>(string groupKey) where T : UnityPoolObject
{
return poolManager.Pop<T>(groupKey);
}
public virtual bool Contains(string groupKey)
{
return poolManager.Contains(groupKey);
}
public virtual void Clear()
{
poolManager.Clear();
}
protected virtual T CreateObject<T>(T prefab, Vector3 position, Quaternion rotation) where T : UnityPoolObject
{
GameObject go = Instantiate(prefab.gameObject, position, rotation) as GameObject;
T result = go.GetComponent<T>();
result.name = prefab.name;
return result;
}
}
По сути это просто обвертка над первым пулом и Сингелтон.
PopOrCreate() — нам понадобится вот этот метод для создания объектов.
Push() — у самих пул объектов или Push в менеджере.
Теперь нам понадобится сам GameObject
using UnityEngine;
using System.Collections;
public class UnityPoolObject : MonoBehaviour, IPoolObject<string>
{
public virtual string Group { get {return name;} }
public Transform MyTransform { get { return myTransform; } }
protected Transform myTransform;
protected virtual void Awake()
{
myTransform = transform;
}
public virtual void SetTransform(Vector3 position, Quaternion rotation)
{
myTransform.position = position;
myTransform.rotation = rotation;
}
public virtual void Create()
{
gameObject.SetActive(true);
}
public virtual void OnPush()
{
gameObject.SetActive(false);
}
public virtual void Push()
{
UnityPoolManager.Instance.Push(Group, this);
}
public void FailedPush()
{
Debug.Log("FailedPush"); // !!!
Destroy(gameObject);
}
}
Все объекты будем наследовать от него.
FailedPush() — стоит обратить внимание на этот метод. Возможно вы захотите бросать исключение, мол пул забит или что-то еще.
Теперь перейдем к использованию. На примере пули следов и UI List item.
public class Bullet : UnityPoolObject
{
...
}
// создание
Bullet bullet = UnityPoolManager.Instance.PopOrCreate<Bullet>(bulletPrefab, bulletPoint.position, Quaternion.identity);
bullet.Execute(sender, bulletPoint.position, CalculateTarget(target, accuracy01), damage, blockTime, range, bulletSpeed);
// уничтожение, точней возращаем в пул
timer-= Time.deltaTime;
if (timer< 0)
{
Push();
}
public class StepObject : UnityPoolObject
{
...
}
/// ---
StepObject stepObject = UnityPoolManager.Instance.PopOrCreate<StepObject>(prefab, sender.position, sender.rotation);
FXManager.Instance.InitDecal(null, stepObject.gameObject, hit, direction);
stepObject.MyTransform.rotation *= rotation;
StartCoroutine(ApplyDecalC(stepObject));
/// ---
protected virtual IEnumerator ApplyDecalC(StepObject decalObject)
{
yield return new WaitForSeconds(waitTime);
yield return StartCoroutine(FXManager.Instance.HideOjectC(decalObject.gameObject, hideTime));
decalObject.Push();
}
public class ProfileListItem : UnityPoolObject
{
...
}
// ---
ProfileListItem profileItem = UnityPoolManager.Instance.PopOrCreate(prefab);
...
profileItem.profileId = profileId;
list.AddItem(profileItem);
// ----
Надеюсь данный пример поможет вам в написании своих проектов на Unity3d.
Автор: derek_streyt