Working generic object pooling, pool monitor editor tool and batch component adder editor tool

This commit is contained in:
Michal Pikulski
2025-09-16 15:02:50 +02:00
parent bcc6f05058
commit 75be338065
26 changed files with 1393 additions and 469 deletions

View File

@@ -1,11 +1,12 @@
using UnityEngine;
using Pooling;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Represents a single bubble, handling its movement, wobble effect, scaling, and sprite assignment.
/// </summary>
public class Bubble : MonoBehaviour
public class Bubble : MonoBehaviour, IPoolableWithReference<BubblePool>
{
public float speed = 1f;
public float wobbleSpeed = 1f;
@@ -87,6 +88,22 @@ namespace Minigames.DivingForPictures
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>
/// Sets the main sprite for the bubble.
/// </summary>

View File

@@ -1,82 +1,23 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine;
using Pooling;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Manages a pool of bubble objects to reduce garbage collection overhead.
/// </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>
/// Gets a bubble from the pool, or creates a new one if the pool is empty
/// </summary>
/// <returns>A bubble instance ready to use</returns>
public Bubble GetBubble()
{
Bubble bubble;
Bubble bubble = Get();
if (pooledBubbles.Count > 0)
{
bubble = pooledBubbles.Pop();
}
else
{
bubble = CreateNewBubble();
}
// Ensure the bubble has a reference to this pool
// Set reference to this pool so the bubble can return itself
bubble.SetPool(this);
bubble.gameObject.SetActive(true);
bubble.ResetState();
return bubble;
}
@@ -87,31 +28,15 @@ namespace Minigames.DivingForPictures
/// <param name="bubble">The bubble to return to the pool</param>
public void ReturnBubble(Bubble bubble)
{
if (bubble == null) return;
// Only add to pool if we're under the maximum size
if (pooledBubbles.Count < maxPoolSize)
{
// Deactivate and reparent
bubble.gameObject.SetActive(false);
bubble.transform.SetParent(transform);
// Add to pool
pooledBubbles.Push(bubble);
totalBubblesReturned++;
}
else
{
Destroy(bubble.gameObject);
}
Return(bubble);
}
/// <summary>
/// Logs pool statistics
/// </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})");
}
}
}

View File

@@ -1,4 +1,5 @@
using UnityEngine;
using Pooling;
namespace Minigames.DivingForPictures
{
@@ -43,6 +44,11 @@ namespace Minigames.DivingForPictures
_bubblePool.initialPoolSize = initialPoolSize;
_bubblePool.maxPoolSize = maxPoolSize;
_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
bubble.SetWobbleScaleLimits(wobbleMinScale, wobbleMaxScale);
}
/// <summary>
/// Logs the current pool statistics for debugging
/// </summary>
private void LogPoolStats()
{
if (_bubblePool != null)
{
_bubblePool.LogPoolStats();
}
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using UnityEngine;
using Pooling;
namespace Minigames.DivingForPictures
{
@@ -7,238 +8,38 @@ namespace Minigames.DivingForPictures
/// Manages a pool of trench tile objects to reduce garbage collection overhead.
/// Optimized for handling a large number of different prefab types.
/// </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>
/// Initialize the pool with the tile prefabs
/// Returns a tile to the pool
/// </summary>
/// <param name="prefabs">List of tile prefabs to use</param>
public void Initialize(List<GameObject> prefabs)
/// <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)
{
tilePrefabs = prefabs;
// Initialize usage tracking
for (int i = 0; i < prefabs.Count; i++)
if (tile != null)
{
prefabUsageCount[i] = 0;
pooledTiles[i] = new Stack<GameObject>();
}
// 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++)
Tile tileComponent = tile.GetComponent<Tile>();
if (tileComponent != null)
{
for (int j = 0; j < perPrefab; j++)
{
if (totalPooledCount >= totalMaxPoolSize) break;
CreateNewTile(i);
}
Return(tileComponent, prefabIndex);
}
else
{
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>
/// Gets a tile from the pool, or creates a new one if the pool is empty
/// </summary>
/// <returns>A tile instance ready to use</returns>
public GameObject GetTile(int prefabIndex)
{
GameObject tile;
// Track usage frequency
if (prefabUsageCount.ContainsKey(prefabIndex))
{
prefabUsageCount[prefabIndex]++;
}
else
{
prefabUsageCount[prefabIndex] = 1;
}
if (pooledTiles.ContainsKey(prefabIndex) && pooledTiles[prefabIndex].Count > 0)
{
tile = pooledTiles[prefabIndex].Pop();
totalPooledCount--;
}
else
{
// Create new tile without adding to pool
GameObject prefab = tilePrefabs[prefabIndex];
tile = Instantiate(prefab, transform);
}
tile.SetActive(true);
return tile;
Tile tileComponent = Get(prefabIndex);
return tileComponent.gameObject;
}
/// <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
}
}

View File

@@ -2,6 +2,7 @@
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
using Pooling;
namespace Minigames.DivingForPictures
{
@@ -12,50 +13,105 @@ namespace Minigames.DivingForPictures
{
[Header("Tile Prefabs")]
[Tooltip("List of possible trench tile prefabs.")]
public List<GameObject> tilePrefabs;
[SerializeField] private List<GameObject> tilePrefabs;
[Header("Tile Settings")]
private Dictionary<GameObject, float> _tileHeights = new Dictionary<GameObject, float>();
public int initialTileCount = 3;
public float tileSpawnBuffer = 1f;
[SerializeField] private int initialTileCount = 3;
[SerializeField] private float tileSpawnBuffer = 1f;
[Header("Movement Settings")]
public float moveSpeed = 3f;
public float speedUpFactor = 0.2f;
public float speedUpInterval = 10f;
public float maxMoveSpeed = 12f; // Added a cap to the movement speed
[SerializeField] private float moveSpeed = 3f;
[SerializeField] private float speedUpFactor = 0.2f;
[SerializeField] private float speedUpInterval = 10f;
[SerializeField] private float maxMoveSpeed = 12f;
[Header("Object Pooling")]
public bool useObjectPooling = true;
public bool preInstantiateTiles = false; // Added option to control pre-instantiation
public int initialTilesPerPrefab = 2;
public int maxPerPrefabPoolSize = 2;
public int totalMaxPoolSize = 10; // Total pool size across all prefab types
[SerializeField] private bool useObjectPooling = true;
[SerializeField] private int maxPerPrefabPoolSize = 2;
[SerializeField] private int totalMaxPoolSize = 10;
[FormerlySerializedAs("OnTileSpawned")] [Header("Events")]
[Header("Events")]
[FormerlySerializedAs("OnTileSpawned")]
public UnityEvent<GameObject> onTileSpawned;
[FormerlySerializedAs("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 Dictionary<int, int> _tileLastUsed = new Dictionary<int, int>();
private int _spawnCounter;
private float _speedUpTimer;
private Camera _mainCamera;
private float _screenBottom;
private float _screenTop;
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;
// 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)
{
if (prefab == null)
{
Debug.LogError("Null prefab found in tilePrefabs list!");
continue;
}
Renderer renderer = prefab.GetComponentInChildren<Renderer>();
if (renderer != null)
{
@@ -64,31 +120,56 @@ namespace Minigames.DivingForPictures
else
{
// Fallback in case no renderer is found
_tileHeights[prefab] = 5f;
Debug.LogWarning($"No renderer found in prefab {prefab.name}. Using default height of 5.");
_tileHeights[prefab] = DefaultTileHeight;
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++)
{
float y = _screenBottom;
@@ -103,22 +184,30 @@ namespace Minigames.DivingForPictures
}
}
void Update()
{
MoveTiles();
HandleTileDestruction();
HandleTileSpawning();
HandleSpeedRamping();
}
/// <summary>
/// Calculate the screen bounds in world space
/// </summary>
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 top = _mainCamera.ViewportToWorldPoint(new Vector3(0.5f, 1f, _mainCamera.nearClipPlane));
_screenBottom = bottom.y;
_screenTop = top.y;
}
/// <summary>
/// Move all active tiles upward
/// </summary>
private void MoveTiles()
{
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()
{
if (_activeTiles.Count == 0) return;
GameObject topTile = _activeTiles[0];
if (topTile == null)
{
_activeTiles.RemoveAt(0);
return;
}
float tileHeight = GetTileHeight(topTile);
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()
{
if (_activeTiles.Count == 0) return;
GameObject bottomTile = _activeTiles[^1];
if (bottomTile == null)
{
_activeTiles.RemoveAt(_activeTiles.Count - 1);
return;
}
float tileHeight = GetTileHeight(bottomTile);
float bottomEdge = bottomTile.transform.position.y - tileHeight / 2;
if (bottomEdge > _screenBottom - tileSpawnBuffer)
@@ -174,6 +283,9 @@ namespace Minigames.DivingForPictures
}
}
/// <summary>
/// Handle increasing the movement speed over time
/// </summary>
private void HandleSpeedRamping()
{
_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)
{
if (tilePrefabs == null || tilePrefabs.Count == 0)
{
Debug.LogError("No tile prefabs available for spawning!");
return;
}
int prefabIndex = GetWeightedRandomTileIndex();
GameObject prefab = tilePrefabs[prefabIndex];
if (prefab == null)
{
Debug.LogError($"Tile prefab at index {prefabIndex} is null!");
return;
}
GameObject tile;
if (useObjectPooling && _tilePool != null)
{
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.rotation = prefab.transform.rotation;
tile.transform.SetParent(transform);
@@ -208,27 +342,40 @@ namespace Minigames.DivingForPictures
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()
{
// Weight tiles not used recently higher
int n = tilePrefabs.Count;
List<float> weights = new List<float>(n);
for (int i = 0; i < n; i++)
int prefabCount = tilePrefabs.Count;
List<float> weights = new List<float>(prefabCount);
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;
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);
}
float total = 0f;
foreach (var w in weights) total += w;
float r = Random.value * total;
for (int i = 0; i < n; i++)
float totalWeight = 0f;
foreach (var weight in weights)
{
if (r < weights[i]) return i;
r -= weights[i];
totalWeight += weight;
}
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>
@@ -238,9 +385,17 @@ namespace Minigames.DivingForPictures
/// <returns>The height of the tile</returns>
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
foreach (var prefab in tilePrefabs)
{
if (prefab == null) continue;
// Check if this tile was created from this prefab
if (tile.name.StartsWith(prefab.name))
{
@@ -259,7 +414,7 @@ namespace Minigames.DivingForPictures
}
// Fallback
return 5f;
return DefaultTileHeight;
}
/// <summary>
@@ -269,8 +424,15 @@ namespace Minigames.DivingForPictures
/// <returns>The index of the prefab or -1 if not found</returns>
private int GetPrefabIndex(GameObject tile)
{
if (tile == null || tilePrefabs == null)
{
return -1;
}
for (int i = 0; i < tilePrefabs.Count; i++)
{
if (tilePrefabs[i] == null) continue;
if (tile.name.StartsWith(tilePrefabs[i].name))
{
return i;
@@ -291,14 +453,26 @@ namespace Minigames.DivingForPictures
}
#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
Gizmos.color = Color.cyan;
for (int i = 0; i < initialTileCount; i++)
{
float height = 5f;
if (tilePrefabs.Count > 0 && _tileHeights.TryGetValue(tilePrefabs[0], out float h))
float height = DefaultTileHeight;
if (tilePrefabs != null && tilePrefabs.Count > 0 && tilePrefabs[0] != null &&
_tileHeights.TryGetValue(tilePrefabs[0], out float h))
{
height = h;
}