📜 ⬆️ ⬇️

Pool objects for unity3d

Everyone knows that creating and deleting objects is not cheap. For example, creating a bullet each time and destroying it is quite expensive for the same mobile devices. Maybe we should not destroy the bullet, but hide it. So I decided to share my implementation of Pool Manager.

Structures


First, you need to create an interface:
public interface IPoolObject<T> { T Group { get; } void Create(); void OnPush(); void FailedPush(); } 

What is the interface for? Universally, let the pool not know what objects it works with, it only knows that they have special methods and properties.
T - in this case, the group ID. An example further.
The Create () method will play the role of a pseudo-constructor. After all, when you get an object from the pool, its state will not be determined, which may adversely affect its further use.
The OnPush () method will play the role of a pseudo-destructor. When an object enters the pool, you may need to do something, for example, turn off some related partitions or something else.
Method FailedPush () - Will be called on the object, in case it is not possible to get into the pool. For example, the pool is full. And something the object would not remain ownerless, it may need to be destroyed.

Now Pool Manager itself
 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); } } } } 

What you should pay attention to.
MaxInstances - the field of the maximum number of pool objects. In case it is not possible to place another object in the pool.
protected Dictionary <K, List> objects -Container is represented as a group - list. When it enters the pool, it is sent to the appropriate group. When it is required, the pool will return the first corresponding object in the group. protected Dictionary <Type, List> cache - cache of objects by type. It is necessary, exclusively, for the beautiful method Pop ().
Method Pop (Compare comparer) - may need to get an object by condition.

Shared Pool ready. Now we need to make a variation for Unity3d. Let's get started
')
 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; } } 

In fact, this is just a wrapper over the first pool and Singelton. It could be wrapped differently. But it would turn out something like UnityPoolManager.Instance.Pool.Pop (), and additionally create only a couple of methods specifically for the unit. But it is at the discretion of the reader. The code will be less but an additional Pool will appear.

PopOrCreate () - we need this method for creating objects.
Push () - at the pool of objects themselves or Push in the manager.

Now we need the GameObject itself.

 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); } } 

All objects will inherit from it, and it is possible to override the Create and OnPush methods.

We now turn to use. On the example of the bullet traces and 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); //    //  ,   .      Push foreach(ProfileListItem item in list) { item.Push(); } 


I hope this example will help you in writing your projects on Unity3d. Special thanks to @ lexxpavlov who suggested that you need to describe in more detail than just the interface.

Source: https://habr.com/ru/post/255499/


All Articles