From efac0019ba03949217b93adcc4c0095d6e670abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerard=20Gasc=C3=B3n?= <52170489+GerardGascon@users.noreply.github.com> Date: Fri, 19 Apr 2024 15:57:25 +0200 Subject: [PATCH] feat: spawning particles with object pooling --- Assets/Prefabs/Particle_Grow.prefab | 1 + Assets/Scripts/Pooling.meta | 3 + Assets/Scripts/Pooling/IPooledObject.cs | 5 + Assets/Scripts/Pooling/IPooledObject.cs.meta | 3 + Assets/Scripts/Pooling/Pooler.cs | 152 ++++++++++++++++++ Assets/Scripts/Pooling/Pooler.cs.meta | 3 + Assets/Scripts/Pooling/PoolerExtensions.cs | 39 +++++ .../Scripts/Pooling/PoolerExtensions.cs.meta | 3 + .../Scripts/Pooling/SantJordi.Pooling.asmdef | 3 + .../Pooling/SantJordi.Pooling.asmdef.meta | 7 + Assets/Scripts/View/SantJordi.View.asmdef | 3 +- Assets/Scripts/View/Scene/GrowParticle.cs | 11 +- .../View/Scene/GrowParticlesSpawner.cs | 7 +- fmod_editor.log | 16 -- 14 files changed, 235 insertions(+), 21 deletions(-) create mode 100644 Assets/Scripts/Pooling.meta create mode 100644 Assets/Scripts/Pooling/IPooledObject.cs create mode 100644 Assets/Scripts/Pooling/IPooledObject.cs.meta create mode 100644 Assets/Scripts/Pooling/Pooler.cs create mode 100644 Assets/Scripts/Pooling/Pooler.cs.meta create mode 100644 Assets/Scripts/Pooling/PoolerExtensions.cs create mode 100644 Assets/Scripts/Pooling/PoolerExtensions.cs.meta create mode 100644 Assets/Scripts/Pooling/SantJordi.Pooling.asmdef create mode 100644 Assets/Scripts/Pooling/SantJordi.Pooling.asmdef.meta diff --git a/Assets/Prefabs/Particle_Grow.prefab b/Assets/Prefabs/Particle_Grow.prefab index ab3f08c..52b7fd6 100644 --- a/Assets/Prefabs/Particle_Grow.prefab +++ b/Assets/Prefabs/Particle_Grow.prefab @@ -112,3 +112,4 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: animator: {fileID: 1908639354391246343} + particleAnimation: {fileID: 11400000, guid: 3aa2da34174b2354d94d7764f109d1c0, type: 2} diff --git a/Assets/Scripts/Pooling.meta b/Assets/Scripts/Pooling.meta new file mode 100644 index 0000000..38e43ba --- /dev/null +++ b/Assets/Scripts/Pooling.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a956b08a87cc495ca71443493064210d +timeCreated: 1713519317 \ No newline at end of file diff --git a/Assets/Scripts/Pooling/IPooledObject.cs b/Assets/Scripts/Pooling/IPooledObject.cs new file mode 100644 index 0000000..bee7d88 --- /dev/null +++ b/Assets/Scripts/Pooling/IPooledObject.cs @@ -0,0 +1,5 @@ +namespace Pooling { + public interface IPooledObject { + void OnObjectSpawn(); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Pooling/IPooledObject.cs.meta b/Assets/Scripts/Pooling/IPooledObject.cs.meta new file mode 100644 index 0000000..bbd0085 --- /dev/null +++ b/Assets/Scripts/Pooling/IPooledObject.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 75e27a032d064219bd1f9314952bdbb8 +timeCreated: 1713534220 \ No newline at end of file diff --git a/Assets/Scripts/Pooling/Pooler.cs b/Assets/Scripts/Pooling/Pooler.cs new file mode 100644 index 0000000..ed8458a --- /dev/null +++ b/Assets/Scripts/Pooling/Pooler.cs @@ -0,0 +1,152 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Pooling { + public class Pooler : MonoBehaviour { + private static Pooler _instance; + + public static Pooler Instance { + get { + if (_instance != null) + return _instance; + + _instance = FindObjectOfType(); + if (_instance != null) + return _instance; + + GameObject obj = new("[Object Pool]"); + _instance = obj.AddComponent(); + return _instance; + } + } + + private static List _tempList = new(); + + private Dictionary> _pooledObjects = new(); + private Dictionary _spawnedObjects = new(); + + public static void CreatePool(T prefab, int initialPoolSize) where T : Component { + CreatePool(prefab.gameObject, initialPoolSize); + } + + public static void CreatePool(GameObject prefab, int initialPoolSize) { + if (prefab == null || Instance._pooledObjects.ContainsKey(prefab)) + return; + + List list = new(); + Instance._pooledObjects.Add(prefab, list); + + if (initialPoolSize <= 0) return; + + bool active = prefab.activeSelf; + prefab.SetActive(false); + Transform parent = Instance.transform; + while (list.Count < initialPoolSize) { + GameObject obj = Instantiate(prefab, parent); + list.Add(obj); + } + prefab.SetActive(active); + } + + public static T Spawn(T prefab, Transform parent, Vector3 position, Quaternion rotation) + where T : Component => + Spawn(prefab.gameObject, parent, position, rotation).GetComponent(); + + public static T Spawn(T prefab, Vector3 position, Quaternion rotation) where T : Component => + Spawn(prefab.gameObject, null, position, rotation).GetComponent(); + + public static T Spawn(T prefab, Transform parent, Vector3 position) where T : Component => + Spawn(prefab.gameObject, parent, position, Quaternion.identity).GetComponent(); + + public static T Spawn(T prefab, Vector3 position) where T : Component => + Spawn(prefab.gameObject, null, position, Quaternion.identity).GetComponent(); + + public static T Spawn(T prefab, Transform parent) where T : Component => + Spawn(prefab.gameObject, parent, Vector3.zero, Quaternion.identity).GetComponent(); + + public static T Spawn(T prefab) where T : Component => + Spawn(prefab.gameObject, null, Vector3.zero, Quaternion.identity).GetComponent(); + + public static GameObject Spawn(GameObject prefab, Transform parent, Vector3 position) => + Spawn(prefab, parent, position, Quaternion.identity); + + public static GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation) => + Spawn(prefab, null, position, rotation); + + public static GameObject Spawn(GameObject prefab, Transform parent) => + Spawn(prefab, parent, Vector3.zero, Quaternion.identity); + + public static GameObject Spawn(GameObject prefab, Vector3 position) => + Spawn(prefab, null, position, Quaternion.identity); + + public static GameObject Spawn(GameObject prefab) => + Spawn(prefab, null, Vector3.zero, Quaternion.identity); + + public static GameObject Spawn(GameObject prefab, Transform parent, Vector3 position, Quaternion rotation) { + List list; + Transform trans; + GameObject obj; + + if (Instance._pooledObjects.TryGetValue(prefab, out list)) { + obj = null; + if (list.Count > 0) { + while (!obj && list.Count > 0) { + obj = list[0]; + list.RemoveAt(0); + } + if (obj != null) { + trans = obj.transform; + trans.parent = parent; + trans.localPosition = position; + trans.localRotation = rotation; + obj.SetActive(true); + Instance._spawnedObjects.Add(obj, prefab); + TryCallPooledObject(obj); + return obj; + } + } + + obj = Instantiate(prefab); + trans = obj.transform; + trans.parent = parent; + trans.localPosition = position; + trans.localRotation = rotation; + Instance._spawnedObjects.Add(obj, prefab); + TryCallPooledObject(obj); + return obj; + } + + obj = Instantiate(prefab); + trans = obj.transform; + trans.parent = parent; + trans.localPosition = position; + trans.localRotation = rotation; + TryCallPooledObject(obj); + return obj; + } + + private static void TryCallPooledObject(GameObject obj) { + if(obj.TryGetComponent(out IPooledObject pooledObject)) + pooledObject.OnObjectSpawn(); + } + + public static void Recycle(T obj) where T : Component { + Recycle(obj.gameObject); + } + + public static void Recycle(GameObject obj) { + GameObject prefab; + if (Instance._spawnedObjects.TryGetValue(obj, out prefab)) + Recycle(obj, prefab); + else + Destroy(obj); + } + + private static void Recycle(GameObject obj, GameObject prefab) { + Instance._pooledObjects[prefab].Add(obj); + Instance._spawnedObjects.Remove(obj); + obj.transform.parent = Instance.transform; + obj.SetActive(false); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Pooling/Pooler.cs.meta b/Assets/Scripts/Pooling/Pooler.cs.meta new file mode 100644 index 0000000..d00cfd0 --- /dev/null +++ b/Assets/Scripts/Pooling/Pooler.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b8fc72fe7498488581bb9ecd15cdb057 +timeCreated: 1713519317 \ No newline at end of file diff --git a/Assets/Scripts/Pooling/PoolerExtensions.cs b/Assets/Scripts/Pooling/PoolerExtensions.cs new file mode 100644 index 0000000..a7b4083 --- /dev/null +++ b/Assets/Scripts/Pooling/PoolerExtensions.cs @@ -0,0 +1,39 @@ +using UnityEngine; + +namespace Pooling { + public static class PoolerExtensions { + public static void CreatePool(this T prefab) where T : Component => + Pooler.CreatePool(prefab, 0); + public static void CreatePool(this T prefab, int initialPoolSize) where T : Component => + Pooler.CreatePool(prefab, initialPoolSize); + public static void CreatePool(this GameObject prefab) => + Pooler.CreatePool(prefab, 0); + public static void CreatePool(this GameObject prefab, int initialPoolSize) => + Pooler.CreatePool(prefab, initialPoolSize); + + public static T Spawn(this T prefab, Transform parent, Vector3 position, Quaternion rotation) where T : Component => + Pooler.Spawn(prefab, parent, position, rotation); + public static T Spawn(this T prefab, Vector3 position, Quaternion rotation) where T : Component => + Pooler.Spawn(prefab, null, position, rotation); + public static T Spawn(this T prefab, Transform parent, Vector3 position) where T : Component => + Pooler.Spawn(prefab, parent, position, Quaternion.identity); + public static T Spawn(this T prefab, Vector3 position) where T : Component => + Pooler.Spawn(prefab, null, position, Quaternion.identity); + public static T Spawn(this T prefab, Transform parent) where T : Component => + Pooler.Spawn(prefab, parent, Vector3.zero, Quaternion.identity); + public static T Spawn(this T prefab) where T : Component => + Pooler.Spawn(prefab, null, Vector3.zero, Quaternion.identity); + public static GameObject Spawn(this GameObject prefab, Transform parent, Vector3 position, Quaternion rotation) => + Pooler.Spawn(prefab, parent, position, rotation); + public static GameObject Spawn(this GameObject prefab, Vector3 position, Quaternion rotation) => + Pooler.Spawn(prefab, null, position, rotation); + public static GameObject Spawn(this GameObject prefab, Transform parent, Vector3 position) => + Pooler.Spawn(prefab, parent, position, Quaternion.identity); + public static GameObject Spawn(this GameObject prefab, Vector3 position) => + Pooler.Spawn(prefab, null, position, Quaternion.identity); + public static GameObject Spawn(this GameObject prefab, Transform parent) => + Pooler.Spawn(prefab, parent, Vector3.zero, Quaternion.identity); + public static GameObject Spawn(this GameObject prefab) => + Pooler.Spawn(prefab, null, Vector3.zero, Quaternion.identity); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Pooling/PoolerExtensions.cs.meta b/Assets/Scripts/Pooling/PoolerExtensions.cs.meta new file mode 100644 index 0000000..1da9aa0 --- /dev/null +++ b/Assets/Scripts/Pooling/PoolerExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ea3218d098d947b6b008751f3c2cd8f4 +timeCreated: 1713522613 \ No newline at end of file diff --git a/Assets/Scripts/Pooling/SantJordi.Pooling.asmdef b/Assets/Scripts/Pooling/SantJordi.Pooling.asmdef new file mode 100644 index 0000000..a69103f --- /dev/null +++ b/Assets/Scripts/Pooling/SantJordi.Pooling.asmdef @@ -0,0 +1,3 @@ +{ + "name": "SantJordi.Pooling" +} diff --git a/Assets/Scripts/Pooling/SantJordi.Pooling.asmdef.meta b/Assets/Scripts/Pooling/SantJordi.Pooling.asmdef.meta new file mode 100644 index 0000000..2f17287 --- /dev/null +++ b/Assets/Scripts/Pooling/SantJordi.Pooling.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1e89d5d1db358ce43890b8310d696377 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/View/SantJordi.View.asmdef b/Assets/Scripts/View/SantJordi.View.asmdef index 5b7c343..05a411d 100644 --- a/Assets/Scripts/View/SantJordi.View.asmdef +++ b/Assets/Scripts/View/SantJordi.View.asmdef @@ -8,7 +8,8 @@ "GUID:1220ccfff01d26041a9bb8cd7ae584af", "GUID:58f2f98b0cec4e74998cb65ad59190b4", "GUID:0c752da273b17c547ae705acf0f2adf2", - "GUID:a8b14a26d6924d6bb65739ca56ae8187" + "GUID:a8b14a26d6924d6bb65739ca56ae8187", + "GUID:1e89d5d1db358ce43890b8310d696377" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Assets/Scripts/View/Scene/GrowParticle.cs b/Assets/Scripts/View/Scene/GrowParticle.cs index 20f1ddf..c15bcb9 100644 --- a/Assets/Scripts/View/Scene/GrowParticle.cs +++ b/Assets/Scripts/View/Scene/GrowParticle.cs @@ -1,14 +1,19 @@ using System; +using Pooling; using Presenter; using UnityEngine; +using UnityEngine.Serialization; +using Animation = FramedAnimator.Animation; using Animator = FramedAnimator.Animator; namespace View.Scene { - public class GrowParticle : MonoBehaviour { + public class GrowParticle : MonoBehaviour, IPooledObject { [SerializeField] private Animator animator; + [SerializeField] private Animation particleAnimation; - private void Start() { - animator.OnAnimationEnd += _ => Destroy(gameObject); + public void OnObjectSpawn() { + animator.ChangeAnimation(particleAnimation); + animator.OnAnimationEnd += _ => Pooler.Recycle(gameObject); animator.PlayUntil(1f); } } diff --git a/Assets/Scripts/View/Scene/GrowParticlesSpawner.cs b/Assets/Scripts/View/Scene/GrowParticlesSpawner.cs index 1c50d8c..a16eeb8 100644 --- a/Assets/Scripts/View/Scene/GrowParticlesSpawner.cs +++ b/Assets/Scripts/View/Scene/GrowParticlesSpawner.cs @@ -1,4 +1,5 @@ using System; +using Pooling; using Presenter; using UnityEngine; using Random = UnityEngine.Random; @@ -9,9 +10,13 @@ namespace View.Scene { [SerializeField] private Transform growParticlePositions; [SerializeField, Range(0, 180)] private float angleRange; + private void Awake() { + growParticle.CreatePool(); + } + public void OnInputReceived() { float randomRotation = Random.Range(-angleRange / 2f, angleRange / 2f); - Instantiate(growParticle, growParticlePositions.position, Quaternion.Euler(0f, 0f, randomRotation)); + growParticle.Spawn(growParticlePositions.position, Quaternion.Euler(0, 0, randomRotation)); } private void OnDrawGizmosSelected() { diff --git a/fmod_editor.log b/fmod_editor.log index 223aa2f..e69de29 100644 --- a/fmod_editor.log +++ b/fmod_editor.log @@ -1,16 +0,0 @@ -[LOG] System::create : Header version = 2.02.06. Current version = 2.02.06. -[LOG] Manager::init : maxchannels = 256 studioflags = 00000006 flags 00000000 extradriverdata 0000000000000000. -[LOG] SystemI::init : Initialize version=20206 (124257), maxchannels=256, flags=0x00020000 -[LOG] SystemI::setOutputInternal : Setting output to 'FMOD WASAPI Output' -[LOG] OutputWASAPI::init : Mix Format (WAVEFORMATEX): wFormatTag=0xFFFE, nChannels=2, nSamplesPerSec=48000, nAvgBytesPerSec=384000, nBlockAlign=8, wBitsPerSample=32, cbSize=22. -[LOG] OutputWASAPI::init : Mix Format (WAVEFORMATEXTENSIBLE): wValidBitsPerSample=32, dwChannelMask=0x00000003, SubFormat=00000003-0000-0010-8000-00AA00389B71. -[LOG] OutputWASAPI::init : Output buffer size: 4096 samples, latency: 0.00ms, period: 10.67ms, DSP buffer: 1024 * 4 -[LOG] Thread::initThread : Init FMOD stream thread. Affinity: 0x4000000000000003, Priority: 0xFFFF7FFB, Stack Size: 98304, Semaphore: No, Sleep Time: 10, Looping: Yes. -[LOG] Thread::initThread : Init FMOD mixer thread. Affinity: 0x4000000000000001, Priority: 0xFFFF7FFA, Stack Size: 81920, Semaphore: No, Sleep Time: 0, Looping: Yes. -[LOG] AsyncManager::init : manager 000002035DC49838 isAsync 0 updatePeriod 0.02 -[LOG] AsyncManager::init : done -[LOG] PlaybackSystem::init : -[LOG] Thread::initThread : Init FMOD Studio sample load thread. Affinity: 0x4000000000000003, Priority: 0xFFFF7FFD, Stack Size: 98304, Semaphore: No, Sleep Time: 1, Looping: No. -[LOG] PlaybackSystem::init : done -[LOG] Thread::initThread : Init FMOD Studio bank load thread. Affinity: 0x4000000000000003, Priority: 0xFFFF7FFD, Stack Size: 98304, Semaphore: No, Sleep Time: 1, Looping: No. -[LOG] Manager::init : done.