2025-09-21 07:32:56 +00:00
using System.Collections ;
using System.Collections.Generic ;
using UnityEngine ;
using UnityEngine.Events ;
using Pooling ;
2025-09-24 13:33:43 +00:00
using AppleHills.Core.Settings ;
2025-10-08 12:36:08 +02:00
using AppleHills.Core.Interfaces ;
2025-10-14 15:53:58 +02:00
using Core ;
2025-09-21 07:32:56 +00:00
namespace Minigames.DivingForPictures
{
/// <summary>
/// Spawns and manages mobile obstacles for the diving minigame.
/// Uses object pooling and validates spawn positions to avoid colliding with tiles.
/// </summary>
2025-10-08 12:36:08 +02:00
public class ObstacleSpawner : MonoBehaviour , IPausable
2025-09-21 07:32:56 +00:00
{
[Header("Obstacle Prefabs")]
[Tooltip("List of possible obstacle prefabs to spawn")]
[SerializeField] private List < GameObject > obstaclePrefabs ;
[Header("Events")]
2025-09-24 13:33:43 +00:00
[Tooltip("Invoked when a new obstacle is spawned")]
2025-09-21 07:32:56 +00:00
public UnityEvent < GameObject > onObstacleSpawned ;
2025-09-24 13:33:43 +00:00
[Tooltip("Invoked when an obstacle is destroyed or returned to pool")]
2025-09-21 07:32:56 +00:00
public UnityEvent < GameObject > onObstacleDestroyed ;
2025-09-24 13:33:43 +00:00
// Settings references
private IDivingMinigameSettings _settings ;
private DivingDeveloperSettings _devSettings ;
2025-09-21 07:32:56 +00:00
// Private fields
private ObstaclePool _obstaclePool ;
2025-10-10 14:31:51 +02:00
private UnityEngine . Camera _mainCamera ;
2025-09-21 07:32:56 +00:00
private float _screenBottom ;
private float _spawnRangeX ;
private Coroutine _spawnCoroutine ;
2025-10-08 12:36:08 +02:00
private Coroutine _moveCoroutine ;
private Coroutine _despawnCoroutine ;
2025-09-21 07:32:56 +00:00
private readonly List < GameObject > _activeObstacles = new List < GameObject > ( ) ;
private int _obstacleCounter = 0 ; // Counter for unique obstacle naming
2025-09-22 12:16:32 +00:00
private bool _isSurfacing = false ; // Flag to track surfacing state
private float _velocityFactor = 1.0f ; // Current velocity factor from the game manager
2025-09-21 07:32:56 +00:00
2025-10-08 12:36:08 +02:00
// Pause state
private bool _isPaused = false ;
// IPausable implementation
public bool IsPaused = > _isPaused ;
2025-09-21 07:32:56 +00:00
private void Awake ( )
{
2025-10-10 14:31:51 +02:00
_mainCamera = UnityEngine . Camera . main ;
2025-09-21 07:32:56 +00:00
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 ( "[ObstacleSpawner] Failed to load diving minigame settings!" ) ;
}
if ( _devSettings = = null )
{
Debug . LogError ( "[ObstacleSpawner] Failed to load diving developer settings!" ) ;
}
2025-09-21 07:32:56 +00:00
// Validate obstacle prefabs
ValidateObstaclePrefabs ( ) ;
2025-09-24 13:33:43 +00:00
if ( _devSettings ? . ObstacleUseObjectPooling ? ? false )
2025-09-21 07:32:56 +00:00
{
InitializeObjectPool ( ) ;
}
2025-09-24 13:33:43 +00:00
// Initialize events if null
if ( onObstacleSpawned = = null )
onObstacleSpawned = new UnityEvent < GameObject > ( ) ;
if ( onObstacleDestroyed = = null )
onObstacleDestroyed = new UnityEvent < GameObject > ( ) ;
2025-09-21 07:32:56 +00:00
}
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
// Clean up any active coroutines
StopAllCoroutines ( ) ;
}
2025-09-30 13:13:37 +02:00
/// <summary>
/// Initializes the obstacle spawner when triggered by DivingGameManager
/// </summary>
private void Initialize ( )
2025-09-21 07:32:56 +00:00
{
2025-10-08 12:36:08 +02:00
// Calculate screen bounds
2025-09-21 07:32:56 +00:00
CalculateScreenBounds ( ) ;
2025-10-08 12:36:08 +02:00
// Start coroutines if not paused
StartSpawnCoroutine ( ) ;
StartMoveCoroutine ( ) ;
StartDespawnCoroutine ( ) ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( "[ObstacleSpawner] Initialized" ) ;
2025-09-21 07:32:56 +00:00
}
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 spawning coroutine
if ( _spawnCoroutine ! = null )
{
StopCoroutine ( _spawnCoroutine ) ;
_spawnCoroutine = null ;
}
// Pause all active obstacles
foreach ( var obstacle in _activeObstacles . ToArray ( ) )
{
if ( obstacle ! = null )
{
FloatingObstacle obstacleComponent = obstacle . GetComponent < FloatingObstacle > ( ) ;
if ( obstacleComponent ! = null )
{
obstacleComponent . Pause ( ) ;
}
}
}
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Paused with {_activeObstacles.Count} active obstacles" ) ;
2025-10-08 12:36:08 +02:00
}
/// <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 spawning coroutine if not in surfacing mode
if ( ! _isSurfacing )
{
StartSpawnCoroutine ( ) ;
}
// Resume all active obstacles
foreach ( var obstacle in _activeObstacles . ToArray ( ) )
{
if ( obstacle ! = null )
{
FloatingObstacle obstacleComponent = obstacle . GetComponent < FloatingObstacle > ( ) ;
if ( obstacleComponent ! = null )
{
2025-10-10 14:31:51 +02:00
obstacleComponent . DoResume ( ) ;
2025-10-08 12:36:08 +02:00
}
}
}
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Resumed with {_activeObstacles.Count} active obstacles" ) ;
2025-10-08 12:36:08 +02:00
}
/// <summary>
/// Starts the obstacle spawning coroutine if not already running
/// </summary>
private void StartSpawnCoroutine ( )
{
if ( _spawnCoroutine = = null & & ! _isPaused & & ! _isSurfacing )
{
_spawnCoroutine = StartCoroutine ( SpawnObstacleRoutine ( ) ) ;
}
}
/// <summary>
/// Starts the obstacle movement coroutine if not already running
/// </summary>
private void StartMoveCoroutine ( )
{
if ( _moveCoroutine = = null & & ! _isPaused )
{
_moveCoroutine = StartCoroutine ( MoveObstaclesRoutine ( ) ) ;
}
}
/// <summary>
/// Starts the obstacle despawning coroutine if not already running
/// </summary>
private void StartDespawnCoroutine ( )
2025-09-21 07:32:56 +00:00
{
2025-10-08 12:36:08 +02:00
if ( _despawnCoroutine = = null & & ! _isPaused )
{
_despawnCoroutine = StartCoroutine ( DespawnObstaclesRoutine ( ) ) ;
}
2025-09-21 07:32:56 +00:00
}
/// <summary>
/// Validates that all prefabs have required components
/// </summary>
private void ValidateObstaclePrefabs ( )
{
2025-09-24 13:33:43 +00:00
if ( _devSettings = = null ) return ;
2025-09-21 07:32:56 +00:00
for ( int i = 0 ; i < obstaclePrefabs . Count ; i + + )
{
if ( obstaclePrefabs [ i ] = = null ) continue ;
// Check if the prefab has a FloatingObstacle component
if ( obstaclePrefabs [ i ] . GetComponent < FloatingObstacle > ( ) = = null )
{
2025-10-14 15:53:58 +02:00
Logging . Warning ( $"Obstacle prefab {obstaclePrefabs[i].name} does not have a FloatingObstacle component. Adding one automatically." ) ;
2025-09-21 07:32:56 +00:00
obstaclePrefabs [ i ] . AddComponent < FloatingObstacle > ( ) ;
}
// Ensure the prefab is on the correct layer (using configurable obstacleLayer)
2025-09-24 13:33:43 +00:00
if ( obstaclePrefabs [ i ] . layer ! = _devSettings . ObstacleLayer )
2025-09-21 07:32:56 +00:00
{
2025-10-14 15:53:58 +02:00
Logging . Warning ( $"Obstacle prefab {obstaclePrefabs[i].name} is not on the configured obstacle layer ({_devSettings.ObstacleLayer}). Setting layer automatically." ) ;
2025-09-24 13:33:43 +00:00
SetLayerRecursively ( obstaclePrefabs [ i ] , _devSettings . ObstacleLayer ) ;
2025-09-21 07:32:56 +00:00
}
}
}
/// <summary>
/// Sets the layer of a GameObject and all its children
/// </summary>
private void SetLayerRecursively ( GameObject obj , int layer )
{
obj . layer = layer ;
foreach ( Transform child in obj . transform )
{
SetLayerRecursively ( child . gameObject , layer ) ;
}
}
/// <summary>
/// Initialize the object pool system
/// </summary>
private void InitializeObjectPool ( )
{
GameObject poolGO = new GameObject ( "ObstaclePool" ) ;
poolGO . transform . SetParent ( transform ) ;
_obstaclePool = poolGO . AddComponent < ObstaclePool > ( ) ;
// Set up pool configuration
2025-09-24 13:33:43 +00:00
_obstaclePool . maxPerPrefabPoolSize = _devSettings . ObstacleMaxPerPrefabPoolSize ;
_obstaclePool . totalMaxPoolSize = _devSettings . ObstacleTotalMaxPoolSize ;
2025-09-21 07:32:56 +00:00
// Convert GameObject list to FloatingObstacle list
List < FloatingObstacle > prefabObstacles = new List < FloatingObstacle > ( obstaclePrefabs . Count ) ;
foreach ( var prefab in obstaclePrefabs )
{
if ( prefab ! = null )
{
FloatingObstacle obstacleComponent = prefab . GetComponent < FloatingObstacle > ( ) ;
if ( obstacleComponent ! = null )
{
prefabObstacles . Add ( obstacleComponent ) ;
}
else
{
Debug . LogError ( $"Obstacle prefab {prefab.name} is missing a FloatingObstacle component!" ) ;
}
}
}
// Initialize the pool
_obstaclePool . Initialize ( prefabObstacles ) ;
}
/// <summary>
/// Calculate screen bounds in world space dynamically
/// </summary>
private void CalculateScreenBounds ( )
{
if ( _mainCamera = = null )
{
2025-10-10 14:31:51 +02:00
_mainCamera = UnityEngine . Camera . main ;
2025-09-21 07:32:56 +00:00
if ( _mainCamera = = null )
{
Debug . LogError ( "[ObstacleSpawner] No main camera found!" ) ;
return ;
}
}
// Calculate screen bottom (Y spawn position will be 2 units below this)
Vector3 bottomWorldPoint = _mainCamera . ViewportToWorldPoint ( new Vector3 ( 0.5f , 0f , _mainCamera . nearClipPlane ) ) ;
_screenBottom = bottomWorldPoint . y ;
// Calculate screen width in world units
Vector3 leftWorldPoint = _mainCamera . ViewportToWorldPoint ( new Vector3 ( 0f , 0.5f , _mainCamera . nearClipPlane ) ) ;
Vector3 rightWorldPoint = _mainCamera . ViewportToWorldPoint ( new Vector3 ( 1f , 0.5f , _mainCamera . nearClipPlane ) ) ;
float screenWidth = rightWorldPoint . x - leftWorldPoint . x ;
// Calculate spawn range based on 80% of screen width (40% on each side from center)
_spawnRangeX = ( screenWidth * 0.8f ) / 2f ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Screen calculated - Width: {screenWidth:F2}, Bottom: {_screenBottom:F2}, Spawn Range X: ±{_spawnRangeX:F2}" ) ;
2025-09-21 07:32:56 +00:00
}
/// <summary>
/// Starts the obstacle spawning coroutine
/// </summary>
public void StartSpawning ( )
{
if ( _spawnCoroutine = = null )
{
_spawnCoroutine = StartCoroutine ( SpawnObstaclesCoroutine ( ) ) ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( "[ObstacleSpawner] Started spawning obstacles" ) ;
2025-09-21 07:32:56 +00:00
}
}
/// <summary>
/// Stops the obstacle spawning coroutine
/// </summary>
public void StopSpawning ( )
{
if ( _spawnCoroutine ! = null )
{
StopCoroutine ( _spawnCoroutine ) ;
_spawnCoroutine = null ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( "[ObstacleSpawner] Stopped spawning obstacles" ) ;
2025-09-21 07:32:56 +00:00
}
}
/// <summary>
/// Main spawning coroutine that runs continuously
/// </summary>
private IEnumerator SpawnObstaclesCoroutine ( )
{
while ( true )
{
// Calculate next spawn time with variation
2025-09-24 13:33:43 +00:00
float nextSpawnTime = _settings . ObstacleSpawnInterval +
Random . Range ( - _settings . ObstacleSpawnIntervalVariation ,
_settings . ObstacleSpawnIntervalVariation ) ;
2025-09-21 07:32:56 +00:00
nextSpawnTime = Mathf . Max ( 0.1f , nextSpawnTime ) ; // Ensure minimum interval
yield return new WaitForSeconds ( nextSpawnTime ) ;
// Attempt to spawn an obstacle
TrySpawnObstacle ( ) ;
}
}
/// <summary>
/// Attempts to spawn an obstacle at a valid position
/// </summary>
private void TrySpawnObstacle ( )
{
2025-09-22 12:16:32 +00:00
// Don't spawn new obstacles when surfacing
if ( _isSurfacing )
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( "[ObstacleSpawner] Skipping obstacle spawn - currently surfacing" ) ;
2025-09-22 12:16:32 +00:00
return ;
}
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] TrySpawnObstacle called at {Time.time:F2}" ) ;
2025-09-21 07:32:56 +00:00
if ( obstaclePrefabs = = null | | obstaclePrefabs . Count = = 0 )
{
2025-10-14 15:53:58 +02:00
Logging . Warning ( "[ObstacleSpawner] No obstacle prefabs available for spawning!" ) ;
2025-09-21 07:32:56 +00:00
return ;
}
Vector3 spawnPosition ;
bool foundValidPosition = false ;
// Try to find a valid spawn position
2025-09-24 13:33:43 +00:00
for ( int attempts = 0 ; attempts < _settings . ObstacleMaxSpawnAttempts ; attempts + + )
2025-09-21 07:32:56 +00:00
{
spawnPosition = GetRandomSpawnPosition ( ) ;
if ( IsValidSpawnPosition ( spawnPosition ) )
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Found valid position at {spawnPosition} after {attempts + 1} attempts" ) ;
2025-09-21 07:32:56 +00:00
SpawnObstacleAt ( spawnPosition ) ;
foundValidPosition = true ;
break ;
}
else
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Position {spawnPosition} invalid (attempt {attempts + 1}/{_settings.ObstacleMaxSpawnAttempts})" ) ;
2025-09-21 07:32:56 +00:00
}
}
if ( ! foundValidPosition )
{
2025-10-14 15:53:58 +02:00
Logging . Warning ( $"[ObstacleSpawner] SPAWN MISSED: Could not find valid spawn position after {_settings.ObstacleMaxSpawnAttempts} attempts at {Time.time:F2}" ) ;
2025-09-21 07:32:56 +00:00
}
}
/// <summary>
/// Gets a random spawn position below the screen
/// </summary>
private Vector3 GetRandomSpawnPosition ( )
{
// Use dynamically calculated spawn range (80% of screen width)
float randomX = Random . Range ( - _spawnRangeX , _spawnRangeX ) ;
// Spawn 2 units below screen bottom
float spawnY = _screenBottom - 2f ;
return new Vector3 ( randomX , spawnY , 0f ) ;
}
/// <summary>
/// Checks if a spawn position is valid (not colliding with tiles)
/// </summary>
private bool IsValidSpawnPosition ( Vector3 position )
{
2025-09-24 13:33:43 +00:00
// Use OverlapCircle to check for collisions with tiles using just the layer
// Convert the single layer to a layer mask inline (1 << layerNumber)
Collider2D collision = Physics2D . OverlapCircle ( position , _settings . ObstacleSpawnCollisionRadius , 1 < < _devSettings . TrenchTileLayer ) ;
2025-09-21 07:32:56 +00:00
return collision = = null ;
}
/// <summary>
/// Spawns an obstacle at the specified position
/// </summary>
private void SpawnObstacleAt ( Vector3 position )
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] SpawnObstacleAt called for position {position}" ) ;
2025-09-21 07:32:56 +00:00
// Select random prefab
int prefabIndex = Random . Range ( 0 , obstaclePrefabs . Count ) ;
GameObject prefab = obstaclePrefabs [ prefabIndex ] ;
if ( prefab = = null )
{
Debug . LogError ( $"[ObstacleSpawner] SPAWN FAILED: Obstacle prefab at index {prefabIndex} is null!" ) ;
return ;
}
GameObject obstacle ;
// Spawn using pool or instantiate directly
2025-09-24 13:33:43 +00:00
if ( _devSettings . ObstacleUseObjectPooling & & _obstaclePool ! = null )
2025-09-21 07:32:56 +00:00
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Requesting obstacle from pool (prefab index {prefabIndex})" ) ;
2025-09-21 07:32:56 +00:00
obstacle = _obstaclePool . GetObstacle ( prefabIndex ) ;
if ( obstacle = = null )
{
Debug . LogError ( $"[ObstacleSpawner] SPAWN FAILED: Failed to get obstacle from pool for prefab index {prefabIndex}!" ) ;
return ;
}
2025-09-22 12:16:32 +00:00
// Important: Set position/parent/rotation BEFORE activation to avoid visual glitches
obstacle . transform . position = position ;
obstacle . transform . rotation = prefab . transform . rotation ;
obstacle . transform . SetParent ( transform ) ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Got obstacle {obstacle.name} from pool, active state: {obstacle.activeInHierarchy}" ) ;
2025-09-21 07:32:56 +00:00
2025-09-22 12:16:32 +00:00
// ENHANCED FORCE ACTIVATION - more robust approach
2025-09-21 07:32:56 +00:00
if ( ! obstacle . activeInHierarchy )
{
2025-10-14 15:53:58 +02:00
Logging . Warning ( $"[ObstacleSpawner] Pool returned inactive object {obstacle.name}, force activating!" ) ;
2025-09-22 12:16:32 +00:00
// Configure obstacle BEFORE activation
ConfigureObstacle ( obstacle , prefabIndex ) ;
// Force activate the obstacle
2025-09-21 07:32:56 +00:00
obstacle . SetActive ( true ) ;
2025-09-22 12:16:32 +00:00
// Double-check activation status
if ( ! obstacle . activeInHierarchy )
{
Debug . LogError ( $"[ObstacleSpawner] CRITICAL ERROR: Failed to activate {obstacle.name} after multiple attempts!" ) ;
// Last resort: try to instantiate a new one instead
GameObject newObstacle = Instantiate ( prefab , position , prefab . transform . rotation , transform ) ;
if ( newObstacle ! = null )
{
obstacle = newObstacle ;
ConfigureObstacle ( obstacle , prefabIndex ) ;
}
}
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] After force activation, {obstacle.name} active state: {obstacle.activeInHierarchy}" ) ;
2025-09-21 07:32:56 +00:00
}
2025-09-22 12:16:32 +00:00
else
{
// Still configure if already active
ConfigureObstacle ( obstacle , prefabIndex ) ;
}
2025-09-21 07:32:56 +00:00
}
else
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Instantiating new obstacle (pooling disabled)" ) ;
2025-09-21 07:32:56 +00:00
obstacle = Instantiate ( prefab , position , prefab . transform . rotation , transform ) ;
2025-09-22 12:16:32 +00:00
// Configure the newly instantiated obstacle
ConfigureObstacle ( obstacle , prefabIndex ) ;
2025-09-21 07:32:56 +00:00
}
// Assign unique name with counter
_obstacleCounter + + ;
string oldName = obstacle . name ;
obstacle . name = $"Obstacle{_obstacleCounter:D3}" ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Renamed obstacle from '{oldName}' to '{obstacle.name}', active state: {obstacle.activeInHierarchy}" ) ;
2025-09-21 07:32:56 +00:00
// Track active obstacles
_activeObstacles . Add ( obstacle ) ;
// Invoke events
onObstacleSpawned ? . Invoke ( obstacle ) ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] After events, obstacle {obstacle.name} active state: {obstacle.activeInHierarchy}" ) ;
2025-09-21 07:32:56 +00:00
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Successfully spawned obstacle {obstacle.name} at {position}. Active count: {_activeObstacles.Count}, Final active state: {obstacle.activeInHierarchy}" ) ;
2025-09-21 07:32:56 +00:00
}
/// <summary>
/// Configures an obstacle with randomized properties
/// </summary>
private void ConfigureObstacle ( GameObject obstacle , int prefabIndex )
{
FloatingObstacle obstacleComponent = obstacle . GetComponent < FloatingObstacle > ( ) ;
if ( obstacleComponent ! = null )
{
// Set prefab index
obstacleComponent . PrefabIndex = prefabIndex ;
2025-09-24 13:33:43 +00:00
// Randomize properties using settings
obstacleComponent . MoveSpeed = Random . Range (
_settings . ObstacleMinMoveSpeed ,
_settings . ObstacleMaxMoveSpeed ) ;
2025-09-21 07:32:56 +00:00
2025-10-08 12:36:08 +02:00
// Set spawner reference
2025-09-21 07:32:56 +00:00
obstacleComponent . SetSpawner ( this ) ;
2025-10-08 12:36:08 +02:00
// If spawner is already paused, pause the obstacle immediately
if ( _isPaused )
{
obstacleComponent . Pause ( ) ;
}
2025-09-21 07:32:56 +00:00
}
}
/// <summary>
/// Returns an obstacle to the pool (called by FloatingObstacle)
/// </summary>
public void ReturnObstacleToPool ( GameObject obstacle , int prefabIndex )
{
if ( obstacle = = null ) return ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] ReturnObstacleToPool called for {obstacle.name}, active state: {obstacle.activeInHierarchy}" ) ;
2025-09-21 07:32:56 +00:00
// Remove from active list
_activeObstacles . Remove ( obstacle ) ;
// Invoke events
onObstacleDestroyed ? . Invoke ( obstacle ) ;
// Return to pool or destroy
2025-09-24 13:33:43 +00:00
if ( _devSettings . ObstacleUseObjectPooling & & _obstaclePool ! = null )
2025-09-21 07:32:56 +00:00
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Returning {obstacle.name} to pool" ) ;
2025-09-21 07:32:56 +00:00
_obstaclePool . ReturnObstacle ( obstacle , prefabIndex ) ;
}
else
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Destroying {obstacle.name} (pooling disabled)" ) ;
2025-09-21 07:32:56 +00:00
Destroy ( obstacle ) ;
}
}
/// <summary>
/// Public method to change spawn interval at runtime
/// </summary>
public void SetSpawnInterval ( float interval )
{
2025-09-24 13:33:43 +00:00
// This method can no longer directly modify the settings
// Consider implementing a runtime settings override system if needed
2025-10-14 15:53:58 +02:00
Logging . Warning ( "[ObstacleSpawner] SetSpawnInterval no longer modifies settings directly. Settings are now centralized." ) ;
2025-09-21 07:32:56 +00:00
}
/// <summary>
/// Public method to set speed range at runtime
/// </summary>
public void SetSpeedRange ( float min , float max )
{
2025-09-24 13:33:43 +00:00
// This method can no longer directly modify the settings
// Consider implementing a runtime settings override system if needed
2025-10-14 15:53:58 +02:00
Logging . Warning ( "[ObstacleSpawner] SetSpeedRange no longer modifies settings directly. Settings are now centralized." ) ;
2025-09-21 07:32:56 +00:00
}
/// <summary>
/// Public method to recalculate screen bounds (useful if camera changes)
/// </summary>
public void RecalculateScreenBounds ( )
{
CalculateScreenBounds ( ) ;
}
2025-09-22 12:16:32 +00:00
/// <summary>
/// Called when the velocity factor changes from the DivingGameManager
/// </summary>
public void OnVelocityFactorChanged ( float velocityFactor )
{
_velocityFactor = velocityFactor ;
// Update all active obstacles with the new velocity factor
foreach ( GameObject obstacle in _activeObstacles )
{
if ( obstacle ! = null )
{
FloatingObstacle obstacleComponent = obstacle . GetComponent < FloatingObstacle > ( ) ;
if ( obstacleComponent ! = null )
{
obstacleComponent . OnVelocityFactorChanged ( velocityFactor ) ;
}
}
}
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Velocity factor updated to {_velocityFactor:F2}, propagated to {_activeObstacles.Count} active obstacles" ) ;
2025-09-22 12:16:32 +00:00
}
/// <summary>
/// Start surfacing mode - reverse direction of existing obstacles and stop spawning new ones
/// </summary>
public void StartSurfacing ( )
{
if ( _isSurfacing ) return ; // Already surfacing
_isSurfacing = true ;
// Notify obstacles about surfacing state (for direction-based logic)
foreach ( GameObject obstacle in _activeObstacles )
{
if ( obstacle ! = null )
{
FloatingObstacle obstacleComponent = obstacle . GetComponent < FloatingObstacle > ( ) ;
if ( obstacleComponent ! = null )
{
// Call StartSurfacing on the obstacle component itself
obstacleComponent . StartSurfacing ( ) ;
}
}
}
2025-10-14 15:53:58 +02:00
Logging . Debug ( $"[ObstacleSpawner] Started surfacing mode for {_activeObstacles.Count} active obstacles" ) ;
2025-09-22 12:16:32 +00:00
}
2025-09-21 07:32:56 +00:00
/// <summary>
/// Gets the count of currently active obstacles
/// </summary>
public int ActiveObstacleCount = > _activeObstacles . Count ;
2025-10-08 12:36:08 +02:00
/// <summary>
/// Coroutine that handles obstacle spawning at regular intervals
/// </summary>
private IEnumerator SpawnObstacleRoutine ( )
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( "[ObstacleSpawner] Started spawning coroutine" ) ;
2025-10-08 12:36:08 +02:00
while ( enabled & & gameObject . activeInHierarchy & & ! _isPaused & & ! _isSurfacing )
{
// Calculate next spawn time with variation
float nextSpawnTime = _settings . ObstacleSpawnInterval +
Random . Range ( - _settings . ObstacleSpawnIntervalVariation ,
_settings . ObstacleSpawnIntervalVariation ) ;
nextSpawnTime = Mathf . Max ( 0.1f , nextSpawnTime ) ; // Ensure minimum interval
// Attempt to spawn an obstacle
TrySpawnObstacle ( ) ;
yield return new WaitForSeconds ( nextSpawnTime ) ;
}
// Clear coroutine reference when stopped
_spawnCoroutine = null ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( "[ObstacleSpawner] Spawning coroutine ended" ) ;
2025-10-08 12:36:08 +02:00
}
/// <summary>
/// Coroutine that handles checking obstacle positions
/// Unlike the previous implementation, we don't need to move obstacles manually
/// since the FloatingObstacle handles its own movement via coroutines
/// </summary>
private IEnumerator MoveObstaclesRoutine ( )
{
2025-10-14 15:53:58 +02:00
Logging . Debug ( "[ObstacleSpawner] Started obstacle monitoring coroutine" ) ;
2025-10-08 12:36:08 +02:00
// This coroutine now just monitors obstacles, not moves them
while ( enabled & & gameObject . activeInHierarchy & & ! _isPaused )
{
// Clean up any null references in the active obstacles list
_activeObstacles . RemoveAll ( obstacle = > obstacle = = null ) ;
yield return new WaitForSeconds ( 0.5f ) ;
}
// Clear coroutine reference when stopped
_moveCoroutine = null ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( "[ObstacleSpawner] Obstacle monitoring coroutine ended" ) ;
2025-10-08 12:36:08 +02:00
}
/// <summary>
/// Coroutine that checks for obstacles that are off-screen and should be despawned
/// </summary>
private IEnumerator DespawnObstaclesRoutine ( )
{
const float checkInterval = 0.5f ; // Check every half second
2025-10-14 15:53:58 +02:00
Logging . Debug ( "[ObstacleSpawner] Started despawn coroutine with interval: " + checkInterval ) ;
2025-10-08 12:36:08 +02:00
while ( enabled & & gameObject . activeInHierarchy & & ! _isPaused )
{
// Calculate screen bounds for despawning
float despawnBuffer = 2f ; // Extra buffer beyond screen edges
if ( _mainCamera = = null )
{
2025-10-10 14:31:51 +02:00
_mainCamera = UnityEngine . Camera . main ;
2025-10-08 12:36:08 +02:00
if ( _mainCamera = = null )
{
yield return new WaitForSeconds ( checkInterval ) ;
continue ;
}
}
Vector3 topWorldPoint = _mainCamera . ViewportToWorldPoint ( new Vector3 ( 0.5f , 1f , _mainCamera . transform . position . z ) ) ;
float _screenTop = topWorldPoint . y ;
Vector3 bottomWorldPoint = _mainCamera . ViewportToWorldPoint ( new Vector3 ( 0.5f , 0f , _mainCamera . transform . position . z ) ) ;
float _screenBottom = bottomWorldPoint . y ;
float topEdge = _screenTop + despawnBuffer ;
float bottomEdge = _screenBottom - despawnBuffer ;
// Find obstacles that need to be despawned
List < GameObject > obstaclesToRemove = new List < GameObject > ( ) ;
foreach ( var obstacle in _activeObstacles )
{
if ( obstacle = = null )
{
obstaclesToRemove . Add ( obstacle ) ;
continue ;
}
// Check if obstacle is out of screen bounds
float obstacleY = obstacle . transform . position . y ;
bool shouldDespawn ;
if ( _isSurfacing )
{
// When surfacing, despawn obstacles below the bottom edge
shouldDespawn = obstacleY < bottomEdge ;
}
else
{
// When descending, despawn obstacles above the top edge
shouldDespawn = obstacleY > topEdge ;
}
if ( shouldDespawn )
{
obstaclesToRemove . Add ( obstacle ) ;
}
}
// Remove and despawn obstacles
foreach ( var obstacle in obstaclesToRemove )
{
FloatingObstacle obstacleComponent = obstacle ? . GetComponent < FloatingObstacle > ( ) ;
if ( obstacleComponent ! = null )
{
// Instead of calling a non-existent DespawnObstacle method,
// use FloatingObstacle's ForceReturnToPool method
obstacleComponent . ForceReturnToPool ( ) ;
}
else
{
// Fallback if component not found
ReturnObstacleToPool ( obstacle , - 1 ) ;
}
}
yield return new WaitForSeconds ( checkInterval ) ;
}
// Clear coroutine reference when stopped
_despawnCoroutine = null ;
2025-10-14 15:53:58 +02:00
Logging . Debug ( "[ObstacleSpawner] Despawn coroutine ended" ) ;
2025-10-08 12:36:08 +02:00
}
2025-09-21 07:32:56 +00:00
#if UNITY_EDITOR
private void OnDrawGizmosSelected ( )
{
2025-09-24 13:33:43 +00:00
// Only draw if screen bounds have been calculated and settings are available
if ( _spawnRangeX > 0f & & _settings ! = null )
2025-09-21 07:32:56 +00:00
{
// Draw spawn area using dynamic calculations
Gizmos . color = Color . yellow ;
Vector3 center = new Vector3 ( 0f , _screenBottom - 2f , 0f ) ;
Vector3 size = new Vector3 ( _spawnRangeX * 2f , 1f , 1f ) ;
Gizmos . DrawWireCube ( center , size ) ;
// Draw collision radius at spawn point
Gizmos . color = Color . red ;
2025-09-24 13:33:43 +00:00
Gizmos . DrawWireSphere ( center , _settings . ObstacleSpawnCollisionRadius ) ;
2025-09-21 07:32:56 +00:00
}
}
#endif
}
}