From bcc6f05058aca34b79c6b7040022245eac2b9390 Mon Sep 17 00:00:00 2001 From: Michal Pikulski Date: Tue, 16 Sep 2025 12:23:25 +0200 Subject: [PATCH] Working single-purpose object pooling solution --- .../AddressableAssetSettings.asset | 2 +- Assets/Editor/Utilities.meta | 3 + Assets/Editor/Utilities/PoolMonitorWindow.cs | 244 ++++++++++++++++++ .../Utilities/PoolMonitorWindow.cs.meta | 3 + Assets/Prefabs/FX/Bubble.prefab | 5 +- .../Minigames/DivingForPictures/Tile1.prefab | 12 +- .../DivingForPictures/Tile1_flipped.prefab | 12 +- .../Minigames/DivingForPictures/Tile2.prefab | 12 +- .../DivingForPictures/Tile2_flipped.prefab | 12 +- .../Minigames/DivingForPictures/Tile3.prefab | 12 +- .../DivingForPictures/Tile3_flipped.prefab | 12 +- .../Scenes/MiniGames/DivingForPictures.unity | 95 ++++--- .../Minigames/DivingForPictures/Bubble.cs | 91 +++++-- .../Minigames/DivingForPictures/BubblePool.cs | 117 +++++++++ .../DivingForPictures/BubblePool.cs.meta | 3 + .../DivingForPictures/BubbleSpawner.cs | 48 +++- .../DivingForPictures/TrenchTilePool.cs | 244 ++++++++++++++++++ .../DivingForPictures/TrenchTilePool.cs.meta | 3 + .../DivingForPictures/TrenchTileSpawner.cs | 175 ++++++++++++- 19 files changed, 990 insertions(+), 115 deletions(-) create mode 100644 Assets/Editor/Utilities.meta create mode 100644 Assets/Editor/Utilities/PoolMonitorWindow.cs create mode 100644 Assets/Editor/Utilities/PoolMonitorWindow.cs.meta create mode 100644 Assets/Scripts/Minigames/DivingForPictures/BubblePool.cs create mode 100644 Assets/Scripts/Minigames/DivingForPictures/BubblePool.cs.meta create mode 100644 Assets/Scripts/Minigames/DivingForPictures/TrenchTilePool.cs create mode 100644 Assets/Scripts/Minigames/DivingForPictures/TrenchTilePool.cs.meta diff --git a/Assets/AddressableAssetsData/AddressableAssetSettings.asset b/Assets/AddressableAssetsData/AddressableAssetSettings.asset index 690bbee6..8b61dba3 100644 --- a/Assets/AddressableAssetsData/AddressableAssetSettings.asset +++ b/Assets/AddressableAssetsData/AddressableAssetSettings.asset @@ -15,7 +15,7 @@ MonoBehaviour: m_DefaultGroup: 6f3207429a65b3e4b83935ac19791077 m_currentHash: serializedVersion: 2 - Hash: c0cf00979528ae95d3583c572e4eb343 + Hash: 00000000000000000000000000000000 m_OptimizeCatalogSize: 0 m_BuildRemoteCatalog: 0 m_CatalogRequestsTimeout: 0 diff --git a/Assets/Editor/Utilities.meta b/Assets/Editor/Utilities.meta new file mode 100644 index 00000000..7286bbeb --- /dev/null +++ b/Assets/Editor/Utilities.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8b28cee1553b4a15aa1c3be950983fee +timeCreated: 1758016486 \ No newline at end of file diff --git a/Assets/Editor/Utilities/PoolMonitorWindow.cs b/Assets/Editor/Utilities/PoolMonitorWindow.cs new file mode 100644 index 00000000..6cf0e618 --- /dev/null +++ b/Assets/Editor/Utilities/PoolMonitorWindow.cs @@ -0,0 +1,244 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; +using Minigames.DivingForPictures; + +namespace Editor.Utilities +{ + public class PoolMonitorWindow : EditorWindow + { + private Vector2 scrollPosition; + private bool autoRefresh = true; + private float refreshInterval = 1.0f; + private float lastRefreshTime; + private bool showTrenchTiles = true; + private bool showBubbles = true; + + [MenuItem("Tools/Pool Monitor")] + public static void ShowWindow() + { + GetWindow("Pool Monitor"); + } + + void OnGUI() + { + EditorGUILayout.BeginVertical(); + + EditorGUILayout.LabelField("Object Pool Monitor", EditorStyles.boldLabel); + + EditorGUILayout.BeginHorizontal(); + autoRefresh = EditorGUILayout.Toggle("Auto Refresh", autoRefresh); + if (autoRefresh) + { + refreshInterval = EditorGUILayout.Slider("Refresh Interval", refreshInterval, 0.1f, 5f); + } + if (GUILayout.Button("Refresh Now")) + { + RefreshPoolInfo(); + } + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + // Display toggles for showing different pool types + EditorGUILayout.BeginHorizontal(); + showTrenchTiles = EditorGUILayout.ToggleLeft("Show Trench Tile Pools", showTrenchTiles, GUILayout.Width(200)); + showBubbles = EditorGUILayout.ToggleLeft("Show Bubble Pools", showBubbles, GUILayout.Width(200)); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + + if (Application.isPlaying) + { + DisplayPoolInfo(); + } + else + { + EditorGUILayout.HelpBox("Enter play mode to see pool statistics.", MessageType.Info); + } + + EditorGUILayout.EndScrollView(); + EditorGUILayout.EndVertical(); + } + + void Update() + { + if (autoRefresh && Application.isPlaying) + { + float currentTime = (float)EditorApplication.timeSinceStartup; + if (currentTime - lastRefreshTime > refreshInterval) + { + lastRefreshTime = currentTime; + RefreshPoolInfo(); + Repaint(); + } + } + } + + void RefreshPoolInfo() + { + if (!Application.isPlaying) return; + + // Call LogPoolStats on all pool instances to update their stats in the console + if (showTrenchTiles) + { + TrenchTilePool[] tilePools = Object.FindObjectsByType(FindObjectsSortMode.None); + foreach (var pool in tilePools) + { + pool.LogPoolStats(); + } + } + + if (showBubbles) + { + BubblePool[] bubblePools = Object.FindObjectsByType(FindObjectsSortMode.None); + foreach (var pool in bubblePools) + { + if (pool != null && pool.gameObject.activeInHierarchy) + { + // If BubblePool has a LogPoolStats method, call it + var logMethod = typeof(BubblePool).GetMethod("LogPoolStats"); + if (logMethod != null) + { + logMethod.Invoke(pool, null); + } + } + } + } + } + + void DisplayPoolInfo() + { + EditorGUILayout.LabelField("Scene Statistics:", EditorStyles.boldLabel); + EditorGUILayout.LabelField($"Total GameObjects: {Object.FindObjectsByType(FindObjectsSortMode.None).Length}"); + + EditorGUILayout.Space(); + + if (showTrenchTiles) + { + DisplayTrenchTilePoolInfo(); + } + + if (showBubbles) + { + DisplayBubblePoolInfo(); + } + } + + void DisplayTrenchTilePoolInfo() + { + TrenchTilePool[] pools = Object.FindObjectsByType(FindObjectsSortMode.None); + if (pools.Length == 0) + { + EditorGUILayout.HelpBox("No trench tile pools found in the scene.", MessageType.Info); + } + else + { + EditorGUILayout.LabelField("Trench Tile Pools", EditorStyles.boldLabel); + foreach (var pool in pools) + { + EditorGUILayout.LabelField($"Pool: {pool.name}", EditorStyles.boldLabel); + EditorGUI.indentLevel++; + + // Get private field values using reflection + System.Reflection.FieldInfo totalCountField = typeof(TrenchTilePool).GetField("totalPooledCount", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + System.Reflection.FieldInfo pooledTilesField = typeof(TrenchTilePool).GetField("pooledTiles", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + System.Reflection.FieldInfo prefabUsageField = typeof(TrenchTilePool).GetField("prefabUsageCount", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + if (totalCountField != null) + { + int totalCount = (int)totalCountField.GetValue(pool); + EditorGUILayout.LabelField($"Total Pooled Objects: {totalCount}/{pool.totalMaxPoolSize}"); + } + + if (pooledTilesField != null && prefabUsageField != null) + { + var pooledTiles = pooledTilesField.GetValue(pool) as Dictionary>; + var usageCounts = prefabUsageField.GetValue(pool) as Dictionary; + + if (pooledTiles != null) + { + EditorGUILayout.LabelField("Prefab Details:", EditorStyles.boldLabel); + EditorGUI.indentLevel++; + + foreach (var entry in pooledTiles) + { + int prefabIndex = entry.Key; + Stack stack = entry.Value; + int count = stack != null ? stack.Count : 0; + + int usageCount = 0; + if (usageCounts != null && usageCounts.TryGetValue(prefabIndex, out int usage)) + { + usageCount = usage; + } + + EditorGUILayout.LabelField($"Prefab {prefabIndex}: {count} pooled, {usageCount} usages"); + } + + EditorGUI.indentLevel--; + } + } + + EditorGUI.indentLevel--; + EditorGUILayout.Space(); + } + } + } + + void DisplayBubblePoolInfo() + { + BubblePool[] pools = Object.FindObjectsByType(FindObjectsSortMode.None); + if (pools.Length == 0) + { + EditorGUILayout.HelpBox("No bubble pools found in the scene.", MessageType.Info); + } + else + { + EditorGUILayout.LabelField("Bubble Pools", EditorStyles.boldLabel); + foreach (var pool in pools) + { + EditorGUILayout.LabelField($"Pool: {pool.name}", EditorStyles.boldLabel); + EditorGUI.indentLevel++; + + // Get private field values using reflection + System.Reflection.FieldInfo pooledBubblesField = typeof(BubblePool).GetField("pooledBubbles", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + if (pooledBubblesField != null) + { + var pooledBubbles = pooledBubblesField.GetValue(pool) as Stack; + int count = pooledBubbles != null ? pooledBubbles.Count : 0; + + EditorGUILayout.LabelField($"Pooled Bubbles: {count}/{pool.maxPoolSize}"); + EditorGUILayout.LabelField($"Initial Pool Size: {pool.initialPoolSize}"); + } + + // Try to find active bubbles in the scene + Bubble[] activeBubbles = Object.FindObjectsByType(FindObjectsSortMode.None); + int activeBubbleCount = 0; + + foreach (var bubble in activeBubbles) + { + if (bubble.gameObject.activeInHierarchy) + { + activeBubbleCount++; + } + } + + EditorGUILayout.LabelField($"Active Bubbles: {activeBubbleCount}"); + + EditorGUI.indentLevel--; + EditorGUILayout.Space(); + } + } + } + } +} diff --git a/Assets/Editor/Utilities/PoolMonitorWindow.cs.meta b/Assets/Editor/Utilities/PoolMonitorWindow.cs.meta new file mode 100644 index 00000000..79354754 --- /dev/null +++ b/Assets/Editor/Utilities/PoolMonitorWindow.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 17d6e42e7ca549b8b209f0714c8d106b +timeCreated: 1758016486 \ No newline at end of file diff --git a/Assets/Prefabs/FX/Bubble.prefab b/Assets/Prefabs/FX/Bubble.prefab index df49f9af..c656ee2d 100644 --- a/Assets/Prefabs/FX/Bubble.prefab +++ b/Assets/Prefabs/FX/Bubble.prefab @@ -73,8 +73,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: 622133659 + m_SortingLayer: -1 m_SortingOrder: 0 m_Sprite: {fileID: 8944853044452083345, guid: 4ad95f797558b28478685ca60bd90ff4, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} @@ -134,4 +134,3 @@ MonoBehaviour: m_EditorClassIdentifier: speed: 1 wobbleSpeed: 1 - wobbleAmount: 0.1 diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile1.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile1.prefab index 3b51611e..e206dd86 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile1.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile1.prefab @@ -26,7 +26,7 @@ Transform: m_GameObject: {fileID: 864595161669782950} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: -2.77, y: 0, z: 0} + m_LocalPosition: {x: -3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -73,8 +73,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 7559449286846427561, guid: e3d18475ab86b1246912f497417465f8, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} @@ -113,7 +113,7 @@ Transform: m_GameObject: {fileID: 2171518497100337372} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 2.95, y: 0, z: 0} + m_LocalPosition: {x: 3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -160,8 +160,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 3241551651087908563, guid: 8e7c95ebe5325df4395d97ea2ace65d7, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile1_flipped.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile1_flipped.prefab index caad24a4..accb5c42 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile1_flipped.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile1_flipped.prefab @@ -26,7 +26,7 @@ Transform: m_GameObject: {fileID: 864595161669782950} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: -3.093, y: 0, z: 0} + m_LocalPosition: {x: -3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -73,8 +73,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 7559449286846427561, guid: e3d18475ab86b1246912f497417465f8, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} @@ -113,7 +113,7 @@ Transform: m_GameObject: {fileID: 2171518497100337372} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 2.627, y: 0, z: 0} + m_LocalPosition: {x: 3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -160,8 +160,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 3241551651087908563, guid: 8e7c95ebe5325df4395d97ea2ace65d7, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile2.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile2.prefab index 29be29a0..cf78ee96 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile2.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile2.prefab @@ -26,7 +26,7 @@ Transform: m_GameObject: {fileID: 864595161669782950} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: -2.64, y: 0, z: 0} + m_LocalPosition: {x: -3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -73,8 +73,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 2249565037559538771, guid: 836c1ae2997af4045b714ceaff665a6e, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} @@ -113,7 +113,7 @@ Transform: m_GameObject: {fileID: 2171518497100337372} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 3.08, y: 0, z: 0} + m_LocalPosition: {x: 3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -160,8 +160,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 2764166773722941650, guid: f7ec8080b46b20f459d02e73b12f1694, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile2_flipped.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile2_flipped.prefab index 666262eb..b3fffd4a 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile2_flipped.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile2_flipped.prefab @@ -26,7 +26,7 @@ Transform: m_GameObject: {fileID: 864595161669782950} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: -2.95, y: 0, z: 0} + m_LocalPosition: {x: -3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -73,8 +73,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 2249565037559538771, guid: 836c1ae2997af4045b714ceaff665a6e, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} @@ -113,7 +113,7 @@ Transform: m_GameObject: {fileID: 2171518497100337372} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 2.77, y: 0, z: 0} + m_LocalPosition: {x: 3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -160,8 +160,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 2764166773722941650, guid: f7ec8080b46b20f459d02e73b12f1694, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile3.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile3.prefab index 8bc8ab36..703bc8a9 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile3.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile3.prefab @@ -26,7 +26,7 @@ Transform: m_GameObject: {fileID: 864595161669782950} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: -2.62, y: 0, z: 0} + m_LocalPosition: {x: -3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -73,8 +73,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 2249565037559538771, guid: 836c1ae2997af4045b714ceaff665a6e, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} @@ -113,7 +113,7 @@ Transform: m_GameObject: {fileID: 2171518497100337372} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 2.95, y: 0, z: 0} + m_LocalPosition: {x: 3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -160,8 +160,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 3241551651087908563, guid: 8e7c95ebe5325df4395d97ea2ace65d7, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} diff --git a/Assets/Prefabs/Minigames/DivingForPictures/Tile3_flipped.prefab b/Assets/Prefabs/Minigames/DivingForPictures/Tile3_flipped.prefab index 0dd8beea..d33c5d92 100644 --- a/Assets/Prefabs/Minigames/DivingForPictures/Tile3_flipped.prefab +++ b/Assets/Prefabs/Minigames/DivingForPictures/Tile3_flipped.prefab @@ -26,7 +26,7 @@ Transform: m_GameObject: {fileID: 864595161669782950} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: -2.95, y: 0, z: 0} + m_LocalPosition: {x: -3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -73,8 +73,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 2249565037559538771, guid: 836c1ae2997af4045b714ceaff665a6e, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} @@ -113,7 +113,7 @@ Transform: m_GameObject: {fileID: 2171518497100337372} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 2.62, y: 0, z: 0} + m_LocalPosition: {x: 3.1, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -160,8 +160,8 @@ SpriteRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 + m_SortingLayerID: -1132846201 + m_SortingLayer: 1 m_SortingOrder: 0 m_Sprite: {fileID: 3241551651087908563, guid: 8e7c95ebe5325df4395d97ea2ace65d7, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} diff --git a/Assets/Scenes/MiniGames/DivingForPictures.unity b/Assets/Scenes/MiniGames/DivingForPictures.unity index 8725d2a4..c91a62a9 100644 --- a/Assets/Scenes/MiniGames/DivingForPictures.unity +++ b/Assets/Scenes/MiniGames/DivingForPictures.unity @@ -170,7 +170,7 @@ MonoBehaviour: linePoints: 10 stiffness: 350 damping: 15 - ropeLength: 4.19 + ropeLength: 2 ropeWidth: 0.1 midPointWeight: 1 midPointPosition: 0.5 @@ -220,17 +220,17 @@ LineRenderer: m_SortingLayer: 0 m_SortingOrder: 0 m_Positions: - - {x: 0, y: 2.07, z: 0} - - {x: -0.002, y: 1.5150144, z: 0} - - {x: -0.004, y: 1.0280256, z: 0} - - {x: -0.006, y: 0.6090335, z: 0} - - {x: -0.008, y: 0.25803846, z: 0} - - {x: -0.01, y: -0.024959907, z: 0} - - {x: -0.012, y: -0.23996155, z: 0} - - {x: -0.013999999, y: -0.3869663, z: 0} - - {x: -0.016, y: -0.46597433, z: 0} - - {x: -0.018, y: -0.47698557, z: 0} - - {x: -0.02, y: -0.42000002, z: 0} + - {x: 0, y: 4.1716814, z: 0} + - {x: -0.0011514801, y: 3.9187107, z: 0} + - {x: -0.00230296, y: 3.6922278, z: 0} + - {x: -0.0034544398, y: 3.4922323, z: 0} + - {x: -0.00460592, y: 3.3187256, z: 0} + - {x: -0.0057574, y: 3.1717062, z: 0} + - {x: -0.0069088796, y: 3.0511749, z: 0} + - {x: -0.008060359, y: 2.9571314, z: 0} + - {x: -0.00921184, y: 2.8895762, z: 0} + - {x: -0.010363319, y: 2.8485086, z: 0} + - {x: -0.0115148, y: 2.833929, z: 0} m_Parameters: serializedVersion: 3 widthMultiplier: 1 @@ -488,8 +488,8 @@ Transform: m_GameObject: {fileID: 747976396} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} + m_LocalPosition: {x: 0, y: 2.9799, z: 0} + m_LocalScale: {x: 0.57574, y: 0.57574, z: 0.57574} m_ConstrainProportionsScale: 0 m_Children: - {fileID: 730962733} @@ -573,6 +573,9 @@ MonoBehaviour: spawnY: -6.78 wobbleMinScale: 0.5 wobbleMaxScale: 1.2 + useObjectPooling: 1 + initialPoolSize: 10 + maxPoolSize: 30 --- !u!4 &1003335105 Transform: m_ObjectHideFlags: 0 @@ -639,7 +642,7 @@ MonoBehaviour: linePoints: 10 stiffness: 350 damping: 15 - ropeLength: 4.19 + ropeLength: 2 ropeWidth: 0.1 midPointWeight: 1 midPointPosition: 0.5 @@ -689,17 +692,17 @@ LineRenderer: m_SortingLayer: 0 m_SortingOrder: 0 m_Positions: - - {x: 0, y: 2.07, z: 0} - - {x: 0.06300001, y: 1.5291233, z: 0} - - {x: 0.126, y: 1.053108, z: 0} - - {x: 0.18900001, y: 0.6419541, z: 0} - - {x: 0.25200003, y: 0.29566196, z: 0} - - {x: 0.315, y: 0.01423122, z: 0} - - {x: 0.37800002, y: -0.20233808, z: 0} - - {x: 0.44099998, y: -0.35404575, z: 0} - - {x: 0.504, y: -0.44089204, z: 0} - - {x: 0.567, y: -0.46287674, z: 0} - - {x: 0.63, y: -0.42000002, z: 0} + - {x: 0, y: 4.1716814, z: 0} + - {x: 0.036271624, y: 3.927396, z: 0} + - {x: 0.07254324, y: 3.7076683, z: 0} + - {x: 0.10881486, y: 3.5124984, z: 0} + - {x: 0.14508648, y: 3.3418865, z: 0} + - {x: 0.1813581, y: 3.195832, z: 0} + - {x: 0.21762972, y: 3.0743358, z: 0} + - {x: 0.25390133, y: 2.9773972, z: 0} + - {x: 0.29017296, y: 2.905017, z: 0} + - {x: 0.32644457, y: 2.857194, z: 0} + - {x: 0.3627162, y: 2.833929, z: 0} m_Parameters: serializedVersion: 3 widthMultiplier: 1 @@ -976,7 +979,7 @@ MonoBehaviour: linePoints: 10 stiffness: 350 damping: 15 - ropeLength: 4.19 + ropeLength: 2 ropeWidth: 0.1 midPointWeight: 1 midPointPosition: 0.5 @@ -1026,17 +1029,17 @@ LineRenderer: m_SortingLayer: 0 m_SortingOrder: 0 m_Positions: - - {x: 0, y: 2.07, z: 0} - - {x: -0.058000002, y: 1.5269984, z: 0} - - {x: -0.116, y: 1.0493305, z: 0} - - {x: -0.174, y: 0.63699615, z: 0} - - {x: -0.23200001, y: 0.28999573, z: 0} - - {x: -0.29, y: 0.00832893, z: 0} - - {x: -0.348, y: -0.20800425, z: 0} - - {x: -0.406, y: -0.35900372, z: 0} - - {x: -0.46400002, y: -0.44466949, z: 0} - - {x: -0.52199996, y: -0.46500158, z: 0} - - {x: -0.58, y: -0.42000002, z: 0} + - {x: 0, y: 4.1716814, z: 0} + - {x: -0.03339292, y: 3.9260902, z: 0} + - {x: -0.066785835, y: 3.705347, z: 0} + - {x: -0.10017875, y: 3.5094519, z: 0} + - {x: -0.13357168, y: 3.3384047, z: 0} + - {x: -0.16696459, y: 3.1922054, z: 0} + - {x: -0.20035751, y: 3.0708542, z: 0} + - {x: -0.2337504, y: 2.9743507, z: 0} + - {x: -0.26714337, y: 2.9026957, z: 0} + - {x: -0.30053627, y: 2.8558884, z: 0} + - {x: -0.33392918, y: 2.833929, z: 0} m_Parameters: serializedVersion: 3 widthMultiplier: 1 @@ -1204,8 +1207,14 @@ MonoBehaviour: initialTileCount: 3 tileSpawnBuffer: 1 moveSpeed: 3 - speedUpFactor: 0.2 - speedUpInterval: 2 + speedUpFactor: 0 + speedUpInterval: 0 + maxMoveSpeed: 12 + useObjectPooling: 1 + preInstantiateTiles: 0 + initialTilesPerPrefab: 1 + maxPerPrefabPoolSize: 2 + totalMaxPoolSize: 10 onTileSpawned: m_PersistentCalls: m_Calls: [] @@ -1388,8 +1397,8 @@ Transform: m_GameObject: {fileID: 2106431001} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: -1, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} + m_LocalPosition: {x: 0, y: 2.5, z: 0} + m_LocalScale: {x: 0.57574, y: 0.57574, z: 0.57574} m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1834056337} @@ -1417,7 +1426,7 @@ MonoBehaviour: bottleWobble: {fileID: 747976399} followStiffness: 4 useWobbleOffset: 1 - baseY: -1 + baseY: 2.5 --- !u!114 &2106431004 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/Minigames/DivingForPictures/Bubble.cs b/Assets/Scripts/Minigames/DivingForPictures/Bubble.cs index a84c4907..362d75b0 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Bubble.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Bubble.cs @@ -10,39 +10,83 @@ namespace Minigames.DivingForPictures public float speed = 1f; public float wobbleSpeed = 1f; private SpriteRenderer spriteRenderer; - private SpriteRenderer bottleSpriteRenderer; + private SpriteRenderer bubbleSpriteRenderer; // Renamed from bottleSpriteRenderer private float timeOffset; private float minScale = 0.2f; private float maxScale = 1.2f; + private float baseScale = 1f; // Added to store the initial scale + + private Camera mainCamera; // Cache camera reference + private BubblePool parentPool; // Reference to the pool this bubble came from void Awake() { // Cache references and randomize time offset for wobble spriteRenderer = GetComponent(); timeOffset = Random.value * 100f; - // Find the child named "BottleSprite" and get its SpriteRenderer - Transform bottleSpriteTransform = transform.Find("BubbleSprite"); - if (bottleSpriteTransform != null) + // Find the child named "BubbleSprite" and get its SpriteRenderer + Transform bubbleSpriteTransform = transform.Find("BubbleSprite"); + if (bubbleSpriteTransform != null) { - bottleSpriteRenderer = bottleSpriteTransform.GetComponent(); + bubbleSpriteRenderer = bubbleSpriteTransform.GetComponent(); } + + // Cache camera reference + mainCamera = Camera.main; } void Update() { // Move bubble upward - transform.position += Vector3.up * speed * Time.deltaTime; + transform.position += Vector3.up * (speed * Time.deltaTime); + // Wobble effect (smooth oscillation between min and max scale) float t = (Mathf.Sin((Time.time + timeOffset) * wobbleSpeed) + 1f) * 0.5f; // t in [0,1] - float newScale = Mathf.Lerp(minScale, maxScale, t); - transform.localScale = Vector3.one * newScale; - // Destroy when off screen - if (transform.position.y > Camera.main.orthographicSize + 2f) + float wobbleFactor = Mathf.Lerp(minScale, maxScale, t); + transform.localScale = Vector3.one * (baseScale * wobbleFactor); + + // Destroy when off screen - using cached camera reference + if (mainCamera != null && transform.position.y > mainCamera.orthographicSize + 2f) { - Destroy(gameObject); + OnBubbleDestroy(); } } + /// + /// Called when bubble is about to be destroyed + /// + private void OnBubbleDestroy() + { + // Use the cached pool reference instead of finding it each time + if (parentPool != null) + { + parentPool.ReturnBubble(this); + } + else + { + // Fallback to find the pool if the reference is somehow lost + BubblePool pool = FindFirstObjectByType(); + if (pool != null) + { + Debug.LogWarning("Bubble is missing its parent pool reference, finding pool as fallback"); + pool.ReturnBubble(this); + } + else + { + Destroy(gameObject); + } + } + } + + /// + /// Sets the parent pool for this bubble + /// + /// The bubble pool that created this bubble + public void SetPool(BubblePool pool) + { + parentPool = pool; + } + /// /// Sets the main sprite for the bubble. /// @@ -54,13 +98,22 @@ namespace Minigames.DivingForPictures } /// - /// Sets the sprite for the child "BottleSprite" renderer. + /// Sets the sprite for the child "BubbleSprite" renderer. /// /// Sprite to assign. - public void SetBottleSprite(Sprite sprite) + public void SetBubbleSprite(Sprite sprite) { - if (bottleSpriteRenderer != null) - bottleSpriteRenderer.sprite = sprite; + if (bubbleSpriteRenderer != null) + bubbleSpriteRenderer.sprite = sprite; + } + + /// + /// Sets the base scale for the bubble + /// + /// Base scale value + public void SetBaseScale(float scale) + { + baseScale = scale; } /// @@ -73,5 +126,13 @@ namespace Minigames.DivingForPictures minScale = min; maxScale = max; } + + /// + /// Resets the bubble state for reuse from object pool + /// + public void ResetState() + { + timeOffset = Random.value * 100f; + } } } \ No newline at end of file diff --git a/Assets/Scripts/Minigames/DivingForPictures/BubblePool.cs b/Assets/Scripts/Minigames/DivingForPictures/BubblePool.cs new file mode 100644 index 00000000..0112a12c --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/BubblePool.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Minigames.DivingForPictures +{ + /// + /// Manages a pool of bubble objects to reduce garbage collection overhead. + /// + public class BubblePool : MonoBehaviour + { + [Tooltip("Initial number of bubbles to pre-instantiate")] + public int initialPoolSize = 10; + + [Tooltip("Maximum number of bubbles to keep in the pool")] + public int maxPoolSize = 30; + + private Stack pooledBubbles = new Stack(); + private Bubble bubblePrefab; + private int totalBubblesCreated = 0; + private int totalBubblesReturned = 0; + + /// + /// Initialize the pool with the bubble prefab + /// + /// The bubble prefab to use + public void Initialize(Bubble prefab) + { + bubblePrefab = prefab; + + // Pre-instantiate bubbles + for (int i = 0; i < initialPoolSize; i++) + { + CreateNewBubble(); + } + + Debug.Log($"BubblePool initialized with {initialPoolSize} bubbles"); + } + + /// + /// Creates a new bubble instance and adds it to the pool + /// + private Bubble CreateNewBubble() + { + if (bubblePrefab == null) + { + Debug.LogError("BubblePool: bubblePrefab is null! Call Initialize first."); + return null; + } + + Bubble bubble = Instantiate(bubblePrefab, transform); + bubble.gameObject.SetActive(false); + // Set the pool reference so the bubble knows where to return + bubble.SetPool(this); + pooledBubbles.Push(bubble); + totalBubblesCreated++; + return bubble; + } + + /// + /// Gets a bubble from the pool, or creates a new one if the pool is empty + /// + /// A bubble instance ready to use + public Bubble GetBubble() + { + Bubble bubble; + + if (pooledBubbles.Count > 0) + { + bubble = pooledBubbles.Pop(); + } + else + { + bubble = CreateNewBubble(); + } + + // Ensure the bubble has a reference to this pool + bubble.SetPool(this); + bubble.gameObject.SetActive(true); + bubble.ResetState(); + + return bubble; + } + + /// + /// Returns a bubble to the pool + /// + /// The bubble to return to the pool + public void ReturnBubble(Bubble bubble) + { + if (bubble == null) return; + + // Only add to pool if we're under the maximum size + if (pooledBubbles.Count < maxPoolSize) + { + // Deactivate and reparent + bubble.gameObject.SetActive(false); + bubble.transform.SetParent(transform); + + // Add to pool + pooledBubbles.Push(bubble); + totalBubblesReturned++; + } + else + { + Destroy(bubble.gameObject); + } + } + + /// + /// Logs pool statistics + /// + public void LogPoolStats() + { + Debug.Log($"[BubblePool] Pooled bubbles: {pooledBubbles.Count}/{maxPoolSize} (Created: {totalBubblesCreated}, Returned: {totalBubblesReturned})"); + } + } +} diff --git a/Assets/Scripts/Minigames/DivingForPictures/BubblePool.cs.meta b/Assets/Scripts/Minigames/DivingForPictures/BubblePool.cs.meta new file mode 100644 index 00000000..81e3864b --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/BubblePool.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 45cdaed0c047423bbb0b7380cd3687f3 +timeCreated: 1758015081 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/DivingForPictures/BubbleSpawner.cs b/Assets/Scripts/Minigames/DivingForPictures/BubbleSpawner.cs index 5ad09d36..afba3c80 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/BubbleSpawner.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/BubbleSpawner.cs @@ -19,9 +19,32 @@ namespace Minigames.DivingForPictures public float spawnY = -5f; public float wobbleMinScale = 0.2f; public float wobbleMaxScale = 1.2f; + + [Header("Object Pooling")] + public bool useObjectPooling = true; + public int initialPoolSize = 10; + public int maxPoolSize = 30; private float _timer; private float _nextSpawnInterval; + private BubblePool _bubblePool; + private Camera _mainCamera; // Cache camera reference + + void Awake() + { + _mainCamera = Camera.main; + + if (useObjectPooling) + { + // Create the bubble pool + GameObject poolGO = new GameObject("BubblePool"); + poolGO.transform.SetParent(transform); + _bubblePool = poolGO.AddComponent(); + _bubblePool.initialPoolSize = initialPoolSize; + _bubblePool.maxPoolSize = maxPoolSize; + _bubblePool.Initialize(bubblePrefab); + } + } void Start() { @@ -56,21 +79,36 @@ namespace Minigames.DivingForPictures { float x = Random.Range(spawnXMin, spawnXMax); Vector3 spawnPos = new Vector3(x, spawnY, 0f); - Bubble bubble = Instantiate(bubblePrefab, spawnPos, Quaternion.identity, transform); + + Bubble bubble; + if (useObjectPooling && _bubblePool != null) + { + bubble = _bubblePool.GetBubble(); + bubble.transform.position = spawnPos; + } + else + { + bubble = Instantiate(bubblePrefab, spawnPos, Quaternion.identity, transform); + } + // Randomize bubble properties bubble.speed = Random.Range(speedRange.x, speedRange.y); bubble.wobbleSpeed = Random.Range(wobbleSpeedRange.x, wobbleSpeedRange.y); - float scale = Random.Range(scaleRange.x, scaleRange.y); - bubble.transform.localScale = Vector3.one * scale; - // Assign random sprite to BottleSprite + + // Set base scale (initial size) for the bubble + float baseScale = Random.Range(scaleRange.x, scaleRange.y); + bubble.SetBaseScale(baseScale); + + // Assign random sprite to BubbleSprite (fixed naming from BottleSprite) if (bubbleSprites != null && bubbleSprites.Length > 0) { Sprite randomSprite = bubbleSprites[Random.Range(0, bubbleSprites.Length)]; - bubble.SetBottleSprite(randomSprite); + bubble.SetBubbleSprite(randomSprite); } // Random rotation bubble.transform.rotation = Quaternion.Euler(0f, 0f, Random.Range(0f, 360f)); + // Pass min/max scale for wobble clamping bubble.SetWobbleScaleLimits(wobbleMinScale, wobbleMaxScale); } diff --git a/Assets/Scripts/Minigames/DivingForPictures/TrenchTilePool.cs b/Assets/Scripts/Minigames/DivingForPictures/TrenchTilePool.cs new file mode 100644 index 00000000..4f143eaa --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/TrenchTilePool.cs @@ -0,0 +1,244 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Minigames.DivingForPictures +{ + /// + /// Manages a pool of trench tile objects to reduce garbage collection overhead. + /// Optimized for handling a large number of different prefab types. + /// + public class TrenchTilePool : MonoBehaviour + { + [Tooltip("Whether to pre-instantiate tiles during initialization or create them on demand")] + public bool preInstantiateTiles = false; + + [Tooltip("Initial number of tiles to pre-instantiate per prefab (if preInstantiateTiles is true)")] + public int initialTilesPerPrefab = 2; + + [Tooltip("Maximum number of tiles to keep in the pool across all prefab types")] + public int totalMaxPoolSize = 50; + + [Tooltip("Maximum number of inactive instances to keep per prefab type")] + public int maxPerPrefabPoolSize = 5; + + [Tooltip("Maximum number of tiles to keep in the pool (legacy, use maxPerPrefabPoolSize instead)")] + public int maxPoolSize = 5; + + private Dictionary> pooledTiles = new Dictionary>(); + private Dictionary prefabUsageCount = new Dictionary(); + private List tilePrefabs; + private int totalPooledCount = 0; + + /// + /// Initialize the pool with the tile prefabs + /// + /// List of tile prefabs to use + public void Initialize(List prefabs) + { + tilePrefabs = prefabs; + + // Initialize usage tracking + for (int i = 0; i < prefabs.Count; i++) + { + prefabUsageCount[i] = 0; + pooledTiles[i] = new Stack(); + } + + // Pre-instantiate tiles only if enabled + if (preInstantiateTiles) + { + // Calculate how many to pre-instantiate based on available pool size + int totalToCreate = Mathf.Min(totalMaxPoolSize, prefabs.Count * initialTilesPerPrefab); + int perPrefab = Mathf.Max(1, totalToCreate / prefabs.Count); + + for (int i = 0; i < prefabs.Count; i++) + { + for (int j = 0; j < perPrefab; j++) + { + if (totalPooledCount >= totalMaxPoolSize) break; + CreateNewTile(i); + } + } + } + } + + /// + /// Creates a new tile instance and adds it to the pool + /// + private GameObject CreateNewTile(int prefabIndex) + { + if (tilePrefabs == null || prefabIndex >= tilePrefabs.Count) + { + Debug.LogError("TrenchTilePool: Invalid prefab index or tilePrefabs is null!"); + return null; + } + + GameObject prefab = tilePrefabs[prefabIndex]; + GameObject tile = Instantiate(prefab, transform); + tile.SetActive(false); + + if (!pooledTiles.ContainsKey(prefabIndex)) + { + pooledTiles[prefabIndex] = new Stack(); + } + + pooledTiles[prefabIndex].Push(tile); + totalPooledCount++; + return tile; + } + + /// + /// Gets a tile from the pool, or creates a new one if the pool is empty + /// + /// A tile instance ready to use + public GameObject GetTile(int prefabIndex) + { + GameObject tile; + + // Track usage frequency + if (prefabUsageCount.ContainsKey(prefabIndex)) + { + prefabUsageCount[prefabIndex]++; + } + else + { + prefabUsageCount[prefabIndex] = 1; + } + + if (pooledTiles.ContainsKey(prefabIndex) && pooledTiles[prefabIndex].Count > 0) + { + tile = pooledTiles[prefabIndex].Pop(); + totalPooledCount--; + } + else + { + // Create new tile without adding to pool + GameObject prefab = tilePrefabs[prefabIndex]; + tile = Instantiate(prefab, transform); + } + + tile.SetActive(true); + return tile; + } + + /// + /// Returns a tile to the pool + /// + /// The tile to return to the pool + /// The index of the prefab this tile was created from + public void ReturnTile(GameObject tile, int prefabIndex) + { + if (tile == null) return; + + // Check if we're under the maximum pool size for this prefab type + bool keepTile = totalPooledCount < totalMaxPoolSize; + + // Additional constraint: don't keep too many of any single prefab type + if (pooledTiles.ContainsKey(prefabIndex) && + pooledTiles[prefabIndex].Count >= maxPerPrefabPoolSize) + { + keepTile = false; + } + + if (keepTile) + { + tile.SetActive(false); + tile.transform.SetParent(transform); + + if (!pooledTiles.ContainsKey(prefabIndex)) + { + pooledTiles[prefabIndex] = new Stack(); + } + + pooledTiles[prefabIndex].Push(tile); + totalPooledCount++; + } + else + { + Destroy(tile); + } + } + + /// + /// Trims the pool to remove excess objects + /// Can be called periodically or when memory pressure is high + /// + public void TrimExcess() + { + // If we're under the limit, no need to trim + if (totalPooledCount <= totalMaxPoolSize) return; + + // Calculate how many to remove + int excessCount = totalPooledCount - totalMaxPoolSize; + + // Get prefab indices sorted by usage (least used first) + List> sortedUsage = new List>(prefabUsageCount); + sortedUsage.Sort((a, b) => a.Value.CompareTo(b.Value)); + + // Remove tiles from least used prefabs first + foreach (var usage in sortedUsage) + { + int prefabIndex = usage.Key; + if (!pooledTiles.ContainsKey(prefabIndex) || pooledTiles[prefabIndex].Count == 0) continue; + + // How many to remove from this prefab type + int toRemove = Mathf.Min(pooledTiles[prefabIndex].Count, excessCount); + + for (int i = 0; i < toRemove; i++) + { + if (pooledTiles[prefabIndex].Count == 0) break; + + GameObject tile = pooledTiles[prefabIndex].Pop(); + Destroy(tile); + totalPooledCount--; + excessCount--; + + if (excessCount <= 0) return; + } + } + } + + /// + /// Logs pool statistics to the console + /// + public void LogPoolStats() + { + Debug.Log($"[TrenchTilePool] Total pooled objects: {totalPooledCount}/{totalMaxPoolSize}"); + + string prefabDetails = ""; + int index = 0; + foreach (var entry in pooledTiles) + { + int prefabIndex = entry.Key; + int count = entry.Value.Count; + int usageCount = prefabUsageCount.ContainsKey(prefabIndex) ? prefabUsageCount[prefabIndex] : 0; + + string prefabName = prefabIndex < tilePrefabs.Count ? tilePrefabs[prefabIndex].name : "Unknown"; + prefabDetails += $"\n - {prefabName}: {count} pooled, {usageCount} usages"; + + // Limit the output to avoid too much text + if (++index >= 10 && pooledTiles.Count > 10) + { + prefabDetails += $"\n - ...and {pooledTiles.Count - 10} more prefab types"; + break; + } + } + + Debug.Log($"[TrenchTilePool] Pool details:{prefabDetails}"); + } + +#if UNITY_EDITOR + private float _lastLogTime = 0f; + + void Update() + { + // Log pool stats every 5 seconds if in the editor + if (Time.time - _lastLogTime > 5f) + { + LogPoolStats(); + _lastLogTime = Time.time; + } + } +#endif + } +} diff --git a/Assets/Scripts/Minigames/DivingForPictures/TrenchTilePool.cs.meta b/Assets/Scripts/Minigames/DivingForPictures/TrenchTilePool.cs.meta new file mode 100644 index 00000000..5caa545f --- /dev/null +++ b/Assets/Scripts/Minigames/DivingForPictures/TrenchTilePool.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fc32ce01955e4e44b4c1d28871d64049 +timeCreated: 1758015121 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/DivingForPictures/TrenchTileSpawner.cs b/Assets/Scripts/Minigames/DivingForPictures/TrenchTileSpawner.cs index dc09fc37..3c2cbfce 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/TrenchTileSpawner.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/TrenchTileSpawner.cs @@ -15,7 +15,7 @@ namespace Minigames.DivingForPictures public List tilePrefabs; [Header("Tile Settings")] - private const float TileHeight = 5f; // Set to match prefab height + private Dictionary _tileHeights = new Dictionary(); public int initialTileCount = 3; public float tileSpawnBuffer = 1f; @@ -23,6 +23,14 @@ namespace Minigames.DivingForPictures public float moveSpeed = 3f; public float speedUpFactor = 0.2f; public float speedUpInterval = 10f; + public float maxMoveSpeed = 12f; // Added a cap to the movement speed + + [Header("Object Pooling")] + public bool useObjectPooling = true; + public bool preInstantiateTiles = false; // Added option to control pre-instantiation + public int initialTilesPerPrefab = 2; + public int maxPerPrefabPoolSize = 2; + public int totalMaxPoolSize = 10; // Total pool size across all prefab types [FormerlySerializedAs("OnTileSpawned")] [Header("Events")] public UnityEvent onTileSpawned; @@ -37,14 +45,61 @@ namespace Minigames.DivingForPictures private Camera _mainCamera; private float _screenBottom; private float _screenTop; + private TrenchTilePool _tilePool; + + private const float TileSpawnZ = -1f; // All spawned tiles should have z = -1 + + void Awake() + { + _mainCamera = Camera.main; + + // Calculate tile heights for each prefab + foreach (var prefab in tilePrefabs) + { + Renderer renderer = prefab.GetComponentInChildren(); + if (renderer != null) + { + _tileHeights[prefab] = renderer.bounds.size.y; + } + else + { + // Fallback in case no renderer is found + _tileHeights[prefab] = 5f; + Debug.LogWarning($"No renderer found in prefab {prefab.name}. Using default height of 5."); + } + } + + if (useObjectPooling) + { + // Create the tile pool + GameObject poolGO = new GameObject("TrenchTilePool"); + poolGO.transform.SetParent(transform); + _tilePool = poolGO.AddComponent(); + _tilePool.preInstantiateTiles = preInstantiateTiles; + _tilePool.initialTilesPerPrefab = initialTilesPerPrefab; + _tilePool.maxPerPrefabPoolSize = maxPerPrefabPoolSize; + _tilePool.totalMaxPoolSize = totalMaxPoolSize; + _tilePool.Initialize(tilePrefabs); + + // Periodically trim the pool to optimize memory usage + InvokeRepeating(nameof(TrimExcessPooledTiles), 10f, 30f); + } + } void Start() { - _mainCamera = Camera.main; CalculateScreenBounds(); for (int i = 0; i < initialTileCount; i++) { - SpawnTileAtY(_screenBottom + i * TileHeight); + float y = _screenBottom; + // Calculate proper Y position based on previous tiles + if (i > 0 && _activeTiles.Count > 0) + { + GameObject prevTile = _activeTiles[_activeTiles.Count - 1]; + float prevHeight = GetTileHeight(prevTile); + y = prevTile.transform.position.y - prevHeight; + } + SpawnTileAtY(y); } } @@ -80,11 +135,29 @@ namespace Minigames.DivingForPictures { if (_activeTiles.Count == 0) return; GameObject topTile = _activeTiles[0]; - if (topTile.transform.position.y - TileHeight / 2 > _screenTop + tileSpawnBuffer) + float tileHeight = GetTileHeight(topTile); + if (topTile.transform.position.y - tileHeight / 2 > _screenTop + tileSpawnBuffer) { _activeTiles.RemoveAt(0); onTileDestroyed?.Invoke(topTile); - Destroy(topTile); + + if (useObjectPooling && _tilePool != null) + { + // Find the prefab index for this tile + int prefabIndex = GetPrefabIndex(topTile); + if (prefabIndex >= 0) + { + _tilePool.ReturnTile(topTile, prefabIndex); + } + else + { + Destroy(topTile); + } + } + else + { + Destroy(topTile); + } } } @@ -92,10 +165,11 @@ namespace Minigames.DivingForPictures { if (_activeTiles.Count == 0) return; GameObject bottomTile = _activeTiles[^1]; - float bottomEdge = bottomTile.transform.position.y - TileHeight / 2; + float tileHeight = GetTileHeight(bottomTile); + float bottomEdge = bottomTile.transform.position.y - tileHeight / 2; if (bottomEdge > _screenBottom - tileSpawnBuffer) { - float newY = bottomTile.transform.position.y - TileHeight; + float newY = bottomTile.transform.position.y - tileHeight; SpawnTileAtY(newY); } } @@ -105,7 +179,7 @@ namespace Minigames.DivingForPictures _speedUpTimer += Time.deltaTime; if (_speedUpTimer >= speedUpInterval) { - moveSpeed += speedUpFactor; + moveSpeed = Mathf.Min(moveSpeed + speedUpFactor, maxMoveSpeed); _speedUpTimer = 0f; } } @@ -114,8 +188,21 @@ namespace Minigames.DivingForPictures { int prefabIndex = GetWeightedRandomTileIndex(); GameObject prefab = tilePrefabs[prefabIndex]; - // Use the prefab's original rotation - GameObject tile = Instantiate(prefab, new Vector3(0f, y, 0f), prefab.transform.rotation, transform); + GameObject tile; + + if (useObjectPooling && _tilePool != null) + { + tile = _tilePool.GetTile(prefabIndex); + tile.transform.position = new Vector3(0f, y, TileSpawnZ); + tile.transform.rotation = prefab.transform.rotation; + tile.transform.SetParent(transform); + } + else + { + // Use the prefab's original rotation + tile = Instantiate(prefab, new Vector3(0f, y, TileSpawnZ), prefab.transform.rotation, transform); + } + _activeTiles.Add(tile); _tileLastUsed[prefabIndex] = _spawnCounter++; onTileSpawned?.Invoke(tile); @@ -143,6 +230,65 @@ namespace Minigames.DivingForPictures } return Random.Range(0, n); // fallback } + + /// + /// Gets the height of a tile based on its prefab or renderer bounds + /// + /// The tile to measure + /// The height of the tile + private float GetTileHeight(GameObject tile) + { + // Check if this is a known prefab + foreach (var prefab in tilePrefabs) + { + // Check if this tile was created from this prefab + if (tile.name.StartsWith(prefab.name)) + { + if (_tileHeights.TryGetValue(prefab, out float height)) + { + return height; + } + } + } + + // If not found, calculate it from the renderer + Renderer renderer = tile.GetComponentInChildren(); + if (renderer != null) + { + return renderer.bounds.size.y; + } + + // Fallback + return 5f; + } + + /// + /// Gets the index of the prefab that was used to create this tile + /// + /// The tile to check + /// The index of the prefab or -1 if not found + private int GetPrefabIndex(GameObject tile) + { + for (int i = 0; i < tilePrefabs.Count; i++) + { + if (tile.name.StartsWith(tilePrefabs[i].name)) + { + return i; + } + } + return -1; + } + + /// + /// Called periodically to trim excess pooled tiles + /// + private void TrimExcessPooledTiles() + { + if (_tilePool != null) + { + _tilePool.TrimExcess(); + } + } #if UNITY_EDITOR void OnDrawGizmosSelected() @@ -151,8 +297,13 @@ namespace Minigames.DivingForPictures Gizmos.color = Color.cyan; for (int i = 0; i < initialTileCount; i++) { - Vector3 center = new Vector3(0f, _screenBottom + i * TileHeight, 0f); - Gizmos.DrawWireCube(center, new Vector3(10f, TileHeight, 1f)); + float height = 5f; + if (tilePrefabs.Count > 0 && _tileHeights.TryGetValue(tilePrefabs[0], out float h)) + { + height = h; + } + Vector3 center = new Vector3(0f, _screenBottom + i * height, 0f); + Gizmos.DrawWireCube(center, new Vector3(10f, height, 1f)); } } #endif