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");
}
///