Pool объектов для Unity3d

в 16:59, , рубрики: C#, game development, unity3d, unity3d уроки, оптимизация

Все знают что операции создания и удаления объектов не дешевые. Например создавать каждый раз пулю и уничтожать, довольно накладно для тех же мобильных устройств. Может стоит не уничтожать пулю, а скрывать ее. Вот решил поделится своей реализацией 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js