diff --git a/Assets/Scripts/Core/GameManager.cs b/Assets/Scripts/Core/GameManager.cs index 53fe12f9..ceaac3c7 100644 --- a/Assets/Scripts/Core/GameManager.cs +++ b/Assets/Scripts/Core/GameManager.cs @@ -1,6 +1,9 @@ using UnityEngine; using AppleHills.Core.Settings; using System; +using System.Collections.Generic; +using AppleHills.Core.Interfaces; +using UI; /// /// Singleton manager for global game state and settings. Provides accessors for various gameplay parameters. @@ -36,6 +39,21 @@ public class GameManager : MonoBehaviour [SerializeField] private bool _developerSettingsLoaded = false; public bool SettingsLoaded => _settingsLoaded; public bool DeveloperSettingsLoaded => _developerSettingsLoaded; + + [Header("Game State")] + [SerializeField] private bool _isPaused = false; + + /// + /// Current pause state of the game + /// + public bool IsPaused => _isPaused; + + // List of pausable components that have registered with the GameManager + private List _pausableComponents = new List(); + + // Events for pause state changes + public event Action OnGamePaused; + public event Action OnGameResumed; void Awake() { @@ -53,6 +71,136 @@ public class GameManager : MonoBehaviour // DontDestroyOnLoad(gameObject); } + + private void Start() + { + // Find and subscribe to PauseMenu events + PauseMenu pauseMenu = PauseMenu.Instance; + if (pauseMenu != null) + { + pauseMenu.OnGamePaused += OnPauseMenuPaused; + pauseMenu.OnGameResumed += OnPauseMenuResumed; + + Debug.Log("[GameManager] Subscribed to PauseMenu events"); + } + else + { + Debug.LogWarning("[GameManager] PauseMenu not found. Pause functionality won't work properly."); + } + } + + private void OnDestroy() + { + // Unsubscribe from PauseMenu events + PauseMenu pauseMenu = FindFirstObjectByType(); + if (pauseMenu != null) + { + pauseMenu.OnGamePaused -= OnPauseMenuPaused; + pauseMenu.OnGameResumed -= OnPauseMenuResumed; + } + } + + /// + /// Register a component as pausable, so it receives pause/resume notifications + /// + /// The pausable component to register + public void RegisterPausableComponent(IPausable component) + { + if (component != null && !_pausableComponents.Contains(component)) + { + _pausableComponents.Add(component); + + // If the game is already paused, pause the component immediately + if (_isPaused) + { + component.Pause(); + } + + Debug.Log($"[GameManager] Registered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}"); + } + } + + /// + /// Unregister a pausable component + /// + /// The pausable component to unregister + public void UnregisterPausableComponent(IPausable component) + { + if (component != null && _pausableComponents.Contains(component)) + { + _pausableComponents.Remove(component); + Debug.Log($"[GameManager] Unregistered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}"); + } + } + + /// + /// Called when the PauseMenu broadcasts a pause event + /// + private void OnPauseMenuPaused() + { + PauseGame(); + } + + /// + /// Called when the PauseMenu broadcasts a resume event + /// + private void OnPauseMenuResumed() + { + ResumeGame(); + } + + /// + /// Pause the game and notify all registered pausable components + /// + public void PauseGame() + { + if (_isPaused) return; // Already paused + + _isPaused = true; + + // Pause all registered components + foreach (var component in _pausableComponents) + { + component.Pause(); + } + + // Broadcast pause event + OnGamePaused?.Invoke(); + + Debug.Log($"[GameManager] Game paused. Paused {_pausableComponents.Count} components."); + } + + /// + /// Resume the game and notify all registered pausable components + /// + public void ResumeGame() + { + if (!_isPaused) return; // Already running + + _isPaused = false; + + // Resume all registered components + foreach (var component in _pausableComponents) + { + component.Resume(); + } + + // Broadcast resume event + OnGameResumed?.Invoke(); + + Debug.Log($"[GameManager] Game resumed. Resumed {_pausableComponents.Count} components."); + } + + /// + /// Toggle the pause state of the game + /// + public void TogglePause() + { + if (_isPaused) + ResumeGame(); + else + PauseGame(); + } private void InitializeSettings() { @@ -164,5 +312,4 @@ public class GameManager : MonoBehaviour public float PlayerStopDistance => GetSettings()?.PlayerStopDistance ?? 6.0f; public float PlayerStopDistanceDirectInteraction => GetSettings()?.PlayerStopDistanceDirectInteraction ?? 2.0f; public float DefaultPuzzlePromptRange => GetSettings()?.DefaultPuzzlePromptRange ?? 3.0f; - } diff --git a/Assets/Scripts/Core/Interfaces.meta b/Assets/Scripts/Core/Interfaces.meta new file mode 100644 index 00000000..459681e8 --- /dev/null +++ b/Assets/Scripts/Core/Interfaces.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ba87fe038e914973bc341817149a9ebe +timeCreated: 1759916676 \ No newline at end of file diff --git a/Assets/Scripts/Core/Interfaces/IPausable.cs b/Assets/Scripts/Core/Interfaces/IPausable.cs new file mode 100644 index 00000000..81739c39 --- /dev/null +++ b/Assets/Scripts/Core/Interfaces/IPausable.cs @@ -0,0 +1,25 @@ +using UnityEngine; + +namespace AppleHills.Core.Interfaces +{ + /// + /// Interface for components that can be paused and resumed + /// + public interface IPausable + { + /// + /// Pauses the component's functionality + /// + void Pause(); + + /// + /// Resumes the component's functionality + /// + void Resume(); + + /// + /// Gets whether the component is currently paused + /// + bool IsPaused { get; } + } +} diff --git a/Assets/Scripts/Core/Interfaces/IPausable.cs.meta b/Assets/Scripts/Core/Interfaces/IPausable.cs.meta new file mode 100644 index 00000000..78b5a577 --- /dev/null +++ b/Assets/Scripts/Core/Interfaces/IPausable.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3a493ef684ce47208a3f6e7d67727882 +timeCreated: 1759916676 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/DivingForPictures/Bubbles/Bubble.cs b/Assets/Scripts/Minigames/DivingForPictures/Bubbles/Bubble.cs index 56969ee1..a50d8629 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Bubbles/Bubble.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Bubbles/Bubble.cs @@ -1,6 +1,7 @@ using UnityEngine; using System.Collections; using Pooling; +using AppleHills.Core.Interfaces; namespace Minigames.DivingForPictures { @@ -8,7 +9,7 @@ namespace Minigames.DivingForPictures /// Represents a single bubble, handling its movement, wobble effect, scaling, and sprite assignment. /// Uses coroutines for better performance instead of Update() calls. /// - public class Bubble : MonoBehaviour, IPoolableWithReference + public class Bubble : MonoBehaviour, IPoolableWithReference, IPausable { public float speed = 1f; public float wobbleSpeed = 1f; @@ -26,6 +27,12 @@ namespace Minigames.DivingForPictures private Coroutine _movementCoroutine; private Coroutine _wobbleCoroutine; private Coroutine _offScreenCheckCoroutine; + + // Pause state tracking + private bool _isPaused = false; + + // IPausable implementation + public bool IsPaused => _isPaused; void Awake() { @@ -53,12 +60,42 @@ namespace Minigames.DivingForPictures { StopBubbleBehavior(); } + + /// + /// Pauses all bubble behaviors + /// + public void Pause() + { + if (_isPaused) return; // Already paused + + _isPaused = true; + StopBubbleBehavior(); + + // Debug log for troubleshooting + Debug.Log($"[Bubble] Paused bubble: {name}"); + } + + /// + /// Resumes all bubble behaviors + /// + public void Resume() + { + if (!_isPaused) return; // Already running + + _isPaused = false; + StartBubbleBehavior(); + + // Debug log for troubleshooting + Debug.Log($"[Bubble] Resumed bubble: {name}"); + } /// /// Starts all bubble behavior coroutines /// private void StartBubbleBehavior() { + if (_isPaused) return; // Don't start if paused + _movementCoroutine = StartCoroutine(MovementCoroutine()); _wobbleCoroutine = StartCoroutine(WobbleCoroutine()); _offScreenCheckCoroutine = StartCoroutine(OffScreenCheckCoroutine()); diff --git a/Assets/Scripts/Minigames/DivingForPictures/Bubbles/BubbleSpawner.cs b/Assets/Scripts/Minigames/DivingForPictures/Bubbles/BubbleSpawner.cs index 66aae15b..101d353b 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Bubbles/BubbleSpawner.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Bubbles/BubbleSpawner.cs @@ -1,13 +1,15 @@ -using UnityEngine; +using System.Collections; +using UnityEngine; using Pooling; using AppleHills.Core.Settings; +using AppleHills.Core.Interfaces; namespace Minigames.DivingForPictures { /// /// Spawns bubbles at intervals, randomizing their properties and assigning a random sprite to each. /// - public class BubbleSpawner : MonoBehaviour + public class BubbleSpawner : MonoBehaviour, IPausable { public Bubble bubblePrefab; public Sprite[] bubbleSprites; // Assign in inspector @@ -20,6 +22,15 @@ namespace Minigames.DivingForPictures private BubblePool _bubblePool; private Camera _mainCamera; // Cache camera reference private bool _isSurfacing = false; + + // Pause state + private bool _isPaused = false; + + // Coroutines for pause/resume + private Coroutine _spawnCoroutine; + + // IPausable implementation + public bool IsPaused => _isPaused; void Awake() { @@ -50,23 +61,110 @@ namespace Minigames.DivingForPictures InvokeRepeating(nameof(LogPoolStats), 5f, 30f); #endif } + + SetNextSpawnInterval(); } void Start() { - // Initialize the next spawn interval - _nextSpawnInterval = GetRandomizedInterval(); + // Register with DivingGameManager for pause/resume events + DivingGameManager gameManager = FindFirstObjectByType(); + if (gameManager != null) + { + gameManager.RegisterPausableComponent(this); + } + + // Start spawning if not paused + StartSpawningCoroutine(); + } + + void OnDestroy() + { + // Unregister from DivingGameManager + DivingGameManager gameManager = FindFirstObjectByType(); + if (gameManager != null) + { + gameManager.UnregisterPausableComponent(this); + } + + // Clean up any active coroutines + StopAllCoroutines(); + } + + /// + /// Pauses the bubble spawner and all bubbles + /// + public void Pause() + { + if (_isPaused) return; // Already paused + + _isPaused = true; + + // Stop spawning coroutine + if (_spawnCoroutine != null) + { + StopCoroutine(_spawnCoroutine); + _spawnCoroutine = null; + } + + // Pause all active bubbles + Bubble[] activeBubbles = FindObjectsOfType(); + foreach (var bubble in activeBubbles) + { + if (bubble != null) + { + bubble.Pause(); + } + } + + Debug.Log("[BubbleSpawner] Paused"); + } + + /// + /// Resumes the bubble spawner and all bubbles + /// + public void Resume() + { + if (!_isPaused) return; // Already running + + _isPaused = false; + + // Restart spawning coroutine + StartSpawningCoroutine(); + + // Resume all active bubbles + Bubble[] activeBubbles = FindObjectsOfType(); + foreach (var bubble in activeBubbles) + { + if (bubble != null) + { + bubble.Resume(); + } + } + + Debug.Log("[BubbleSpawner] Resumed"); + } + + /// + /// Starts the bubble spawning coroutine if it's not already running + /// + private void StartSpawningCoroutine() + { + if (_spawnCoroutine == null && !_isPaused) + { + _spawnCoroutine = StartCoroutine(SpawnBubblesRoutine()); + } } - void Update() + /// + /// Sets the next spawn interval using randomized timing + /// + private void SetNextSpawnInterval() { - _timer += Time.deltaTime; - if (_timer >= _nextSpawnInterval) - { - SpawnBubble(); - _timer = 0f; - _nextSpawnInterval = GetRandomizedInterval(); - } + if (_devSettings == null) return; + + _nextSpawnInterval = GetRandomizedInterval(); + Debug.Log($"[BubbleSpawner] Next spawn interval set to: {_nextSpawnInterval:F2}s"); } /// @@ -128,6 +226,12 @@ namespace Minigames.DivingForPictures // Pass min/max scale for wobble clamping bubble.SetWobbleScaleLimits(_devSettings.BubbleWobbleMinScale, _devSettings.BubbleWobbleMaxScale); + + // If the game is already paused, pause this bubble immediately + if (_isPaused) + { + bubble.Pause(); + } } /// @@ -159,5 +263,24 @@ namespace Minigames.DivingForPictures _bubblePool.LogPoolStats(); } } + + /// + /// Coroutine that handles the bubble spawning logic + /// + private IEnumerator SpawnBubblesRoutine() + { + Debug.Log("[BubbleSpawner] Started bubble spawning coroutine"); + + while (enabled && gameObject.activeInHierarchy && !_isPaused) + { + SpawnBubble(); + SetNextSpawnInterval(); // Set interval for next spawn + yield return new WaitForSeconds(_nextSpawnInterval); + } + + _spawnCoroutine = null; + + Debug.Log("[BubbleSpawner] Bubble spawning coroutine ended"); + } } } \ No newline at end of file diff --git a/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs b/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs index fcddf796..646ad61c 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs @@ -6,10 +6,12 @@ using UnityEngine.Events; using UnityEngine.Playables; using AppleHills.Core.Settings; using Utility; +using AppleHills.Core.Interfaces; +using UI; namespace Minigames.DivingForPictures { - public class DivingGameManager : MonoBehaviour + public class DivingGameManager : MonoBehaviour, IPausable { [Header("Monster Prefabs")] [Tooltip("Array of monster prefabs to spawn randomly")] @@ -66,6 +68,15 @@ namespace Minigames.DivingForPictures // Event for game components to subscribe to public event Action OnGameInitialized; + // Pause state + private bool _isPaused = false; + + // List of pausable components controlled by this manager + private List _pausableComponents = new List(); + + // IPausable implementation + public bool IsPaused => _isPaused; + private void Awake() { // Get settings from GameManager @@ -81,6 +92,26 @@ namespace Minigames.DivingForPictures private void Start() { + // Find PauseMenu and subscribe to its events + PauseMenu pauseMenu = PauseMenu.Instance; + if (pauseMenu != null) + { + pauseMenu.OnGamePaused += Pause; + pauseMenu.OnGameResumed += Resume; + + Debug.Log("[DivingGameManager] Subscribed to PauseMenu events"); + } + else + { + Debug.LogWarning("[DivingGameManager] PauseMenu not found. Pause functionality won't work properly."); + } + + // Register this manager with the global GameManager + if (GameManager.Instance != null) + { + GameManager.Instance.RegisterPausableComponent(this); + } + // Subscribe to SceneOrientationEnforcer's event if (SceneOrientationEnforcer.Instance != null) { @@ -144,6 +175,23 @@ namespace Minigames.DivingForPictures { SceneOrientationEnforcer.Instance.OnOrientationCorrect -= InitializeGame; } + + // Unsubscribe from PauseMenu events + PauseMenu pauseMenu = PauseMenu.Instance; + if (pauseMenu != null) + { + pauseMenu.OnGamePaused -= Pause; + pauseMenu.OnGameResumed -= Resume; + } + + // Unregister from GameManager + if (GameManager.Instance != null) + { + GameManager.Instance.UnregisterPausableComponent(this); + } + + // Unregister all pausable components + _pausableComponents.Clear(); } private void Update() @@ -651,5 +699,74 @@ namespace Minigames.DivingForPictures // Final assignment to ensure exact target value OnVelocityFactorChanged?.Invoke(_currentVelocityFactor); } + + /// + /// Register a component as pausable with this manager + /// + /// The pausable component to register + public void RegisterPausableComponent(IPausable component) + { + if (component != null && !_pausableComponents.Contains(component)) + { + _pausableComponents.Add(component); + + // If the game is already paused, pause the component immediately + if (_isPaused) + { + component.Pause(); + } + + Debug.Log($"[DivingGameManager] Registered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}"); + } + } + + /// + /// Unregister a pausable component + /// + /// The pausable component to unregister + public void UnregisterPausableComponent(IPausable component) + { + if (component != null && _pausableComponents.Contains(component)) + { + _pausableComponents.Remove(component); + Debug.Log($"[DivingGameManager] Unregistered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}"); + } + } + + /// + /// Pause the game and all registered components + /// + public void Pause() + { + if (_isPaused) return; // Already paused + + _isPaused = true; + + // Pause all registered components + foreach (var component in _pausableComponents) + { + component.Pause(); + } + + Debug.Log($"[DivingGameManager] Game paused. Paused {_pausableComponents.Count} components."); + } + + /// + /// Resume the game and all registered components + /// + public void Resume() + { + if (!_isPaused) return; // Already running + + _isPaused = false; + + // Resume all registered components + foreach (var component in _pausableComponents) + { + component.Resume(); + } + + Debug.Log($"[DivingGameManager] Game resumed. Resumed {_pausableComponents.Count} components."); + } } } diff --git a/Assets/Scripts/Minigames/DivingForPictures/Obstacles/FloatingObstacle.cs b/Assets/Scripts/Minigames/DivingForPictures/Obstacles/FloatingObstacle.cs index f23a825b..4ad62ade 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Obstacles/FloatingObstacle.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Obstacles/FloatingObstacle.cs @@ -3,6 +3,7 @@ using System.Collections; using AppleHills.Core.Settings; using Pooling; using Utils; +using AppleHills.Core.Interfaces; namespace Minigames.DivingForPictures { @@ -12,7 +13,7 @@ namespace Minigames.DivingForPictures /// Once an obstacle hits the player, its collider is disabled to prevent further collisions. /// Uses coroutines for better performance instead of Update() calls. /// - public class FloatingObstacle : MonoBehaviour, IPoolable + public class FloatingObstacle : MonoBehaviour, IPoolable, IPausable { [Header("Obstacle Properties")] [Tooltip("Index of the prefab this obstacle was created from")] @@ -57,6 +58,12 @@ namespace Minigames.DivingForPictures private float _screenNormalizationFactor = 1.0f; private IDivingMinigameSettings _settings; + // Pause state + private bool _isPaused = false; + + // IPausable implementation + public bool IsPaused => _isPaused; + private void Awake() { _collider = GetComponent(); @@ -115,31 +122,67 @@ namespace Minigames.DivingForPictures private void OnEnable() { - StartObstacleBehavior(); + // Only start coroutines if not paused + if (!_isPaused) + { + StartObstacleCoroutines(); + } + + // Screen bounds are calculated in CheckIfOffScreen method } private void OnDisable() { - StopObstacleBehavior(); + // Stop coroutines when disabled + StopObstacleCoroutines(); } /// - /// Starts the obstacle behavior coroutines + /// Pause this obstacle's movement and behavior /// - private void StartObstacleBehavior() + public void Pause() { - if (enableMovement) + if (_isPaused) return; // Already paused + + _isPaused = true; + StopObstacleCoroutines(); + + Debug.Log($"[FloatingObstacle] Paused obstacle: {name}"); + } + + /// + /// Resume this obstacle's movement and behavior + /// + public void Resume() + { + if (!_isPaused) return; // Already running + + _isPaused = false; + StartObstacleCoroutines(); + + Debug.Log($"[FloatingObstacle] Resumed obstacle: {name}"); + } + + /// + /// Start all coroutines used by this obstacle + /// + private void StartObstacleCoroutines() + { + if (enableMovement && _movementCoroutine == null) { _movementCoroutine = StartCoroutine(MovementCoroutine()); } - _offScreenCheckCoroutine = StartCoroutine(OffScreenCheckCoroutine()); + if (_offScreenCheckCoroutine == null) + { + _offScreenCheckCoroutine = StartCoroutine(OffScreenCheckCoroutine()); + } } /// - /// Stops all obstacle behavior coroutines + /// Stop all coroutines used by this obstacle /// - private void StopObstacleBehavior() + private void StopObstacleCoroutines() { if (_movementCoroutine != null) { @@ -270,7 +313,7 @@ namespace Minigames.DivingForPictures { // CRITICAL: Stop all behavior first to prevent race conditions // This ensures no more off-screen checks or movement happen during pool return - StopObstacleBehavior(); + StopObstacleCoroutines(); if (spawner != null) { @@ -333,7 +376,7 @@ namespace Minigames.DivingForPictures public void OnDespawn() { // Stop all coroutines before returning to pool - StopObstacleBehavior(); + StopObstacleCoroutines(); // Re-enable collider for next use (in case it was disabled) if (_collider != null) @@ -364,8 +407,8 @@ namespace Minigames.DivingForPictures // Restart coroutines to apply movement change if (gameObject.activeInHierarchy) { - StopObstacleBehavior(); - StartObstacleBehavior(); + StopObstacleCoroutines(); + StartObstacleCoroutines(); } } diff --git a/Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleSpawner.cs b/Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleSpawner.cs index d30c766f..e03bd5ec 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleSpawner.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Obstacles/ObstacleSpawner.cs @@ -4,6 +4,7 @@ using UnityEngine; using UnityEngine.Events; using Pooling; using AppleHills.Core.Settings; +using AppleHills.Core.Interfaces; namespace Minigames.DivingForPictures { @@ -11,7 +12,7 @@ namespace Minigames.DivingForPictures /// Spawns and manages mobile obstacles for the diving minigame. /// Uses object pooling and validates spawn positions to avoid colliding with tiles. /// - public class ObstacleSpawner : MonoBehaviour + public class ObstacleSpawner : MonoBehaviour, IPausable { [Header("Obstacle Prefabs")] [Tooltip("List of possible obstacle prefabs to spawn")] @@ -34,11 +35,19 @@ namespace Minigames.DivingForPictures private float _screenBottom; private float _spawnRangeX; private Coroutine _spawnCoroutine; + private Coroutine _moveCoroutine; + private Coroutine _despawnCoroutine; private readonly List _activeObstacles = new List(); private int _obstacleCounter = 0; // Counter for unique obstacle naming private bool _isSurfacing = false; // Flag to track surfacing state private float _velocityFactor = 1.0f; // Current velocity factor from the game manager + // Pause state + private bool _isPaused = false; + + // IPausable implementation + public bool IsPaused => _isPaused; + private void Awake() { _mainCamera = Camera.main; @@ -75,14 +84,15 @@ namespace Minigames.DivingForPictures private void Start() { - - // Find DivingGameManager and subscribe to its initialization event DivingGameManager gameManager = FindFirstObjectByType(); if (gameManager != null) { gameManager.OnGameInitialized += Initialize; + // Register with the DivingGameManager for pause/resume events + gameManager.RegisterPausableComponent(this); + // If game is already initialized, initialize immediately if (gameManager.GetType().GetField("_isGameInitialized", System.Reflection.BindingFlags.NonPublic | @@ -97,20 +107,130 @@ namespace Minigames.DivingForPictures Initialize(); } } + + private void OnDestroy() + { + // Unregister from DivingGameManager + DivingGameManager gameManager = FindFirstObjectByType(); + if (gameManager != null) + { + gameManager.UnregisterPausableComponent(this); + } + + // Clean up any active coroutines + StopAllCoroutines(); + } /// /// Initializes the obstacle spawner when triggered by DivingGameManager /// private void Initialize() { + // Calculate screen bounds CalculateScreenBounds(); - StartSpawning(); + + // Start coroutines if not paused + StartSpawnCoroutine(); + StartMoveCoroutine(); + StartDespawnCoroutine(); + Debug.Log("[ObstacleSpawner] Initialized"); } - - private void OnDestroy() + + /// + /// Pauses the spawner and all associated processes + /// + public void Pause() { - StopSpawning(); + 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(); + if (obstacleComponent != null) + { + obstacleComponent.Pause(); + } + } + } + + Debug.Log($"[ObstacleSpawner] Paused with {_activeObstacles.Count} active obstacles"); + } + + /// + /// Resumes the spawner and all associated processes + /// + public void Resume() + { + 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(); + if (obstacleComponent != null) + { + obstacleComponent.Resume(); + } + } + } + + Debug.Log($"[ObstacleSpawner] Resumed with {_activeObstacles.Count} active obstacles"); + } + + /// + /// Starts the obstacle spawning coroutine if not already running + /// + private void StartSpawnCoroutine() + { + if (_spawnCoroutine == null && !_isPaused && !_isSurfacing) + { + _spawnCoroutine = StartCoroutine(SpawnObstacleRoutine()); + } + } + + /// + /// Starts the obstacle movement coroutine if not already running + /// + private void StartMoveCoroutine() + { + if (_moveCoroutine == null && !_isPaused) + { + _moveCoroutine = StartCoroutine(MoveObstaclesRoutine()); + } + } + + /// + /// Starts the obstacle despawning coroutine if not already running + /// + private void StartDespawnCoroutine() + { + if (_despawnCoroutine == null && !_isPaused) + { + _despawnCoroutine = StartCoroutine(DespawnObstaclesRoutine()); + } } /// @@ -444,8 +564,14 @@ namespace Minigames.DivingForPictures _settings.ObstacleMinMoveSpeed, _settings.ObstacleMaxMoveSpeed); - // Set spawner reference (since FloatingObstacle has this built-in now) + // Set spawner reference obstacleComponent.SetSpawner(this); + + // If spawner is already paused, pause the obstacle immediately + if (_isPaused) + { + obstacleComponent.Pause(); + } } } @@ -559,6 +685,147 @@ namespace Minigames.DivingForPictures /// public int ActiveObstacleCount => _activeObstacles.Count; + /// + /// Coroutine that handles obstacle spawning at regular intervals + /// + private IEnumerator SpawnObstacleRoutine() + { + Debug.Log("[ObstacleSpawner] Started spawning coroutine"); + + 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; + + Debug.Log("[ObstacleSpawner] Spawning coroutine ended"); + } + + /// + /// 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 + /// + private IEnumerator MoveObstaclesRoutine() + { + Debug.Log("[ObstacleSpawner] Started obstacle monitoring coroutine"); + + // 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; + + Debug.Log("[ObstacleSpawner] Obstacle monitoring coroutine ended"); + } + + /// + /// Coroutine that checks for obstacles that are off-screen and should be despawned + /// + private IEnumerator DespawnObstaclesRoutine() + { + const float checkInterval = 0.5f; // Check every half second + Debug.Log("[ObstacleSpawner] Started despawn coroutine with interval: " + checkInterval); + + while (enabled && gameObject.activeInHierarchy && !_isPaused) + { + // Calculate screen bounds for despawning + float despawnBuffer = 2f; // Extra buffer beyond screen edges + + if (_mainCamera == null) + { + _mainCamera = Camera.main; + 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 obstaclesToRemove = new List(); + + 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(); + 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; + + Debug.Log("[ObstacleSpawner] Despawn coroutine ended"); + } + #if UNITY_EDITOR private void OnDrawGizmosSelected() { diff --git a/Assets/Scripts/Minigames/DivingForPictures/Tiles/TrenchTileSpawner.cs b/Assets/Scripts/Minigames/DivingForPictures/Tiles/TrenchTileSpawner.cs index 827cd6de..f5a68231 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/Tiles/TrenchTileSpawner.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/Tiles/TrenchTileSpawner.cs @@ -6,13 +6,14 @@ using UnityEngine.Serialization; using Pooling; using AppleHills.Core.Settings; using Utils; +using AppleHills.Core.Interfaces; namespace Minigames.DivingForPictures { /// /// Spawns and manages trench wall tiles for the endless descender minigame. /// - public class TrenchTileSpawner : MonoBehaviour + public class TrenchTileSpawner : MonoBehaviour, IPausable { [Header("Tile Prefabs")] [Tooltip("List of possible trench tile prefabs.")] @@ -67,6 +68,12 @@ namespace Minigames.DivingForPictures // Screen normalization private float _screenNormalizationFactor = 1.0f; + // Pause state + private bool _isPaused = false; + + // IPausable implementation + public bool IsPaused => _isPaused; + private void Awake() { _mainCamera = Camera.main; @@ -124,6 +131,9 @@ namespace Minigames.DivingForPictures { gameManager.OnGameInitialized += Initialize; + // Register with the DivingGameManager for pause/resume events + gameManager.RegisterPausableComponent(this); + // If game is already initialized, initialize immediately if (gameManager.GetType().GetField("_isGameInitialized", System.Reflection.BindingFlags.NonPublic | @@ -139,6 +149,71 @@ namespace Minigames.DivingForPictures } } + private void OnDestroy() + { + // Unregister from DivingGameManager + DivingGameManager gameManager = FindFirstObjectByType(); + if (gameManager != null) + { + gameManager.UnregisterPausableComponent(this); + } + } + + /// + /// Pauses the spawner and all associated processes + /// + public void Pause() + { + if (_isPaused) return; // Already paused + + _isPaused = true; + + // Stop all active coroutines but save their references + if (_movementCoroutine != null) + { + StopCoroutine(_movementCoroutine); + _movementCoroutine = null; + } + + if (_tileDestructionCoroutine != null) + { + StopCoroutine(_tileDestructionCoroutine); + _tileDestructionCoroutine = null; + } + + if (_tileSpawningCoroutine != null) + { + StopCoroutine(_tileSpawningCoroutine); + _tileSpawningCoroutine = null; + } + + if (_speedRampingCoroutine != null) + { + StopCoroutine(_speedRampingCoroutine); + _speedRampingCoroutine = null; + } + + Debug.Log("[TrenchTileSpawner] Paused"); + } + + /// + /// Resumes the spawner and all associated processes + /// + public void Resume() + { + if (!_isPaused) return; // Already running + + _isPaused = false; + + // Restart all necessary coroutines + StartMovementCoroutine(); + StartTileDestructionCoroutine(); + StartTileSpawningCoroutine(); + StartSpeedRampingCoroutine(); + + Debug.Log("[TrenchTileSpawner] Resumed"); + } + /// /// Initializes the tile spawner when triggered by DivingGameManager /// @@ -385,25 +460,57 @@ namespace Minigames.DivingForPictures } /// - /// Starts the movement coroutine and stores its reference + /// Starts the movement coroutine if it's not already running /// private void StartMovementCoroutine() { - if (_movementCoroutine != null) + if (_movementCoroutine == null && !_isPaused) { - StopCoroutine(_movementCoroutine); + _movementCoroutine = StartCoroutine(MoveActiveTilesRoutine()); } - _movementCoroutine = StartCoroutine(MovementCoroutine()); } + /// + /// Starts the tile destruction coroutine if it's not already running + /// + private void StartTileDestructionCoroutine() + { + if (_tileDestructionCoroutine == null && !_isPaused) + { + _tileDestructionCoroutine = StartCoroutine(TileDestructionRoutine()); + } + } + + /// + /// Starts the tile spawning coroutine if it's not already running + /// + private void StartTileSpawningCoroutine() + { + if (_tileSpawningCoroutine == null && !_isPaused && !_stopSpawning) + { + _tileSpawningCoroutine = StartCoroutine(TileSpawningRoutine()); + } + } + + /// + /// Starts the speed ramping coroutine if it's not already running + /// + private void StartSpeedRampingCoroutine() + { + if (_speedRampingCoroutine == null && !_isPaused) + { + _speedRampingCoroutine = StartCoroutine(SpeedRampingRoutine()); + } + } + /// /// Coroutine that handles obstacle movement using normalized screen-relative speed /// - private IEnumerator MovementCoroutine() + private IEnumerator MoveActiveTilesRoutine() { Debug.Log($"[TrenchTileSpawner] Started movement coroutine with normalized speed: {_baseMoveSpeed:F3}"); - while (enabled && gameObject.activeInHierarchy) + while (enabled && gameObject.activeInHierarchy && !_isPaused) { // Skip if no active tiles if (_activeTiles.Count == 0) @@ -431,167 +538,194 @@ namespace Minigames.DivingForPictures // Wait for next frame yield return null; } + + // Clear coroutine reference when stopped + _movementCoroutine = null; + } + + /// + /// Coroutine that checks for tiles to destroy periodically + /// + private IEnumerator TileDestructionRoutine() + { + const float checkInterval = 0.5f; // Check every half second + Debug.Log($"[TrenchTileSpawner] Started tile destruction coroutine with interval: {checkInterval}s"); + + while (enabled && gameObject.activeInHierarchy && !_isPaused) + { + // 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); + } + + // Clear coroutine reference when stopped + _tileDestructionCoroutine = null; } /// - /// Handles the movement of all active tiles based on the current velocity + /// Coroutine that checks if new tiles need to be spawned periodically /// - private void HandleMovement() + private IEnumerator TileSpawningRoutine() { - foreach (var tile in _activeTiles) - { - if (tile != null) - { - // Use velocity factor to determine direction - Vector3 direction = Vector3.up * Mathf.Sign(_velocityFactor); - float speed = _currentVelocity; - - // Apply movement - tile.transform.position += direction * speed; - } - } - } - - /// - /// Check for tiles that have moved off screen and should be destroyed or returned to pool - /// - private void HandleTileDestruction() - { - if (_activeTiles.Count == 0) return; + const float checkInterval = 0.2f; // Check every fifth of a second + Debug.Log($"[TrenchTileSpawner] Started tile spawning coroutine with interval: {checkInterval}s"); - GameObject topTile = _activeTiles[0]; - if (topTile == null) + while (enabled && gameObject.activeInHierarchy && !_isPaused && !_stopSpawning) { - _activeTiles.RemoveAt(0); - return; - } - - 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) + // Check if we need to spawn new tiles + if (_activeTiles.Count == 0) { - // Find the prefab index for this tile - int prefabIndex = GetPrefabIndex(topTile); - if (prefabIndex >= 0) - { - _tilePool.ReturnTile(topTile, prefabIndex); - } - else - { - Destroy(topTile); - } - } - else - { - Destroy(topTile); - } - } - } - - /// - /// Check if new tiles need to be spawned - /// - private void HandleTileSpawning() - { - if (_activeTiles.Count == 0) - { - // If we have no active tiles and spawning is stopped, trigger the event - if (_stopSpawning) - { - onLastTileLeft.Invoke(); - } - return; - } - - GameObject bottomTile = _activeTiles[^1]; - if (bottomTile == null) - { - _activeTiles.RemoveAt(_activeTiles.Count - 1); - return; - } - - // Get the tile height once to use in all calculations - float tileHeight = GetTileHeight(bottomTile); - - // If we're in stop spawning mode, don't spawn new tiles - 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 the bottom of the tile is above the top of the screen - isLastTileLeaving = bottomTile.transform.position.y - tileHeight / 2 > _screenTop + _settings.TileSpawnBuffer; - } - else - { - // When descending, check if the top of the tile is below the bottom of the screen - isLastTileLeaving = bottomTile.transform.position.y + tileHeight / 2 < _screenBottom - _settings.TileSpawnBuffer; - } - - if (isLastTileLeaving) + // If we have no active tiles and spawning is stopped, trigger the event + if (_stopSpawning) { onLastTileLeft.Invoke(); } } - return; - } - - 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; + 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); } - if (shouldSpawn) - { - SpawnTileAtY(newY); - } + // Clear coroutine reference when stopped + _tileSpawningCoroutine = null; } - + /// - /// Handle increasing the movement speed over time + /// Coroutine that handles increasing the movement speed over time /// - private void HandleSpeedRamping() + private IEnumerator SpeedRampingRoutine() { - _speedUpTimer += Time.deltaTime; - if (_speedUpTimer >= _settings.SpeedUpInterval) + 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) { + // Increase the base move speed up to the maximum _baseMoveSpeed = Mathf.Min(_baseMoveSpeed + _settings.SpeedUpFactor, _settings.MaxNormalizedMoveSpeed); - _speedUpTimer = 0f; + + // Recalculate velocity with the new base speed + CalculateVelocity(); + + // Wait for the next check interval + yield return new WaitForSeconds(checkInterval); } + + // Clear coroutine reference when stopped + _speedRampingCoroutine = null; } /// @@ -816,217 +950,5 @@ namespace Minigames.DivingForPictures } } } - - /// - /// Starts the tile destruction coroutine and stores its reference - /// - private void StartTileDestructionCoroutine() - { - if (_tileDestructionCoroutine != null) - { - StopCoroutine(_tileDestructionCoroutine); - } - _tileDestructionCoroutine = StartCoroutine(TileDestructionCoroutine()); - } - - /// - /// Coroutine that checks for tiles to destroy periodically - /// - 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); - } - } - - /// - /// Starts the tile spawning coroutine and stores its reference - /// - private void StartTileSpawningCoroutine() - { - if (_tileSpawningCoroutine != null) - { - StopCoroutine(_tileSpawningCoroutine); - } - _tileSpawningCoroutine = StartCoroutine(TileSpawningCoroutine()); - } - - /// - /// Coroutine that checks if new tiles need to be spawned periodically - /// - 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); - } - } - - /// - /// Starts the speed ramping coroutine and stores its reference - /// - private void StartSpeedRampingCoroutine() - { - if (_speedRampingCoroutine != null) - { - StopCoroutine(_speedRampingCoroutine); - } - _speedRampingCoroutine = StartCoroutine(SpeedRampingCoroutine()); - } - - /// - /// Coroutine that handles increasing the movement speed over time - /// - private IEnumerator SpeedRampingCoroutine() - { - const float checkInterval = 1.0f; // Check once per second as requested - Debug.Log($"[TrenchTileSpawner] Started speed ramping coroutine with interval: {checkInterval}s"); - - while (enabled && gameObject.activeInHierarchy) - { - // Increase the base move speed up to the maximum - _baseMoveSpeed = Mathf.Min(_baseMoveSpeed + _settings.SpeedUpFactor, _settings.MaxNormalizedMoveSpeed); - - // Recalculate velocity with the new base speed - CalculateVelocity(); - - // Wait for the next check interval - yield return new WaitForSeconds(checkInterval); - } - } } } diff --git a/Assets/Scripts/UI/PauseMenu.cs b/Assets/Scripts/UI/PauseMenu.cs index 650500e4..436ca170 100644 --- a/Assets/Scripts/UI/PauseMenu.cs +++ b/Assets/Scripts/UI/PauseMenu.cs @@ -35,6 +35,13 @@ namespace UI public event Action OnGamePaused; public event Action OnGameResumed; + private bool _isPaused = false; + + /// + /// Returns whether the game is currently paused + /// + public bool IsPaused => _isPaused; + private void Start() { // Subscribe to scene loaded events @@ -92,6 +99,12 @@ namespace UI // Change input mode to UI when menu is open InputManager.Instance.SetInputMode(InputMode.UI); + + // Set paused flag and broadcast event + _isPaused = true; + OnGamePaused?.Invoke(); + + Debug.Log("[PauseMenu] Game Paused"); } /// @@ -108,6 +121,12 @@ namespace UI // Change input mode back to Game when menu is closed if(resetInput) InputManager.Instance.SetInputMode(InputMode.Game); + + // Clear paused flag and broadcast event + _isPaused = false; + OnGameResumed?.Invoke(); + + Debug.Log("[PauseMenu] Game Resumed"); } ///