如何为 components/scripts 创建泛型池系统?

How to create Generics Pooling System for components/scripts?

我对泛型的认识是它们可以帮助我简化池化,但不知道如何做。

我的池化系统是极简的,但很乱。现在变得笨重和凌乱,而且凌乱。它不能很好地扩展...

My FXDistribrutor.cs class 是初始场景中附加到某个对象的组件,旨在永久存在于游戏的所有场景中。它有一个对自身的静态引用,所以我可以很容易地从任何地方调用它。更多关于最后的设计。我什至不确定这是否是执行此操作的 'right' 方法。但效果很好。

FXDistributor 有一个 public 插槽用于它能够分配的每种类型的 FX 单元,以及一个用于此类 FX 池的数组,以及一个数组索引和池大小.

这里有两个例子:

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

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

在 Unity Start 调用中,我填充了每个 FX Unit 的池:

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

在 class 的正文中,我为每种 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;
}

因此,当我想要这些 FX 之一并使用它时,我会从任何地方这样调用静态引用:

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

我如何使用泛型简化这种方法,以便我可以轻松且不那么混乱地为几十种 FX 单元做这种事情?

我对这个解决方案不是很满意,但是将一个不错的 与使用简单的 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 而不是您的工厂方法来实例化 类,并且没有提供以通用方式自定义池大小的方法。

在 Unity 中,Instantiate() and Destroy() 函数用于创建对象(尤其是预制件)的副本并销毁它们。说到池化,池对象通常在池中表示为 GameObject 的 Type。当您需要从池中访问组件时,您首先检索池 GameObject,然后使用 GetComponent 函数从 GameObject 检索组件。


仔细阅读你的问题和评论,你想避开 GetComponent 部分并只代表组件 而不是 游戏对象,这样你也可以直接访问组件.

如果这是您想要的,那么这就是需要 Unity 的 Component 的地方。请参阅下文了解执行此操作所需的步骤。

请注意,当我说 component/script 时,我指的是派生自 MonoBehaviour 的脚本,这些脚本可以附加到游戏对象或内置的 Unity 组件,例如 RigidbodyBoxCollider.

1。将 components/scripts 存储到 Component.

的列表中
List<Component> components;

2。将组件列表存储在以 Type 为键、以 List<Component> 为值的字典中。这使得按 Type.

分组和查找组件变得更加容易和快速
Dictionary<Type, List<Component>> poolTypeDict;

3。剩下的就很简单了。使从字典中添加或检索池项目的函数成为通用的,然后使用 Convert.ChangeType 在通用类型与 Component 类型之间进行转换,或者从通用类型转换为请求的任何类型returned.

4。当你需要向字典中添加项目时,检查 Type 是否存在,如果存在,检索现有密钥,createadd 使用 Instantiate 函数将其新建 Component,然后将其保存到词典中。

如果 Type 尚不存在,则无需从 Dictionary 中检索任何数据。只需创建一个新的并将其添加到字典中,其 Type

一旦你将项目添加到池中取消激活游戏对象component.gameObject.SetActive(false)

5。当您需要从池中检索项目时,检查 Type 是否作为键存在,然后检索 ComponentList 的值。遍历组件和 return 具有已停用游戏对象的任何组件。您可以通过检查 component.gameObject.activeInHierarchy 是否为 false 来检查这一点。

从池中检索项目后 激活 GameObject component.gameObject.SetActive(true)

如果没有找到组件,您可以决定 return null 或实例化新组件。

6。要在使用完项目后将其回收回池中,您无需调用 Destroy 函数。只需 取消激活 游戏对象 component.gameObject.SetActive(false)*。这将使您下次在 DictionaryList 中搜索可用组件时能够找到该组件。

下面是脚本和组件的最小通用池系统示例:

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

用法:

它可以采用任何预制组件脚本。为此使用预制件,因为池化对象通常是实例化并等待使用的预制件。

示例预制脚本(LandingFXBumperFX):

public class LandingFX : MonoBehaviour { ... }

public class BumperFX : MonoBehaviour { ... }

两个变量来保存预制件引用。您可以使用 public 变量并从编辑器中分配它们或使用 加载它们。

public LandingFX landingFxPrefab;
public BumperFX bumperFxPrefab;

创建新的组件池并禁用自动调整大小

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

为 LandingFX 和 BumperFX 组件创建 2 个池。它可以使用 any 组件

//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();