Clean up IPausable interafaces a little bit and start refactoring the pause-game flow in the minigame

This commit is contained in:
Michal Adam Pikulski
2025-10-23 09:31:09 +02:00
parent 35acaddca5
commit ef3b4bf369
15 changed files with 423 additions and 579 deletions

View File

@@ -7,7 +7,7 @@ using Minigames.DivingForPictures.PictureCamera;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Bootstrap;
using UI;
using UnityEngine;
using UnityEngine.Events;
@@ -38,24 +38,24 @@ namespace Minigames.DivingForPictures
public CameraViewfinderManager viewfinderManager;
// Settings reference
private IDivingMinigameSettings settings;
private IDivingMinigameSettings _settings;
// Private state variables
private int playerScore = 0;
private float currentSpawnProbability;
private float lastSpawnTime = -100f;
private float timeSinceLastSpawn = 0f;
private List<Monster> activeMonsters = new List<Monster>();
private int _playerScore = 0;
private float _currentSpawnProbability;
private float _lastSpawnTime = -100f;
private float _timeSinceLastSpawn = 0f;
private List<Monster> _activeMonsters = new List<Monster>();
// Velocity management
// Velocity state tracking
private float currentVelocityFactor = 1.0f; // 1.0 = normal descent speed, -1.0 * surfacingSpeedFactor = full surfacing speed
private Coroutine velocityTransitionCoroutine;
private Coroutine surfacingSequenceCoroutine;
private float _currentVelocityFactor = 1.0f; // 1.0 = normal descent speed, -1.0 * surfacingSpeedFactor = full surfacing speed
private Coroutine _velocityTransitionCoroutine;
private Coroutine _surfacingSequenceCoroutine;
// Public properties
public int PlayerScore => playerScore;
public float CurrentVelocityFactor => currentVelocityFactor;
public int PlayerScore => _playerScore;
public float CurrentVelocityFactor => _currentVelocityFactor;
// Events
public event Action<int> OnScoreChanged;
@@ -67,8 +67,8 @@ namespace Minigames.DivingForPictures
public event Action<float> OnVelocityFactorChanged;
// Private state variables for rope system
private int currentRopeIndex = 0;
private bool isGameOver = false;
private int _currentRopeIndex = 0;
private bool _isGameOver = false;
private bool _isSurfacing = false;
// Initialization state
@@ -77,23 +77,17 @@ namespace Minigames.DivingForPictures
// Used to track if we're currently surfacing
public bool IsSurfacing => _isSurfacing;
// Event for game components to subscribe to
// TODO: Get rid of this in favor of proper game pausing?
public event Action OnGameInitialized;
// Pause state
private bool _isPaused = false;
// List of pausable components controlled by this manager
private List<IPausable> _pausableComponents = new List<IPausable>();
// IPausable implementation
public bool IsPaused => _isPaused;
// Photo sequence state
private bool _isPhotoSequenceActive = false;
private Monster _currentPhotoTarget = null;
private Dictionary<IPausable, bool> _pauseStateBackup = new Dictionary<IPausable, bool>();
private float _capturedProximity = 0f; // New: tracks how close to target the photo was taken (0-1)
private float _capturedProximity = 0f;
// List of components to exempt from pausing during photo sequence
private List<IPausable> _exemptFromPhotoSequencePausing = new List<IPausable>();
@@ -101,7 +95,6 @@ namespace Minigames.DivingForPictures
// Photo sequence events
public event Action<Monster> OnPhotoSequenceStarted;
public event Action<Monster, float> OnPhotoSequenceCompleted; // Now includes proximity score
public event Action<float> OnPhotoSequenceProgressUpdated;
private static DivingGameManager _instance = null;
private static bool _isQuitting = false;
@@ -113,8 +106,8 @@ namespace Minigames.DivingForPictures
private void Awake()
{
settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
currentSpawnProbability = settings?.BaseSpawnProbability ?? 0.2f;
_settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
_currentSpawnProbability = _settings?.BaseSpawnProbability ?? 0.2f;
if (_instance == null)
{
@@ -133,44 +126,8 @@ 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 += DoResume;
Logging.Debug("[DivingGameManager] Subscribed to PauseMenu events");
}
else
{
Logging.Warning("[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)
{
SceneOrientationEnforcer.Instance.OnOrientationCorrect += InitializeGame;
SceneOrientationEnforcer.Instance.OnOrientationIncorrect += Pause;
// If orientation is already correct, initialize right away
// This prevents issues if the orientation was already correct before subscription
if (SceneOrientationEnforcer.Instance.IsOrientationCorrect())
{
InitializeGame();
}
}
else
{
Logging.Warning("[DivingGameManager] SceneOrientationEnforcer not found. Initializing game immediately.");
InitializeGame();
}
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
// Subscribe to player damage events (this doesn't depend on initialization)
PlayerCollisionBehavior.OnDamageTaken += OnPlayerDamageTaken;
@@ -189,6 +146,7 @@ namespace Minigames.DivingForPictures
viewfinderManager.OnViewfinderTappedDuringAnimation += OnViewfinderTappedDuringAnimation;
viewfinderManager.OnReverseAnimationStarted += OnReverseAnimationStarted;
// TODO: Give this a second look and make sure this is correct
// Add the viewfinder manager to exempt list to ensure it keeps working during photo sequences
if (viewfinderManager is IPausable viewfinderPausable)
{
@@ -197,6 +155,36 @@ namespace Minigames.DivingForPictures
}
OnMonsterSpawned += DoMonsterSpawned;
}
private void InitializePostBoot()
{
// Register this manager with the global GameManager
if (GameManager.Instance != null)
{
GameManager.Instance.RegisterPausableComponent(this);
}
// Subscribe to SceneOrientationEnforcer's event
if (SceneOrientationEnforcer.Instance != null)
{
// TODO: This is a bit of a hack to make sure the game is initialized after the orientation is correct
SceneOrientationEnforcer.Instance.OnOrientationCorrect += InitializeGame;
SceneOrientationEnforcer.Instance.OnOrientationIncorrect += Pause;
// If orientation is already correct, initialize right away
// This prevents issues if the orientation was already correct before subscription
if (SceneOrientationEnforcer.Instance.IsOrientationCorrect())
{
InitializeGame();
}
}
else
{
Logging.Warning("[DivingGameManager] SceneOrientationEnforcer not found. Initializing game immediately.");
InitializeGame();
}
CinematicsManager.Instance.OnCinematicStopped += EndGame;
}
@@ -211,14 +199,6 @@ namespace Minigames.DivingForPictures
SceneOrientationEnforcer.Instance.OnOrientationIncorrect -= Pause;
}
// Unsubscribe from PauseMenu events
PauseMenu pauseMenu = PauseMenu.Instance;
if (pauseMenu != null)
{
pauseMenu.OnGamePaused -= Pause;
pauseMenu.OnGameResumed -= DoResume;
}
// Unregister from GameManager
if (GameManager.Instance != null)
{
@@ -241,19 +221,19 @@ namespace Minigames.DivingForPictures
private void Update()
{
timeSinceLastSpawn += Time.deltaTime;
_timeSinceLastSpawn += Time.deltaTime;
// Gradually increase spawn probability over time
float previousProbability = currentSpawnProbability;
if (currentSpawnProbability < settings.MaxSpawnProbability)
float previousProbability = _currentSpawnProbability;
if (_currentSpawnProbability < _settings.MaxSpawnProbability)
{
currentSpawnProbability += settings.ProbabilityIncreaseRate * Time.deltaTime;
currentSpawnProbability = Mathf.Min(currentSpawnProbability, settings.MaxSpawnProbability);
_currentSpawnProbability += _settings.ProbabilityIncreaseRate * Time.deltaTime;
_currentSpawnProbability = Mathf.Min(_currentSpawnProbability, _settings.MaxSpawnProbability);
// Only fire event if probability changed significantly
if (Mathf.Abs(currentSpawnProbability - previousProbability) > 0.01f)
if (Mathf.Abs(_currentSpawnProbability - previousProbability) > 0.01f)
{
OnSpawnProbabilityChanged?.Invoke(currentSpawnProbability);
OnSpawnProbabilityChanged?.Invoke(_currentSpawnProbability);
}
}
}
@@ -268,14 +248,14 @@ namespace Minigames.DivingForPictures
// If we're surfacing, don't spawn new monsters
if (_isSurfacing) return;
bool forceSpawn = timeSinceLastSpawn >= settings.GuaranteedSpawnTime;
bool onCooldown = timeSinceLastSpawn < settings.SpawnCooldown;
bool forceSpawn = _timeSinceLastSpawn >= _settings.GuaranteedSpawnTime;
bool onCooldown = _timeSinceLastSpawn < _settings.SpawnCooldown;
// Don't spawn if on cooldown, unless forced
if (onCooldown && !forceSpawn) return;
// Check probability or forced spawn
if (forceSpawn || UnityEngine.Random.value <= currentSpawnProbability)
if (forceSpawn || UnityEngine.Random.value <= _currentSpawnProbability)
{
// Pick a random spawn point from this tile
MonsterSpawnPoint spawnPoint = spawnPoints[UnityEngine.Random.Range(0, spawnPoints.Length)];
@@ -284,10 +264,10 @@ namespace Minigames.DivingForPictures
SpawnMonster(spawnPoint.transform);
// Reset timer and adjust probability
lastSpawnTime = Time.time;
timeSinceLastSpawn = 0f;
currentSpawnProbability = settings.BaseSpawnProbability;
OnSpawnProbabilityChanged?.Invoke(currentSpawnProbability);
_lastSpawnTime = Time.time;
_timeSinceLastSpawn = 0f;
_currentSpawnProbability = _settings.BaseSpawnProbability;
OnSpawnProbabilityChanged?.Invoke(_currentSpawnProbability);
}
}
@@ -335,14 +315,14 @@ namespace Minigames.DivingForPictures
private void DoPictureTaken(Monster monster)
{
// Calculate points based on depth
int depthBonus = Mathf.FloorToInt(Mathf.Abs(monster.transform.position.y) * settings.DepthMultiplier);
int pointsAwarded = settings.BasePoints + depthBonus;
int depthBonus = Mathf.FloorToInt(Mathf.Abs(monster.transform.position.y) * _settings.DepthMultiplier);
int pointsAwarded = _settings.BasePoints + depthBonus;
// Add score
playerScore += pointsAwarded;
_playerScore += pointsAwarded;
// Fire events
OnScoreChanged?.Invoke(playerScore);
OnScoreChanged?.Invoke(_playerScore);
OnPictureTaken?.Invoke(monster, pointsAwarded);
}
@@ -351,13 +331,13 @@ namespace Minigames.DivingForPictures
/// </summary>
private void OnPlayerDamageTaken()
{
if (isGameOver) return;
if (_isGameOver) return;
// Break the next rope in sequence
BreakNextRope();
// Check if all ropes are broken
if (currentRopeIndex >= playerRopes.Length)
if (_currentRopeIndex >= playerRopes.Length)
{
TriggerGameOver();
deathAudioPlayer.Play();
@@ -365,7 +345,7 @@ namespace Minigames.DivingForPictures
else
{
// Notify listeners about rope break and remaining ropes
int remainingRopes = playerRopes.Length - currentRopeIndex;
int remainingRopes = playerRopes.Length - _currentRopeIndex;
OnRopeBroken?.Invoke(remainingRopes);
Logging.Debug($"[DivingGameManager] Rope broken! {remainingRopes} ropes remaining.");
@@ -377,9 +357,9 @@ namespace Minigames.DivingForPictures
/// </summary>
private void BreakNextRope()
{
if (currentRopeIndex < playerRopes.Length)
if (_currentRopeIndex < playerRopes.Length)
{
RopeBreaker ropeToBreak = playerRopes[currentRopeIndex];
RopeBreaker ropeToBreak = playerRopes[_currentRopeIndex];
if (ropeToBreak != null)
{
@@ -388,11 +368,11 @@ namespace Minigames.DivingForPictures
}
else
{
Logging.Warning($"[DivingGameManager] Rope at index {currentRopeIndex} is null!");
Logging.Warning($"[DivingGameManager] Rope at index {_currentRopeIndex} is null!");
}
// Move to the next rope regardless if current was null
currentRopeIndex++;
_currentRopeIndex++;
}
}
@@ -401,7 +381,7 @@ namespace Minigames.DivingForPictures
/// </summary>
public void ForceBreakRope()
{
if (!isGameOver)
if (!_isGameOver)
{
OnPlayerDamageTaken();
}
@@ -412,9 +392,9 @@ namespace Minigames.DivingForPictures
/// </summary>
private void TriggerGameOver()
{
if (isGameOver) return;
if (_isGameOver) return;
isGameOver = true;
_isGameOver = true;
Logging.Debug("[DivingGameManager] Game Over! All ropes broken. Starting surfacing sequence...");
// Fire game over event
@@ -450,8 +430,8 @@ namespace Minigames.DivingForPictures
public void ResetRopeSystem()
{
// Reset rope state
currentRopeIndex = 0;
isGameOver = false;
_currentRopeIndex = 0;
_isGameOver = false;
// Restore all broken ropes
if (playerRopes != null)
@@ -478,7 +458,7 @@ namespace Minigames.DivingForPictures
_isSurfacing = true;
// 1. Initiate smooth velocity transition to surfacing speed
float targetVelocityFactor = -1.0f * settings.SurfacingSpeedFactor;
float targetVelocityFactor = -1.0f * _settings.SurfacingSpeedFactor;
SetVelocityFactor(targetVelocityFactor);
// 2. Find and notify trench tile spawner about direction change (for spawning/despawning logic)
@@ -497,7 +477,7 @@ namespace Minigames.DivingForPictures
tileSpawner.StartSurfacing();
// Immediately send current velocity factor
tileSpawner.OnVelocityFactorChanged(currentVelocityFactor);
tileSpawner.OnVelocityFactorChanged(_currentVelocityFactor);
}
// Handle the Rock object - disable components and animate it falling offscreen
@@ -565,15 +545,15 @@ namespace Minigames.DivingForPictures
obstacleSpawner.StartSurfacing();
// Immediately send current velocity factor
obstacleSpawner.OnVelocityFactorChanged(currentVelocityFactor);
obstacleSpawner.OnVelocityFactorChanged(_currentVelocityFactor);
}
// Start the surfacing sequence coroutine
if (surfacingSequenceCoroutine != null)
if (_surfacingSequenceCoroutine != null)
{
StopCoroutine(surfacingSequenceCoroutine);
StopCoroutine(_surfacingSequenceCoroutine);
}
surfacingSequenceCoroutine = StartCoroutine(SurfacingSequence());
_surfacingSequenceCoroutine = StartCoroutine(SurfacingSequence());
Logging.Debug($"[DivingGameManager] Started surfacing with target velocity factor: {targetVelocityFactor}");
}
@@ -649,7 +629,7 @@ namespace Minigames.DivingForPictures
private IEnumerator SurfacingSequence()
{
// Wait for the configured delay
yield return new WaitForSeconds(settings.SurfacingSpawnDelay);
yield return new WaitForSeconds(_settings.SurfacingSpawnDelay);
// Find tile spawner and tell it to stop spawning
TrenchTileSpawner tileSpawner = FindFirstObjectByType<TrenchTileSpawner>();
@@ -686,7 +666,6 @@ namespace Minigames.DivingForPictures
// Call this when the game ends
public void EndGame()
{
// TODO: Investigate why called twice
CinematicsManager.Instance.OnCinematicStopped -= EndGame;
// Start the end game sequence that grants a booster, waits for the UI animation, then shows Game Over.
StartCoroutine(EndGameSequence());
@@ -695,7 +674,7 @@ namespace Minigames.DivingForPictures
private IEnumerator EndGameSequence()
{
// Clean up active monsters
foreach (var monster in activeMonsters.ToArray())
foreach (var monster in _activeMonsters.ToArray())
{
if (monster != null)
{
@@ -703,7 +682,7 @@ namespace Minigames.DivingForPictures
}
}
activeMonsters.Clear();
_activeMonsters.Clear();
// 1) Call the booster pack giver if available
bool completed = false;
@@ -735,7 +714,7 @@ namespace Minigames.DivingForPictures
CinematicsManager.Instance.ShowGameOverScreen();
// Final score could be saved to player prefs or other persistence
Logging.Debug($"Final Score: {playerScore}");
Logging.Debug($"Final Score: {_playerScore}");
}
/// <summary>
@@ -744,12 +723,12 @@ namespace Minigames.DivingForPictures
/// <param name="targetFactor">Target velocity factor (e.g., -1.0 for surfacing speed)</param>
public void SetVelocityFactor(float targetFactor)
{
if (velocityTransitionCoroutine != null)
if (_velocityTransitionCoroutine != null)
{
StopCoroutine(velocityTransitionCoroutine);
StopCoroutine(_velocityTransitionCoroutine);
}
velocityTransitionCoroutine = StartCoroutine(TransitionVelocityFactor(targetFactor));
_velocityTransitionCoroutine = StartCoroutine(TransitionVelocityFactor(targetFactor));
}
/// <summary>
@@ -757,29 +736,29 @@ namespace Minigames.DivingForPictures
/// </summary>
private IEnumerator<WaitForEndOfFrame> TransitionVelocityFactor(float targetFactor)
{
float startFactor = currentVelocityFactor;
float startFactor = _currentVelocityFactor;
float elapsed = 0f;
while (elapsed < settings.SpeedTransitionDuration)
while (elapsed < _settings.SpeedTransitionDuration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / settings.SpeedTransitionDuration);
float t = Mathf.Clamp01(elapsed / _settings.SpeedTransitionDuration);
// Smooth step interpolation
float smoothStep = t * t * (3f - 2f * t);
currentVelocityFactor = Mathf.Lerp(startFactor, targetFactor, smoothStep);
_currentVelocityFactor = Mathf.Lerp(startFactor, targetFactor, smoothStep);
// Notify listeners about the velocity factor change
OnVelocityFactorChanged?.Invoke(currentVelocityFactor);
OnVelocityFactorChanged?.Invoke(_currentVelocityFactor);
yield return null;
}
currentVelocityFactor = targetFactor;
_currentVelocityFactor = targetFactor;
// Final assignment to ensure exact target value
OnVelocityFactorChanged?.Invoke(currentVelocityFactor);
OnVelocityFactorChanged?.Invoke(_currentVelocityFactor);
}
/// <summary>
@@ -793,7 +772,7 @@ namespace Minigames.DivingForPictures
_pausableComponents.Add(component);
// If the game is already paused, pause the component immediately
if (_isPaused)
if (GameManager.Instance.IsPaused)
{
component.Pause();
}
@@ -825,9 +804,7 @@ namespace Minigames.DivingForPictures
public void DoPause(bool turnOffGameInput = true)
{
if (_isPaused) return; // Already paused
_isPaused = true;
if (GameManager.Instance.IsPaused) return; // Already paused
// Pause all registered components
foreach (var component in _pausableComponents)
@@ -847,9 +824,7 @@ namespace Minigames.DivingForPictures
/// </summary>
public void DoResume()
{
if (!_isPaused) return; // Already running
_isPaused = false;
if (!GameManager.Instance.IsPaused) return; // Already running
// Resume all registered components
foreach (var component in _pausableComponents)
@@ -938,23 +913,23 @@ namespace Minigames.DivingForPictures
if (monster == null) return;
// Calculate base points from depth
int depthBonus = Mathf.FloorToInt(Mathf.Abs(monster.transform.position.y) * settings.DepthMultiplier);
int depthBonus = Mathf.FloorToInt(Mathf.Abs(monster.transform.position.y) * _settings.DepthMultiplier);
// Apply proximity multiplier (0-100%)
float proximityMultiplier = Mathf.Clamp01(proximity); // Ensure it's in 0-1 range
int proximityBonus = Mathf.RoundToInt(settings.BasePoints * proximityMultiplier);
int proximityBonus = Mathf.RoundToInt(_settings.BasePoints * proximityMultiplier);
// Calculate total score
int pointsAwarded = settings.BasePoints + proximityBonus + depthBonus;
int pointsAwarded = _settings.BasePoints + proximityBonus + depthBonus;
Logging.Debug($"[DivingGameManager] Picture score calculation: base={proximityBonus} (proximity={proximity:F2}), " +
$"depth bonus={depthBonus}, total={pointsAwarded}");
// Add score
playerScore += pointsAwarded;
_playerScore += pointsAwarded;
// Fire events
OnScoreChanged?.Invoke(playerScore);
OnScoreChanged?.Invoke(_playerScore);
OnPictureTaken?.Invoke(monster, pointsAwarded);
}
@@ -1061,14 +1036,14 @@ namespace Minigames.DivingForPictures
monster.OnMonsterDespawned += DoMonsterDespawned;
// Add to active monsters list
activeMonsters.Add(monster);
_activeMonsters.Add(monster);
}
}
private void DoMonsterDespawned(Monster monster)
{
// Remove from active list
activeMonsters.Remove(monster);
_activeMonsters.Remove(monster);
// Unsubscribe from monster events
if (monster != null)