对象池版本2

Tulenber 12 June, 2020 ⸱ Intermediate ⸱ 3 min ⸱ 2019.3.15f1 ⸱

让我们通过现场测试的结果来改进对象池

不久之前,我们研究了“对象池”模式,并创建了相对简单的实现。我们解决了分配的任务,但结果使用起来不太方便。传递其他名称参数会使安装变得不那么直接。在本文中,我们将介绍没有这些缺点的池的更新版本。

辛格尔顿

可以帮助避免不必要的指针传输的第一件事是将池对象用作单例。此实现与相应文章中给出的实现没有什么不同,只是名称有所不同,目的是防止冲突。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using UnityEngine;

public class KhtSingleton<T> : MonoBehaviour where T : KhtSingleton<T>
{
    private static T _instance;

    public static T Instance
    {
        get => _instance;
    }

    public static bool IsInstantiated
    {
        get => _instance != null;
    }

    protected virtual void Awake()
    {
        if (_instance != null)
        {
            Debug.LogError("["+ typeof(T) +"] '" + transform.name + "' trying to instantiate a second instance of singleton class previously created in '" + _instance.transform.name + "'");
        }
        else
        {
            _instance = (T) this;
        }
    }

    protected void OnDestroy()
    {
        if (_instance == this)
        {
            _instance = null;
        }
    }
}

对象池

此对象池的实现基于GetInstanceID()方法,该方法使您可以在运行时获取对象的唯一标识符。

池发现使用预制标识符。如果给定的预制件没有池,则将创建一个新池,因此您可以跳过初始分配。新对象还通过其标识符绑定到池。
Open Test Runner

如果在编辑器中为池指定了预制件,则可以配置其填充。
Open Test Runner

如果没有提供用于池的单例,则将通过Instantiate()Destroy()创建和删除新对象>方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
using System;
using System.Collections.Generic;
using UnityEngine;

public class KhtPool : KhtSingleton<KhtPool>
{
    // Inspector不支持Dictionary类型,所以让我们使用它
    [Serializable]
    public class PrefabData
    {
        public GameObject prefab;
        public int initPoolSize = 0;
    }
    [SerializeField] private List<PrefabData> prefabDatas = null;
    
    // 将池绑定到预制件ID
    private readonly Dictionary<int, Queue<GameObject>> _pools = new Dictionary<int, Queue<GameObject>>();
    // 通过标识符将对象绑定到池
    private readonly Dictionary<int, int> _objectToPoolDict = new Dictionary<int, int>();

    private new void Awake()
    {
        // 设置单例
        base.Awake();

        // 如有必要,请预先填充对象池
        foreach (var prefabData in prefabDatas)
        {
            _pools.Add(prefabData.prefab.GetInstanceID(), new Queue<GameObject>());
            for (int i = 0; i < prefabData.initPoolSize; i++)
            {
                GameObject retObject = Instantiate(prefabData.prefab, Instance.transform, true);
                Instance._objectToPoolDict.Add(retObject.GetInstanceID(), prefabData.prefab.GetInstanceID());
                Instance._pools[prefabData.prefab.GetInstanceID()].Enqueue(retObject);
                retObject.SetActive(false);
            }
        }
        prefabDatas = null;
    }

    // 从池中获取对象
    public static GameObject GetObject(GameObject prefab)
    {
        // 如果没有单例,则创建一个新对象
        if (!Instance)
        {
            return Instantiate(prefab);
        }

        // 用于绑定到池的预制件的唯一标识符
        int prefabId = prefab.GetInstanceID();
        // 如果预制池不存在,则创建一个新的池
        if (!Instance._pools.ContainsKey(prefabId))
        {
            Instance._pools.Add(prefabId, new Queue<GameObject>());
        }

        // 如果池中有一个对象,则将其返回
        if (Instance._pools[prefabId].Count > 0)
        {
            return Instance._pools[prefabId].Dequeue();
        }

        // 如果对象不足,请创建一个新的
        GameObject retObject = Instantiate(prefab);
        // 通过对象标识符将对象的绑定添加到池中
        Instance._objectToPoolDict.Add(retObject.GetInstanceID(), prefabId);
        
        return retObject;
    }

    // 将对象返回池中
    public static void ReturnObject(GameObject poolObject)
    {
        // 在没有单例的情况下,我们摧毁物体
        if (!Instance)
        {
            Destroy(poolObject);
            return;
        }

        // 池定义的对象标识符
        int objectId = poolObject.GetInstanceID();

        // 如果对象没有绑定到池,我们将其销毁
        if (!Instance._objectToPoolDict.TryGetValue(objectId, out int poolId))
        {
            Destroy(poolObject);
            return;
        }

        // 将对象返回池中
        Instance._pools[poolId].Enqueue(poolObject);
        poolObject.transform.SetParent(Instance.transform);
        poolObject.SetActive(false);
    }
}

用法

使用池中的对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using UnityEngine;

public class Fire : MonoBehaviour
{
    // 非池信息
    [SerializeField] private Transform leftCannon = null;
    [SerializeField] private Transform rightCannon = null;
    [SerializeField] private UnevirseHandler universe = null;
    
    [SerializeField] private Transform space = null;
    
    // 预制以从池中获取对象
    [SerializeField] private GameObject projectilePrefab = null;

    private bool _rightCannon = false;

    void Update()
    {
        if (Input.GetMouseButtonDown (0)) {
            // 从池中获取对象
            GameObject pr = KhtPool.GetObject(projectilePrefab);

            // 池中的对象需要适当的清理和初始化
            pr.transform.SetParent(space);
            pr.transform.SetPositionAndRotation(_rightCannon ? rightCannon.position : leftCannon.position, _rightCannon ? rightCannon.rotation : leftCannon.rotation);
            Projectile prpr = pr.GetComponent<Projectile>();
            prpr.MoveVector = universe.LookVector.normalized;
            pr.SetActive(true);

            _rightCannon = !_rightCannon;
        }
    }
}

存储在池中的对象的示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using UnityEngine;

public class Projectile : MonoBehaviour
{
    [SerializeField] private float speed = 0;

    private Vector2 _moveVector = Vector2.zero;

    public Vector2 MoveVector
    {
        set => _moveVector = value;
    }

    void Update()
    {
        transform.Translate(_moveVector.x * Time.deltaTime * speed, _moveVector.y * Time.deltaTime * speed, 0, Space.World);
    }

    void OnBecameInvisible() {
        // 对象离开屏幕后,将其返回到池中
        KhtPool.ReturnObject(gameObject);
    }
}

结果

按需创建的新对象:
Open Test Runner

结论

更新的版本大大简化了我们以前的实现。但是,它扩展了添加其他功能的可能性,例如禁止创建新池或限制其扩展。但是这些功能的使用频率远低于预填充功能,因此让我们留待将来使用。下次见!^_^


如果您喜欢这篇文章,可以为它提供支持



Privacy policyCookie policyTerms of service
Tulenber 2020