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

@@ -6,13 +6,14 @@ using UnityEngine.Serialization;
using Pooling;
using AppleHills.Core.Settings;
using Utils;
using AppleHills.Core.Interfaces;
namespace Minigames.DivingForPictures
{
/// <summary>
/// Spawns and manages trench wall tiles for the endless descender minigame.
/// </summary>
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<DivingGameManager>();
if (gameManager != null)
{
gameManager.UnregisterPausableComponent(this);
}
}
/// <summary>
/// Pauses the spawner and all associated processes
/// </summary>
public void Pause()
{
if (_isPaused) return; // Already paused
_isPaused = true;
// Stop all active coroutines but save their references
if (_movementCoroutine != null)
{
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");
}
/// <summary>
/// Resumes the spawner and all associated processes
/// </summary>
public void Resume()
{
if (!_isPaused) return; // Already running
_isPaused = false;
// Restart all necessary coroutines
StartMovementCoroutine();
StartTileDestructionCoroutine();
StartTileSpawningCoroutine();
StartSpeedRampingCoroutine();
Debug.Log("[TrenchTileSpawner] Resumed");
}
/// <summary>
/// Initializes the tile spawner when triggered by DivingGameManager
/// </summary>
@@ -385,25 +460,57 @@ namespace Minigames.DivingForPictures
}
/// <summary>
/// Starts the movement coroutine and stores its reference
/// Starts the movement coroutine if it's not already running
/// </summary>
private void StartMovementCoroutine()
{
if (_movementCoroutine != null)
if (_movementCoroutine == null && !_isPaused)
{
StopCoroutine(_movementCoroutine);
_movementCoroutine = StartCoroutine(MoveActiveTilesRoutine());
}
_movementCoroutine = StartCoroutine(MovementCoroutine());
}
/// <summary>
/// Starts the tile destruction coroutine if it's not already running
/// </summary>
private void StartTileDestructionCoroutine()
{
if (_tileDestructionCoroutine == null && !_isPaused)
{
_tileDestructionCoroutine = StartCoroutine(TileDestructionRoutine());
}
}
/// <summary>
/// Starts the tile spawning coroutine if it's not already running
/// </summary>
private void StartTileSpawningCoroutine()
{
if (_tileSpawningCoroutine == null && !_isPaused && !_stopSpawning)
{
_tileSpawningCoroutine = StartCoroutine(TileSpawningRoutine());
}
}
/// <summary>
/// Starts the speed ramping coroutine if it's not already running
/// </summary>
private void StartSpeedRampingCoroutine()
{
if (_speedRampingCoroutine == null && !_isPaused)
{
_speedRampingCoroutine = StartCoroutine(SpeedRampingRoutine());
}
}
/// <summary>
/// Coroutine that handles obstacle movement using normalized screen-relative speed
/// </summary>
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;
}
/// <summary>
/// Coroutine that checks for tiles to destroy periodically
/// </summary>
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;
}
/// <summary>
/// Handles the movement of all active tiles based on the current velocity
/// Coroutine that checks if new tiles need to be spawned periodically
/// </summary>
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;
}
}
}
/// <summary>
/// Check for tiles that have moved off screen and should be destroyed or returned to pool
/// </summary>
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);
}
}
}
/// <summary>
/// Check if new tiles need to be spawned
/// </summary>
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;
}
/// <summary>
/// Handle increasing the movement speed over time
/// Coroutine that handles increasing the movement speed over time
/// </summary>
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;
}
/// <summary>
@@ -816,217 +950,5 @@ namespace Minigames.DivingForPictures
}
}
}
/// <summary>
/// Starts the tile destruction coroutine and stores its reference
/// </summary>
private void StartTileDestructionCoroutine()
{
if (_tileDestructionCoroutine != null)
{
StopCoroutine(_tileDestructionCoroutine);
}
_tileDestructionCoroutine = StartCoroutine(TileDestructionCoroutine());
}
/// <summary>
/// Coroutine that checks for tiles to destroy periodically
/// </summary>
private IEnumerator TileDestructionCoroutine()
{
const float checkInterval = 0.5f; // Check every half second as requested
Debug.Log($"[TrenchTileSpawner] Started tile destruction coroutine with interval: {checkInterval}s");
while (enabled && gameObject.activeInHierarchy)
{
// Check and handle tile destruction
if (_activeTiles.Count > 0)
{
GameObject topTile = _activeTiles[0];
if (topTile == null)
{
_activeTiles.RemoveAt(0);
}
else
{
float tileHeight = GetTileHeight(topTile);
bool shouldDestroy;
if (_isSurfacing)
{
// When surfacing, destroy tiles at the bottom
shouldDestroy = topTile.transform.position.y + tileHeight / 2 < _screenBottom - _settings.TileSpawnBuffer;
}
else
{
// When descending, destroy tiles at the top
shouldDestroy = topTile.transform.position.y - tileHeight / 2 > _screenTop + _settings.TileSpawnBuffer;
}
if (shouldDestroy)
{
_activeTiles.RemoveAt(0);
onTileDestroyed?.Invoke(topTile);
if (_devSettings != null && _devSettings.TrenchTileUseObjectPooling && _tilePool != null)
{
// Find the prefab index for this tile
int prefabIndex = GetPrefabIndex(topTile);
if (prefabIndex >= 0)
{
_tilePool.ReturnTile(topTile, prefabIndex);
}
else
{
Destroy(topTile);
}
}
else
{
Destroy(topTile);
}
}
}
}
// Wait for the next check interval
yield return new WaitForSeconds(checkInterval);
}
}
/// <summary>
/// Starts the tile spawning coroutine and stores its reference
/// </summary>
private void StartTileSpawningCoroutine()
{
if (_tileSpawningCoroutine != null)
{
StopCoroutine(_tileSpawningCoroutine);
}
_tileSpawningCoroutine = StartCoroutine(TileSpawningCoroutine());
}
/// <summary>
/// Coroutine that checks if new tiles need to be spawned periodically
/// </summary>
private IEnumerator TileSpawningCoroutine()
{
const float checkInterval = 0.2f; // Check every half second as requested
Debug.Log($"[TrenchTileSpawner] Started tile spawning coroutine with interval: {checkInterval}s");
while (enabled && gameObject.activeInHierarchy)
{
// Check if we need to spawn new tiles
if (_activeTiles.Count == 0)
{
// If we have no active tiles and spawning is stopped, trigger the event
if (_stopSpawning)
{
onLastTileLeft.Invoke();
}
}
else
{
GameObject bottomTile = _activeTiles[^1];
if (bottomTile == null)
{
_activeTiles.RemoveAt(_activeTiles.Count - 1);
}
else
{
// Get the tile height once to use in all calculations
float tileHeight = GetTileHeight(bottomTile);
// If we're in stop spawning mode, check if last tile is leaving
if (_stopSpawning)
{
// Check if this is the last tile, and if it's about to leave the screen
if (_activeTiles.Count == 1)
{
bool isLastTileLeaving;
if (_isSurfacing)
{
// When surfacing, check if bottom of tile is above top of screen
isLastTileLeaving = bottomTile.transform.position.y - tileHeight / 2 > _screenTop + _settings.TileSpawnBuffer;
}
else
{
// When descending, check if top of tile is below bottom of screen
isLastTileLeaving = bottomTile.transform.position.y + tileHeight / 2 < _screenBottom - _settings.TileSpawnBuffer;
}
if (isLastTileLeaving)
{
onLastTileLeft.Invoke();
}
}
}
else
{
// Normal spawning mode
bool shouldSpawn;
float newY;
if (_isSurfacing)
{
// When surfacing, spawn new tiles at the top
float topEdge = bottomTile.transform.position.y + tileHeight / 2;
shouldSpawn = topEdge < _screenTop + _settings.TileSpawnBuffer;
newY = bottomTile.transform.position.y + tileHeight;
}
else
{
// When descending, spawn new tiles at the bottom
float bottomEdge = bottomTile.transform.position.y - tileHeight / 2;
shouldSpawn = bottomEdge > _screenBottom - _settings.TileSpawnBuffer;
newY = bottomTile.transform.position.y - tileHeight;
}
if (shouldSpawn)
{
SpawnTileAtY(newY);
}
}
}
}
// Wait for the next check interval
yield return new WaitForSeconds(checkInterval);
}
}
/// <summary>
/// Starts the speed ramping coroutine and stores its reference
/// </summary>
private void StartSpeedRampingCoroutine()
{
if (_speedRampingCoroutine != null)
{
StopCoroutine(_speedRampingCoroutine);
}
_speedRampingCoroutine = StartCoroutine(SpeedRampingCoroutine());
}
/// <summary>
/// Coroutine that handles increasing the movement speed over time
/// </summary>
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);
}
}
}
}