2025-09-22 12:16:32 +00:00
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
2025-09-10 12:35:22 +02:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.Events;
|
|
|
|
|
|
using UnityEngine.Serialization;
|
2025-09-16 15:02:50 +02:00
|
|
|
|
using Pooling;
|
2025-09-24 13:33:43 +00:00
|
|
|
|
using AppleHills.Core.Settings;
|
2025-10-07 09:42:59 +02:00
|
|
|
|
using Utils;
|
2025-10-08 12:36:08 +02:00
|
|
|
|
using AppleHills.Core.Interfaces;
|
2025-09-10 12:35:22 +02:00
|
|
|
|
|
|
|
|
|
|
namespace Minigames.DivingForPictures
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Spawns and manages trench wall tiles for the endless descender minigame.
|
|
|
|
|
|
/// </summary>
|
2025-10-08 12:36:08 +02:00
|
|
|
|
public class TrenchTileSpawner : MonoBehaviour, IPausable
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
|
|
|
|
|
[Header("Tile Prefabs")]
|
|
|
|
|
|
[Tooltip("List of possible trench tile prefabs.")]
|
2025-09-16 15:02:50 +02:00
|
|
|
|
[SerializeField] private List<GameObject> tilePrefabs;
|
2025-09-10 12:35:22 +02:00
|
|
|
|
|
2025-10-10 14:45:23 +02:00
|
|
|
|
[Header("Initial Tile")]
|
|
|
|
|
|
[Tooltip("Prefab for the initial trench tile. This will always be spawned first.")]
|
|
|
|
|
|
[SerializeField] private GameObject initialTilePrefab;
|
|
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
[Header("Events")]
|
|
|
|
|
|
[FormerlySerializedAs("OnTileSpawned")]
|
2025-09-10 12:35:22 +02:00
|
|
|
|
public UnityEvent<GameObject> onTileSpawned;
|
2025-09-16 15:02:50 +02:00
|
|
|
|
|
2025-09-10 12:35:22 +02:00
|
|
|
|
[FormerlySerializedAs("OnTileDestroyed")]
|
|
|
|
|
|
public UnityEvent<GameObject> onTileDestroyed;
|
|
|
|
|
|
|
2025-09-24 13:33:43 +00:00
|
|
|
|
// Settings references
|
|
|
|
|
|
private IDivingMinigameSettings _settings;
|
|
|
|
|
|
private DivingDeveloperSettings _devSettings;
|
|
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
// Private fields
|
|
|
|
|
|
private readonly Dictionary<GameObject, float> _tileHeights = new Dictionary<GameObject, float>();
|
2025-09-10 12:35:22 +02:00
|
|
|
|
private readonly List<GameObject> _activeTiles = new List<GameObject>();
|
|
|
|
|
|
private readonly Dictionary<int, int> _tileLastUsed = new Dictionary<int, int>();
|
2025-09-16 15:02:50 +02:00
|
|
|
|
|
2025-09-10 12:35:22 +02:00
|
|
|
|
private int _spawnCounter;
|
|
|
|
|
|
private float _speedUpTimer;
|
2025-10-10 14:31:51 +02:00
|
|
|
|
private UnityEngine.Camera _mainCamera;
|
2025-09-10 12:35:22 +02:00
|
|
|
|
private float _screenBottom;
|
|
|
|
|
|
private float _screenTop;
|
2025-09-16 12:23:25 +02:00
|
|
|
|
private TrenchTilePool _tilePool;
|
2025-09-22 12:16:32 +00:00
|
|
|
|
|
|
|
|
|
|
// Current velocity for tile movement
|
|
|
|
|
|
private float _currentVelocity;
|
2025-09-10 12:35:22 +02:00
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
private const float TileSpawnZ = -1f;
|
|
|
|
|
|
private const float DefaultTileHeight = 5f;
|
2025-09-16 12:23:25 +02:00
|
|
|
|
|
2025-09-22 12:16:32 +00:00
|
|
|
|
// Direction state
|
|
|
|
|
|
private bool _isSurfacing = false;
|
|
|
|
|
|
private bool _stopSpawning = false;
|
|
|
|
|
|
|
|
|
|
|
|
// Event triggered when the last tile leaves the screen after stopping spawning
|
|
|
|
|
|
public UnityEvent onLastTileLeft = new UnityEvent();
|
|
|
|
|
|
|
|
|
|
|
|
// Velocity management
|
|
|
|
|
|
private float _baseMoveSpeed;
|
|
|
|
|
|
private float _velocityFactor = 1.0f;
|
2025-10-07 09:28:41 +02:00
|
|
|
|
|
|
|
|
|
|
// Coroutine references
|
|
|
|
|
|
private Coroutine _movementCoroutine;
|
|
|
|
|
|
private Coroutine _tileDestructionCoroutine;
|
|
|
|
|
|
private Coroutine _tileSpawningCoroutine;
|
|
|
|
|
|
private Coroutine _speedRampingCoroutine;
|
|
|
|
|
|
|
|
|
|
|
|
// Screen normalization
|
|
|
|
|
|
private float _screenNormalizationFactor = 1.0f;
|
2025-09-22 12:16:32 +00:00
|
|
|
|
|
2025-10-10 14:45:23 +02:00
|
|
|
|
// Tracks if a floating area in the middle is currently active
|
|
|
|
|
|
private bool isFloatingAreaActive = false;
|
|
|
|
|
|
|
|
|
|
|
|
// Current depth of the trench
|
|
|
|
|
|
private int _currentDepth = 0;
|
|
|
|
|
|
|
|
|
|
|
|
[System.Serializable]
|
|
|
|
|
|
public struct DepthDifficultyRange
|
|
|
|
|
|
{
|
|
|
|
|
|
public int minDepth;
|
|
|
|
|
|
public int maxDepth;
|
|
|
|
|
|
public int difficulty;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Header("Difficulty Depths")]
|
|
|
|
|
|
[Tooltip("Configure depth ranges and their corresponding difficulty levels.")]
|
|
|
|
|
|
[SerializeField] private List<DepthDifficultyRange> depthDifficultyRanges = new List<DepthDifficultyRange>
|
|
|
|
|
|
{
|
|
|
|
|
|
new DepthDifficultyRange { minDepth = 0, maxDepth = 10, difficulty = 1 },
|
|
|
|
|
|
new DepthDifficultyRange { minDepth = 11, maxDepth = 20, difficulty = 2 },
|
|
|
|
|
|
new DepthDifficultyRange { minDepth = 21, maxDepth = 30, difficulty = 3 },
|
|
|
|
|
|
new DepthDifficultyRange { minDepth = 31, maxDepth = 40, difficulty = 4 },
|
|
|
|
|
|
new DepthDifficultyRange { minDepth = 41, maxDepth = int.MaxValue, difficulty = 5 }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
public int CurrentDifficulty
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var range in depthDifficultyRanges)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_currentDepth >= range.minDepth && _currentDepth <= range.maxDepth)
|
|
|
|
|
|
return range.difficulty;
|
|
|
|
|
|
}
|
|
|
|
|
|
return 1; // Default fallback
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-08 12:36:08 +02:00
|
|
|
|
// Pause state
|
|
|
|
|
|
private bool _isPaused = false;
|
|
|
|
|
|
|
|
|
|
|
|
// IPausable implementation
|
|
|
|
|
|
public bool IsPaused => _isPaused;
|
|
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
private void Awake()
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
2025-10-10 14:31:51 +02:00
|
|
|
|
_mainCamera = UnityEngine.Camera.main;
|
2025-09-24 13:33:43 +00:00
|
|
|
|
|
|
|
|
|
|
// Get settings from GameManager
|
|
|
|
|
|
_settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
|
|
|
|
|
|
_devSettings = GameManager.GetDeveloperSettings<DivingDeveloperSettings>();
|
|
|
|
|
|
|
|
|
|
|
|
if (_settings == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("[TrenchTileSpawner] Failed to load diving minigame settings!");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_devSettings == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("[TrenchTileSpawner] Failed to load diving developer settings!");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 09:28:41 +02:00
|
|
|
|
_baseMoveSpeed = _settings?.NormalizedMoveSpeed ?? 3f; // Store the original base speed
|
2025-09-16 12:23:25 +02:00
|
|
|
|
|
|
|
|
|
|
// Calculate tile heights for each prefab
|
2025-09-16 15:02:50 +02:00
|
|
|
|
CalculateTileHeights();
|
|
|
|
|
|
|
|
|
|
|
|
// Ensure all prefabs have Tile components
|
|
|
|
|
|
ValidateTilePrefabs();
|
|
|
|
|
|
|
2025-09-24 13:33:43 +00:00
|
|
|
|
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling)
|
2025-09-16 15:02:50 +02:00
|
|
|
|
{
|
|
|
|
|
|
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()
|
2025-09-30 13:13:37 +02:00
|
|
|
|
{
|
2025-10-10 14:31:51 +02:00
|
|
|
|
DivingGameManager.Instance.OnGameInitialized += Initialize;
|
|
|
|
|
|
|
|
|
|
|
|
// Register with the DivingGameManager for pause/resume events
|
|
|
|
|
|
DivingGameManager.Instance.RegisterPausableComponent(this);
|
|
|
|
|
|
|
|
|
|
|
|
// If game is already initialized, initialize immediately
|
|
|
|
|
|
if (DivingGameManager.Instance.GetType().GetField("_isGameInitialized",
|
|
|
|
|
|
System.Reflection.BindingFlags.NonPublic |
|
|
|
|
|
|
System.Reflection.BindingFlags.Instance)?.GetValue(DivingGameManager.Instance) is bool isInitialized && isInitialized)
|
2025-09-30 13:13:37 +02:00
|
|
|
|
{
|
|
|
|
|
|
Initialize();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-08 12:36:08 +02:00
|
|
|
|
private void OnDestroy()
|
|
|
|
|
|
{
|
2025-10-10 14:31:51 +02:00
|
|
|
|
DivingGameManager.Instance.UnregisterPausableComponent(this);
|
2025-10-08 12:36:08 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Pauses the spawner and all associated processes
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void Pause()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_isPaused) return; // Already paused
|
|
|
|
|
|
|
|
|
|
|
|
_isPaused = true;
|
|
|
|
|
|
|
|
|
|
|
|
// Stop all active coroutines but save their references
|
|
|
|
|
|
if (_movementCoroutine != null)
|
2025-09-30 13:13:37 +02:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
StopCoroutine(_movementCoroutine);
|
|
|
|
|
|
_movementCoroutine = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_tileDestructionCoroutine != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
StopCoroutine(_tileDestructionCoroutine);
|
|
|
|
|
|
_tileDestructionCoroutine = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_tileSpawningCoroutine != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
StopCoroutine(_tileSpawningCoroutine);
|
|
|
|
|
|
_tileSpawningCoroutine = null;
|
2025-09-30 13:13:37 +02:00
|
|
|
|
}
|
2025-10-08 12:36:08 +02:00
|
|
|
|
|
|
|
|
|
|
if (_speedRampingCoroutine != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
StopCoroutine(_speedRampingCoroutine);
|
|
|
|
|
|
_speedRampingCoroutine = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log("[TrenchTileSpawner] Paused");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Resumes the spawner and all associated processes
|
|
|
|
|
|
/// </summary>
|
2025-10-10 14:31:51 +02:00
|
|
|
|
public void DoResume()
|
2025-10-08 12:36:08 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (!_isPaused) return; // Already running
|
|
|
|
|
|
|
|
|
|
|
|
_isPaused = false;
|
|
|
|
|
|
|
|
|
|
|
|
// Restart all necessary coroutines
|
|
|
|
|
|
StartMovementCoroutine();
|
|
|
|
|
|
StartTileDestructionCoroutine();
|
|
|
|
|
|
StartTileSpawningCoroutine();
|
|
|
|
|
|
StartSpeedRampingCoroutine();
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log("[TrenchTileSpawner] Resumed");
|
2025-09-30 13:13:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initializes the tile spawner when triggered by DivingGameManager
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void Initialize()
|
2025-09-16 15:02:50 +02:00
|
|
|
|
{
|
2025-10-07 09:28:41 +02:00
|
|
|
|
// Calculate screen bounds and normalization factor
|
2025-09-16 15:02:50 +02:00
|
|
|
|
CalculateScreenBounds();
|
2025-10-07 09:28:41 +02:00
|
|
|
|
CalculateScreenNormalizationFactor();
|
|
|
|
|
|
|
2025-09-30 13:13:37 +02:00
|
|
|
|
// Spawn initial tiles to fill the screen
|
2025-09-16 15:02:50 +02:00
|
|
|
|
SpawnInitialTiles();
|
2025-09-22 12:16:32 +00:00
|
|
|
|
|
2025-10-07 09:28:41 +02:00
|
|
|
|
// Initialize velocity and apply screen normalization
|
|
|
|
|
|
_baseMoveSpeed = _settings?.NormalizedMoveSpeed ?? 3f;
|
|
|
|
|
|
_currentVelocity = _baseMoveSpeed * Time.fixedDeltaTime * _screenNormalizationFactor;
|
|
|
|
|
|
|
|
|
|
|
|
// Start all coroutines
|
2025-09-22 12:16:32 +00:00
|
|
|
|
StartCoroutine(VelocityCalculationRoutine());
|
2025-10-07 09:28:41 +02:00
|
|
|
|
StartMovementCoroutine();
|
|
|
|
|
|
StartTileDestructionCoroutine();
|
|
|
|
|
|
StartTileSpawningCoroutine();
|
|
|
|
|
|
StartSpeedRampingCoroutine();
|
2025-09-30 13:13:37 +02:00
|
|
|
|
|
2025-10-07 09:28:41 +02:00
|
|
|
|
Debug.Log("[TrenchTileSpawner] Initialized with normalized speed");
|
2025-09-16 15:02:50 +02:00
|
|
|
|
}
|
2025-09-22 12:16:32 +00:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the current velocity of the tiles
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>The current upward velocity of the tiles</returns>
|
|
|
|
|
|
public float GetCurrentVelocity()
|
|
|
|
|
|
{
|
|
|
|
|
|
return _currentVelocity;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Sets a custom velocity, overriding the calculated one
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="velocity">The new velocity value</param>
|
|
|
|
|
|
public void SetVelocity(float velocity)
|
|
|
|
|
|
{
|
|
|
|
|
|
_currentVelocity = velocity;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Coroutine that periodically calculates the velocity based on game state
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private IEnumerator VelocityCalculationRoutine()
|
|
|
|
|
|
{
|
|
|
|
|
|
while (true)
|
|
|
|
|
|
{
|
|
|
|
|
|
CalculateVelocity();
|
2025-09-24 13:33:43 +00:00
|
|
|
|
yield return new WaitForSeconds(_settings.VelocityCalculationInterval);
|
2025-09-22 12:16:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Calculates the current velocity based on move speed
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void CalculateVelocity()
|
|
|
|
|
|
{
|
2025-09-24 13:33:43 +00:00
|
|
|
|
_currentVelocity = _baseMoveSpeed * Time.fixedDeltaTime;
|
2025-09-22 12:16:32 +00:00
|
|
|
|
}
|
2025-09-16 15:02:50 +02:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Calculate height values for all tile prefabs
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void CalculateTileHeights()
|
|
|
|
|
|
{
|
2025-09-16 12:23:25 +02:00
|
|
|
|
foreach (var prefab in tilePrefabs)
|
|
|
|
|
|
{
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (prefab == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("Null prefab found in tilePrefabs list!");
|
2025-10-10 14:45:23 +02:00
|
|
|
|
return;
|
2025-09-16 15:02:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 12:23:25 +02:00
|
|
|
|
Renderer renderer = prefab.GetComponentInChildren<Renderer>();
|
|
|
|
|
|
if (renderer != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_tileHeights[prefab] = renderer.bounds.size.y;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Fallback in case no renderer is found
|
2025-09-16 15:02:50 +02:00
|
|
|
|
_tileHeights[prefab] = DefaultTileHeight;
|
|
|
|
|
|
Debug.LogWarning($"No renderer found in prefab {prefab.name}. Using default height of {DefaultTileHeight}.");
|
2025-09-16 12:23:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-16 15:02:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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>();
|
2025-09-16 12:23:25 +02:00
|
|
|
|
|
2025-09-24 13:33:43 +00:00
|
|
|
|
// Set up the pool configuration using developer settings
|
|
|
|
|
|
_tilePool.maxPerPrefabPoolSize = _devSettings.TrenchTileMaxPerPrefabPoolSize;
|
|
|
|
|
|
_tilePool.totalMaxPoolSize = _devSettings.TrenchTileTotalMaxPoolSize;
|
2025-09-16 15:02:50 +02:00
|
|
|
|
|
|
|
|
|
|
// Convert the GameObject list to a Tile list
|
|
|
|
|
|
List<Tile> prefabTiles = new List<Tile>(tilePrefabs.Count);
|
|
|
|
|
|
foreach (var prefab in tilePrefabs)
|
2025-09-16 12:23:25 +02:00
|
|
|
|
{
|
2025-09-16 15:02:50 +02:00
|
|
|
|
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!");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-16 12:23:25 +02:00
|
|
|
|
}
|
2025-09-16 15:02:50 +02:00
|
|
|
|
|
|
|
|
|
|
// Initialize the pool with the tile component list
|
|
|
|
|
|
_tilePool.Initialize(prefabTiles);
|
|
|
|
|
|
|
|
|
|
|
|
// Periodically trim the pool to optimize memory usage
|
|
|
|
|
|
InvokeRepeating(nameof(TrimExcessPooledTiles), 10f, 30f);
|
2025-09-16 12:23:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Spawn the initial set of tiles
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void SpawnInitialTiles()
|
2025-09-16 12:23:25 +02:00
|
|
|
|
{
|
2025-09-22 12:16:32 +00:00
|
|
|
|
// Calculate starting Y position - moved 2 tiles up from the bottom of the screen
|
|
|
|
|
|
float startingY = _screenBottom;
|
|
|
|
|
|
|
|
|
|
|
|
// If we have prefab tiles with known heights, use their average height for offset calculation
|
|
|
|
|
|
float tileHeightEstimate = DefaultTileHeight;
|
|
|
|
|
|
if (tilePrefabs != null && tilePrefabs.Count > 0 && tilePrefabs[0] != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_tileHeights.TryGetValue(tilePrefabs[0], out float height))
|
|
|
|
|
|
{
|
|
|
|
|
|
tileHeightEstimate = height;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Move starting position up by 2 tile heights
|
|
|
|
|
|
startingY += tileHeightEstimate * 2;
|
|
|
|
|
|
|
2025-09-24 13:33:43 +00:00
|
|
|
|
for (int i = 0; i < _settings.InitialTileCount; i++)
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
2025-09-22 12:16:32 +00:00
|
|
|
|
float y = startingY;
|
2025-09-16 12:23:25 +02:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
2025-10-10 14:45:23 +02:00
|
|
|
|
if (i == 0 && initialTilePrefab != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
SpawnSpecificTileAtY(initialTilePrefab, y);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
SpawnTileAtY(y);
|
|
|
|
|
|
}
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Calculate the screen bounds in world space
|
|
|
|
|
|
/// </summary>
|
2025-09-10 12:35:22 +02:00
|
|
|
|
private void CalculateScreenBounds()
|
|
|
|
|
|
{
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (_mainCamera == null)
|
|
|
|
|
|
{
|
2025-10-10 14:31:51 +02:00
|
|
|
|
_mainCamera = UnityEngine.Camera.main;
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (_mainCamera == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("No main camera found!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 12:35:22 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 09:28:41 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Calculates the screen normalization factor based on current screen height
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void CalculateScreenNormalizationFactor()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Get reference height from settings with fallback if not available
|
|
|
|
|
|
float referenceHeight = 1080f; // Default fallback value
|
|
|
|
|
|
|
|
|
|
|
|
if (_settings != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
referenceHeight = _settings.ReferenceScreenHeight;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate normalization factor based on screen height
|
|
|
|
|
|
_screenNormalizationFactor = Screen.height / referenceHeight;
|
|
|
|
|
|
Debug.Log($"[TrenchTileSpawner] Screen normalization factor: {_screenNormalizationFactor} (Screen height: {Screen.height}, Reference: {referenceHeight})");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// <summary>
|
2025-09-22 12:16:32 +00:00
|
|
|
|
/// Called when the velocity factor changes from the DivingGameManager
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void OnVelocityFactorChanged(float velocityFactor)
|
|
|
|
|
|
{
|
|
|
|
|
|
_velocityFactor = velocityFactor;
|
|
|
|
|
|
|
|
|
|
|
|
// Update the actual move speed based on the velocity factor
|
|
|
|
|
|
// This keeps the original move speed intact for game logic
|
2025-10-07 09:28:41 +02:00
|
|
|
|
_baseMoveSpeed = _settings.NormalizedMoveSpeed * Mathf.Abs(_velocityFactor);
|
2025-09-22 12:16:32 +00:00
|
|
|
|
|
|
|
|
|
|
// Recalculate velocity immediately
|
|
|
|
|
|
CalculateVelocity();
|
|
|
|
|
|
|
2025-09-24 13:33:43 +00:00
|
|
|
|
Debug.Log($"[TrenchTileSpawner] Velocity factor updated to {_velocityFactor:F2}, moveSpeed: {_baseMoveSpeed:F2}");
|
2025-09-22 12:16:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Reverses direction to start surfacing
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void StartSurfacing()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_isSurfacing) return; // Already surfacing
|
|
|
|
|
|
|
|
|
|
|
|
// Set surfacing flag for spawn/despawn logic
|
|
|
|
|
|
_isSurfacing = true;
|
|
|
|
|
|
|
|
|
|
|
|
// Reverse the active tiles array to maintain consistent indexing logic
|
|
|
|
|
|
_activeTiles.Reverse();
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log("[TrenchTileSpawner] Started surfacing - reversed array order");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Stops spawning new tiles
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// </summary>
|
2025-09-22 12:16:32 +00:00
|
|
|
|
public void StopSpawning()
|
|
|
|
|
|
{
|
|
|
|
|
|
_stopSpawning = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 09:28:41 +02:00
|
|
|
|
/// <summary>
|
2025-10-08 12:36:08 +02:00
|
|
|
|
/// Starts the movement coroutine if it's not already running
|
2025-10-07 09:28:41 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void StartMovementCoroutine()
|
|
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
if (_movementCoroutine == null && !_isPaused)
|
2025-10-07 09:28:41 +02:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
_movementCoroutine = StartCoroutine(MoveActiveTilesRoutine());
|
2025-10-07 09:28:41 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-08 12:36:08 +02:00
|
|
|
|
|
2025-10-07 09:28:41 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Coroutine that handles obstacle movement using normalized screen-relative speed
|
|
|
|
|
|
/// </summary>
|
2025-10-08 12:36:08 +02:00
|
|
|
|
private IEnumerator MoveActiveTilesRoutine()
|
2025-10-07 09:28:41 +02:00
|
|
|
|
{
|
2025-10-07 09:42:59 +02:00
|
|
|
|
Debug.Log($"[TrenchTileSpawner] Started movement coroutine with normalized speed: {_baseMoveSpeed:F3}");
|
2025-10-07 09:28:41 +02:00
|
|
|
|
|
2025-10-08 12:36:08 +02:00
|
|
|
|
while (enabled && gameObject.activeInHierarchy && !_isPaused)
|
2025-10-07 09:28:41 +02:00
|
|
|
|
{
|
|
|
|
|
|
// Skip if no active tiles
|
|
|
|
|
|
if (_activeTiles.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return null;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Use velocity factor sign to determine direction
|
|
|
|
|
|
Vector3 direction = Vector3.up * Mathf.Sign(_velocityFactor);
|
|
|
|
|
|
|
2025-10-07 09:42:59 +02:00
|
|
|
|
// Apply normalized movement using the shared utility method
|
|
|
|
|
|
float speed = AppleHillsUtils.CalculateNormalizedMovementSpeed(_baseMoveSpeed);
|
2025-10-07 09:28:41 +02:00
|
|
|
|
|
|
|
|
|
|
// Move all active tiles
|
|
|
|
|
|
foreach (var tile in _activeTiles)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (tile != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Apply movement in correct direction
|
|
|
|
|
|
tile.transform.position += direction * speed;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for next frame
|
|
|
|
|
|
yield return null;
|
|
|
|
|
|
}
|
2025-10-08 12:36:08 +02:00
|
|
|
|
|
|
|
|
|
|
// Clear coroutine reference when stopped
|
|
|
|
|
|
_movementCoroutine = null;
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// <summary>
|
2025-10-08 12:36:08 +02:00
|
|
|
|
/// Coroutine that checks for tiles to destroy periodically
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// </summary>
|
2025-10-08 12:36:08 +02:00
|
|
|
|
private IEnumerator TileDestructionRoutine()
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
const float checkInterval = 0.5f; // Check every half second
|
|
|
|
|
|
Debug.Log($"[TrenchTileSpawner] Started tile destruction coroutine with interval: {checkInterval}s");
|
2025-09-22 12:16:32 +00:00
|
|
|
|
|
2025-10-08 12:36:08 +02:00
|
|
|
|
while (enabled && gameObject.activeInHierarchy && !_isPaused)
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
// Check and handle tile destruction
|
|
|
|
|
|
if (_activeTiles.Count > 0)
|
2025-09-16 12:23:25 +02:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
GameObject topTile = _activeTiles[0];
|
|
|
|
|
|
if (topTile == null)
|
2025-09-16 12:23:25 +02:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
_activeTiles.RemoveAt(0);
|
2025-09-16 12:23:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
float tileHeight = GetTileHeight(topTile);
|
|
|
|
|
|
|
|
|
|
|
|
bool shouldDestroy;
|
|
|
|
|
|
if (_isSurfacing)
|
|
|
|
|
|
{
|
|
|
|
|
|
// When surfacing, destroy tiles at the bottom
|
|
|
|
|
|
shouldDestroy = topTile.transform.position.y + tileHeight / 2 < _screenBottom - _settings.TileSpawnBuffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// When descending, destroy tiles at the top
|
|
|
|
|
|
shouldDestroy = topTile.transform.position.y - tileHeight / 2 > _screenTop + _settings.TileSpawnBuffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldDestroy)
|
|
|
|
|
|
{
|
|
|
|
|
|
_activeTiles.RemoveAt(0);
|
|
|
|
|
|
onTileDestroyed?.Invoke(topTile);
|
|
|
|
|
|
|
|
|
|
|
|
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling && _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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-16 12:23:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-08 12:36:08 +02:00
|
|
|
|
|
|
|
|
|
|
// Wait for the next check interval
|
|
|
|
|
|
yield return new WaitForSeconds(checkInterval);
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
2025-10-08 12:36:08 +02:00
|
|
|
|
|
|
|
|
|
|
// Clear coroutine reference when stopped
|
|
|
|
|
|
_tileDestructionCoroutine = null;
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
2025-10-08 12:36:08 +02:00
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// <summary>
|
2025-10-08 12:36:08 +02:00
|
|
|
|
/// Coroutine that checks if new tiles need to be spawned periodically
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// </summary>
|
2025-10-08 12:36:08 +02:00
|
|
|
|
private IEnumerator TileSpawningRoutine()
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
const float checkInterval = 0.2f; // Check every fifth of a second
|
|
|
|
|
|
Debug.Log($"[TrenchTileSpawner] Started tile spawning coroutine with interval: {checkInterval}s");
|
2025-09-22 12:16:32 +00:00
|
|
|
|
|
2025-10-08 12:36:08 +02:00
|
|
|
|
while (enabled && gameObject.activeInHierarchy && !_isPaused && !_stopSpawning)
|
2025-09-22 12:16:32 +00:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
// Check if we need to spawn new tiles
|
|
|
|
|
|
if (_activeTiles.Count == 0)
|
2025-09-22 12:16:32 +00:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
// If we have no active tiles and spawning is stopped, trigger the event
|
|
|
|
|
|
if (_stopSpawning)
|
2025-09-22 12:16:32 +00:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
onLastTileLeft.Invoke();
|
2025-09-22 12:16:32 +00:00
|
|
|
|
}
|
2025-10-08 12:36:08 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
GameObject bottomTile = _activeTiles[^1];
|
|
|
|
|
|
if (bottomTile == null)
|
2025-09-22 12:16:32 +00:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
_activeTiles.RemoveAt(_activeTiles.Count - 1);
|
2025-09-22 12:16:32 +00:00
|
|
|
|
}
|
2025-10-08 12:36:08 +02:00
|
|
|
|
else
|
2025-09-22 12:16:32 +00:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
// Get the tile height once to use in all calculations
|
|
|
|
|
|
float tileHeight = GetTileHeight(bottomTile);
|
|
|
|
|
|
|
|
|
|
|
|
// If we're in stop spawning mode, check if last tile is leaving
|
|
|
|
|
|
if (_stopSpawning)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Check if this is the last tile, and if it's about to leave the screen
|
|
|
|
|
|
if (_activeTiles.Count == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool isLastTileLeaving;
|
|
|
|
|
|
|
|
|
|
|
|
if (_isSurfacing)
|
|
|
|
|
|
{
|
|
|
|
|
|
// When surfacing, check if bottom of tile is above top of screen
|
|
|
|
|
|
isLastTileLeaving = bottomTile.transform.position.y - tileHeight / 2 > _screenTop + _settings.TileSpawnBuffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// When descending, check if top of tile is below bottom of screen
|
|
|
|
|
|
isLastTileLeaving = bottomTile.transform.position.y + tileHeight / 2 < _screenBottom - _settings.TileSpawnBuffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isLastTileLeaving)
|
|
|
|
|
|
{
|
|
|
|
|
|
onLastTileLeft.Invoke();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Normal spawning mode
|
|
|
|
|
|
bool shouldSpawn;
|
|
|
|
|
|
float newY;
|
|
|
|
|
|
|
|
|
|
|
|
if (_isSurfacing)
|
|
|
|
|
|
{
|
|
|
|
|
|
// When surfacing, spawn new tiles at the top
|
|
|
|
|
|
float topEdge = bottomTile.transform.position.y + tileHeight / 2;
|
|
|
|
|
|
shouldSpawn = topEdge < _screenTop + _settings.TileSpawnBuffer;
|
|
|
|
|
|
newY = bottomTile.transform.position.y + tileHeight;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// When descending, spawn new tiles at the bottom
|
|
|
|
|
|
float bottomEdge = bottomTile.transform.position.y - tileHeight / 2;
|
|
|
|
|
|
shouldSpawn = bottomEdge > _screenBottom - _settings.TileSpawnBuffer;
|
|
|
|
|
|
newY = bottomTile.transform.position.y - tileHeight;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldSpawn)
|
|
|
|
|
|
{
|
|
|
|
|
|
SpawnTileAtY(newY);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-22 12:16:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-08 12:36:08 +02:00
|
|
|
|
|
|
|
|
|
|
// Wait for the next check interval
|
|
|
|
|
|
yield return new WaitForSeconds(checkInterval);
|
2025-09-22 12:16:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-08 12:36:08 +02:00
|
|
|
|
// Clear coroutine reference when stopped
|
|
|
|
|
|
_tileSpawningCoroutine = null;
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
2025-10-08 12:36:08 +02:00
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// <summary>
|
2025-10-08 12:36:08 +02:00
|
|
|
|
/// Coroutine that handles increasing the movement speed over time
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// </summary>
|
2025-10-08 12:36:08 +02:00
|
|
|
|
private IEnumerator SpeedRampingRoutine()
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
const float checkInterval = 1.0f; // Check once per second
|
|
|
|
|
|
Debug.Log($"[TrenchTileSpawner] Started speed ramping coroutine with interval: {checkInterval}s");
|
|
|
|
|
|
|
|
|
|
|
|
while (enabled && gameObject.activeInHierarchy && !_isPaused)
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
2025-10-08 12:36:08 +02:00
|
|
|
|
// Increase the base move speed up to the maximum
|
2025-10-07 09:28:41 +02:00
|
|
|
|
_baseMoveSpeed = Mathf.Min(_baseMoveSpeed + _settings.SpeedUpFactor, _settings.MaxNormalizedMoveSpeed);
|
2025-10-08 12:36:08 +02:00
|
|
|
|
|
|
|
|
|
|
// Recalculate velocity with the new base speed
|
|
|
|
|
|
CalculateVelocity();
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for the next check interval
|
|
|
|
|
|
yield return new WaitForSeconds(checkInterval);
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
2025-10-08 12:36:08 +02:00
|
|
|
|
|
|
|
|
|
|
// Clear coroutine reference when stopped
|
|
|
|
|
|
_speedRampingCoroutine = null;
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-10 14:45:23 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Checks if two tiles are fully compatible for spawning, following clarified floating area rules.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private bool AreTilesFullyCompatible(Tile prev, Tile next)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Path compatibility: at least one path is open in both Out (prev) and In (next)
|
|
|
|
|
|
bool pathCompatible =
|
|
|
|
|
|
(prev.pathOutLeft && next.pathInLeft) ||
|
|
|
|
|
|
(prev.pathOutCenter && next.pathInCenter) ||
|
|
|
|
|
|
(prev.pathOutRight && next.pathInRight);
|
|
|
|
|
|
|
|
|
|
|
|
// --- Updated floating area rules ---
|
|
|
|
|
|
// Continue tile: can only spawn after Start, and must be followed by End
|
|
|
|
|
|
if (next.continuesFloatingAreaMiddle)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!prev.hasFloatingAreaMiddle)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
// End tile: can only spawn after Start or Continue
|
|
|
|
|
|
if (next.endsFloatingAreaMiddle)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!(prev.hasFloatingAreaMiddle || prev.continuesFloatingAreaMiddle))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
// After a Start tile, only Continue or End can follow
|
|
|
|
|
|
if (prev.hasFloatingAreaMiddle)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!(next.continuesFloatingAreaMiddle || next.endsFloatingAreaMiddle))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
// After a Continue tile, only End can follow
|
|
|
|
|
|
if (prev.continuesFloatingAreaMiddle)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!next.endsFloatingAreaMiddle)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Otherwise, normal tiles are always allowed
|
|
|
|
|
|
return pathCompatible;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets a weighted random index among fully compatible tiles, prioritizing least recently used.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int GetWeightedCompatibleTileIndex(Tile prevTile)
|
|
|
|
|
|
{
|
|
|
|
|
|
var compatibleIndices = new List<int>();
|
|
|
|
|
|
int currentDifficulty = CurrentDifficulty;
|
|
|
|
|
|
for (int i = 0; i < tilePrefabs.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var candidateGO = tilePrefabs[i];
|
|
|
|
|
|
var candidate = candidateGO?.GetComponent<Tile>();
|
|
|
|
|
|
if (candidate == null) continue;
|
|
|
|
|
|
if (candidate.difficultyLevel > currentDifficulty) continue;
|
|
|
|
|
|
bool compatible = AreTilesFullyCompatible(prevTile, candidate);
|
|
|
|
|
|
if (compatible)
|
|
|
|
|
|
compatibleIndices.Add(i);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (compatibleIndices.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("No compatible tile found for previous tile and current difficulty. Spawning aborted.");
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
List<float> weights = new List<float>(compatibleIndices.Count);
|
|
|
|
|
|
foreach (var i in compatibleIndices)
|
|
|
|
|
|
{
|
|
|
|
|
|
int lastUsed = _tileLastUsed.TryGetValue(i, out var value) ? value : -tilePrefabs.Count;
|
|
|
|
|
|
int age = _spawnCounter - lastUsed;
|
|
|
|
|
|
float weight = Mathf.Clamp(age, 1, tilePrefabs.Count * 2);
|
|
|
|
|
|
weights.Add(weight);
|
|
|
|
|
|
}
|
|
|
|
|
|
float totalWeight = 0f;
|
|
|
|
|
|
foreach (var weight in weights)
|
|
|
|
|
|
totalWeight += weight;
|
|
|
|
|
|
float randomValue = Random.value * totalWeight;
|
|
|
|
|
|
for (int idx = 0; idx < compatibleIndices.Count; idx++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (randomValue < weights[idx])
|
|
|
|
|
|
return compatibleIndices[idx];
|
|
|
|
|
|
randomValue -= weights[idx];
|
|
|
|
|
|
}
|
|
|
|
|
|
return compatibleIndices[Random.Range(0, compatibleIndices.Count)];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Spawn a new tile at the specified Y position
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="y">The Y position to spawn at</param>
|
2025-09-10 12:35:22 +02:00
|
|
|
|
private void SpawnTileAtY(float y)
|
|
|
|
|
|
{
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (tilePrefabs == null || tilePrefabs.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("No tile prefabs available for spawning!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-10 14:45:23 +02:00
|
|
|
|
int prefabIndex;
|
|
|
|
|
|
// First tile: any tile
|
|
|
|
|
|
if (_activeTiles.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
prefabIndex = GetWeightedRandomTileIndex();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
GameObject prevTileGO = _activeTiles[_activeTiles.Count - 1];
|
|
|
|
|
|
Tile prevTile = prevTileGO != null ? prevTileGO.GetComponent<Tile>() : null;
|
|
|
|
|
|
if (prevTile != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Use weighted compatible selection
|
|
|
|
|
|
prefabIndex = GetWeightedCompatibleTileIndex(prevTile);
|
|
|
|
|
|
if (prefabIndex == -1)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("No compatible tile can be spawned after previous tile. Aborting spawn.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
prefabIndex = GetWeightedRandomTileIndex();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 12:35:22 +02:00
|
|
|
|
GameObject prefab = tilePrefabs[prefabIndex];
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (prefab == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError($"Tile prefab at index {prefabIndex} is null!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 12:23:25 +02:00
|
|
|
|
GameObject tile;
|
2025-09-24 13:33:43 +00:00
|
|
|
|
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling && _tilePool != null)
|
2025-09-16 12:23:25 +02:00
|
|
|
|
{
|
|
|
|
|
|
tile = _tilePool.GetTile(prefabIndex);
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (tile == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("Failed to get tile from pool!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-09-16 12:23:25 +02:00
|
|
|
|
tile.transform.position = new Vector3(0f, y, TileSpawnZ);
|
|
|
|
|
|
tile.transform.rotation = prefab.transform.rotation;
|
|
|
|
|
|
tile.transform.SetParent(transform);
|
2025-09-24 13:33:43 +00:00
|
|
|
|
if (_devSettings != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
tile.layer = _devSettings.TrenchTileLayer;
|
|
|
|
|
|
SetLayerRecursively(tile, _devSettings.TrenchTileLayer);
|
|
|
|
|
|
}
|
2025-09-16 12:23:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
tile = Instantiate(prefab, new Vector3(0f, y, TileSpawnZ), prefab.transform.rotation, transform);
|
2025-09-24 13:33:43 +00:00
|
|
|
|
if (_devSettings != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
tile.layer = _devSettings.TrenchTileLayer;
|
|
|
|
|
|
SetLayerRecursively(tile, _devSettings.TrenchTileLayer);
|
|
|
|
|
|
}
|
2025-09-16 12:23:25 +02:00
|
|
|
|
}
|
2025-09-10 12:35:22 +02:00
|
|
|
|
_activeTiles.Add(tile);
|
|
|
|
|
|
_tileLastUsed[prefabIndex] = _spawnCounter++;
|
2025-10-10 14:45:23 +02:00
|
|
|
|
_currentDepth++;
|
|
|
|
|
|
Debug.Log($"[TrenchTileSpawner] Current Depth: {_currentDepth}");
|
|
|
|
|
|
onTileSpawned?.Invoke(tile);
|
|
|
|
|
|
|
|
|
|
|
|
// --- FLOATING AREA STATE MANAGEMENT ---
|
|
|
|
|
|
Tile spawnedTile = tile.GetComponent<Tile>();
|
|
|
|
|
|
if (spawnedTile != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (spawnedTile.hasFloatingAreaMiddle || spawnedTile.continuesFloatingAreaMiddle)
|
|
|
|
|
|
{
|
|
|
|
|
|
isFloatingAreaActive = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (spawnedTile.endsFloatingAreaMiddle)
|
|
|
|
|
|
{
|
|
|
|
|
|
isFloatingAreaActive = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Spawn a specific tile at the specified Y position
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="prefab">The tile prefab to spawn</param>
|
|
|
|
|
|
/// <param name="y">The Y position to spawn at</param>
|
|
|
|
|
|
private void SpawnSpecificTileAtY(GameObject prefab, float y)
|
|
|
|
|
|
{
|
|
|
|
|
|
GameObject tile;
|
|
|
|
|
|
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling && _tilePool != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
int prefabIndex = tilePrefabs.IndexOf(prefab);
|
|
|
|
|
|
if (prefabIndex >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
tile = _tilePool.GetTile(prefabIndex);
|
|
|
|
|
|
if (tile == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("Failed to get initial tile from pool!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
tile = Instantiate(prefab, new Vector3(0f, y, TileSpawnZ), prefab.transform.rotation, transform);
|
|
|
|
|
|
}
|
|
|
|
|
|
tile.transform.position = new Vector3(0f, y, TileSpawnZ);
|
|
|
|
|
|
tile.transform.rotation = prefab.transform.rotation;
|
|
|
|
|
|
tile.transform.SetParent(transform);
|
|
|
|
|
|
if (_devSettings != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
tile.layer = _devSettings.TrenchTileLayer;
|
|
|
|
|
|
SetLayerRecursively(tile, _devSettings.TrenchTileLayer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
tile = Instantiate(prefab, new Vector3(0f, y, TileSpawnZ), prefab.transform.rotation, transform);
|
|
|
|
|
|
if (_devSettings != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
tile.layer = _devSettings.TrenchTileLayer;
|
|
|
|
|
|
SetLayerRecursively(tile, _devSettings.TrenchTileLayer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_activeTiles.Add(tile);
|
|
|
|
|
|
_currentDepth++;
|
|
|
|
|
|
Debug.Log($"[TrenchTileSpawner] Current Depth: {_currentDepth}");
|
2025-09-10 12:35:22 +02:00
|
|
|
|
onTileSpawned?.Invoke(tile);
|
2025-10-10 14:45:23 +02:00
|
|
|
|
// Optionally update floating area state if needed
|
|
|
|
|
|
Tile spawnedTile = tile.GetComponent<Tile>();
|
|
|
|
|
|
if (spawnedTile != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (spawnedTile.hasFloatingAreaMiddle || spawnedTile.continuesFloatingAreaMiddle)
|
|
|
|
|
|
{
|
|
|
|
|
|
isFloatingAreaActive = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (spawnedTile.endsFloatingAreaMiddle)
|
|
|
|
|
|
{
|
|
|
|
|
|
isFloatingAreaActive = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets a list of allowed tile indices for the current difficulty
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>List of allowed prefab indices</returns>
|
|
|
|
|
|
private List<int> GetAllowedTileIndicesForCurrentDifficulty()
|
|
|
|
|
|
{
|
|
|
|
|
|
var allowedIndices = new List<int>();
|
|
|
|
|
|
int currentDifficulty = CurrentDifficulty;
|
|
|
|
|
|
for (int i = 0; i < tilePrefabs.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var tileComponent = tilePrefabs[i]?.GetComponent<Tile>();
|
|
|
|
|
|
if (tileComponent != null && tileComponent.difficultyLevel <= currentDifficulty)
|
|
|
|
|
|
{
|
|
|
|
|
|
allowedIndices.Add(i);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return allowedIndices;
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets a weighted random tile index, favoring tiles that haven't been used recently
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>The selected prefab index</returns>
|
2025-09-10 12:35:22 +02:00
|
|
|
|
private int GetWeightedRandomTileIndex()
|
|
|
|
|
|
{
|
2025-10-10 14:45:23 +02:00
|
|
|
|
var allowedIndices = GetAllowedTileIndicesForCurrentDifficulty();
|
|
|
|
|
|
if (allowedIndices.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("No allowed tiles for current difficulty!");
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
List<float> weights = new List<float>(allowedIndices.Count);
|
2025-09-24 13:33:43 +00:00
|
|
|
|
|
2025-10-10 14:45:23 +02:00
|
|
|
|
for (int i = 0; i < allowedIndices.Count; i++)
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
2025-10-10 14:45:23 +02:00
|
|
|
|
int lastUsed = _tileLastUsed.TryGetValue(allowedIndices[i], out var value) ? value : -tilePrefabs.Count;
|
2025-09-10 12:35:22 +02:00
|
|
|
|
int age = _spawnCounter - lastUsed;
|
2025-10-10 14:45:23 +02:00
|
|
|
|
float weight = Mathf.Clamp(age, 1, tilePrefabs.Count * 2); // More unused = higher weight
|
2025-09-10 12:35:22 +02:00
|
|
|
|
weights.Add(weight);
|
|
|
|
|
|
}
|
2025-09-24 13:33:43 +00:00
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
float totalWeight = 0f;
|
|
|
|
|
|
foreach (var weight in weights)
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
2025-09-16 15:02:50 +02:00
|
|
|
|
totalWeight += weight;
|
|
|
|
|
|
}
|
2025-09-24 13:33:43 +00:00
|
|
|
|
|
2025-09-16 15:02:50 +02:00
|
|
|
|
float randomValue = Random.value * totalWeight;
|
2025-10-10 14:45:23 +02:00
|
|
|
|
for (int i = 0; i < allowedIndices.Count; i++)
|
2025-09-16 15:02:50 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (randomValue < weights[i])
|
|
|
|
|
|
{
|
2025-10-10 14:45:23 +02:00
|
|
|
|
return allowedIndices[i];
|
2025-09-16 15:02:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
randomValue -= weights[i];
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
2025-09-24 13:33:43 +00:00
|
|
|
|
|
2025-10-10 14:45:23 +02:00
|
|
|
|
return Random.Range(0, allowedIndices.Count); // fallback
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
2025-09-24 13:33:43 +00:00
|
|
|
|
|
2025-09-16 12:23:25 +02:00
|
|
|
|
/// <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)
|
|
|
|
|
|
{
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (tile == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning("Attempted to get height of null tile!");
|
|
|
|
|
|
return DefaultTileHeight;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 12:23:25 +02:00
|
|
|
|
// Check if this is a known prefab
|
|
|
|
|
|
foreach (var prefab in tilePrefabs)
|
|
|
|
|
|
{
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (prefab == null) continue;
|
|
|
|
|
|
|
2025-09-16 12:23:25 +02:00
|
|
|
|
// Check if this tile was created from this prefab
|
|
|
|
|
|
if (tile.name.StartsWith(prefab.name))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_tileHeights.TryGetValue(prefab, out float height))
|
|
|
|
|
|
{
|
|
|
|
|
|
return height;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-24 13:33:43 +00:00
|
|
|
|
|
2025-09-16 12:23:25 +02:00
|
|
|
|
// If not found, calculate it from the renderer
|
|
|
|
|
|
Renderer renderer = tile.GetComponentInChildren<Renderer>();
|
|
|
|
|
|
if (renderer != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return renderer.bounds.size.y;
|
|
|
|
|
|
}
|
2025-09-24 13:33:43 +00:00
|
|
|
|
|
2025-09-16 12:23:25 +02:00
|
|
|
|
// Fallback
|
2025-09-16 15:02:50 +02:00
|
|
|
|
return DefaultTileHeight;
|
2025-09-16 12:23:25 +02:00
|
|
|
|
}
|
2025-09-24 13:33:43 +00:00
|
|
|
|
|
2025-09-16 12:23:25 +02:00
|
|
|
|
/// <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)
|
|
|
|
|
|
{
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (tile == null || tilePrefabs == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-16 12:23:25 +02:00
|
|
|
|
for (int i = 0; i < tilePrefabs.Count; i++)
|
|
|
|
|
|
{
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (tilePrefabs[i] == null) continue;
|
2025-09-24 13:33:43 +00:00
|
|
|
|
|
2025-09-16 12:23:25 +02:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-10 12:35:22 +02:00
|
|
|
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
2025-09-16 15:02:50 +02:00
|
|
|
|
private void OnDrawGizmosSelected()
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (!Application.isPlaying)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Only try to calculate this if _screenBottom hasn't been set by the game
|
2025-10-10 14:31:51 +02:00
|
|
|
|
UnityEngine.Camera editorCam = UnityEngine.Camera.main;
|
2025-09-16 15:02:50 +02:00
|
|
|
|
if (editorCam != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector3 bottom = editorCam.ViewportToWorldPoint(new Vector3(0.5f, 0f, editorCam.nearClipPlane));
|
|
|
|
|
|
_screenBottom = bottom.y;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-10 12:35:22 +02:00
|
|
|
|
// Draw tile bounds for debugging
|
|
|
|
|
|
Gizmos.color = Color.cyan;
|
2025-09-24 13:33:43 +00:00
|
|
|
|
if (_settings != null)
|
2025-09-10 12:35:22 +02:00
|
|
|
|
{
|
2025-09-24 13:33:43 +00:00
|
|
|
|
for (int i = 0; i < _settings.InitialTileCount; i++)
|
2025-09-16 12:23:25 +02:00
|
|
|
|
{
|
2025-09-24 13:33:43 +00:00
|
|
|
|
float height = DefaultTileHeight;
|
|
|
|
|
|
if (tilePrefabs != null && tilePrefabs.Count > 0 && tilePrefabs[0] != null &&
|
|
|
|
|
|
_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));
|
2025-09-16 12:23:25 +02:00
|
|
|
|
}
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2025-09-24 13:33:43 +00:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Set the layer of a GameObject and all its children recursively
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="obj">The GameObject to set the layer for</param>
|
|
|
|
|
|
/// <param name="layer">The layer index to set</param>
|
|
|
|
|
|
private void SetLayerRecursively(GameObject obj, int layer)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (obj == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
obj.layer = layer;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (Transform child in obj.transform)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (child != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
SetLayerRecursively(child.gameObject, layer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-07 09:28:41 +02:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Starts the tile destruction coroutine and stores its reference
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void StartTileDestructionCoroutine()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_tileDestructionCoroutine != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
StopCoroutine(_tileDestructionCoroutine);
|
|
|
|
|
|
}
|
|
|
|
|
|
_tileDestructionCoroutine = StartCoroutine(TileDestructionCoroutine());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Coroutine that checks for tiles to destroy periodically
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private IEnumerator TileDestructionCoroutine()
|
|
|
|
|
|
{
|
|
|
|
|
|
const float checkInterval = 0.5f; // Check every half second as requested
|
|
|
|
|
|
Debug.Log($"[TrenchTileSpawner] Started tile destruction coroutine with interval: {checkInterval}s");
|
|
|
|
|
|
|
|
|
|
|
|
while (enabled && gameObject.activeInHierarchy)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Check and handle tile destruction
|
|
|
|
|
|
if (_activeTiles.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
GameObject topTile = _activeTiles[0];
|
|
|
|
|
|
if (topTile == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_activeTiles.RemoveAt(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
float tileHeight = GetTileHeight(topTile);
|
|
|
|
|
|
|
|
|
|
|
|
bool shouldDestroy;
|
|
|
|
|
|
if (_isSurfacing)
|
|
|
|
|
|
{
|
|
|
|
|
|
// When surfacing, destroy tiles at the bottom
|
|
|
|
|
|
shouldDestroy = topTile.transform.position.y + tileHeight / 2 < _screenBottom - _settings.TileSpawnBuffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// When descending, destroy tiles at the top
|
|
|
|
|
|
shouldDestroy = topTile.transform.position.y - tileHeight / 2 > _screenTop + _settings.TileSpawnBuffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldDestroy)
|
|
|
|
|
|
{
|
|
|
|
|
|
_activeTiles.RemoveAt(0);
|
|
|
|
|
|
onTileDestroyed?.Invoke(topTile);
|
|
|
|
|
|
|
|
|
|
|
|
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling && _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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for the next check interval
|
|
|
|
|
|
yield return new WaitForSeconds(checkInterval);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Starts the tile spawning coroutine and stores its reference
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void StartTileSpawningCoroutine()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_tileSpawningCoroutine != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
StopCoroutine(_tileSpawningCoroutine);
|
|
|
|
|
|
}
|
|
|
|
|
|
_tileSpawningCoroutine = StartCoroutine(TileSpawningCoroutine());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Coroutine that checks if new tiles need to be spawned periodically
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private IEnumerator TileSpawningCoroutine()
|
|
|
|
|
|
{
|
|
|
|
|
|
const float checkInterval = 0.2f; // Check every half second as requested
|
|
|
|
|
|
Debug.Log($"[TrenchTileSpawner] Started tile spawning coroutine with interval: {checkInterval}s");
|
|
|
|
|
|
|
|
|
|
|
|
while (enabled && gameObject.activeInHierarchy)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Check if we need to spawn new tiles
|
|
|
|
|
|
if (_activeTiles.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// If we have no active tiles and spawning is stopped, trigger the event
|
|
|
|
|
|
if (_stopSpawning)
|
|
|
|
|
|
{
|
|
|
|
|
|
onLastTileLeft.Invoke();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
GameObject bottomTile = _activeTiles[^1];
|
|
|
|
|
|
if (bottomTile == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_activeTiles.RemoveAt(_activeTiles.Count - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Get the tile height once to use in all calculations
|
|
|
|
|
|
float tileHeight = GetTileHeight(bottomTile);
|
|
|
|
|
|
|
|
|
|
|
|
// If we're in stop spawning mode, check if last tile is leaving
|
|
|
|
|
|
if (_stopSpawning)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Check if this is the last tile, and if it's about to leave the screen
|
|
|
|
|
|
if (_activeTiles.Count == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool isLastTileLeaving;
|
|
|
|
|
|
|
|
|
|
|
|
if (_isSurfacing)
|
|
|
|
|
|
{
|
|
|
|
|
|
// When surfacing, check if bottom of tile is above top of screen
|
|
|
|
|
|
isLastTileLeaving = bottomTile.transform.position.y - tileHeight / 2 > _screenTop + _settings.TileSpawnBuffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// When descending, check if top of tile is below bottom of screen
|
|
|
|
|
|
isLastTileLeaving = bottomTile.transform.position.y + tileHeight / 2 < _screenBottom - _settings.TileSpawnBuffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isLastTileLeaving)
|
|
|
|
|
|
{
|
|
|
|
|
|
onLastTileLeft.Invoke();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Normal spawning mode
|
|
|
|
|
|
bool shouldSpawn;
|
|
|
|
|
|
float newY;
|
|
|
|
|
|
|
|
|
|
|
|
if (_isSurfacing)
|
|
|
|
|
|
{
|
|
|
|
|
|
// When surfacing, spawn new tiles at the top
|
|
|
|
|
|
float topEdge = bottomTile.transform.position.y + tileHeight / 2;
|
|
|
|
|
|
shouldSpawn = topEdge < _screenTop + _settings.TileSpawnBuffer;
|
|
|
|
|
|
newY = bottomTile.transform.position.y + tileHeight;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// When descending, spawn new tiles at the bottom
|
|
|
|
|
|
float bottomEdge = bottomTile.transform.position.y - tileHeight / 2;
|
|
|
|
|
|
shouldSpawn = bottomEdge > _screenBottom - _settings.TileSpawnBuffer;
|
|
|
|
|
|
newY = bottomTile.transform.position.y - tileHeight;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (shouldSpawn)
|
|
|
|
|
|
{
|
|
|
|
|
|
SpawnTileAtY(newY);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for the next check interval
|
|
|
|
|
|
yield return new WaitForSeconds(checkInterval);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Starts the speed ramping coroutine and stores its reference
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void StartSpeedRampingCoroutine()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_speedRampingCoroutine != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
StopCoroutine(_speedRampingCoroutine);
|
|
|
|
|
|
}
|
2025-10-10 14:45:23 +02:00
|
|
|
|
_speedRampingCoroutine = StartCoroutine(SpeedRampingRoutine());
|
2025-10-07 09:28:41 +02:00
|
|
|
|
}
|
2025-09-10 12:35:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|