using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.Serialization; namespace Minigames.DivingForPictures { /// /// Spawns and manages trench wall tiles for the endless descender minigame. /// public class TrenchTileSpawner : MonoBehaviour { [Header("Tile Prefabs")] [Tooltip("List of possible trench tile prefabs.")] public List tilePrefabs; [Header("Tile Settings")] private const float TileHeight = 5f; // Set to match prefab height public int initialTileCount = 3; public float tileSpawnBuffer = 1f; [Header("Movement Settings")] public float moveSpeed = 3f; public float speedUpFactor = 0.2f; public float speedUpInterval = 10f; [FormerlySerializedAs("OnTileSpawned")] [Header("Events")] public UnityEvent onTileSpawned; [FormerlySerializedAs("OnTileDestroyed")] public UnityEvent onTileDestroyed; private readonly List _activeTiles = new List(); private readonly Dictionary _tileLastUsed = new Dictionary(); private int _spawnCounter; private float _speedUpTimer; private Camera _mainCamera; private float _screenBottom; private float _screenTop; void Start() { _mainCamera = Camera.main; CalculateScreenBounds(); for (int i = 0; i < initialTileCount; i++) { SpawnTileAtY(_screenBottom + i * TileHeight); } } 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]; if (topTile.transform.position.y - TileHeight / 2 > _screenTop + tileSpawnBuffer) { _activeTiles.RemoveAt(0); onTileDestroyed?.Invoke(topTile); Destroy(topTile); } } private void HandleTileSpawning() { if (_activeTiles.Count == 0) return; GameObject bottomTile = _activeTiles[^1]; 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 += speedUpFactor; _speedUpTimer = 0f; } } private void SpawnTileAtY(float y) { int prefabIndex = GetWeightedRandomTileIndex(); GameObject prefab = tilePrefabs[prefabIndex]; // Use the prefab's original rotation GameObject tile = Instantiate(prefab, new Vector3(0f, y, 0f), prefab.transform.rotation, transform); _activeTiles.Add(tile); _tileLastUsed[prefabIndex] = _spawnCounter++; onTileSpawned?.Invoke(tile); } private int GetWeightedRandomTileIndex() { // Weight tiles not used recently higher int n = tilePrefabs.Count; List weights = new List(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 } #if UNITY_EDITOR void OnDrawGizmosSelected() { // Draw tile bounds for debugging Gizmos.color = Color.cyan; for (int i = 0; i < initialTileCount; i++) { Vector3 center = new Vector3(0f, _screenBottom + i * TileHeight, 0f); Gizmos.DrawWireCube(center, new Vector3(10f, TileHeight, 1f)); } } #endif } }