对象池版本2
Tulenber ⸱
12 June, 2020 ⸱
Intermediate ⸱
3 min ⸱
2019.3.15f1 ⸱
让我们通过现场测试的结果来改进对象池 。
不久之前,我们研究了“对象池” 模式,并创建了相对简单的实现。我们解决了分配的任务,但结果使用起来不太方便。传递其他名称参数会使安装变得不那么直接。在本文中,我们将介绍没有这些缺点的池的更新版本。
辛格尔顿
可以帮助避免不必要的指针传输的第一件事是将池对象用作单例。此实现与相应文章 中给出的实现没有什么不同,只是名称有所不同,目的是防止冲突。
KhtSingleton.cs
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()
方法,该方法使您可以在运行时获取对象的唯一标识符。
池发现使用预制标识符。如果给定的预制件没有池,则将创建一个新池,因此您可以跳过初始分配。新对象还通过其标识符绑定到池。
如果在编辑器中为池指定了预制件,则可以配置其填充。
如果没有提供用于池的单例,则将通过Instantiate()
和Destroy()
创建和删除新对象>方法。
KhtPool.cs
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 );
}
}
用法
使用池中的对象
Fire.cs
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 ;
}
}
}
存储在池中的对象的示例
Projectile.cs
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 );
}
}
结果
按需创建的新对象:
结论
更新的版本大大简化了我们以前的实现。但是,它扩展了添加其他功能的可能性,例如禁止创建新池或限制其扩展。但是这些功能的使用频率远低于预填充功能,因此让我们留待将来使用。下次见!^_^
如果您喜欢这篇文章,可以为它提供支持