Add a pausable interface and make minigameobjecs pausable

This commit is contained in:
Michal Pikulski
2025-10-08 12:36:08 +02:00
parent 3807ac652c
commit 0e17516df7
11 changed files with 1101 additions and 395 deletions

View File

@@ -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.
/// </summary>
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<GameObject> _activeObstacles = new List<GameObject>();
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<DivingGameManager>();
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<DivingGameManager>();
if (gameManager != null)
{
gameManager.UnregisterPausableComponent(this);
}
// Clean up any active coroutines
StopAllCoroutines();
}
/// <summary>
/// Initializes the obstacle spawner when triggered by DivingGameManager
/// </summary>
private void Initialize()
{
// Calculate screen bounds
CalculateScreenBounds();
StartSpawning();
// Start coroutines if not paused
StartSpawnCoroutine();
StartMoveCoroutine();
StartDespawnCoroutine();
Debug.Log("[ObstacleSpawner] Initialized");
}
private void OnDestroy()
/// <summary>
/// Pauses the spawner and all associated processes
/// </summary>
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<FloatingObstacle>();
if (obstacleComponent != null)
{
obstacleComponent.Pause();
}
}
}
Debug.Log($"[ObstacleSpawner] Paused with {_activeObstacles.Count} active obstacles");
}
/// <summary>
/// Resumes the spawner and all associated processes
/// </summary>
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<FloatingObstacle>();
if (obstacleComponent != null)
{
obstacleComponent.Resume();
}
}
}
Debug.Log($"[ObstacleSpawner] Resumed with {_activeObstacles.Count} active obstacles");
}
/// <summary>
/// Starts the obstacle spawning coroutine if not already running
/// </summary>
private void StartSpawnCoroutine()
{
if (_spawnCoroutine == null && !_isPaused && !_isSurfacing)
{
_spawnCoroutine = StartCoroutine(SpawnObstacleRoutine());
}
}
/// <summary>
/// Starts the obstacle movement coroutine if not already running
/// </summary>
private void StartMoveCoroutine()
{
if (_moveCoroutine == null && !_isPaused)
{
_moveCoroutine = StartCoroutine(MoveObstaclesRoutine());
}
}
/// <summary>
/// Starts the obstacle despawning coroutine if not already running
/// </summary>
private void StartDespawnCoroutine()
{
if (_despawnCoroutine == null && !_isPaused)
{
_despawnCoroutine = StartCoroutine(DespawnObstaclesRoutine());
}
}
/// <summary>
@@ -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
/// </summary>
public int ActiveObstacleCount => _activeObstacles.Count;
/// <summary>
/// Coroutine that handles obstacle spawning at regular intervals
/// </summary>
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");
}
/// <summary>
/// Coroutine that handles checking obstacle positions
/// Unlike the previous implementation, we don't need to move obstacles manually
/// since the FloatingObstacle handles its own movement via coroutines
/// </summary>
private IEnumerator MoveObstaclesRoutine()
{
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");
}
/// <summary>
/// Coroutine that checks for obstacles that are off-screen and should be despawned
/// </summary>
private IEnumerator DespawnObstaclesRoutine()
{
const float checkInterval = 0.5f; // Check every half second
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<GameObject> obstaclesToRemove = new List<GameObject>();
foreach (var obstacle in _activeObstacles)
{
if (obstacle == null)
{
obstaclesToRemove.Add(obstacle);
continue;
}
// Check if obstacle is out of screen bounds
float obstacleY = obstacle.transform.position.y;
bool shouldDespawn;
if (_isSurfacing)
{
// When surfacing, despawn obstacles below the bottom edge
shouldDespawn = obstacleY < bottomEdge;
}
else
{
// When descending, despawn obstacles above the top edge
shouldDespawn = obstacleY > topEdge;
}
if (shouldDespawn)
{
obstaclesToRemove.Add(obstacle);
}
}
// Remove and despawn obstacles
foreach (var obstacle in obstaclesToRemove)
{
FloatingObstacle obstacleComponent = obstacle?.GetComponent<FloatingObstacle>();
if (obstacleComponent != null)
{
// Instead of calling a non-existent DespawnObstacle method,
// use FloatingObstacle's ForceReturnToPool method
obstacleComponent.ForceReturnToPool();
}
else
{
// Fallback if component not found
ReturnObstacleToPool(obstacle, -1);
}
}
yield return new WaitForSeconds(checkInterval);
}
// Clear coroutine reference when stopped
_despawnCoroutine = null;
Debug.Log("[ObstacleSpawner] Despawn coroutine ended");
}
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{