【问题标题】:How to create Generics Pooling System for components/scripts?如何为组件/脚本创建泛型池系统?
【发布时间】:2018-11-08 22:17:39
【问题描述】:

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

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

我的 FXDistribrutor.cs 类是一个附加到初始场景中的对象的组件,旨在永久存在于游戏的所有场景中。它对自身有一个静态引用,因此我可以轻松地从任何地方调用它。更多关于最后的设计。我什至不确定这是否是“正确”的方法。但是效果很好。

FXDistributor 为它能够分发的每种类型的 FX 单元都有一个公共插槽,以及一个用于此类 FX 池的数组,以及数组的索引和池的大小。

这里有两个例子:

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

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

在 Unity Start 调用中,我填充了每个 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;
}

所以当我想要其中一种 FX 并使用它时,我会像这样从任何地方调用静态引用:

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

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

【问题讨论】:

  • BumperFX 和 LandingFX 有共同的父类吗?
  • 并非如此。它们都是 MonoBehaviours,这是 Unity 基类之类的东西,但不是普通父类的正常意义。而且我对 C# 中的层次结构并不是很熟悉,所以我的整个项目都是扁平的。我有点认为这是我需要学习克服的东西,使用泛型。我来自斯威夫特。
  • 通用池是否应该用于组件/脚本或游戏对象预制件?
  • 嘿@Programmer,你这个传奇!池是游戏对象,通常有许多子对象。父级通常具有整个小对象树所在的 FX 的激活组件和控制器组件。
  • 我没有收到关于您的评论的通知。我的意思是,当您有两个对象时,例如 BulletObject、GrenadeObjects,您可以创建使池以 GameObject 作为参数,并使用两个不同的池实例创建 BulletObject 和 GrenadeObjects 池。你了解我吗?我应该放代码给你看吗?

标签: c# unity3d generics pooling


【解决方案1】:

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


仔细阅读您的问题和 cmets,您希望避免使用 GetComponent 部分并仅表示组件而不是游戏对象,以便您也可以直接访问组件。

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

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

1。将组件/脚本存储到Component 的列表中。

List<Component> components;

2。将组件列表存储在字典中,以 Type 作为键,List&lt;Component&gt; 作为值。这使得通过Type 对组件进行分组和查找变得更加容易和快捷。

Dictionary<Type, List<Component>> poolTypeDict;

3。其余的真的很容易。使从 Dictionary 中添加或检索池项的函数为泛型,然后使用 Convert.ChangeType 在泛型类型之间转换为 Component 类型或从泛型转换为请求返回的任何类型。

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

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

将项目添加到池后停用游戏对象component.gameObject.SetActive(false)

5。当您需要从池中检索项目时,请检查 Type 是否作为键存在,然后检索 List 的值 Component。循环组件并返回任何具有停用游戏对象的组件。您可以通过检查 component.gameObject.activeInHierarchy 是否为 false 来检查。

一旦您从池中检索项目激活游戏对象component.gameObject.SetActive(true)

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

6。要在使用完毕后将项目回收回池中,请不要调用 Destroy 函数。只需使用component.gameObject.SetActive(false)*取消激活 GameObject。这将使您下次在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 { ... }

保存预制件引用的两个变量。您可以使用公共变量并从编辑器中分配它们,也可以使用Resources API 加载它们。

public LandingFX landingFxPrefab;
public BumperFX bumperFxPrefab;

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

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

为 LandingFX 和 BumperFX 组件创建 2 个池。它可以采用任何组件

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

【讨论】:

  • 也许是无关的问题。获得组件并将其用作池化和访问的东西后,访问父变换以在需要时定位每个池化对象是否是一个很大的层次攀升?
  • 对不起,我不明白评论。你能改写一下吗?
  • 当然。 @程序员。我需要将这些池化对象中的每一个定位到它们将产生有效效果的位置。所以我需要在某个地方抓取父变换,并告诉它去哪里。这样做有很多开销吗?还是只是正常的“transform.position = goHereDoYourStuff;”?
  • 我真的不知道这样做会对性能造成什么影响,但是更改 transform.position 比更改父对象更有意义,因为更改父 pos 将不必要地更改/更新所有子对象也变身。
  • 我在考虑转换问题。超级简单。该组件可以访问其主机的转换... DOH!谢谢!!!!
【解决方案2】:

我对这个解决方案不太满意,但是将漂亮的 object pool 与简单的 Dictionary&lt;K, V&gt; 结合使用会产生以下结果:

// 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 而不是您的工厂方法实例化类,并且不提供以通用方式自定义池大小的方法。

【讨论】:

  • 对不起我的无知(它没有界限),用 new 实例化类有什么问题?我在哪里以及如何使用工厂方法?我什至不知道我是怎么想出我的方法的。我了解数组,并选择使用它们。如果一个池化对象正在使用中,我只需将其停用,从而重置它并使用它。有点像一个好的键盘上的复音如何在复音用完时偷走最早演奏的音符。
  • 使用new 没什么,问题是new() 要求T 类型提供公共无参数构造函数。我假设您使用工厂方法是因为调用了_poolOfLndngFX[i] = Instantiate( LndngFX, transform );,我假设InstantiateActivator.CreateInstance 的一些包装。
  • 啊。我想我明白了。不,实例化是一个 Unity 系统的东西。它会复制东西,所以……是的。可能是工厂函数。
猜你喜欢
  • 2019-06-05
  • 1970-01-01
  • 2022-10-04
  • 1970-01-01
  • 2019-08-25
  • 2017-09-08
  • 2022-11-02
  • 1970-01-01
  • 2020-04-20
相关资源
最近更新 更多