Files
AppleHillsProduction/Assets/Scripts/Minigames/DivingForPictures/TrenchTileSpawner.cs
2025-09-16 15:02:57 +02:00

312 lines
11 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Spawns and manages trench wall tiles for the endless descender minigame.
/// </summary>
public class TrenchTileSpawner : MonoBehaviour
{
[Header("Tile Prefabs")]
[Tooltip("List of possible trench tile prefabs.")]
public List<GameObject> tilePrefabs;
[Header("Tile Settings")]
private Dictionary<GameObject, float> _tileHeights = new Dictionary<GameObject, float>();
public int initialTileCount = 3;
public 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
[Header("Object Pooling")]
public bool useObjectPooling = true;
public bool preInstantiateTiles = false; // Added option to control pre-instantiation
public int initialTilesPerPrefab = 2;
public int maxPerPrefabPoolSize = 2;
public int totalMaxPoolSize = 10; // Total pool size across all prefab types
[FormerlySerializedAs("OnTileSpawned")] [Header("Events")]
public UnityEvent<GameObject> onTileSpawned;
[FormerlySerializedAs("OnTileDestroyed")]
public UnityEvent<GameObject> onTileDestroyed;
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
void Awake()
{
_mainCamera = Camera.main;
// Calculate tile heights for each prefab
foreach (var prefab in tilePrefabs)
{
Renderer renderer = prefab.GetComponentInChildren<Renderer>();
if (renderer != null)
{
_tileHeights[prefab] = renderer.bounds.size.y;
}
else
{
// Fallback in case no renderer is found
_tileHeights[prefab] = 5f;
Debug.LogWarning($"No renderer found in prefab {prefab.name}. Using default height of 5.");
}
}
if (useObjectPooling)
{
// Create the tile pool
GameObject poolGO = new GameObject("TrenchTilePool");
poolGO.transform.SetParent(transform);
_tilePool = poolGO.AddComponent<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()
{
CalculateScreenBounds();
for (int i = 0; i < initialTileCount; i++)
{
float y = _screenBottom;
// Calculate proper Y position based on previous tiles
if (i > 0 && _activeTiles.Count > 0)
{
GameObject prevTile = _activeTiles[_activeTiles.Count - 1];
float prevHeight = GetTileHeight(prevTile);
y = prevTile.transform.position.y - prevHeight;
}
SpawnTileAtY(y);
}
}
void Update()
{
MoveTiles();
HandleTileDestruction();
HandleTileSpawning();
HandleSpeedRamping();
}
private void CalculateScreenBounds()
{
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;
}
private void MoveTiles()
{
float moveDelta = moveSpeed * Time.deltaTime;
foreach (var tile in _activeTiles)
{
if (tile != null)
{
tile.transform.position += Vector3.up * moveDelta;
}
}
}
private void HandleTileDestruction()
{
if (_activeTiles.Count == 0) return;
GameObject topTile = _activeTiles[0];
float tileHeight = GetTileHeight(topTile);
if (topTile.transform.position.y - tileHeight / 2 > _screenTop + tileSpawnBuffer)
{
_activeTiles.RemoveAt(0);
onTileDestroyed?.Invoke(topTile);
if (useObjectPooling && _tilePool != null)
{
// Find the prefab index for this tile
int prefabIndex = GetPrefabIndex(topTile);
if (prefabIndex >= 0)
{
_tilePool.ReturnTile(topTile, prefabIndex);
}
else
{
Destroy(topTile);
}
}
else
{
Destroy(topTile);
}
}
}
private void HandleTileSpawning()
{
if (_activeTiles.Count == 0) return;
GameObject bottomTile = _activeTiles[^1];
float tileHeight = GetTileHeight(bottomTile);
float bottomEdge = bottomTile.transform.position.y - tileHeight / 2;
if (bottomEdge > _screenBottom - tileSpawnBuffer)
{
float newY = bottomTile.transform.position.y - tileHeight;
SpawnTileAtY(newY);
}
}
private void HandleSpeedRamping()
{
_speedUpTimer += Time.deltaTime;
if (_speedUpTimer >= speedUpInterval)
{
moveSpeed = Mathf.Min(moveSpeed + speedUpFactor, maxMoveSpeed);
_speedUpTimer = 0f;
}
}
private void SpawnTileAtY(float y)
{
int prefabIndex = GetWeightedRandomTileIndex();
GameObject prefab = tilePrefabs[prefabIndex];
GameObject tile;
if (useObjectPooling && _tilePool != null)
{
tile = _tilePool.GetTile(prefabIndex);
tile.transform.position = new Vector3(0f, y, TileSpawnZ);
tile.transform.rotation = prefab.transform.rotation;
tile.transform.SetParent(transform);
}
else
{
// Use the prefab's original rotation
tile = Instantiate(prefab, new Vector3(0f, y, TileSpawnZ), prefab.transform.rotation, transform);
}
_activeTiles.Add(tile);
_tileLastUsed[prefabIndex] = _spawnCounter++;
onTileSpawned?.Invoke(tile);
}
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 lastUsed = _tileLastUsed.TryGetValue(i, out var value) ? value : -n;
int age = _spawnCounter - lastUsed;
float weight = Mathf.Clamp(age, 1, n * 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++)
{
if (r < weights[i]) return i;
r -= weights[i];
}
return Random.Range(0, n); // fallback
}
/// <summary>
/// Gets the height of a tile based on its prefab or renderer bounds
/// </summary>
/// <param name="tile">The tile to measure</param>
/// <returns>The height of the tile</returns>
private float GetTileHeight(GameObject tile)
{
// Check if this is a known prefab
foreach (var prefab in tilePrefabs)
{
// Check if this tile was created from this prefab
if (tile.name.StartsWith(prefab.name))
{
if (_tileHeights.TryGetValue(prefab, out float height))
{
return height;
}
}
}
// If not found, calculate it from the renderer
Renderer renderer = tile.GetComponentInChildren<Renderer>();
if (renderer != null)
{
return renderer.bounds.size.y;
}
// Fallback
return 5f;
}
/// <summary>
/// Gets the index of the prefab that was used to create this tile
/// </summary>
/// <param name="tile">The tile to check</param>
/// <returns>The index of the prefab or -1 if not found</returns>
private int GetPrefabIndex(GameObject tile)
{
for (int i = 0; i < tilePrefabs.Count; i++)
{
if (tile.name.StartsWith(tilePrefabs[i].name))
{
return i;
}
}
return -1;
}
/// <summary>
/// Called periodically to trim excess pooled tiles
/// </summary>
private void TrimExcessPooledTiles()
{
if (_tilePool != null)
{
_tilePool.TrimExcess();
}
}
#if UNITY_EDITOR
void OnDrawGizmosSelected()
{
// 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))
{
height = h;
}
Vector3 center = new Vector3(0f, _screenBottom + i * height, 0f);
Gizmos.DrawWireCube(center, new Vector3(10f, height, 1f));
}
}
#endif
}
}