Working generic object pooling, pool monitor editor tool and batch component adder editor tool
This commit is contained in:
227
Assets/Editor/Utilities/BatchComponentAdder.cs
Normal file
227
Assets/Editor/Utilities/BatchComponentAdder.cs
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Editor.Utilities
|
||||||
|
{
|
||||||
|
public class BatchComponentAdder : EditorWindow
|
||||||
|
{
|
||||||
|
private Vector2 scrollPosition;
|
||||||
|
private List<GameObject> selectedPrefabs = new List<GameObject>();
|
||||||
|
private string searchText = "";
|
||||||
|
private List<Type> availableComponentTypes = new List<Type>();
|
||||||
|
private List<Type> filteredComponentTypes = new List<Type>();
|
||||||
|
private int selectedComponentIndex = -1;
|
||||||
|
private bool showScriptsOnly = true;
|
||||||
|
private bool showBuiltInComponents = false;
|
||||||
|
|
||||||
|
[MenuItem("Tools/Batch Component Adder")]
|
||||||
|
public static void ShowWindow()
|
||||||
|
{
|
||||||
|
GetWindow<BatchComponentAdder>("Batch Component Adder");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
// Get all component types when the window is opened
|
||||||
|
RefreshComponentTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshComponentTypes()
|
||||||
|
{
|
||||||
|
// Get all types that derive from Component
|
||||||
|
availableComponentTypes = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
.SelectMany(assembly => assembly.GetTypes())
|
||||||
|
.Where(type => type.IsSubclassOf(typeof(Component)) && !type.IsAbstract)
|
||||||
|
.OrderBy(type => type.Name)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Apply initial filtering
|
||||||
|
FilterComponentTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FilterComponentTypes()
|
||||||
|
{
|
||||||
|
filteredComponentTypes = availableComponentTypes
|
||||||
|
.Where(type => {
|
||||||
|
if (!showBuiltInComponents && type.Namespace != null && type.Namespace.StartsWith("UnityEngine"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (showScriptsOnly && type.Namespace != null && type.Namespace.StartsWith("UnityEngine"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(searchText))
|
||||||
|
return type.Name.ToLower().Contains(searchText.ToLower());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Reset selection if it's no longer valid
|
||||||
|
if (selectedComponentIndex >= filteredComponentTypes.Count)
|
||||||
|
selectedComponentIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGUI()
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginVertical();
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField("Batch Component Adder", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.HelpBox("Select multiple prefabs, choose a component type, and add it to the root of all selected prefabs.", MessageType.Info);
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// Prefab selection section
|
||||||
|
EditorGUILayout.LabelField("Selected Prefabs", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
if (GUILayout.Button("Add Selected Assets"))
|
||||||
|
{
|
||||||
|
AddSelectedAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(150));
|
||||||
|
|
||||||
|
for (int i = 0; i < selectedPrefabs.Count; i++)
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
|
||||||
|
selectedPrefabs[i] = (GameObject)EditorGUILayout.ObjectField(selectedPrefabs[i], typeof(GameObject), false);
|
||||||
|
|
||||||
|
if (GUILayout.Button("X", GUILayout.Width(20)))
|
||||||
|
{
|
||||||
|
selectedPrefabs.RemoveAt(i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndScrollView();
|
||||||
|
|
||||||
|
if (GUILayout.Button("Clear All"))
|
||||||
|
{
|
||||||
|
selectedPrefabs.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// Component selection section
|
||||||
|
EditorGUILayout.LabelField("Component to Add", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
showScriptsOnly = EditorGUILayout.Toggle("Scripts Only", showScriptsOnly);
|
||||||
|
showBuiltInComponents = EditorGUILayout.Toggle("Show Built-in Components", showBuiltInComponents);
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
string newSearchText = EditorGUILayout.TextField("Search", searchText);
|
||||||
|
if (newSearchText != searchText)
|
||||||
|
{
|
||||||
|
searchText = newSearchText;
|
||||||
|
FilterComponentTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] componentNames = filteredComponentTypes.Select(t => t.Name).ToArray();
|
||||||
|
|
||||||
|
selectedComponentIndex = EditorGUILayout.Popup("Component Type", selectedComponentIndex, componentNames);
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// Validate and add the component
|
||||||
|
GUI.enabled = selectedPrefabs.Count > 0 && selectedComponentIndex >= 0 && selectedComponentIndex < filteredComponentTypes.Count;
|
||||||
|
|
||||||
|
if (GUILayout.Button("Add Component to Prefabs"))
|
||||||
|
{
|
||||||
|
AddComponentToPrefabs();
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.enabled = true;
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddSelectedAssets()
|
||||||
|
{
|
||||||
|
UnityEngine.Object[] selectedObjects = Selection.objects;
|
||||||
|
|
||||||
|
foreach (var obj in selectedObjects)
|
||||||
|
{
|
||||||
|
if (obj is GameObject go)
|
||||||
|
{
|
||||||
|
string path = AssetDatabase.GetAssetPath(go);
|
||||||
|
if (!string.IsNullOrEmpty(path) && path.EndsWith(".prefab"))
|
||||||
|
{
|
||||||
|
if (!selectedPrefabs.Contains(go))
|
||||||
|
{
|
||||||
|
selectedPrefabs.Add(go);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddComponentToPrefabs()
|
||||||
|
{
|
||||||
|
if (selectedComponentIndex < 0 || selectedComponentIndex >= filteredComponentTypes.Count)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Type componentType = filteredComponentTypes[selectedComponentIndex];
|
||||||
|
int successCount = 0;
|
||||||
|
List<string> failedPrefabs = new List<string>();
|
||||||
|
|
||||||
|
// For undo operations
|
||||||
|
Undo.RecordObjects(selectedPrefabs.ToArray(), "Add Component To Prefabs");
|
||||||
|
|
||||||
|
foreach (GameObject prefab in selectedPrefabs)
|
||||||
|
{
|
||||||
|
// Skip null entries
|
||||||
|
if (prefab == null) continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Open the prefab for editing
|
||||||
|
string prefabPath = AssetDatabase.GetAssetPath(prefab);
|
||||||
|
GameObject prefabRoot = PrefabUtility.LoadPrefabContents(prefabPath);
|
||||||
|
|
||||||
|
// Check if the component already exists
|
||||||
|
if (prefabRoot.GetComponent(componentType) == null)
|
||||||
|
{
|
||||||
|
// Add the component
|
||||||
|
prefabRoot.AddComponent(componentType);
|
||||||
|
|
||||||
|
// Save the prefab
|
||||||
|
PrefabUtility.SaveAsPrefabAsset(prefabRoot, prefabPath);
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
failedPrefabs.Add($"{prefab.name} (already has component)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload the prefab
|
||||||
|
PrefabUtility.UnloadPrefabContents(prefabRoot);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError($"Error adding component to {prefab.name}: {e.Message}");
|
||||||
|
failedPrefabs.Add($"{prefab.name} (error)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show results
|
||||||
|
if (successCount > 0)
|
||||||
|
{
|
||||||
|
Debug.Log($"Successfully added {componentType.Name} to {successCount} prefabs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failedPrefabs.Count > 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"Failed to add component to {failedPrefabs.Count} prefabs: {string.Join(", ", failedPrefabs)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the asset database to show changes
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Editor/Utilities/BatchComponentAdder.cs.meta
Normal file
3
Assets/Editor/Utilities/BatchComponentAdder.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 34bcaf56206d4ec29cfa108c96622c37
|
||||||
|
timeCreated: 1758027437
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Minigames.DivingForPictures;
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Collections;
|
||||||
|
using Pooling;
|
||||||
|
|
||||||
namespace Editor.Utilities
|
namespace Editor.Utilities
|
||||||
{
|
{
|
||||||
@@ -11,8 +14,8 @@ namespace Editor.Utilities
|
|||||||
private bool autoRefresh = true;
|
private bool autoRefresh = true;
|
||||||
private float refreshInterval = 1.0f;
|
private float refreshInterval = 1.0f;
|
||||||
private float lastRefreshTime;
|
private float lastRefreshTime;
|
||||||
private bool showTrenchTiles = true;
|
private bool showSinglePrefabPools = true;
|
||||||
private bool showBubbles = true;
|
private bool showMultiPrefabPools = true;
|
||||||
|
|
||||||
[MenuItem("Tools/Pool Monitor")]
|
[MenuItem("Tools/Pool Monitor")]
|
||||||
public static void ShowWindow()
|
public static void ShowWindow()
|
||||||
@@ -42,8 +45,8 @@ namespace Editor.Utilities
|
|||||||
|
|
||||||
// Display toggles for showing different pool types
|
// Display toggles for showing different pool types
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
showTrenchTiles = EditorGUILayout.ToggleLeft("Show Trench Tile Pools", showTrenchTiles, GUILayout.Width(200));
|
showSinglePrefabPools = EditorGUILayout.ToggleLeft("Show Single Prefab Pools", showSinglePrefabPools, GUILayout.Width(200));
|
||||||
showBubbles = EditorGUILayout.ToggleLeft("Show Bubble Pools", showBubbles, GUILayout.Width(200));
|
showMultiPrefabPools = EditorGUILayout.ToggleLeft("Show Multi-Prefab Pools", showMultiPrefabPools, GUILayout.Width(200));
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
@@ -81,25 +84,31 @@ namespace Editor.Utilities
|
|||||||
{
|
{
|
||||||
if (!Application.isPlaying) return;
|
if (!Application.isPlaying) return;
|
||||||
|
|
||||||
// Call LogPoolStats on all pool instances to update their stats in the console
|
// Find all pool types and call LogPoolStats
|
||||||
if (showTrenchTiles)
|
if (showSinglePrefabPools)
|
||||||
{
|
{
|
||||||
TrenchTilePool[] tilePools = Object.FindObjectsByType<TrenchTilePool>(FindObjectsSortMode.None);
|
// Find all types that derive from BaseObjectPool<T>
|
||||||
foreach (var pool in tilePools)
|
foreach (var pool in FindObjectsOfBaseType(typeof(Component), typeof(BaseObjectPool<>)))
|
||||||
{
|
|
||||||
pool.LogPoolStats();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showBubbles)
|
|
||||||
{
|
|
||||||
BubblePool[] bubblePools = Object.FindObjectsByType<BubblePool>(FindObjectsSortMode.None);
|
|
||||||
foreach (var pool in bubblePools)
|
|
||||||
{
|
{
|
||||||
if (pool != null && pool.gameObject.activeInHierarchy)
|
if (pool != null && pool.gameObject.activeInHierarchy)
|
||||||
{
|
{
|
||||||
// If BubblePool has a LogPoolStats method, call it
|
var logMethod = pool.GetType().GetMethod("LogPoolStats");
|
||||||
var logMethod = typeof(BubblePool).GetMethod("LogPoolStats");
|
if (logMethod != null)
|
||||||
|
{
|
||||||
|
logMethod.Invoke(pool, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showMultiPrefabPools)
|
||||||
|
{
|
||||||
|
// Find all types that derive from MultiPrefabPool<T>
|
||||||
|
foreach (var pool in FindObjectsOfBaseType(typeof(Component), typeof(MultiPrefabPool<>)))
|
||||||
|
{
|
||||||
|
if (pool != null && pool.gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
var logMethod = pool.GetType().GetMethod("LogPoolStats");
|
||||||
if (logMethod != null)
|
if (logMethod != null)
|
||||||
{
|
{
|
||||||
logMethod.Invoke(pool, null);
|
logMethod.Invoke(pool, null);
|
||||||
@@ -112,133 +121,312 @@ namespace Editor.Utilities
|
|||||||
void DisplayPoolInfo()
|
void DisplayPoolInfo()
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("Scene Statistics:", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Scene Statistics:", EditorStyles.boldLabel);
|
||||||
EditorGUILayout.LabelField($"Total GameObjects: {Object.FindObjectsByType<GameObject>(FindObjectsSortMode.None).Length}");
|
EditorGUILayout.LabelField($"Total GameObjects: {UnityEngine.Object.FindObjectsByType<GameObject>(FindObjectsSortMode.None).Length}");
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
if (showTrenchTiles)
|
if (showSinglePrefabPools)
|
||||||
{
|
{
|
||||||
DisplayTrenchTilePoolInfo();
|
DisplaySinglePrefabPoolInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showBubbles)
|
if (showMultiPrefabPools)
|
||||||
{
|
{
|
||||||
DisplayBubblePoolInfo();
|
DisplayMultiPrefabPoolInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayTrenchTilePoolInfo()
|
void DisplaySinglePrefabPoolInfo()
|
||||||
{
|
{
|
||||||
TrenchTilePool[] pools = Object.FindObjectsByType<TrenchTilePool>(FindObjectsSortMode.None);
|
// Find all types that derive from BaseObjectPool<T>
|
||||||
|
Component[] pools = FindObjectsOfBaseType(typeof(Component), typeof(BaseObjectPool<>));
|
||||||
|
|
||||||
if (pools.Length == 0)
|
if (pools.Length == 0)
|
||||||
{
|
{
|
||||||
EditorGUILayout.HelpBox("No trench tile pools found in the scene.", MessageType.Info);
|
EditorGUILayout.HelpBox("No single prefab pools found in the scene.", MessageType.Info);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
EditorGUILayout.LabelField("Single Prefab Pools", EditorStyles.boldLabel);
|
||||||
|
foreach (var poolComponent in pools)
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("Trench Tile Pools", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField($"Pool: {poolComponent.name} ({poolComponent.GetType().Name})", EditorStyles.boldLabel);
|
||||||
foreach (var pool in pools)
|
EditorGUI.indentLevel++;
|
||||||
|
|
||||||
|
// Get private field values using reflection
|
||||||
|
Type poolType = poolComponent.GetType();
|
||||||
|
FieldInfo pooledObjectsField = poolType.GetField("pooledObjects",
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
|
FieldInfo totalCreatedField = poolType.GetField("totalCreated",
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
|
FieldInfo totalReturnedField = poolType.GetField("totalReturned",
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
|
PropertyInfo maxPoolSizeProp = poolType.GetProperty("maxPoolSize") ??
|
||||||
|
poolType.GetField("maxPoolSize")?.DeclaringType.GetProperty("maxPoolSize");
|
||||||
|
|
||||||
|
PropertyInfo initialPoolSizeProp = poolType.GetProperty("initialPoolSize") ??
|
||||||
|
poolType.GetField("initialPoolSize")?.DeclaringType.GetProperty("initialPoolSize");
|
||||||
|
|
||||||
|
if (pooledObjectsField != null)
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField($"Pool: {pool.name}", EditorStyles.boldLabel);
|
object pooledObjects = pooledObjectsField.GetValue(poolComponent);
|
||||||
EditorGUI.indentLevel++;
|
int count = 0;
|
||||||
|
|
||||||
// Get private field values using reflection
|
// Handle Stack<T>
|
||||||
System.Reflection.FieldInfo totalCountField = typeof(TrenchTilePool).GetField("totalPooledCount",
|
if (pooledObjects is System.Collections.ICollection collection)
|
||||||
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);
|
count = collection.Count;
|
||||||
EditorGUILayout.LabelField($"Total Pooled Objects: {totalCount}/{pool.totalMaxPoolSize}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pooledTilesField != null && prefabUsageField != null)
|
int maxSize = 0;
|
||||||
|
if (maxPoolSizeProp != null)
|
||||||
{
|
{
|
||||||
var pooledTiles = pooledTilesField.GetValue(pool) as Dictionary<int, Stack<GameObject>>;
|
maxSize = (int)maxPoolSizeProp.GetValue(poolComponent);
|
||||||
var usageCounts = prefabUsageField.GetValue(pool) as Dictionary<int, int>;
|
}
|
||||||
|
else
|
||||||
if (pooledTiles != null)
|
{
|
||||||
|
FieldInfo maxPoolSizeField = poolType.GetField("maxPoolSize",
|
||||||
|
BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
if (maxPoolSizeField != null)
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("Prefab Details:", EditorStyles.boldLabel);
|
maxSize = (int)maxPoolSizeField.GetValue(poolComponent);
|
||||||
EditorGUI.indentLevel++;
|
}
|
||||||
|
}
|
||||||
foreach (var entry in pooledTiles)
|
|
||||||
|
EditorGUILayout.LabelField($"Pooled Objects: {count}/{maxSize}");
|
||||||
|
|
||||||
|
int initialSize = 0;
|
||||||
|
if (initialPoolSizeProp != null)
|
||||||
|
{
|
||||||
|
initialSize = (int)initialPoolSizeProp.GetValue(poolComponent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FieldInfo initialPoolSizeField = poolType.GetField("initialPoolSize",
|
||||||
|
BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
if (initialPoolSizeField != null)
|
||||||
|
{
|
||||||
|
initialSize = (int)initialPoolSizeField.GetValue(poolComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField($"Initial Pool Size: {initialSize}");
|
||||||
|
|
||||||
|
if (totalCreatedField != null && totalReturnedField != null)
|
||||||
|
{
|
||||||
|
int created = (int)totalCreatedField.GetValue(poolComponent);
|
||||||
|
int returned = (int)totalReturnedField.GetValue(poolComponent);
|
||||||
|
EditorGUILayout.LabelField($"Created: {created}, Returned: {returned}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find active objects of the pool's type
|
||||||
|
if (poolType.BaseType.IsGenericType)
|
||||||
|
{
|
||||||
|
Type elementType = poolType.BaseType.GetGenericArguments()[0];
|
||||||
|
|
||||||
|
// More accurately count only active objects in the current scene
|
||||||
|
int activeCount = 0;
|
||||||
|
|
||||||
|
// First, try to get a more accurate count from the current scene
|
||||||
|
foreach (var obj in UnityEngine.Object.FindObjectsByType(elementType, FindObjectsSortMode.None))
|
||||||
|
{
|
||||||
|
var comp = obj as Component;
|
||||||
|
if (comp != null && comp.gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
activeCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField($"Active Objects (Current Scene): {activeCount}");
|
||||||
|
|
||||||
|
// Add a note about pooling status
|
||||||
|
if (activeCount > 0)
|
||||||
|
{
|
||||||
|
int pooledCount = 0;
|
||||||
|
if (pooledObjectsField != null)
|
||||||
|
{
|
||||||
|
object pooledObjects = pooledObjectsField.GetValue(poolComponent);
|
||||||
|
if (pooledObjects is ICollection collection)
|
||||||
{
|
{
|
||||||
int prefabIndex = entry.Key;
|
pooledCount = collection.Count;
|
||||||
Stack<GameObject> stack = entry.Value;
|
}
|
||||||
int count = stack != null ? stack.Count : 0;
|
}
|
||||||
|
|
||||||
int usageCount = 0;
|
EditorGUILayout.LabelField($"Pooling Efficiency: {pooledCount} ready in pool, {activeCount} active");
|
||||||
if (usageCounts != null && usageCounts.TryGetValue(prefabIndex, out int usage))
|
}
|
||||||
{
|
}
|
||||||
usageCount = usage;
|
|
||||||
}
|
EditorGUI.indentLevel--;
|
||||||
|
EditorGUILayout.Space();
|
||||||
EditorGUILayout.LabelField($"Prefab {prefabIndex}: {count} pooled, {usageCount} usages");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayMultiPrefabPoolInfo()
|
||||||
|
{
|
||||||
|
// Find all types that derive from MultiPrefabPool<T>
|
||||||
|
Component[] pools = FindObjectsOfBaseType(typeof(Component), typeof(MultiPrefabPool<>));
|
||||||
|
|
||||||
|
if (pools.Length == 0)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("No multi-prefab pools found in the scene.", MessageType.Info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField("Multi-Prefab Pools", EditorStyles.boldLabel);
|
||||||
|
foreach (var poolComponent in pools)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField($"Pool: {poolComponent.name} ({poolComponent.GetType().Name})", EditorStyles.boldLabel);
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
|
||||||
|
// Get private field values using reflection
|
||||||
|
Type poolType = poolComponent.GetType();
|
||||||
|
FieldInfo totalPooledCountField = poolType.GetField("totalPooledCount",
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
|
FieldInfo pooledObjectsField = poolType.GetField("pooledObjects",
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
|
FieldInfo prefabUsageField = poolType.GetField("prefabUsageCount",
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
|
PropertyInfo totalMaxPoolSizeProp = poolType.GetProperty("totalMaxPoolSize") ??
|
||||||
|
poolType.GetField("totalMaxPoolSize")?.DeclaringType.GetProperty("totalMaxPoolSize");
|
||||||
|
|
||||||
|
if (totalPooledCountField != null && totalMaxPoolSizeProp != null)
|
||||||
|
{
|
||||||
|
int totalCount = (int)totalPooledCountField.GetValue(poolComponent);
|
||||||
|
int maxSize = 0;
|
||||||
|
|
||||||
|
if (totalMaxPoolSizeProp != null)
|
||||||
|
{
|
||||||
|
maxSize = (int)totalMaxPoolSizeProp.GetValue(poolComponent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FieldInfo totalMaxPoolSizeField = poolType.GetField("totalMaxPoolSize",
|
||||||
|
BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
if (totalMaxPoolSizeField != null)
|
||||||
|
{
|
||||||
|
maxSize = (int)totalMaxPoolSizeField.GetValue(poolComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField($"Total Pooled Objects: {totalCount}/{maxSize}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pooledObjectsField != null && prefabUsageField != null)
|
||||||
|
{
|
||||||
|
// This is more complex because we don't know the exact generic types
|
||||||
|
// Just show basic information
|
||||||
|
object pooledTiles = pooledObjectsField.GetValue(poolComponent);
|
||||||
|
object usageCounts = prefabUsageField.GetValue(poolComponent);
|
||||||
|
|
||||||
|
if (pooledTiles != null && pooledTiles is IDictionary poolDict)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("Prefab Details:", EditorStyles.boldLabel);
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
|
||||||
|
foreach (DictionaryEntry entry in poolDict)
|
||||||
|
{
|
||||||
|
int prefabIndex = Convert.ToInt32(entry.Key);
|
||||||
|
object value = entry.Value;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
// Handle Stack<T>
|
||||||
|
if (value is ICollection collection)
|
||||||
|
{
|
||||||
|
count = collection.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorGUI.indentLevel--;
|
int usageCount = 0;
|
||||||
|
if (usageCounts is IDictionary usageDict)
|
||||||
|
{
|
||||||
|
if (usageDict.Contains(entry.Key))
|
||||||
|
{
|
||||||
|
usageCount = Convert.ToInt32(usageDict[entry.Key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField($"Prefab {prefabIndex}: {count} pooled, {usageCount} usages");
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find active objects of the pool's type
|
||||||
|
if (poolType.BaseType.IsGenericType)
|
||||||
|
{
|
||||||
|
Type elementType = poolType.BaseType.GetGenericArguments()[0];
|
||||||
|
int activeCount = 0;
|
||||||
|
|
||||||
|
// Count active objects of the specific pool's component type
|
||||||
|
foreach (var obj in UnityEngine.Object.FindObjectsByType(elementType, FindObjectsSortMode.None))
|
||||||
|
{
|
||||||
|
var comp = obj as Component;
|
||||||
|
if (comp != null && comp.gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
activeCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorGUI.indentLevel--;
|
EditorGUILayout.LabelField($"Active Objects (Current Scene): {activeCount}");
|
||||||
EditorGUILayout.Space();
|
|
||||||
|
// Add a note about pooling status
|
||||||
|
if (activeCount > 0 && totalPooledCountField != null)
|
||||||
|
{
|
||||||
|
int pooledCount = (int)totalPooledCountField.GetValue(poolComponent);
|
||||||
|
EditorGUILayout.LabelField($"Pooling Efficiency: {pooledCount} ready in pool, {activeCount} active");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
EditorGUILayout.Space();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayBubblePoolInfo()
|
/// <summary>
|
||||||
|
/// Finds all objects that derive from a generic base type
|
||||||
|
/// </summary>
|
||||||
|
private Component[] FindObjectsOfBaseType(Type baseComponentType, Type genericBaseType)
|
||||||
{
|
{
|
||||||
BubblePool[] pools = Object.FindObjectsByType<BubblePool>(FindObjectsSortMode.None);
|
List<Component> results = new List<Component>();
|
||||||
if (pools.Length == 0)
|
|
||||||
|
// Find all components in the scene
|
||||||
|
Component[] allComponents = UnityEngine.Object.FindObjectsByType<Component>(FindObjectsSortMode.None);
|
||||||
|
|
||||||
|
foreach (var component in allComponents)
|
||||||
{
|
{
|
||||||
EditorGUILayout.HelpBox("No bubble pools found in the scene.", MessageType.Info);
|
Type componentType = component.GetType();
|
||||||
}
|
|
||||||
else
|
// Check if this type derives from the generic base type
|
||||||
{
|
while (componentType != null && componentType != typeof(object))
|
||||||
EditorGUILayout.LabelField("Bubble Pools", EditorStyles.boldLabel);
|
|
||||||
foreach (var pool in pools)
|
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField($"Pool: {pool.name}", EditorStyles.boldLabel);
|
if (componentType.IsGenericType &&
|
||||||
EditorGUI.indentLevel++;
|
componentType.GetGenericTypeDefinition() == genericBaseType)
|
||||||
|
|
||||||
// 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<Bubble>;
|
results.Add(component);
|
||||||
int count = pooledBubbles != null ? pooledBubbles.Count : 0;
|
break;
|
||||||
|
|
||||||
EditorGUILayout.LabelField($"Pooled Bubbles: {count}/{pool.maxPoolSize}");
|
|
||||||
EditorGUILayout.LabelField($"Initial Pool Size: {pool.initialPoolSize}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find active bubbles in the scene
|
// Also check for non-generic derived types
|
||||||
Bubble[] activeBubbles = Object.FindObjectsByType<Bubble>(FindObjectsSortMode.None);
|
if (componentType.BaseType != null &&
|
||||||
int activeBubbleCount = 0;
|
componentType.BaseType.IsGenericType &&
|
||||||
|
componentType.BaseType.GetGenericTypeDefinition() == genericBaseType)
|
||||||
foreach (var bubble in activeBubbles)
|
|
||||||
{
|
{
|
||||||
if (bubble.gameObject.activeInHierarchy)
|
results.Add(component);
|
||||||
{
|
break;
|
||||||
activeBubbleCount++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorGUILayout.LabelField($"Active Bubbles: {activeBubbleCount}");
|
componentType = componentType.BaseType;
|
||||||
|
|
||||||
EditorGUI.indentLevel--;
|
|
||||||
EditorGUILayout.Space();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return results.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ GameObject:
|
|||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 4925660644986369589}
|
- component: {fileID: 4925660644986369589}
|
||||||
|
- component: {fileID: 2488201930835981397}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Tile1
|
m_Name: Tile1
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -207,3 +208,16 @@ Transform:
|
|||||||
- {fileID: 1003080013996268193}
|
- {fileID: 1003080013996268193}
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &2488201930835981397
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2956826569642009690}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 41def183b6714aca97663d74cc2d0678, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
tileIndex: 0
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ GameObject:
|
|||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 4925660644986369589}
|
- component: {fileID: 4925660644986369589}
|
||||||
|
- component: {fileID: 7876353970701168068}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Tile1_flipped
|
m_Name: Tile1_flipped
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -207,3 +208,16 @@ Transform:
|
|||||||
- {fileID: 1003080013996268193}
|
- {fileID: 1003080013996268193}
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 180}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 180}
|
||||||
|
--- !u!114 &7876353970701168068
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2956826569642009690}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 41def183b6714aca97663d74cc2d0678, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
tileIndex: 0
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ GameObject:
|
|||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 4925660644986369589}
|
- component: {fileID: 4925660644986369589}
|
||||||
|
- component: {fileID: 2017387953723006367}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Tile2
|
m_Name: Tile2
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -207,3 +208,16 @@ Transform:
|
|||||||
- {fileID: 1003080013996268193}
|
- {fileID: 1003080013996268193}
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &2017387953723006367
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2956826569642009690}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 41def183b6714aca97663d74cc2d0678, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
tileIndex: 0
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ GameObject:
|
|||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 4925660644986369589}
|
- component: {fileID: 4925660644986369589}
|
||||||
|
- component: {fileID: 451715946189956124}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Tile2_flipped
|
m_Name: Tile2_flipped
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -207,3 +208,16 @@ Transform:
|
|||||||
- {fileID: 1003080013996268193}
|
- {fileID: 1003080013996268193}
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 180}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 180}
|
||||||
|
--- !u!114 &451715946189956124
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2956826569642009690}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 41def183b6714aca97663d74cc2d0678, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
tileIndex: 0
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ GameObject:
|
|||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 4925660644986369589}
|
- component: {fileID: 4925660644986369589}
|
||||||
|
- component: {fileID: 8822397971507360111}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Tile3
|
m_Name: Tile3
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -207,3 +208,16 @@ Transform:
|
|||||||
- {fileID: 1003080013996268193}
|
- {fileID: 1003080013996268193}
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &8822397971507360111
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2956826569642009690}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 41def183b6714aca97663d74cc2d0678, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
tileIndex: 0
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ GameObject:
|
|||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 4925660644986369589}
|
- component: {fileID: 4925660644986369589}
|
||||||
|
- component: {fileID: 2006557459409230470}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Tile3_flipped
|
m_Name: Tile3_flipped
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -207,3 +208,16 @@ Transform:
|
|||||||
- {fileID: 1003080013996268193}
|
- {fileID: 1003080013996268193}
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 180}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 180}
|
||||||
|
--- !u!114 &2006557459409230470
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2956826569642009690}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 41def183b6714aca97663d74cc2d0678, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
tileIndex: 0
|
||||||
|
|||||||
@@ -1211,10 +1211,8 @@ MonoBehaviour:
|
|||||||
speedUpInterval: 0
|
speedUpInterval: 0
|
||||||
maxMoveSpeed: 12
|
maxMoveSpeed: 12
|
||||||
useObjectPooling: 1
|
useObjectPooling: 1
|
||||||
preInstantiateTiles: 0
|
|
||||||
initialTilesPerPrefab: 1
|
|
||||||
maxPerPrefabPoolSize: 2
|
maxPerPrefabPoolSize: 2
|
||||||
totalMaxPoolSize: 10
|
totalMaxPoolSize: 25
|
||||||
onTileSpawned:
|
onTileSpawned:
|
||||||
m_PersistentCalls:
|
m_PersistentCalls:
|
||||||
m_Calls: []
|
m_Calls: []
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Pooling;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a single bubble, handling its movement, wobble effect, scaling, and sprite assignment.
|
/// Represents a single bubble, handling its movement, wobble effect, scaling, and sprite assignment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Bubble : MonoBehaviour
|
public class Bubble : MonoBehaviour, IPoolableWithReference<BubblePool>
|
||||||
{
|
{
|
||||||
public float speed = 1f;
|
public float speed = 1f;
|
||||||
public float wobbleSpeed = 1f;
|
public float wobbleSpeed = 1f;
|
||||||
@@ -87,6 +88,22 @@ namespace Minigames.DivingForPictures
|
|||||||
parentPool = pool;
|
parentPool = pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the object is retrieved from the pool.
|
||||||
|
/// </summary>
|
||||||
|
public void OnSpawn()
|
||||||
|
{
|
||||||
|
ResetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the object is returned to the pool.
|
||||||
|
/// </summary>
|
||||||
|
public void OnDespawn()
|
||||||
|
{
|
||||||
|
// Nothing to do here for now, but we could clean up resources
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the main sprite for the bubble.
|
/// Sets the main sprite for the bubble.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,82 +1,23 @@
|
|||||||
using System.Collections.Generic;
|
using UnityEngine;
|
||||||
using UnityEngine;
|
using Pooling;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages a pool of bubble objects to reduce garbage collection overhead.
|
/// Manages a pool of bubble objects to reduce garbage collection overhead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BubblePool : MonoBehaviour
|
public class BubblePool : BaseObjectPool<Bubble>
|
||||||
{
|
{
|
||||||
[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<Bubble> pooledBubbles = new Stack<Bubble>();
|
|
||||||
private Bubble bubblePrefab;
|
|
||||||
private int totalBubblesCreated = 0;
|
|
||||||
private int totalBubblesReturned = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize the pool with the bubble prefab
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="prefab">The bubble prefab to use</param>
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new bubble instance and adds it to the pool
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a bubble from the pool, or creates a new one if the pool is empty
|
/// Gets a bubble from the pool, or creates a new one if the pool is empty
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A bubble instance ready to use</returns>
|
/// <returns>A bubble instance ready to use</returns>
|
||||||
public Bubble GetBubble()
|
public Bubble GetBubble()
|
||||||
{
|
{
|
||||||
Bubble bubble;
|
Bubble bubble = Get();
|
||||||
|
|
||||||
if (pooledBubbles.Count > 0)
|
// Set reference to this pool so the bubble can return itself
|
||||||
{
|
|
||||||
bubble = pooledBubbles.Pop();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bubble = CreateNewBubble();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the bubble has a reference to this pool
|
|
||||||
bubble.SetPool(this);
|
bubble.SetPool(this);
|
||||||
bubble.gameObject.SetActive(true);
|
|
||||||
bubble.ResetState();
|
|
||||||
|
|
||||||
return bubble;
|
return bubble;
|
||||||
}
|
}
|
||||||
@@ -87,31 +28,15 @@ namespace Minigames.DivingForPictures
|
|||||||
/// <param name="bubble">The bubble to return to the pool</param>
|
/// <param name="bubble">The bubble to return to the pool</param>
|
||||||
public void ReturnBubble(Bubble bubble)
|
public void ReturnBubble(Bubble bubble)
|
||||||
{
|
{
|
||||||
if (bubble == null) return;
|
Return(bubble);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logs pool statistics
|
/// Logs pool statistics
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void LogPoolStats()
|
public override void LogPoolStats()
|
||||||
{
|
{
|
||||||
Debug.Log($"[BubblePool] Pooled bubbles: {pooledBubbles.Count}/{maxPoolSize} (Created: {totalBubblesCreated}, Returned: {totalBubblesReturned})");
|
Debug.Log($"[BubblePool] Pooled bubbles: {pooledObjects.Count}/{maxPoolSize} (Created: {totalCreated}, Returned: {totalReturned})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Pooling;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -43,6 +44,11 @@ namespace Minigames.DivingForPictures
|
|||||||
_bubblePool.initialPoolSize = initialPoolSize;
|
_bubblePool.initialPoolSize = initialPoolSize;
|
||||||
_bubblePool.maxPoolSize = maxPoolSize;
|
_bubblePool.maxPoolSize = maxPoolSize;
|
||||||
_bubblePool.Initialize(bubblePrefab);
|
_bubblePool.Initialize(bubblePrefab);
|
||||||
|
|
||||||
|
// Periodically check for pool statistics in debug builds
|
||||||
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
|
InvokeRepeating(nameof(LogPoolStats), 5f, 30f);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,5 +118,16 @@ namespace Minigames.DivingForPictures
|
|||||||
// Pass min/max scale for wobble clamping
|
// Pass min/max scale for wobble clamping
|
||||||
bubble.SetWobbleScaleLimits(wobbleMinScale, wobbleMaxScale);
|
bubble.SetWobbleScaleLimits(wobbleMinScale, wobbleMaxScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs the current pool statistics for debugging
|
||||||
|
/// </summary>
|
||||||
|
private void LogPoolStats()
|
||||||
|
{
|
||||||
|
if (_bubblePool != null)
|
||||||
|
{
|
||||||
|
_bubblePool.LogPoolStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Pooling;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -7,238 +8,38 @@ namespace Minigames.DivingForPictures
|
|||||||
/// Manages a pool of trench tile objects to reduce garbage collection overhead.
|
/// Manages a pool of trench tile objects to reduce garbage collection overhead.
|
||||||
/// Optimized for handling a large number of different prefab types.
|
/// Optimized for handling a large number of different prefab types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TrenchTilePool : MonoBehaviour
|
public class TrenchTilePool : MultiPrefabPool<Tile>
|
||||||
{
|
{
|
||||||
[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<int, Stack<GameObject>> pooledTiles = new Dictionary<int, Stack<GameObject>>();
|
|
||||||
private Dictionary<int, int> prefabUsageCount = new Dictionary<int, int>();
|
|
||||||
private List<GameObject> tilePrefabs;
|
|
||||||
private int totalPooledCount = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize the pool with the tile prefabs
|
/// Returns a tile to the pool
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="prefabs">List of tile prefabs to use</param>
|
/// <param name="tile">The tile to return to the pool</param>
|
||||||
public void Initialize(List<GameObject> prefabs)
|
/// <param name="prefabIndex">The index of the prefab this tile was created from</param>
|
||||||
|
public void ReturnTile(GameObject tile, int prefabIndex)
|
||||||
{
|
{
|
||||||
tilePrefabs = prefabs;
|
if (tile != null)
|
||||||
|
|
||||||
// Initialize usage tracking
|
|
||||||
for (int i = 0; i < prefabs.Count; i++)
|
|
||||||
{
|
{
|
||||||
prefabUsageCount[i] = 0;
|
Tile tileComponent = tile.GetComponent<Tile>();
|
||||||
pooledTiles[i] = new Stack<GameObject>();
|
if (tileComponent != null)
|
||||||
}
|
|
||||||
|
|
||||||
// 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++)
|
Return(tileComponent, prefabIndex);
|
||||||
{
|
}
|
||||||
if (totalPooledCount >= totalMaxPoolSize) break;
|
else
|
||||||
CreateNewTile(i);
|
{
|
||||||
}
|
Debug.LogWarning($"Attempted to return a GameObject without a Tile component: {tile.name}");
|
||||||
|
Destroy(tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new tile instance and adds it to the pool
|
|
||||||
/// </summary>
|
|
||||||
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<GameObject>();
|
|
||||||
}
|
|
||||||
|
|
||||||
pooledTiles[prefabIndex].Push(tile);
|
|
||||||
totalPooledCount++;
|
|
||||||
return tile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a tile from the pool, or creates a new one if the pool is empty
|
/// Gets a tile from the pool, or creates a new one if the pool is empty
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A tile instance ready to use</returns>
|
/// <returns>A tile instance ready to use</returns>
|
||||||
public GameObject GetTile(int prefabIndex)
|
public GameObject GetTile(int prefabIndex)
|
||||||
{
|
{
|
||||||
GameObject tile;
|
Tile tileComponent = Get(prefabIndex);
|
||||||
|
return tileComponent.gameObject;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a tile to the pool
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tile">The tile to return to the pool</param>
|
|
||||||
/// <param name="prefabIndex">The index of the prefab this tile was created from</param>
|
|
||||||
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<GameObject>();
|
|
||||||
}
|
|
||||||
|
|
||||||
pooledTiles[prefabIndex].Push(tile);
|
|
||||||
totalPooledCount++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Destroy(tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Trims the pool to remove excess objects
|
|
||||||
/// Can be called periodically or when memory pressure is high
|
|
||||||
/// </summary>
|
|
||||||
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<KeyValuePair<int, int>> sortedUsage = new List<KeyValuePair<int, int>>(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Logs pool statistics to the console
|
|
||||||
/// </summary>
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
using UnityEngine.Events;
|
||||||
using UnityEngine.Serialization;
|
using UnityEngine.Serialization;
|
||||||
|
using Pooling;
|
||||||
|
|
||||||
namespace Minigames.DivingForPictures
|
namespace Minigames.DivingForPictures
|
||||||
{
|
{
|
||||||
@@ -12,50 +13,105 @@ namespace Minigames.DivingForPictures
|
|||||||
{
|
{
|
||||||
[Header("Tile Prefabs")]
|
[Header("Tile Prefabs")]
|
||||||
[Tooltip("List of possible trench tile prefabs.")]
|
[Tooltip("List of possible trench tile prefabs.")]
|
||||||
public List<GameObject> tilePrefabs;
|
[SerializeField] private List<GameObject> tilePrefabs;
|
||||||
|
|
||||||
[Header("Tile Settings")]
|
[Header("Tile Settings")]
|
||||||
private Dictionary<GameObject, float> _tileHeights = new Dictionary<GameObject, float>();
|
[SerializeField] private int initialTileCount = 3;
|
||||||
public int initialTileCount = 3;
|
[SerializeField] private float tileSpawnBuffer = 1f;
|
||||||
public float tileSpawnBuffer = 1f;
|
|
||||||
|
|
||||||
[Header("Movement Settings")]
|
[Header("Movement Settings")]
|
||||||
public float moveSpeed = 3f;
|
[SerializeField] private float moveSpeed = 3f;
|
||||||
public float speedUpFactor = 0.2f;
|
[SerializeField] private float speedUpFactor = 0.2f;
|
||||||
public float speedUpInterval = 10f;
|
[SerializeField] private float speedUpInterval = 10f;
|
||||||
public float maxMoveSpeed = 12f; // Added a cap to the movement speed
|
[SerializeField] private float maxMoveSpeed = 12f;
|
||||||
|
|
||||||
[Header("Object Pooling")]
|
[Header("Object Pooling")]
|
||||||
public bool useObjectPooling = true;
|
[SerializeField] private bool useObjectPooling = true;
|
||||||
public bool preInstantiateTiles = false; // Added option to control pre-instantiation
|
[SerializeField] private int maxPerPrefabPoolSize = 2;
|
||||||
public int initialTilesPerPrefab = 2;
|
[SerializeField] private int totalMaxPoolSize = 10;
|
||||||
public int maxPerPrefabPoolSize = 2;
|
|
||||||
public int totalMaxPoolSize = 10; // Total pool size across all prefab types
|
|
||||||
|
|
||||||
[FormerlySerializedAs("OnTileSpawned")] [Header("Events")]
|
[Header("Events")]
|
||||||
|
[FormerlySerializedAs("OnTileSpawned")]
|
||||||
public UnityEvent<GameObject> onTileSpawned;
|
public UnityEvent<GameObject> onTileSpawned;
|
||||||
|
|
||||||
[FormerlySerializedAs("OnTileDestroyed")]
|
[FormerlySerializedAs("OnTileDestroyed")]
|
||||||
public UnityEvent<GameObject> onTileDestroyed;
|
public UnityEvent<GameObject> onTileDestroyed;
|
||||||
|
|
||||||
|
// Private fields
|
||||||
|
private readonly Dictionary<GameObject, float> _tileHeights = new Dictionary<GameObject, float>();
|
||||||
private readonly List<GameObject> _activeTiles = new List<GameObject>();
|
private readonly List<GameObject> _activeTiles = new List<GameObject>();
|
||||||
private readonly Dictionary<int, int> _tileLastUsed = new Dictionary<int, int>();
|
private readonly Dictionary<int, int> _tileLastUsed = new Dictionary<int, int>();
|
||||||
|
|
||||||
private int _spawnCounter;
|
private int _spawnCounter;
|
||||||
private float _speedUpTimer;
|
private float _speedUpTimer;
|
||||||
|
|
||||||
private Camera _mainCamera;
|
private Camera _mainCamera;
|
||||||
private float _screenBottom;
|
private float _screenBottom;
|
||||||
private float _screenTop;
|
private float _screenTop;
|
||||||
private TrenchTilePool _tilePool;
|
private TrenchTilePool _tilePool;
|
||||||
|
|
||||||
private const float TileSpawnZ = -1f; // All spawned tiles should have z = -1
|
private const float TileSpawnZ = -1f;
|
||||||
|
private const float DefaultTileHeight = 5f;
|
||||||
|
|
||||||
void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_mainCamera = Camera.main;
|
_mainCamera = Camera.main;
|
||||||
|
|
||||||
// Calculate tile heights for each prefab
|
// Calculate tile heights for each prefab
|
||||||
|
CalculateTileHeights();
|
||||||
|
|
||||||
|
// Ensure all prefabs have Tile components
|
||||||
|
ValidateTilePrefabs();
|
||||||
|
|
||||||
|
if (useObjectPooling)
|
||||||
|
{
|
||||||
|
InitializeObjectPool();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that all prefabs have Tile components
|
||||||
|
private void ValidateTilePrefabs()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < tilePrefabs.Count; i++)
|
||||||
|
{
|
||||||
|
if (tilePrefabs[i] == null) continue;
|
||||||
|
|
||||||
|
// Check if the prefab has a Tile component
|
||||||
|
if (tilePrefabs[i].GetComponent<Tile>() == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"Prefab {tilePrefabs[i].name} does not have a Tile component. Adding one automatically.");
|
||||||
|
// Add the Tile component if it doesn't exist
|
||||||
|
tilePrefabs[i].AddComponent<Tile>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
CalculateScreenBounds();
|
||||||
|
SpawnInitialTiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
MoveTiles();
|
||||||
|
HandleTileDestruction();
|
||||||
|
HandleTileSpawning();
|
||||||
|
HandleSpeedRamping();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate height values for all tile prefabs
|
||||||
|
/// </summary>
|
||||||
|
private void CalculateTileHeights()
|
||||||
|
{
|
||||||
foreach (var prefab in tilePrefabs)
|
foreach (var prefab in tilePrefabs)
|
||||||
{
|
{
|
||||||
|
if (prefab == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("Null prefab found in tilePrefabs list!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Renderer renderer = prefab.GetComponentInChildren<Renderer>();
|
Renderer renderer = prefab.GetComponentInChildren<Renderer>();
|
||||||
if (renderer != null)
|
if (renderer != null)
|
||||||
{
|
{
|
||||||
@@ -64,31 +120,56 @@ namespace Minigames.DivingForPictures
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Fallback in case no renderer is found
|
// Fallback in case no renderer is found
|
||||||
_tileHeights[prefab] = 5f;
|
_tileHeights[prefab] = DefaultTileHeight;
|
||||||
Debug.LogWarning($"No renderer found in prefab {prefab.name}. Using default height of 5.");
|
Debug.LogWarning($"No renderer found in prefab {prefab.name}. Using default height of {DefaultTileHeight}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useObjectPooling)
|
|
||||||
{
|
|
||||||
// Create the tile pool
|
|
||||||
GameObject poolGO = new GameObject("TrenchTilePool");
|
|
||||||
poolGO.transform.SetParent(transform);
|
|
||||||
_tilePool = poolGO.AddComponent<TrenchTilePool>();
|
|
||||||
_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()
|
/// <summary>
|
||||||
|
/// Initialize the object pool system
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeObjectPool()
|
||||||
|
{
|
||||||
|
// Create the tile pool
|
||||||
|
GameObject poolGO = new GameObject("TrenchTilePool");
|
||||||
|
poolGO.transform.SetParent(transform);
|
||||||
|
_tilePool = poolGO.AddComponent<TrenchTilePool>();
|
||||||
|
|
||||||
|
// Set up the pool configuration
|
||||||
|
_tilePool.maxPerPrefabPoolSize = maxPerPrefabPoolSize;
|
||||||
|
_tilePool.totalMaxPoolSize = totalMaxPoolSize;
|
||||||
|
|
||||||
|
// Convert the GameObject list to a Tile list
|
||||||
|
List<Tile> prefabTiles = new List<Tile>(tilePrefabs.Count);
|
||||||
|
foreach (var prefab in tilePrefabs)
|
||||||
|
{
|
||||||
|
if (prefab != null)
|
||||||
|
{
|
||||||
|
Tile tileComponent = prefab.GetComponent<Tile>();
|
||||||
|
if (tileComponent != null)
|
||||||
|
{
|
||||||
|
prefabTiles.Add(tileComponent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError($"Prefab {prefab.name} is missing a Tile component!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the pool with the tile component list
|
||||||
|
_tilePool.Initialize(prefabTiles);
|
||||||
|
|
||||||
|
// Periodically trim the pool to optimize memory usage
|
||||||
|
InvokeRepeating(nameof(TrimExcessPooledTiles), 10f, 30f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spawn the initial set of tiles
|
||||||
|
/// </summary>
|
||||||
|
private void SpawnInitialTiles()
|
||||||
{
|
{
|
||||||
CalculateScreenBounds();
|
|
||||||
for (int i = 0; i < initialTileCount; i++)
|
for (int i = 0; i < initialTileCount; i++)
|
||||||
{
|
{
|
||||||
float y = _screenBottom;
|
float y = _screenBottom;
|
||||||
@@ -103,22 +184,30 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
/// <summary>
|
||||||
{
|
/// Calculate the screen bounds in world space
|
||||||
MoveTiles();
|
/// </summary>
|
||||||
HandleTileDestruction();
|
|
||||||
HandleTileSpawning();
|
|
||||||
HandleSpeedRamping();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CalculateScreenBounds()
|
private void CalculateScreenBounds()
|
||||||
{
|
{
|
||||||
|
if (_mainCamera == null)
|
||||||
|
{
|
||||||
|
_mainCamera = Camera.main;
|
||||||
|
if (_mainCamera == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("No main camera found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Vector3 bottom = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 0f, _mainCamera.nearClipPlane));
|
Vector3 bottom = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 0f, _mainCamera.nearClipPlane));
|
||||||
Vector3 top = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 1f, _mainCamera.nearClipPlane));
|
Vector3 top = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 1f, _mainCamera.nearClipPlane));
|
||||||
_screenBottom = bottom.y;
|
_screenBottom = bottom.y;
|
||||||
_screenTop = top.y;
|
_screenTop = top.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Move all active tiles upward
|
||||||
|
/// </summary>
|
||||||
private void MoveTiles()
|
private void MoveTiles()
|
||||||
{
|
{
|
||||||
float moveDelta = moveSpeed * Time.deltaTime;
|
float moveDelta = moveSpeed * Time.deltaTime;
|
||||||
@@ -131,10 +220,20 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check for tiles that have moved off screen and should be destroyed or returned to pool
|
||||||
|
/// </summary>
|
||||||
private void HandleTileDestruction()
|
private void HandleTileDestruction()
|
||||||
{
|
{
|
||||||
if (_activeTiles.Count == 0) return;
|
if (_activeTiles.Count == 0) return;
|
||||||
|
|
||||||
GameObject topTile = _activeTiles[0];
|
GameObject topTile = _activeTiles[0];
|
||||||
|
if (topTile == null)
|
||||||
|
{
|
||||||
|
_activeTiles.RemoveAt(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float tileHeight = GetTileHeight(topTile);
|
float tileHeight = GetTileHeight(topTile);
|
||||||
if (topTile.transform.position.y - tileHeight / 2 > _screenTop + tileSpawnBuffer)
|
if (topTile.transform.position.y - tileHeight / 2 > _screenTop + tileSpawnBuffer)
|
||||||
{
|
{
|
||||||
@@ -161,10 +260,20 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if new tiles need to be spawned
|
||||||
|
/// </summary>
|
||||||
private void HandleTileSpawning()
|
private void HandleTileSpawning()
|
||||||
{
|
{
|
||||||
if (_activeTiles.Count == 0) return;
|
if (_activeTiles.Count == 0) return;
|
||||||
|
|
||||||
GameObject bottomTile = _activeTiles[^1];
|
GameObject bottomTile = _activeTiles[^1];
|
||||||
|
if (bottomTile == null)
|
||||||
|
{
|
||||||
|
_activeTiles.RemoveAt(_activeTiles.Count - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float tileHeight = GetTileHeight(bottomTile);
|
float tileHeight = GetTileHeight(bottomTile);
|
||||||
float bottomEdge = bottomTile.transform.position.y - tileHeight / 2;
|
float bottomEdge = bottomTile.transform.position.y - tileHeight / 2;
|
||||||
if (bottomEdge > _screenBottom - tileSpawnBuffer)
|
if (bottomEdge > _screenBottom - tileSpawnBuffer)
|
||||||
@@ -174,6 +283,9 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle increasing the movement speed over time
|
||||||
|
/// </summary>
|
||||||
private void HandleSpeedRamping()
|
private void HandleSpeedRamping()
|
||||||
{
|
{
|
||||||
_speedUpTimer += Time.deltaTime;
|
_speedUpTimer += Time.deltaTime;
|
||||||
@@ -184,15 +296,37 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spawn a new tile at the specified Y position
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="y">The Y position to spawn at</param>
|
||||||
private void SpawnTileAtY(float y)
|
private void SpawnTileAtY(float y)
|
||||||
{
|
{
|
||||||
|
if (tilePrefabs == null || tilePrefabs.Count == 0)
|
||||||
|
{
|
||||||
|
Debug.LogError("No tile prefabs available for spawning!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int prefabIndex = GetWeightedRandomTileIndex();
|
int prefabIndex = GetWeightedRandomTileIndex();
|
||||||
GameObject prefab = tilePrefabs[prefabIndex];
|
GameObject prefab = tilePrefabs[prefabIndex];
|
||||||
|
if (prefab == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"Tile prefab at index {prefabIndex} is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GameObject tile;
|
GameObject tile;
|
||||||
|
|
||||||
if (useObjectPooling && _tilePool != null)
|
if (useObjectPooling && _tilePool != null)
|
||||||
{
|
{
|
||||||
tile = _tilePool.GetTile(prefabIndex);
|
tile = _tilePool.GetTile(prefabIndex);
|
||||||
|
if (tile == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("Failed to get tile from pool!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
tile.transform.position = new Vector3(0f, y, TileSpawnZ);
|
tile.transform.position = new Vector3(0f, y, TileSpawnZ);
|
||||||
tile.transform.rotation = prefab.transform.rotation;
|
tile.transform.rotation = prefab.transform.rotation;
|
||||||
tile.transform.SetParent(transform);
|
tile.transform.SetParent(transform);
|
||||||
@@ -208,27 +342,40 @@ namespace Minigames.DivingForPictures
|
|||||||
onTileSpawned?.Invoke(tile);
|
onTileSpawned?.Invoke(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a weighted random tile index, favoring tiles that haven't been used recently
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The selected prefab index</returns>
|
||||||
private int GetWeightedRandomTileIndex()
|
private int GetWeightedRandomTileIndex()
|
||||||
{
|
{
|
||||||
// Weight tiles not used recently higher
|
int prefabCount = tilePrefabs.Count;
|
||||||
int n = tilePrefabs.Count;
|
List<float> weights = new List<float>(prefabCount);
|
||||||
List<float> weights = new List<float>(n);
|
|
||||||
for (int i = 0; i < n; i++)
|
for (int i = 0; i < prefabCount; i++)
|
||||||
{
|
{
|
||||||
int lastUsed = _tileLastUsed.TryGetValue(i, out var value) ? value : -n;
|
int lastUsed = _tileLastUsed.TryGetValue(i, out var value) ? value : -prefabCount;
|
||||||
int age = _spawnCounter - lastUsed;
|
int age = _spawnCounter - lastUsed;
|
||||||
float weight = Mathf.Clamp(age, 1, n * 2); // More unused = higher weight
|
float weight = Mathf.Clamp(age, 1, prefabCount * 2); // More unused = higher weight
|
||||||
weights.Add(weight);
|
weights.Add(weight);
|
||||||
}
|
}
|
||||||
float total = 0f;
|
|
||||||
foreach (var w in weights) total += w;
|
float totalWeight = 0f;
|
||||||
float r = Random.value * total;
|
foreach (var weight in weights)
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
{
|
||||||
if (r < weights[i]) return i;
|
totalWeight += weight;
|
||||||
r -= weights[i];
|
|
||||||
}
|
}
|
||||||
return Random.Range(0, n); // fallback
|
|
||||||
|
float randomValue = Random.value * totalWeight;
|
||||||
|
for (int i = 0; i < prefabCount; i++)
|
||||||
|
{
|
||||||
|
if (randomValue < weights[i])
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
randomValue -= weights[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Random.Range(0, prefabCount); // fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -238,9 +385,17 @@ namespace Minigames.DivingForPictures
|
|||||||
/// <returns>The height of the tile</returns>
|
/// <returns>The height of the tile</returns>
|
||||||
private float GetTileHeight(GameObject tile)
|
private float GetTileHeight(GameObject tile)
|
||||||
{
|
{
|
||||||
|
if (tile == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("Attempted to get height of null tile!");
|
||||||
|
return DefaultTileHeight;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this is a known prefab
|
// Check if this is a known prefab
|
||||||
foreach (var prefab in tilePrefabs)
|
foreach (var prefab in tilePrefabs)
|
||||||
{
|
{
|
||||||
|
if (prefab == null) continue;
|
||||||
|
|
||||||
// Check if this tile was created from this prefab
|
// Check if this tile was created from this prefab
|
||||||
if (tile.name.StartsWith(prefab.name))
|
if (tile.name.StartsWith(prefab.name))
|
||||||
{
|
{
|
||||||
@@ -259,7 +414,7 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback
|
// Fallback
|
||||||
return 5f;
|
return DefaultTileHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -269,8 +424,15 @@ namespace Minigames.DivingForPictures
|
|||||||
/// <returns>The index of the prefab or -1 if not found</returns>
|
/// <returns>The index of the prefab or -1 if not found</returns>
|
||||||
private int GetPrefabIndex(GameObject tile)
|
private int GetPrefabIndex(GameObject tile)
|
||||||
{
|
{
|
||||||
|
if (tile == null || tilePrefabs == null)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < tilePrefabs.Count; i++)
|
for (int i = 0; i < tilePrefabs.Count; i++)
|
||||||
{
|
{
|
||||||
|
if (tilePrefabs[i] == null) continue;
|
||||||
|
|
||||||
if (tile.name.StartsWith(tilePrefabs[i].name))
|
if (tile.name.StartsWith(tilePrefabs[i].name))
|
||||||
{
|
{
|
||||||
return i;
|
return i;
|
||||||
@@ -291,14 +453,26 @@ namespace Minigames.DivingForPictures
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
void OnDrawGizmosSelected()
|
private void OnDrawGizmosSelected()
|
||||||
{
|
{
|
||||||
|
if (!Application.isPlaying)
|
||||||
|
{
|
||||||
|
// Only try to calculate this if _screenBottom hasn't been set by the game
|
||||||
|
Camera editorCam = Camera.main;
|
||||||
|
if (editorCam != null)
|
||||||
|
{
|
||||||
|
Vector3 bottom = editorCam.ViewportToWorldPoint(new Vector3(0.5f, 0f, editorCam.nearClipPlane));
|
||||||
|
_screenBottom = bottom.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw tile bounds for debugging
|
// Draw tile bounds for debugging
|
||||||
Gizmos.color = Color.cyan;
|
Gizmos.color = Color.cyan;
|
||||||
for (int i = 0; i < initialTileCount; i++)
|
for (int i = 0; i < initialTileCount; i++)
|
||||||
{
|
{
|
||||||
float height = 5f;
|
float height = DefaultTileHeight;
|
||||||
if (tilePrefabs.Count > 0 && _tileHeights.TryGetValue(tilePrefabs[0], out float h))
|
if (tilePrefabs != null && tilePrefabs.Count > 0 && tilePrefabs[0] != null &&
|
||||||
|
_tileHeights.TryGetValue(tilePrefabs[0], out float h))
|
||||||
{
|
{
|
||||||
height = h;
|
height = h;
|
||||||
}
|
}
|
||||||
|
|||||||
3
Assets/Scripts/Pooling.meta
Normal file
3
Assets/Scripts/Pooling.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a4883b41055949e99282264685fcd4f8
|
||||||
|
timeCreated: 1758019051
|
||||||
148
Assets/Scripts/Pooling/BaseObjectPool.cs
Normal file
148
Assets/Scripts/Pooling/BaseObjectPool.cs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Pooling
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Abstract base class for object pools.
|
||||||
|
/// Provides common functionality for creating, retrieving, and returning pooled objects.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of component to pool</typeparam>
|
||||||
|
public abstract class BaseObjectPool<T> : MonoBehaviour where T : Component
|
||||||
|
{
|
||||||
|
[Tooltip("Initial number of objects to pre-instantiate")]
|
||||||
|
public int initialPoolSize = 10;
|
||||||
|
|
||||||
|
[Tooltip("Maximum number of objects to keep in the pool")]
|
||||||
|
public int maxPoolSize = 30;
|
||||||
|
|
||||||
|
protected Stack<T> pooledObjects = new Stack<T>();
|
||||||
|
protected T prefab;
|
||||||
|
protected int totalCreated = 0;
|
||||||
|
protected int totalReturned = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the pool with the specified prefab
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="prefabToPool">The prefab to use for this pool</param>
|
||||||
|
public virtual void Initialize(T prefabToPool)
|
||||||
|
{
|
||||||
|
prefab = prefabToPool;
|
||||||
|
|
||||||
|
// Pre-instantiate objects
|
||||||
|
for (int i = 0; i < initialPoolSize; i++)
|
||||||
|
{
|
||||||
|
CreateNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"[{GetType().Name}] Initialized with {initialPoolSize} objects");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance and adds it to the pool
|
||||||
|
/// </summary>
|
||||||
|
protected virtual T CreateNew()
|
||||||
|
{
|
||||||
|
if (prefab == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[{GetType().Name}] Prefab is null! Call Initialize first.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
T obj = Instantiate(prefab, transform);
|
||||||
|
obj.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
// Initialize IPoolable components if present
|
||||||
|
IPoolable poolable = obj.GetComponent<IPoolable>();
|
||||||
|
if (poolable != null)
|
||||||
|
{
|
||||||
|
poolable.OnDespawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
pooledObjects.Push(obj);
|
||||||
|
totalCreated++;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an object from the pool, or creates a new one if the pool is empty
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An object ready to use</returns>
|
||||||
|
public virtual T Get()
|
||||||
|
{
|
||||||
|
T obj;
|
||||||
|
|
||||||
|
if (pooledObjects.Count > 0)
|
||||||
|
{
|
||||||
|
obj = pooledObjects.Pop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
obj = CreateNew();
|
||||||
|
pooledObjects.Pop(); // Remove from pool since we're returning it
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.gameObject.SetActive(true);
|
||||||
|
|
||||||
|
// Call OnSpawn for IPoolable components
|
||||||
|
IPoolable poolable = obj.GetComponent<IPoolable>();
|
||||||
|
if (poolable != null)
|
||||||
|
{
|
||||||
|
poolable.OnSpawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an object to the pool
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object to return</param>
|
||||||
|
public virtual void Return(T obj)
|
||||||
|
{
|
||||||
|
if (obj == null) return;
|
||||||
|
|
||||||
|
// Call OnDespawn for IPoolable components
|
||||||
|
IPoolable poolable = obj.GetComponent<IPoolable>();
|
||||||
|
if (poolable != null)
|
||||||
|
{
|
||||||
|
poolable.OnDespawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add to pool if we're under the maximum size
|
||||||
|
if (pooledObjects.Count < maxPoolSize)
|
||||||
|
{
|
||||||
|
obj.gameObject.SetActive(false);
|
||||||
|
obj.transform.SetParent(transform);
|
||||||
|
pooledObjects.Push(obj);
|
||||||
|
totalReturned++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Destroy(obj.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs pool statistics
|
||||||
|
/// </summary>
|
||||||
|
public virtual void LogPoolStats()
|
||||||
|
{
|
||||||
|
Debug.Log($"[{GetType().Name}] Pooled objects: {pooledObjects.Count}/{maxPoolSize} (Created: {totalCreated}, Returned: {totalReturned})");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
private float _lastLogTime = 0f;
|
||||||
|
|
||||||
|
protected virtual void Update()
|
||||||
|
{
|
||||||
|
// Log pool stats every 5 seconds if in the editor
|
||||||
|
if (Time.time - _lastLogTime > 5f)
|
||||||
|
{
|
||||||
|
LogPoolStats();
|
||||||
|
_lastLogTime = Time.time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Scripts/Pooling/BaseObjectPool.cs.meta
Normal file
3
Assets/Scripts/Pooling/BaseObjectPool.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 693c1b68b3cd4ad695dce000d53b04ee
|
||||||
|
timeCreated: 1758019542
|
||||||
21
Assets/Scripts/Pooling/IPoolable.cs
Normal file
21
Assets/Scripts/Pooling/IPoolable.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
namespace Pooling
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for objects that can be pooled.
|
||||||
|
/// Implement this interface on any component that should be managed by an ObjectPool.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPoolable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the object is retrieved from the pool.
|
||||||
|
/// Use this to reset the object's state.
|
||||||
|
/// </summary>
|
||||||
|
void OnSpawn();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the object is returned to the pool.
|
||||||
|
/// Use this to clean up any resources or state.
|
||||||
|
/// </summary>
|
||||||
|
void OnDespawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Scripts/Pooling/IPoolable.cs.meta
Normal file
3
Assets/Scripts/Pooling/IPoolable.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 13d9095c12514097b9d5953c5b2a45a2
|
||||||
|
timeCreated: 1758019051
|
||||||
18
Assets/Scripts/Pooling/IPoolableWithReference.cs
Normal file
18
Assets/Scripts/Pooling/IPoolableWithReference.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Pooling
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for poolable objects that need to reference their parent pool.
|
||||||
|
/// Implement this interface on objects that need to return themselves to their pool.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of pool component</typeparam>
|
||||||
|
public interface IPoolableWithReference<T> : IPoolable where T : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the parent pool for this object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pool">The pool this object belongs to</param>
|
||||||
|
void SetPool(T pool);
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Scripts/Pooling/IPoolableWithReference.cs.meta
Normal file
3
Assets/Scripts/Pooling/IPoolableWithReference.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 99a99ac50049492195a73883d9129ea8
|
||||||
|
timeCreated: 1758019587
|
||||||
266
Assets/Scripts/Pooling/MultiPrefabPool.cs
Normal file
266
Assets/Scripts/Pooling/MultiPrefabPool.cs
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Pooling
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Object pool that supports multiple prefab types with usage tracking and intelligent trimming.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of component to pool</typeparam>
|
||||||
|
public abstract class MultiPrefabPool<T> : MonoBehaviour where T : Component
|
||||||
|
{
|
||||||
|
[Tooltip("Whether to pre-instantiate objects during initialization or create them on demand")]
|
||||||
|
public bool preInstantiatePrefabs = false;
|
||||||
|
|
||||||
|
[Tooltip("Initial number of objects to pre-instantiate per prefab (if preInstantiatePrefabs is true)")]
|
||||||
|
public int initialObjectsPerPrefab = 2;
|
||||||
|
|
||||||
|
[Tooltip("Maximum number of objects 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;
|
||||||
|
|
||||||
|
protected Dictionary<int, Stack<T>> pooledObjects = new Dictionary<int, Stack<T>>();
|
||||||
|
protected Dictionary<int, int> prefabUsageCount = new Dictionary<int, int>();
|
||||||
|
protected List<T> prefabs;
|
||||||
|
protected int totalPooledCount = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the pool with the specified prefabs
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="prefabsToPool">The list of prefabs to use for this pool</param>
|
||||||
|
public virtual void Initialize(List<T> prefabsToPool)
|
||||||
|
{
|
||||||
|
prefabs = prefabsToPool;
|
||||||
|
|
||||||
|
// Initialize usage tracking
|
||||||
|
for (int i = 0; i < prefabs.Count; i++)
|
||||||
|
{
|
||||||
|
prefabUsageCount[i] = 0;
|
||||||
|
pooledObjects[i] = new Stack<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-instantiate objects only if enabled
|
||||||
|
if (preInstantiatePrefabs)
|
||||||
|
{
|
||||||
|
// Calculate how many to pre-instantiate based on available pool size
|
||||||
|
int totalToCreate = Mathf.Min(totalMaxPoolSize, prefabs.Count * initialObjectsPerPrefab);
|
||||||
|
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;
|
||||||
|
CreateNew(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"[{GetType().Name}] Initialized with {prefabs.Count} prefab types");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance and adds it to the pool
|
||||||
|
/// </summary>
|
||||||
|
protected virtual T CreateNew(int prefabIndex)
|
||||||
|
{
|
||||||
|
if (prefabs == null || prefabIndex >= prefabs.Count)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[{GetType().Name}] Invalid prefab index or prefabs list is null!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
T prefab = prefabs[prefabIndex];
|
||||||
|
T obj = Instantiate(prefab, transform);
|
||||||
|
obj.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
// Initialize IPoolable components if present
|
||||||
|
IPoolable poolable = obj.GetComponent<IPoolable>();
|
||||||
|
if (poolable != null)
|
||||||
|
{
|
||||||
|
poolable.OnDespawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pooledObjects.ContainsKey(prefabIndex))
|
||||||
|
{
|
||||||
|
pooledObjects[prefabIndex] = new Stack<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
pooledObjects[prefabIndex].Push(obj);
|
||||||
|
totalPooledCount++;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an object from the pool for the specified prefab index, or creates a new one if the pool is empty
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="prefabIndex">The index of the prefab to get from the pool</param>
|
||||||
|
/// <returns>An object ready to use</returns>
|
||||||
|
public virtual T Get(int prefabIndex)
|
||||||
|
{
|
||||||
|
T obj;
|
||||||
|
|
||||||
|
// Track usage frequency
|
||||||
|
if (prefabUsageCount.ContainsKey(prefabIndex))
|
||||||
|
{
|
||||||
|
prefabUsageCount[prefabIndex]++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prefabUsageCount[prefabIndex] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pooledObjects.ContainsKey(prefabIndex) && pooledObjects[prefabIndex].Count > 0)
|
||||||
|
{
|
||||||
|
obj = pooledObjects[prefabIndex].Pop();
|
||||||
|
totalPooledCount--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create new object without adding to pool
|
||||||
|
T prefab = prefabs[prefabIndex];
|
||||||
|
obj = Instantiate(prefab, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.gameObject.SetActive(true);
|
||||||
|
|
||||||
|
// Call OnSpawn for IPoolable components
|
||||||
|
IPoolable poolable = obj.GetComponent<IPoolable>();
|
||||||
|
if (poolable != null)
|
||||||
|
{
|
||||||
|
poolable.OnSpawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an object to the pool
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object to return</param>
|
||||||
|
/// <param name="prefabIndex">The index of the prefab this object was created from</param>
|
||||||
|
public virtual void Return(T obj, int prefabIndex)
|
||||||
|
{
|
||||||
|
if (obj == null) return;
|
||||||
|
|
||||||
|
// Call OnDespawn for IPoolable components
|
||||||
|
IPoolable poolable = obj.GetComponent<IPoolable>();
|
||||||
|
if (poolable != null)
|
||||||
|
{
|
||||||
|
poolable.OnDespawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're under the maximum pool size for this prefab type
|
||||||
|
bool keepObject = totalPooledCount < totalMaxPoolSize;
|
||||||
|
|
||||||
|
// Additional constraint: don't keep too many of any single prefab type
|
||||||
|
if (pooledObjects.ContainsKey(prefabIndex) &&
|
||||||
|
pooledObjects[prefabIndex].Count >= maxPerPrefabPoolSize)
|
||||||
|
{
|
||||||
|
keepObject = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keepObject)
|
||||||
|
{
|
||||||
|
obj.gameObject.SetActive(false);
|
||||||
|
obj.transform.SetParent(transform);
|
||||||
|
|
||||||
|
if (!pooledObjects.ContainsKey(prefabIndex))
|
||||||
|
{
|
||||||
|
pooledObjects[prefabIndex] = new Stack<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
pooledObjects[prefabIndex].Push(obj);
|
||||||
|
totalPooledCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Destroy(obj.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trims the pool to remove excess objects
|
||||||
|
/// Can be called periodically or when memory pressure is high
|
||||||
|
/// </summary>
|
||||||
|
public virtual 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<KeyValuePair<int, int>> sortedUsage = new List<KeyValuePair<int, int>>(prefabUsageCount);
|
||||||
|
sortedUsage.Sort((a, b) => a.Value.CompareTo(b.Value));
|
||||||
|
|
||||||
|
// Remove objects from least used prefabs first
|
||||||
|
foreach (var usage in sortedUsage)
|
||||||
|
{
|
||||||
|
int prefabIndex = usage.Key;
|
||||||
|
if (!pooledObjects.ContainsKey(prefabIndex) || pooledObjects[prefabIndex].Count == 0) continue;
|
||||||
|
|
||||||
|
// How many to remove from this prefab type
|
||||||
|
int toRemove = Mathf.Min(pooledObjects[prefabIndex].Count, excessCount);
|
||||||
|
|
||||||
|
for (int i = 0; i < toRemove; i++)
|
||||||
|
{
|
||||||
|
if (pooledObjects[prefabIndex].Count == 0) break;
|
||||||
|
|
||||||
|
T obj = pooledObjects[prefabIndex].Pop();
|
||||||
|
Destroy(obj.gameObject);
|
||||||
|
totalPooledCount--;
|
||||||
|
excessCount--;
|
||||||
|
|
||||||
|
if (excessCount <= 0) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs pool statistics to the console
|
||||||
|
/// </summary>
|
||||||
|
public virtual void LogPoolStats()
|
||||||
|
{
|
||||||
|
Debug.Log($"[{GetType().Name}] Total pooled objects: {totalPooledCount}/{totalMaxPoolSize}");
|
||||||
|
|
||||||
|
string prefabDetails = "";
|
||||||
|
int index = 0;
|
||||||
|
foreach (var entry in pooledObjects)
|
||||||
|
{
|
||||||
|
int prefabIndex = entry.Key;
|
||||||
|
int count = entry.Value.Count;
|
||||||
|
int usageCount = prefabUsageCount.ContainsKey(prefabIndex) ? prefabUsageCount[prefabIndex] : 0;
|
||||||
|
|
||||||
|
string prefabName = prefabIndex < prefabs.Count ? prefabs[prefabIndex].name : "Unknown";
|
||||||
|
prefabDetails += $"\n - {prefabName}: {count} pooled, {usageCount} usages";
|
||||||
|
|
||||||
|
// Limit the output to avoid too much text
|
||||||
|
if (++index >= 10 && pooledObjects.Count > 10)
|
||||||
|
{
|
||||||
|
prefabDetails += $"\n - ...and {pooledObjects.Count - 10} more prefab types";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"[{GetType().Name}] Pool details:{prefabDetails}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
private float _lastLogTime = 0f;
|
||||||
|
|
||||||
|
protected virtual void Update()
|
||||||
|
{
|
||||||
|
// Log pool stats every 5 seconds if in the editor
|
||||||
|
if (Time.time - _lastLogTime > 5f)
|
||||||
|
{
|
||||||
|
LogPoolStats();
|
||||||
|
_lastLogTime = Time.time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Scripts/Pooling/MultiPrefabPool.cs.meta
Normal file
3
Assets/Scripts/Pooling/MultiPrefabPool.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 344a70ff5291463bbd636705705ace79
|
||||||
|
timeCreated: 1758019578
|
||||||
19
Assets/Scripts/Tile.cs
Normal file
19
Assets/Scripts/Tile.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A simple marker component to identify game objects as tiles.
|
||||||
|
/// This allows the pool system to specifically track and manage tile objects.
|
||||||
|
/// </summary>
|
||||||
|
public class Tile : MonoBehaviour
|
||||||
|
{
|
||||||
|
// This is primarily a marker component, but we could add tile-specific properties here if needed
|
||||||
|
|
||||||
|
// Optional: Add properties that might be useful for all tiles
|
||||||
|
[SerializeField] private int tileIndex;
|
||||||
|
|
||||||
|
public int TileIndex
|
||||||
|
{
|
||||||
|
get => tileIndex;
|
||||||
|
set => tileIndex = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Assets/Scripts/Tile.cs.meta
Normal file
3
Assets/Scripts/Tile.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 41def183b6714aca97663d74cc2d0678
|
||||||
|
timeCreated: 1758027131
|
||||||
Reference in New Issue
Block a user