Как создать Generics Pooling System для компонентов / скриптов? - PullRequest
0 голосов
/ 09 ноября 2018

Я осведомлен о дженериках, что они могут помочь мне упростить мое объединение, но не могут понять, как.

Моя система объединения минималистична, но грязна. А теперь становится громоздким и грязным и МЕССИ. Не очень хорошо масштабируется ...

Мой класс FXDistribrutor.cs - это компонент, прикрепленный к объекту в начальной сцене, предназначенный для постоянного присутствия во всех сценах игры. Он имеет статическую ссылку на себя, так что я могу легко вызывать его из любого места. Подробнее об этом устройстве в конце. Я даже не уверен, что это «правильный» способ сделать это. Но это работает хорошо.

FXDistributor имеет открытый слот для каждого типа модуля FX, который он может распространять, и массив для пула этого типа FX, а также индекс для массива и размера пула.

Вот два примера:

    public BumperFX BmprFX;
    BumperFX[] _poolOfBumperFX;
    int _indexBumperFX, _poolSize = 10;

    public LandingFX LndngFX;
    LandingFX[] _poolOfLndngFX;
    int _indexLndngFX, _poolSizeLndngFX = 5;

В стартовом вызове Unity я заполняю пулы каждой единицы FX:

void Start(){

    _poolOfBumperFX = new BumperFX[_poolSize];
    for (var i = 0; i < _poolSize; i++) {
    _poolOfBumperFX[i] = Instantiate(BmprFX, transform );
    }

    _poolOfLndngFX = new LandingFX[_poolSizeLndngFX];
    for ( var i = 0; i < _poolSizeLndngFX; i++ ) {
    _poolOfLndngFX[i] = Instantiate( LndngFX, transform );
    }
}

И в теле класса у меня есть куча методов для каждого типа FX, чтобы предоставить их там, где они нужны:

public LandingFX GimmeLandingFX ( ){
    if ( _indexLndngFX == _poolSizeLndngFX ) _indexLndngFX = 0;
    var lndngFX = _poolOfLndngFX[_indexLndngFX];
    _indexLndngFX++; return lndngFX;
}
public BumperFX GimmeBumperFX ( ) {
    if ( _indexBumperFX == _poolSize ) _indexBumperFX = 0;
    var bumperFX = _poolOfBumperFX[_indexBumperFX];
    _indexBumperFX++;   return bumperFX;
}

Поэтому, когда я хочу один из этих эффектов и использовать его, я вызываю вот так из любой точки мира в статическую ссылку:

    FXDistributor.sRef.GimmeLandingFX( ).Bounce(
            bounce.point,
            bounce.tangentImpulse,
            bounce.normalImpulse 
            );

Как мне упростить этот подход с помощью Generics, чтобы я мог легко и без лишних хлопот делать подобные вещи для пары десятков типов единиц FX?

Ответы [ 2 ]

0 голосов
/ 17 ноября 2018

В Unity функции Instantiate() и Destroy() используются для создания копий объектов, особенно префабов, и их уничтожения. Когда дело доходит до пула, объект пула обычно представляется в пуле как тип GameObject. Когда вам нужно получить доступ к компоненту из пула, вы сначала получаете пул GameObject, а затем используете функцию GetComponent для извлечения компонента из GameObject.


Внимательно читая свой вопрос и комментарии, вы хотите избежать раздела GetComponent и представлять только компоненты , а не GameObject, чтобы вы также могли получить прямой доступ к компонентам.

Если это то, что вам нужно, то именно здесь требуется Unity Component. Ниже приведены шаги, необходимые для этого.

Обратите внимание, что когда я говорю компонент / скрипт, я имею в виду ваши скрипты, которые происходят из MonoBehaviour, которые могут быть присоединены к GameObjects или встроенным компонентам Unity, таким как Rigidbody и BoxCollider.

1 . Сохраните компоненты / сценарии в списке Component.

List<Component> components;

2 . Сохраните список компонентов в словаре, указав Type в качестве ключа и List<Component> в качестве значения. Это упрощает и ускоряет группирование и поиск компонентов по Type.

Dictionary<Type, List<Component>> poolTypeDict;

3 . Остальное действительно легко. Сделайте функцию, которая добавляет или получает элементы пула из словаря и в него, чтобы они были универсальными, затем используйте Convert.ChangeType для приведения между универсальным типом к типу Component или из универсального к тому типу, который запрашивается подлежит возврату.

4 . Когда вам нужно добавить элемент в словарь, проверьте, существует ли еще Type, если это так, извлеките существующий ключ, создайте и добавьте new Component к нему с помощью функции Instantiate, затем сохраните его в Словаре.

Если Type еще не существует, нет необходимости извлекать данные из Dictionary. Просто создайте новый и добавьте его в словарь с Type.

После добавления предмета в пул деактивация GameObject с component.gameObject.SetActive(false)

5 . Когда вам нужно извлечь элемент из пула, проверьте, существует ли Type в качестве ключа, а затем извлеките значение, равное List из Component. Цикл по компонентам и вернуть любой компонент, который имеет деактивированный GameObject. Вы можете проверить это, проверив, является ли component.gameObject.activeInHierarchy false.

Как только вы получите предмет из пула активируйте GameObject с component.gameObject.SetActive(true)

Если компонент не найден, вы можете решить вернуть либо ноль, либо создать новый компонент.

6 . Чтобы вернуть элемент обратно в пул после его использования, вы не должны вызывать функцию Destroy. Просто деактивируйте GameObject с component.gameObject.SetActive(false)*. Это позволит найти компонент в следующий раз, когда вы будете искать доступные компоненты в Dictionary и List.

Ниже приведен пример минимальной системы общего пула для скриптов и компонентов:

public class ComponentPool
{
    //Determines if pool should expand when no pool is available or just return null
    public bool autoExpand = true;
    //Links the type of the componet with the component
    Dictionary<Type, List<Component>> poolTypeDict = new Dictionary<Type, List<Component>>();

    public ComponentPool() { }


    //Adds Prefab component to the ComponentPool
    public void AddPrefab<T>(T prefabReference, int count = 1)
    {
        _AddComponentType<T>(prefabReference, count);
    }

    private Component _AddComponentType<T>(T prefabReference, int count = 1)
    {
        Type compType = typeof(T);

        if (count <= 0)
        {
            Debug.LogError("Count cannot be <= 0");
            return null;
        }

        //Check if the component type already exist in the Dictionary
        List<Component> comp;
        if (poolTypeDict.TryGetValue(compType, out comp))
        {
            if (comp == null)
                comp = new List<Component>();

            //Create the type of component x times
            for (int i = 0; i < count; i++)
            {
                //Instantiate new component and UPDATE the List of components
                Component original = (Component)Convert.ChangeType(prefabReference, typeof(T));
                Component instance = Instantiate(original);
                //De-activate each one until when needed
                instance.gameObject.SetActive(false);
                comp.Add(instance);
            }
        }
        else
        {
            //Create the type of component x times
            comp = new List<Component>();
            for (int i = 0; i < count; i++)
            {
                //Instantiate new component and UPDATE the List of components
                Component original = (Component)Convert.ChangeType(prefabReference, typeof(T));
                Component instance = Instantiate(original);
                //De-activate each one until when needed
                instance.gameObject.SetActive(false);
                comp.Add(instance);
            }
        }

        //UPDATE the Dictionary with the new List of components
        poolTypeDict[compType] = comp;

        /*Return last data added to the List
         Needed in the GetAvailableObject function when there is no Component
         avaiable to return. New one is then created and returned
         */
        return comp[comp.Count - 1];
    }


    //Get available component in the ComponentPool
    public T GetAvailableObject<T>(T prefabReference)
    {
        Type compType = typeof(T);

        //Get all component with the requested type from  the Dictionary
        List<Component> comp;
        if (poolTypeDict.TryGetValue(compType, out comp))
        {
            //Get de-activated GameObject in the loop
            for (int i = 0; i < comp.Count; i++)
            {
                if (!comp[i].gameObject.activeInHierarchy)
                {
                    //Activate the GameObject then return it
                    comp[i].gameObject.SetActive(true);
                    return (T)Convert.ChangeType(comp[i], typeof(T));
                }
            }
        }

        //No available object in the pool. Expand array if enabled or return null
        if (autoExpand)
        {
            //Create new component, activate the GameObject and return it
            Component instance = _AddComponentType<T>(prefabReference, 1);
            instance.gameObject.SetActive(true);
            return (T)Convert.ChangeType(instance, typeof(T));
        }
        return default(T);
    }
}

public static class ExtensionMethod
{
    public static void RecyclePool(this Component component)
    {
        //Reset position and then de-activate the GameObject of the component
        GameObject obj = component.gameObject;
        obj.transform.position = Vector3.zero;
        obj.transform.rotation = Quaternion.identity;
        component.gameObject.SetActive(false);
    }
}

ИСПОЛЬЗОВАНИЕ:

Может принимать любой готовый компонентный скрипт. Для этого используются префабы, так как объекты пула обычно создаются в виде префабов и ожидают использования.

Примеры готовых сценариев (LandingFX, BumperFX):

public class LandingFX : MonoBehaviour { ... }

и

public class BumperFX : MonoBehaviour { ... }

Две переменные для хранения ссылок Prefabs. Вы можете использовать открытые переменные и назначить их из редактора или загрузить их с помощью Resources API .

public LandingFX landingFxPrefab;
public BumperFX bumperFxPrefab;

Создание нового пула компонентов и отключение автоматического изменения размера

ComponentPool cmpPool = new ComponentPool();
cmpPool.autoExpand = false;

Создание 2 пулов для компонентов LandingFX и BumperFX. Это может занять любой компонент

//AddPrefab 2 objects type of LandingFX
cmpPool.AddPrefab(landingFxPrefab, 2);
//AddPrefab 2 objects type of BumperFX
cmpPool.AddPrefab(bumperFxPrefab, 2);

Когда вам нужно LandingFX из пула, вы можете получить их, как показано ниже:

LandingFX lndngFX1 = cmpPool.GetAvailableObject(landingFxPrefab);
LandingFX lndngFX2 = cmpPool.GetAvailableObject(landingFxPrefab);

Когда вам нужно BumperFX из пула, вы можете получить их, как показано ниже:

BumperFX bmpFX1 = cmpPool.GetAvailableObject(bumperFxPrefab);
BumperFX bmpFX2 = cmpPool.GetAvailableObject(bumperFxPrefab);

Когда вы закончите использовать извлеченный компонент, верните их обратно в пул, а не уничтожайте их:

lndngFX1.RecyclePool();
lndngFX2.RecyclePool();
bmpFX1.RecyclePool();
bmpFX2.RecyclePool();
0 голосов
/ 09 ноября 2018

Я не так доволен решением, но сочетание пула объектов с использованием простого Dictionary<K, V> дает следующее:

// pool of single object type, uses new for instantiation
public class ObjectPool<T> where T : new()
{
    // this will hold all the instances, notice that it's up to caller to make sure
    // the pool size is big enough not to reuse an object that's still in use
    private readonly T[] _pool = new T[_maxObjects];
    private int _current = 0;

    public ObjectPool()
    {
        // performs initialization, one may consider doing lazy initialization afterwards
        for (int i = 0; i < _maxObjects; ++i)
            _pool[i] = new T();
    }

    private const int _maxObjects = 100;  // Set this to whatever

    public T Get()
    {
        return _pool[_current++ % _maxObjects];
    }
}

// pool of generic pools
public class PoolPool
{
    // this holds a reference to pools of known (previously used) object pools
    // I'm dissatisfied with an use of object here, but that's a way around the generics :/
    private readonly Dictionary<Type, object> _pool = new Dictionary<Type, object>();

    public T Get<T>() where T : new()
    {
        // is the pool already instantiated?
        if (_pool.TryGetValue(typeof(T), out var o))
        {
            // if yes, reuse it (we know o should be of type ObjectPool<T>,
            // where T matches the current generic argument
            return ((ObjectPool<T>)o).Get();
        }

        // First time we see T, create new pool and store it in lookup dictionary
        // for later use
        ObjectPool<T> pool = new ObjectPool<T>();
        _pool.Add(typeof(T), pool);

        return pool.Get();
    }
}

Теперь вы можете просто сделать следующее:

pool.Get<A>().SayHello();
pool.Get<B>().Bark();

Однако это все еще оставляет место для улучшений, так как создает экземпляры классов с new вместо вашего фабричного метода, а также не предоставляет способа индивидуальной настройки размера пула.

...