com.amanotes.prefabpool 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Editor/PoolEntryDrawer.cs +43 -0
- package/README.md +105 -0
- package/Runtime/Amanotes.PrefabPool.asmdef +14 -0
- package/Runtime/MaterialPool.cs +105 -0
- package/Runtime/MeshRendererExtensions.cs +80 -0
- package/Runtime/PrefabPool.cs +413 -0
- package/package.json +17 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#if UNITY_EDITOR
|
|
2
|
+
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
using UnityEditor;
|
|
5
|
+
|
|
6
|
+
namespace Amanotes.Core
|
|
7
|
+
{
|
|
8
|
+
[CustomPropertyDrawer(typeof(PoolEntry))]
|
|
9
|
+
internal class PoolEntryDrawer : PropertyDrawer
|
|
10
|
+
{
|
|
11
|
+
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
|
12
|
+
{
|
|
13
|
+
EditorGUI.BeginProperty(position, label, property);
|
|
14
|
+
|
|
15
|
+
var tileNameProp = property.FindPropertyRelative(nameof(PoolEntry.id));
|
|
16
|
+
var tilePrefabProp = property.FindPropertyRelative(nameof(PoolEntry.prefab));
|
|
17
|
+
var prewarmProp = property.FindPropertyRelative(nameof(PoolEntry.prewarmCount));
|
|
18
|
+
|
|
19
|
+
var thirdWidth = position.width / 3f - 2f;
|
|
20
|
+
var nameRect = new Rect(position.x, position.y, thirdWidth, EditorGUIUtility.singleLineHeight);
|
|
21
|
+
var prefabRect = new Rect(position.x + thirdWidth + 3f, position.y, thirdWidth, EditorGUIUtility.singleLineHeight);
|
|
22
|
+
var sliderRect = new Rect(position.x + (thirdWidth + 3f) * 2, position.y, thirdWidth, EditorGUIUtility.singleLineHeight);
|
|
23
|
+
|
|
24
|
+
EditorGUI.PropertyField(nameRect, tileNameProp, GUIContent.none);
|
|
25
|
+
EditorGUI.PropertyField(prefabRect, tilePrefabProp, GUIContent.none);
|
|
26
|
+
|
|
27
|
+
EditorGUI.BeginChangeCheck();
|
|
28
|
+
var sliderValue = EditorGUI.IntSlider(sliderRect, prewarmProp.intValue, 0, 10);
|
|
29
|
+
if (EditorGUI.EndChangeCheck())
|
|
30
|
+
{
|
|
31
|
+
prewarmProp.intValue = sliderValue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
EditorGUI.EndProperty();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
|
38
|
+
{
|
|
39
|
+
return EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
#endif
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Prefab Pool
|
|
2
|
+
|
|
3
|
+
Generic object pooling system for Unity.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ Simple API for Get/Return pattern
|
|
8
|
+
- ✅ Automatic prefab instantiation
|
|
9
|
+
- ✅ `IPoolPrefab` interface for lifecycle callbacks
|
|
10
|
+
- ✅ Pre-warming support
|
|
11
|
+
- ✅ Material pooling support
|
|
12
|
+
- ✅ No dependencies on other packages
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Add to your `manifest.json`:
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"com.amanotes.prefabpool": "1.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```csharp
|
|
28
|
+
using Amanotes.PrefabPool;
|
|
29
|
+
|
|
30
|
+
public class Example : MonoBehaviour
|
|
31
|
+
{
|
|
32
|
+
void Start()
|
|
33
|
+
{
|
|
34
|
+
var pool = new PrefabPool();
|
|
35
|
+
var group = CreatePoolGroup();
|
|
36
|
+
pool.RegisterGroup(group, transform);
|
|
37
|
+
|
|
38
|
+
// Get object from pool
|
|
39
|
+
var obj = pool.Get<SpriteRenderer>("enemy");
|
|
40
|
+
|
|
41
|
+
// Return to pool when done
|
|
42
|
+
pool.Return(obj.gameObject);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### Implement IPoolPrefab
|
|
50
|
+
|
|
51
|
+
```csharp
|
|
52
|
+
public class Enemy : MonoBehaviour, IPoolPrefab
|
|
53
|
+
{
|
|
54
|
+
public void OnGet()
|
|
55
|
+
{
|
|
56
|
+
// Called when retrieved from pool
|
|
57
|
+
gameObject.SetActive(true);
|
|
58
|
+
ResetState();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public void OnReturn()
|
|
62
|
+
{
|
|
63
|
+
// Called when returned to pool
|
|
64
|
+
gameObject.SetActive(false);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Create Pool Configuration
|
|
70
|
+
|
|
71
|
+
```csharp
|
|
72
|
+
var poolGroup = ScriptableObject.CreateInstance<PoolGroup>();
|
|
73
|
+
poolGroup.name = "Enemies";
|
|
74
|
+
poolGroup.entries = new List<PoolEntry>
|
|
75
|
+
{
|
|
76
|
+
new PoolEntry
|
|
77
|
+
{
|
|
78
|
+
id = "enemy",
|
|
79
|
+
prefab = enemyPrefab,
|
|
80
|
+
prewarmCount = 10
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## API Reference
|
|
86
|
+
|
|
87
|
+
### IPrefabPool Interface
|
|
88
|
+
|
|
89
|
+
```csharp
|
|
90
|
+
void RegisterGroup(PoolGroup group, Transform parent);
|
|
91
|
+
T Get<T>(string type) where T : Component;
|
|
92
|
+
void Return(GameObject obj);
|
|
93
|
+
IEnumerator Initialize();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### IPoolPrefab Interface
|
|
97
|
+
|
|
98
|
+
```csharp
|
|
99
|
+
void OnGet(); // Called when retrieved
|
|
100
|
+
void OnReturn(); // Called when returned
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
Copyright © Amanotes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Amanotes.PrefabPool",
|
|
3
|
+
"rootNamespace": "Amanotes.Core",
|
|
4
|
+
"references": [],
|
|
5
|
+
"includePlatforms": [],
|
|
6
|
+
"excludePlatforms": [],
|
|
7
|
+
"allowUnsafeCode": false,
|
|
8
|
+
"overrideReferences": false,
|
|
9
|
+
"precompiledReferences": [],
|
|
10
|
+
"autoReferenced": true,
|
|
11
|
+
"defineConstraints": [],
|
|
12
|
+
"versionDefines": [],
|
|
13
|
+
"noEngineReferences": false
|
|
14
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
using UnityObject = UnityEngine.Object;
|
|
5
|
+
|
|
6
|
+
namespace Amanotes.Core
|
|
7
|
+
{
|
|
8
|
+
|
|
9
|
+
public static class MaterialExtensions
|
|
10
|
+
{
|
|
11
|
+
public static void SetAlpha(this Material m, float a)
|
|
12
|
+
{
|
|
13
|
+
if (m == null) return;
|
|
14
|
+
var isCloned = (m.hideFlags & HideFlags.DontSave) != 0;
|
|
15
|
+
if (!isCloned) Debug.LogWarning($"Modify material directly <{m}>!");
|
|
16
|
+
|
|
17
|
+
var c = m.color;
|
|
18
|
+
c.a = a;
|
|
19
|
+
m.color = c;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
public partial class MaterialPool
|
|
25
|
+
{
|
|
26
|
+
[NonSerialized] private static readonly Dictionary<int, MaterialPool> _map = new Dictionary<int, MaterialPool>();
|
|
27
|
+
[NonSerialized] private static readonly Dictionary<int, MaterialPool> _cloned = new Dictionary<int, MaterialPool>();
|
|
28
|
+
|
|
29
|
+
private static MaterialPool GetPool(Material sourceOrClone)
|
|
30
|
+
{
|
|
31
|
+
if (sourceOrClone == null) return null;
|
|
32
|
+
var instId = sourceOrClone.GetInstanceID();
|
|
33
|
+
var isCloned = sourceOrClone.hideFlags == HideFlags.DontSave;
|
|
34
|
+
return isCloned
|
|
35
|
+
? _cloned.GetValueOrDefault(instId)
|
|
36
|
+
: _map.GetValueOrDefault(instId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public static Material Take(Material sourceOrClone, int borrowId)
|
|
40
|
+
{
|
|
41
|
+
if (sourceOrClone == null) return null;
|
|
42
|
+
var pool = GetPool(sourceOrClone) ?? new MaterialPool(sourceOrClone);
|
|
43
|
+
return pool.InternalTake(borrowId);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public static void Return(int borrowId)
|
|
47
|
+
{
|
|
48
|
+
foreach (var kvp in _map)
|
|
49
|
+
{
|
|
50
|
+
kvp.Value.InternalReturn(borrowId);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public partial class MaterialPool
|
|
56
|
+
{
|
|
57
|
+
private readonly Material original;
|
|
58
|
+
private readonly List<Material> pool = new List<Material>();
|
|
59
|
+
private readonly Dictionary<int, Material> borrowed = new Dictionary<int, Material>();
|
|
60
|
+
|
|
61
|
+
private MaterialPool(Material source)
|
|
62
|
+
{
|
|
63
|
+
original = source;
|
|
64
|
+
_map[original.GetInstanceID()] = this;
|
|
65
|
+
|
|
66
|
+
if (source.hideFlags == HideFlags.DontSave)
|
|
67
|
+
{
|
|
68
|
+
Debug.LogWarning($"Invalid source hideFlags: {source.name} : {source.hideFlags}");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// public bool IsOriginalColor(Color c) => original.color == c;
|
|
73
|
+
// public bool IsOriginalAlpha(float a) => Mathf.Approximately(original.color.a, a);
|
|
74
|
+
|
|
75
|
+
private Material GetOne()
|
|
76
|
+
{
|
|
77
|
+
Material result;
|
|
78
|
+
if (pool.Count <= 0)
|
|
79
|
+
{
|
|
80
|
+
result = new Material(original) { hideFlags = HideFlags.DontSave };
|
|
81
|
+
_cloned[result.GetInstanceID()] = this;
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
result = pool[pool.Count - 1];
|
|
86
|
+
pool.RemoveAt(pool.Count - 1);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private Material InternalTake(int borrowId)
|
|
91
|
+
{
|
|
92
|
+
if (borrowed.TryGetValue(borrowId, out var mat)) return mat;
|
|
93
|
+
|
|
94
|
+
var material = GetOne();
|
|
95
|
+
material.name = $"{original.name}_{borrowId}";
|
|
96
|
+
borrowed[borrowId] = material;
|
|
97
|
+
return material;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private void InternalReturn(int borrowId)
|
|
101
|
+
{
|
|
102
|
+
if (borrowed.Remove(borrowId, out var mat)) pool.Add(mat);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
using System.Collections.Generic;
|
|
2
|
+
using UnityEditor;
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
|
|
5
|
+
namespace Amanotes.Core
|
|
6
|
+
{
|
|
7
|
+
public static class MeshRendererExtensions
|
|
8
|
+
{
|
|
9
|
+
public static void SetAlpha(this List<SpriteRenderer> sprites, float value)
|
|
10
|
+
{
|
|
11
|
+
for (var i = 0; i < sprites.Count; i++)
|
|
12
|
+
{
|
|
13
|
+
var sprt = sprites[i];
|
|
14
|
+
if (sprt == null) continue;
|
|
15
|
+
|
|
16
|
+
var c = sprt.color;
|
|
17
|
+
c.a = value;
|
|
18
|
+
sprt.color = c;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
public static void SetColor(this List<MeshRenderer> renderers, Color c, bool keepAlpha = true)
|
|
24
|
+
{
|
|
25
|
+
for (var i = 0; i < renderers.Count; i++)
|
|
26
|
+
{
|
|
27
|
+
renderers[i].SetColor(c, keepAlpha);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public static void SetColor(this List<SpriteRenderer> sprites, Color c, bool keepAlpha = true)
|
|
32
|
+
{
|
|
33
|
+
for (var i = 0; i < sprites.Count; i++)
|
|
34
|
+
{
|
|
35
|
+
sprites[i].SetColor(c, keepAlpha);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public static void SetColor(this MeshRenderer renderer, Color c, bool keepAlpha = true)
|
|
40
|
+
{
|
|
41
|
+
if (renderer == null) return;
|
|
42
|
+
var m = renderer.sharedMaterial;
|
|
43
|
+
if (m == null) return;
|
|
44
|
+
|
|
45
|
+
if (keepAlpha) c.a = m.color.a;
|
|
46
|
+
renderer.sharedMaterial.color = c;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static void SetColor(this SpriteRenderer sprt, Color c, bool keepAlpha = true)
|
|
50
|
+
{
|
|
51
|
+
if (sprt == null) return;
|
|
52
|
+
if (keepAlpha) c.a = sprt.color.a;
|
|
53
|
+
sprt.color = c;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public static void SetAlpha(this SpriteRenderer sprt, float alpha)
|
|
57
|
+
{
|
|
58
|
+
if (sprt == null) return;
|
|
59
|
+
var c = sprt.color;
|
|
60
|
+
c.a = sprt.color.a;
|
|
61
|
+
sprt.color = c;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public static void CloneAndSetAlpha(this MeshRenderer renderer, float alpha)
|
|
65
|
+
{
|
|
66
|
+
// Prevent accidentally changes to prefab's material
|
|
67
|
+
#if UNITY_EDITOR
|
|
68
|
+
if (PrefabUtility.IsPartOfPrefabAsset(renderer)) return;
|
|
69
|
+
if (!Application.isPlaying) return;
|
|
70
|
+
#endif
|
|
71
|
+
if (renderer == null) return;
|
|
72
|
+
|
|
73
|
+
var oMat = renderer.sharedMaterial;
|
|
74
|
+
var cMat = MaterialPool.Take(oMat, renderer.GetInstanceID());
|
|
75
|
+
renderer.sharedMaterial = cMat;
|
|
76
|
+
|
|
77
|
+
cMat.SetAlpha(alpha);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections;
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using UnityEditor;
|
|
5
|
+
using UnityEngine;
|
|
6
|
+
using UnityObject = UnityEngine.Object;
|
|
7
|
+
|
|
8
|
+
namespace Amanotes.Core
|
|
9
|
+
{
|
|
10
|
+
public interface IPoolPrefab
|
|
11
|
+
{
|
|
12
|
+
void OnGet();
|
|
13
|
+
void OnReturn();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
[Serializable] public class PoolEntry
|
|
17
|
+
{
|
|
18
|
+
public string id;
|
|
19
|
+
public GameObject prefab;
|
|
20
|
+
public int prewarmCount;
|
|
21
|
+
|
|
22
|
+
[NonSerialized] private readonly List<GameObject> _pool = new List<GameObject>();
|
|
23
|
+
[NonSerialized] private PoolGroup _parentGroup;
|
|
24
|
+
|
|
25
|
+
public void SetParentGroup(PoolGroup parentGroup) => _parentGroup = parentGroup;
|
|
26
|
+
|
|
27
|
+
public GameObject Get(Transform poolParent, Transform spawnParent, Vector3 localPosition)
|
|
28
|
+
{
|
|
29
|
+
GameObject resultGameObject = null;
|
|
30
|
+
|
|
31
|
+
if (prefab == null)
|
|
32
|
+
{
|
|
33
|
+
Debug.LogWarning($"Prefab pool object is null. <{id}>");
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (_pool.Count > 0)
|
|
38
|
+
{
|
|
39
|
+
var lastIndex = _pool.Count - 1;
|
|
40
|
+
resultGameObject = _pool[lastIndex];
|
|
41
|
+
_pool.RemoveAt(lastIndex);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (resultGameObject == null)
|
|
45
|
+
{
|
|
46
|
+
// Create prefab inside the pool's transform
|
|
47
|
+
resultGameObject = UnityObject.Instantiate(prefab, poolParent.position, Quaternion.identity, poolParent);
|
|
48
|
+
resultGameObject.hideFlags = HideFlags.DontSave;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Move to spawn parent when retrieved
|
|
52
|
+
var t = resultGameObject.transform;
|
|
53
|
+
t.SetParent(spawnParent, false);
|
|
54
|
+
t.localPosition = localPosition;
|
|
55
|
+
t.localRotation = Quaternion.identity;
|
|
56
|
+
|
|
57
|
+
// Call OnGet on any IPoolPrefab components
|
|
58
|
+
var poolComponents = resultGameObject.GetComponents<IPoolPrefab>();
|
|
59
|
+
foreach (var pp in poolComponents)
|
|
60
|
+
{
|
|
61
|
+
pp.OnGet();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return resultGameObject;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public void Return(GameObject gameObject, Transform poolParent)
|
|
68
|
+
{
|
|
69
|
+
if (gameObject == null) return;
|
|
70
|
+
|
|
71
|
+
// Call OnReturn on any IPoolPrefab components
|
|
72
|
+
var poolComponents = gameObject.GetComponents<IPoolPrefab>();
|
|
73
|
+
foreach (var pp in poolComponents)
|
|
74
|
+
{
|
|
75
|
+
pp.OnReturn();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
var t = gameObject.transform;
|
|
79
|
+
t.SetParent(poolParent, false);
|
|
80
|
+
t.localPosition = Vector3.zero;
|
|
81
|
+
_pool.Add(gameObject);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public void DestroyPool()
|
|
85
|
+
{
|
|
86
|
+
for (var i = 0; i < _pool.Count; i++)
|
|
87
|
+
{
|
|
88
|
+
if (_pool[i] != null)
|
|
89
|
+
{
|
|
90
|
+
UnityObject.Destroy(_pool[i]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
_pool.Clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public IEnumerator PreAllocate(Transform poolParent)
|
|
97
|
+
{
|
|
98
|
+
for (var i = 0; i < prewarmCount; i++)
|
|
99
|
+
{
|
|
100
|
+
var instance = UnityObject.Instantiate(prefab, poolParent.position, Quaternion.identity, poolParent);
|
|
101
|
+
instance.hideFlags = HideFlags.DontSave;
|
|
102
|
+
_pool.Add(instance);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
yield return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
[Serializable] public class PoolGroup
|
|
110
|
+
{
|
|
111
|
+
public string id;
|
|
112
|
+
public List<PoolEntry> entries = new List<PoolEntry>();
|
|
113
|
+
public PoolEntry fallbackEntry;
|
|
114
|
+
|
|
115
|
+
[NonSerialized] private IPrefabPool _prefabPool;
|
|
116
|
+
[NonSerialized] private Transform _spawnParent;
|
|
117
|
+
|
|
118
|
+
public void Register(IPrefabPool prefabPool, Transform spawnParent)
|
|
119
|
+
{
|
|
120
|
+
_prefabPool = prefabPool;
|
|
121
|
+
_spawnParent = spawnParent;
|
|
122
|
+
|
|
123
|
+
// Set parent group reference for each entry
|
|
124
|
+
foreach (var entry in entries)
|
|
125
|
+
{
|
|
126
|
+
entry.SetParentGroup(this);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Set parent group reference for fallback entry
|
|
130
|
+
if (fallbackEntry != null)
|
|
131
|
+
{
|
|
132
|
+
fallbackEntry.SetParentGroup(this);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public GameObject Get(string prefabId, Vector3 localPosition = default, Transform customParent = null)
|
|
137
|
+
{
|
|
138
|
+
return _prefabPool?.Get(this, prefabId, localPosition, customParent);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public T Get<T>(string prefabId, Vector3 localPosition = default) where T : class
|
|
142
|
+
{
|
|
143
|
+
var go = Get(prefabId, localPosition);
|
|
144
|
+
if (go == null) return default(T);
|
|
145
|
+
var c = go.GetComponent<T>();
|
|
146
|
+
return c ?? go.GetComponentWithInterface<T>();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public void Return(GameObject gameObject)
|
|
150
|
+
{
|
|
151
|
+
_prefabPool?.Return(this, gameObject);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public Transform SpawnParent => _spawnParent;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public interface IPrefabPool
|
|
158
|
+
{
|
|
159
|
+
bool IsReady { get; }
|
|
160
|
+
void RegisterGroup(PoolGroup group, Transform spawnParent);
|
|
161
|
+
void SetSpawnParent(string groupId, Transform spawnParent);
|
|
162
|
+
GameObject Get(PoolGroup group, string prefabId, Vector3 localPosition = default, Transform customParent = null);
|
|
163
|
+
void Return(PoolGroup group, GameObject gameObject);
|
|
164
|
+
IEnumerator Initialize();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public static class PrefabPoolExtensions
|
|
168
|
+
{
|
|
169
|
+
public static void DestroyLast<T>(this List<T> list) where T : Component
|
|
170
|
+
{
|
|
171
|
+
if (list.Count == 0) return;
|
|
172
|
+
var last = list[list.Count - 1];
|
|
173
|
+
list.RemoveAt(list.Count-1);
|
|
174
|
+
if (last == null) return;
|
|
175
|
+
|
|
176
|
+
if (Application.isPlaying)
|
|
177
|
+
{
|
|
178
|
+
UnityObject.Destroy(last.gameObject);
|
|
179
|
+
}
|
|
180
|
+
else
|
|
181
|
+
{
|
|
182
|
+
UnityObject.DestroyImmediate(last.gameObject);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public static T ClonePrefab<T>(this GameObject prefab, Transform parent) where T : Component
|
|
187
|
+
{
|
|
188
|
+
#if UNITY_EDITOR
|
|
189
|
+
var c = Application.isPlaying
|
|
190
|
+
? UnityObject.Instantiate(prefab, parent.position, Quaternion.identity, parent)
|
|
191
|
+
: (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent);
|
|
192
|
+
#else
|
|
193
|
+
var c = UnityObject.Instantiate(prefab, parent.position, Quaternion.identity, parent);
|
|
194
|
+
#endif
|
|
195
|
+
return c.GetComponent<T>();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public static void ResetAndClear<T>(this List<T> list, GameObject prefab, PrefabPool pool = null)
|
|
199
|
+
where T : Component
|
|
200
|
+
{
|
|
201
|
+
var usePool = pool != null;
|
|
202
|
+
|
|
203
|
+
#if UNITY_EDITOR
|
|
204
|
+
if (!Application.isPlaying) usePool = false;
|
|
205
|
+
#endif
|
|
206
|
+
if (usePool)
|
|
207
|
+
{
|
|
208
|
+
pool.Resize<T>(prefab, list, 0, null);
|
|
209
|
+
}
|
|
210
|
+
else
|
|
211
|
+
{
|
|
212
|
+
while (list.Count > 0) list.DestroyLast();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public static void Resize<T>(this List<T> list, int count, GameObject prefab, Transform parent, PrefabPool pool) where T : Component
|
|
217
|
+
{
|
|
218
|
+
var usePool = pool != null;
|
|
219
|
+
|
|
220
|
+
#if UNITY_EDITOR
|
|
221
|
+
if (!Application.isPlaying) usePool = false;
|
|
222
|
+
#endif
|
|
223
|
+
if (usePool)
|
|
224
|
+
{
|
|
225
|
+
pool.Resize<T>(prefab, list, count, parent);
|
|
226
|
+
}
|
|
227
|
+
else
|
|
228
|
+
{
|
|
229
|
+
while (list.Count > count) list.DestroyLast();
|
|
230
|
+
while (list.Count < count)
|
|
231
|
+
{
|
|
232
|
+
list.Add(prefab.ClonePrefab<T>(parent));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for (var i = 0; i < list.Count; i++)
|
|
236
|
+
{
|
|
237
|
+
if (list[i] != null) continue;
|
|
238
|
+
list[i] = prefab.ClonePrefab<T>(parent);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
public class PrefabPool : MonoBehaviour, IPrefabPool
|
|
247
|
+
{
|
|
248
|
+
[NonSerialized] private bool _isDestroyed = false;
|
|
249
|
+
[NonSerialized] private bool _isReady = false;
|
|
250
|
+
[NonSerialized] private readonly Dictionary<string, PoolGroup> _groups = new Dictionary<string, PoolGroup>();
|
|
251
|
+
[NonSerialized] private readonly Dictionary<int, PoolEntry> _instanceTypeMap = new Dictionary<int, PoolEntry>();
|
|
252
|
+
[NonSerialized] private readonly Dictionary<GameObject, PoolEntry> _customPool = new Dictionary<GameObject, PoolEntry>();
|
|
253
|
+
|
|
254
|
+
public void Resize<T>(GameObject prefab, List<T> list, int count, Transform parent) where T: Component
|
|
255
|
+
{
|
|
256
|
+
if (_customPool == null)
|
|
257
|
+
{
|
|
258
|
+
Debug.LogWarning("Something wrong: _customPool is null!");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (prefab == null)
|
|
263
|
+
{
|
|
264
|
+
Debug.LogWarning($"Prefab is null??? {prefab}");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!_customPool.TryGetValue(prefab, out var entry))
|
|
269
|
+
{
|
|
270
|
+
entry = new PoolEntry()
|
|
271
|
+
{
|
|
272
|
+
id = prefab.name,
|
|
273
|
+
prefab = prefab
|
|
274
|
+
};
|
|
275
|
+
_customPool[prefab] = entry;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
while (list.Count > count)
|
|
279
|
+
{
|
|
280
|
+
var last = list[list.Count - 1];
|
|
281
|
+
if (last != null) entry.Return(last.gameObject, transform);
|
|
282
|
+
list.RemoveAt(list.Count - 1);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
while (list.Count < count)
|
|
286
|
+
{
|
|
287
|
+
var c = entry.Get(transform, parent, Vector3.zero);
|
|
288
|
+
list.Add(c.GetComponent<T>());
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
public bool IsReady => _isReady;
|
|
293
|
+
|
|
294
|
+
public void RegisterGroup(PoolGroup group, Transform spawnParent)
|
|
295
|
+
{
|
|
296
|
+
if (group == null) return;
|
|
297
|
+
|
|
298
|
+
_groups[group.id] = group;
|
|
299
|
+
group.Register(this, spawnParent);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
public void SetSpawnParent(string groupId, Transform spawnParent)
|
|
303
|
+
{
|
|
304
|
+
if (_groups.TryGetValue(groupId, out var group))
|
|
305
|
+
{
|
|
306
|
+
group.Register(this, spawnParent);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
public GameObject Get(PoolGroup group, string prefabId, Vector3 localPosition = default, Transform customParent = null)
|
|
311
|
+
{
|
|
312
|
+
if (_isDestroyed || group == null) return null;
|
|
313
|
+
if (string.IsNullOrEmpty(prefabId)) return null;
|
|
314
|
+
|
|
315
|
+
var entry = group.entries.Find(e => e.id == prefabId);
|
|
316
|
+
if (entry == null)
|
|
317
|
+
{
|
|
318
|
+
// Try fallback entry if available
|
|
319
|
+
entry = group.fallbackEntry;
|
|
320
|
+
if (entry == null) return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
var p = customParent == null ? group.SpawnParent : customParent;
|
|
324
|
+
var result = entry.Get(transform, p, localPosition);
|
|
325
|
+
if (result != null)
|
|
326
|
+
{
|
|
327
|
+
_instanceTypeMap[result.GetInstanceID()] = entry;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
public void Return(PoolGroup group, GameObject go)
|
|
334
|
+
{
|
|
335
|
+
if (_isDestroyed || group == null || go == null) return;
|
|
336
|
+
|
|
337
|
+
var instanceId = go.GetInstanceID();
|
|
338
|
+
if (_instanceTypeMap.TryGetValue(instanceId, out var entry))
|
|
339
|
+
{
|
|
340
|
+
entry.Return(go, transform);
|
|
341
|
+
_instanceTypeMap.Remove(instanceId);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
public IEnumerator Initialize()
|
|
346
|
+
{
|
|
347
|
+
if (_isReady) yield break;
|
|
348
|
+
_isReady = false;
|
|
349
|
+
|
|
350
|
+
#if UNITY_EDITOR
|
|
351
|
+
EditorApplication.playModeStateChanged -= HandlePlayModeChanged;
|
|
352
|
+
EditorApplication.playModeStateChanged += HandlePlayModeChanged;
|
|
353
|
+
#endif
|
|
354
|
+
|
|
355
|
+
foreach (var group in _groups.Values)
|
|
356
|
+
{
|
|
357
|
+
foreach (var entry in group.entries)
|
|
358
|
+
{
|
|
359
|
+
if (entry.prefab == null) continue;
|
|
360
|
+
yield return entry.PreAllocate(transform);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Pre-allocate fallback entry if available
|
|
364
|
+
if (group.fallbackEntry != null && group.fallbackEntry.prefab != null)
|
|
365
|
+
{
|
|
366
|
+
yield return group.fallbackEntry.PreAllocate(transform);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
_isReady = true;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
#if UNITY_EDITOR
|
|
374
|
+
protected void HandlePlayModeChanged(PlayModeStateChange changed)
|
|
375
|
+
{
|
|
376
|
+
if (changed == PlayModeStateChange.ExitingPlayMode)
|
|
377
|
+
{
|
|
378
|
+
DestroyPool();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
#endif
|
|
382
|
+
|
|
383
|
+
public void DestroyPool()
|
|
384
|
+
{
|
|
385
|
+
_isDestroyed = true;
|
|
386
|
+
|
|
387
|
+
foreach (var group in _groups.Values)
|
|
388
|
+
{
|
|
389
|
+
foreach (var entry in group.entries)
|
|
390
|
+
{
|
|
391
|
+
entry.DestroyPool();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Destroy fallback entry if available
|
|
395
|
+
if (group.fallbackEntry != null)
|
|
396
|
+
{
|
|
397
|
+
group.fallbackEntry.DestroyPool();
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
_groups.Clear();
|
|
402
|
+
|
|
403
|
+
#if UNITY_EDITOR
|
|
404
|
+
foreach (var kvp in _instanceTypeMap)
|
|
405
|
+
{
|
|
406
|
+
var obj = EditorUtility.InstanceIDToObject(kvp.Key);
|
|
407
|
+
if (obj != null) UnityObject.DestroyImmediate(obj);
|
|
408
|
+
}
|
|
409
|
+
_instanceTypeMap.Clear();
|
|
410
|
+
#endif
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "com.amanotes.prefabpool",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"displayName": "Amanotes Prefab Pool",
|
|
5
|
+
"description": "Generic object pooling system for Unity",
|
|
6
|
+
"unity": "2022.3",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"pooling",
|
|
9
|
+
"object-pool",
|
|
10
|
+
"performance",
|
|
11
|
+
"optimization"
|
|
12
|
+
],
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "Amanotes",
|
|
15
|
+
"url": "https://amanotes.com"
|
|
16
|
+
}
|
|
17
|
+
}
|