1112 lines
42 KiB
C#
1112 lines
42 KiB
C#
using AppleHills.Core.Interfaces;
|
|
using AppleHills.Core.Settings;
|
|
using Cinematics;
|
|
using Core;
|
|
using Core.Lifecycle;
|
|
using Input;
|
|
using Minigames.DivingForPictures.PictureCamera;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Minigames.DivingForPictures.Bubbles;
|
|
using UI.Core;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.Playables;
|
|
|
|
namespace Minigames.DivingForPictures
|
|
{
|
|
public class DivingGameManager : ManagedBehaviour, IPausable
|
|
{
|
|
[Header("Monster Prefabs")]
|
|
[Tooltip("Array of monster prefabs to spawn randomly")]
|
|
[SerializeField] private GameObject[] monsterPrefabs;
|
|
|
|
[Header("Rope Damage System")]
|
|
[Tooltip("Ropes that will break one by one as player takes damage")]
|
|
[SerializeField] private RopeBreaker[] playerRopes;
|
|
|
|
[Header("Surfacing Settings")]
|
|
[Tooltip("Reference to the PlayableDirector that will play the surfacing timeline")]
|
|
[SerializeField] private PlayableDirector surfacingTimeline;
|
|
|
|
[Header("Flash Effect Reference")]
|
|
[Tooltip("Reference to the Flash Effect when the picture is taken")]
|
|
[SerializeField] private GameObject flashRef;
|
|
|
|
|
|
public CameraViewfinderManager viewfinderManager;
|
|
|
|
// Settings reference
|
|
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>();
|
|
|
|
// 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;
|
|
|
|
// Public properties
|
|
public int PlayerScore => _playerScore;
|
|
public float CurrentVelocityFactor => _currentVelocityFactor;
|
|
public int picturesTaken;
|
|
|
|
// Events
|
|
public event Action<int> OnScoreChanged;
|
|
public event Action<Monster> OnMonsterSpawned;
|
|
public event Action<Monster, int> OnPictureTaken;
|
|
public event Action<float> OnSpawnProbabilityChanged;
|
|
public event Action OnGameOver;
|
|
public event Action<int> OnRopeBroken; // Passes remaining ropes count
|
|
public event Action<float> OnVelocityFactorChanged;
|
|
|
|
// Private state variables for rope system
|
|
private int _currentRopeIndex = 0;
|
|
private bool _isGameOver = false;
|
|
private bool _isSurfacing = false;
|
|
|
|
// Initialization state
|
|
private bool _isGameInitialized = false;
|
|
|
|
// Used to track if we're currently surfacing
|
|
public bool IsSurfacing => _isSurfacing;
|
|
|
|
// TODO: Get rid of this in favor of proper game pausing?
|
|
public event Action OnGameInitialized;
|
|
|
|
|
|
// List of pausable components controlled by this manager
|
|
private List<IPausable> _pausableComponents = new List<IPausable>();
|
|
|
|
// Photo sequence state
|
|
private bool _isPhotoSequenceActive = false;
|
|
private Monster _currentPhotoTarget = null;
|
|
private float _capturedProximity = 0f;
|
|
|
|
// List of components to exempt from pausing during photo sequence
|
|
private List<IPausable> _exemptFromPhotoSequencePausing = new List<IPausable>();
|
|
|
|
// Photo sequence events
|
|
public event Action<Monster> OnPhotoSequenceStarted;
|
|
public event Action<Monster, float> OnPhotoSequenceCompleted; // Now includes proximity score
|
|
|
|
private static DivingGameManager _instance = null;
|
|
|
|
public AudioSource deathAudioPlayer;
|
|
public CameraViewfinderManager cameraViewfinderManager;
|
|
|
|
public static DivingGameManager Instance => _instance;
|
|
|
|
public override bool AutoRegisterPausable => true; // Automatic GameManager registration
|
|
|
|
internal override void OnManagedAwake()
|
|
{
|
|
if (_instance == null)
|
|
{
|
|
_instance = this;
|
|
}
|
|
else if (_instance != this)
|
|
{
|
|
Destroy(gameObject);
|
|
}
|
|
}
|
|
|
|
internal override void OnManagedStart()
|
|
{
|
|
_settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
|
|
_currentSpawnProbability = _settings?.BaseSpawnProbability ?? 0.2f;
|
|
|
|
// Ensure any previous run state is reset when this manager awakes
|
|
_isGameOver = false;
|
|
|
|
Logging.Debug("[DivingGameManager] Initialized");
|
|
}
|
|
|
|
internal override void OnSceneReady()
|
|
{
|
|
InitializeGame();
|
|
|
|
// Subscribe to scene-specific events
|
|
CinematicsManager.Instance.OnCinematicStopped += EndGame;
|
|
PlayerCollisionBehavior.OnDamageTaken += OnPlayerDamageTaken;
|
|
OnMonsterSpawned += DoMonsterSpawned;
|
|
|
|
// Validate rope references
|
|
ValidateRopeReferences();
|
|
|
|
viewfinderManager = CameraViewfinderManager.Instance;
|
|
|
|
// Subscribe to viewfinder events if found
|
|
if (viewfinderManager != null)
|
|
{
|
|
viewfinderManager.OnAnimationCompleted += OnViewfinderAnimationCompleted;
|
|
viewfinderManager.OnViewfinderTapped += OnViewfinderTapped;
|
|
viewfinderManager.OnProximityUpdated += OnProximityUpdated;
|
|
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)
|
|
{
|
|
RegisterExemptFromPhotoSequencePausing(viewfinderPausable);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal override void OnManagedDestroy()
|
|
{
|
|
// Unsubscribe from events when the manager is destroyed
|
|
PlayerCollisionBehavior.OnDamageTaken -= OnPlayerDamageTaken;
|
|
OnMonsterSpawned -= DoMonsterSpawned;
|
|
|
|
if (CinematicsManager.Instance != null)
|
|
{
|
|
CinematicsManager.Instance.OnCinematicStopped -= EndGame;
|
|
}
|
|
|
|
// Unregister all pausable components
|
|
_pausableComponents.Clear();
|
|
|
|
// Unsubscribe from viewfinder events
|
|
if (viewfinderManager != null)
|
|
{
|
|
viewfinderManager.OnAnimationCompleted -= OnViewfinderAnimationCompleted;
|
|
viewfinderManager.OnViewfinderTapped -= OnViewfinderTapped;
|
|
viewfinderManager.OnProximityUpdated -= OnProximityUpdated;
|
|
viewfinderManager.OnViewfinderTappedDuringAnimation -= OnViewfinderTappedDuringAnimation;
|
|
viewfinderManager.OnReverseAnimationStarted -= OnReverseAnimationStarted;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if(_settings == null) return;
|
|
|
|
_timeSinceLastSpawn += Time.deltaTime;
|
|
|
|
// Gradually increase spawn probability over time
|
|
float previousProbability = _currentSpawnProbability;
|
|
if (_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)
|
|
{
|
|
OnSpawnProbabilityChanged?.Invoke(_currentSpawnProbability);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnTileSpawned(GameObject tile)
|
|
{
|
|
// Check for spawn points in the new tile
|
|
MonsterSpawnPoint[] spawnPoints = tile.GetComponentsInChildren<MonsterSpawnPoint>();
|
|
|
|
if (spawnPoints.Length == 0) return;
|
|
|
|
// If we're surfacing, don't spawn new monsters
|
|
if (_isSurfacing) return;
|
|
|
|
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)
|
|
{
|
|
// Pick a random spawn point from this tile
|
|
MonsterSpawnPoint spawnPoint = spawnPoints[UnityEngine.Random.Range(0, spawnPoints.Length)];
|
|
|
|
// Spawn the monster at the spawn point and parent it
|
|
SpawnMonster(spawnPoint.transform);
|
|
|
|
// Reset timer and adjust probability
|
|
_lastSpawnTime = Time.time;
|
|
_timeSinceLastSpawn = 0f;
|
|
_currentSpawnProbability = _settings.BaseSpawnProbability;
|
|
OnSpawnProbabilityChanged?.Invoke(_currentSpawnProbability);
|
|
}
|
|
}
|
|
|
|
private void SpawnMonster(Transform spawnPoint)
|
|
{
|
|
Logging.Debug("Spawning monster: " + spawnPoint.name);
|
|
|
|
if (monsterPrefabs.Length == 0)
|
|
{
|
|
Logging.Warning("No monster prefabs assigned to DivingGameManager.");
|
|
return;
|
|
}
|
|
|
|
// Select random monster prefab
|
|
GameObject prefab = monsterPrefabs[UnityEngine.Random.Range(0, monsterPrefabs.Length)];
|
|
|
|
// Instantiate monster at spawn point position
|
|
GameObject monsterObj = Instantiate(prefab, spawnPoint.position, Quaternion.identity);
|
|
Monster monster = monsterObj.GetComponent<Monster>();
|
|
|
|
if (monster != null)
|
|
{
|
|
// Parent the monster to the spawn point so it moves with the tile
|
|
monsterObj.transform.SetParent(spawnPoint);
|
|
|
|
// Flip the spawn point in X axis if facesLeft is true
|
|
MonsterSpawnPoint msp = spawnPoint.GetComponent<MonsterSpawnPoint>();
|
|
if (msp != null && msp.facesLeft)
|
|
{
|
|
Vector3 scale = spawnPoint.localScale;
|
|
scale.x *= -1f;
|
|
spawnPoint.localScale = scale;
|
|
}
|
|
|
|
// Fire event
|
|
OnMonsterSpawned?.Invoke(monster);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"Monster prefab {prefab.name} does not have a Monster component!");
|
|
Destroy(monsterObj);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// Add score
|
|
_playerScore += pointsAwarded;
|
|
|
|
// Add number of pictures taken
|
|
picturesTaken += 1;
|
|
|
|
// Fire events
|
|
OnScoreChanged?.Invoke(picturesTaken);
|
|
OnPictureTaken?.Invoke(monster, picturesTaken);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the player takes damage from any collision
|
|
/// </summary>
|
|
private void OnPlayerDamageTaken()
|
|
{
|
|
if (_isGameOver) return;
|
|
|
|
// Break the next rope in sequence
|
|
BreakNextRope();
|
|
|
|
// Check if all ropes are broken
|
|
if (_currentRopeIndex >= playerRopes.Length)
|
|
{
|
|
TriggerGameOver();
|
|
deathAudioPlayer.Play();
|
|
}
|
|
else
|
|
{
|
|
// Notify listeners about rope break and remaining ropes
|
|
int remainingRopes = playerRopes.Length - _currentRopeIndex;
|
|
OnRopeBroken?.Invoke(remainingRopes);
|
|
|
|
Logging.Debug($"[DivingGameManager] Rope broken! {remainingRopes} ropes remaining.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Breaks the next available rope in the sequence
|
|
/// </summary>
|
|
private void BreakNextRope()
|
|
{
|
|
if (_currentRopeIndex < playerRopes.Length)
|
|
{
|
|
RopeBreaker ropeToBreak = playerRopes[_currentRopeIndex];
|
|
|
|
if (ropeToBreak != null)
|
|
{
|
|
// Let the RopeBreaker component handle the breaking, effects, and sounds
|
|
ropeToBreak.BreakRope();
|
|
}
|
|
else
|
|
{
|
|
Logging.Warning($"[DivingGameManager] Rope at index {_currentRopeIndex} is null!");
|
|
}
|
|
|
|
// Move to the next rope regardless if current was null
|
|
_currentRopeIndex++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Manually break a rope (for testing or external events)
|
|
/// </summary>
|
|
public void ForceBreakRope()
|
|
{
|
|
if (!_isGameOver)
|
|
{
|
|
OnPlayerDamageTaken();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers game over state when all ropes are broken
|
|
/// </summary>
|
|
private void TriggerGameOver()
|
|
{
|
|
if (_isGameOver) return;
|
|
|
|
_isGameOver = true;
|
|
Logging.Debug("[DivingGameManager] Game Over! All ropes broken. Starting surfacing sequence...");
|
|
|
|
// Fire game over event
|
|
OnGameOver?.Invoke();
|
|
|
|
// Start surfacing instead of directly ending the game
|
|
StartSurfacing();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates rope references and logs warnings if any are missing
|
|
/// </summary>
|
|
private void ValidateRopeReferences()
|
|
{
|
|
if (playerRopes == null || playerRopes.Length == 0)
|
|
{
|
|
Logging.Warning("[DivingGameManager] No ropes assigned to break! Damage system won't work properly.");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < playerRopes.Length; i++)
|
|
{
|
|
if (playerRopes[i] == null)
|
|
{
|
|
Logging.Warning($"[DivingGameManager] Rope at index {i} is null!");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the rope system for a new game
|
|
/// </summary>
|
|
public void ResetRopeSystem()
|
|
{
|
|
// Reset rope state
|
|
_currentRopeIndex = 0;
|
|
_isGameOver = false;
|
|
|
|
// Restore all broken ropes
|
|
if (playerRopes != null)
|
|
{
|
|
foreach (var rope in playerRopes)
|
|
{
|
|
if (rope != null)
|
|
{
|
|
rope.RestoreRope();
|
|
}
|
|
}
|
|
}
|
|
|
|
Logging.Debug("[DivingGameManager] Rope system reset.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the surfacing mode - reverses trench direction and adjusts all spawned entities
|
|
/// </summary>
|
|
public void StartSurfacing()
|
|
{
|
|
if (_isSurfacing) return; // Already surfacing
|
|
|
|
_isSurfacing = true;
|
|
|
|
// TODO: Call it here?
|
|
UIPageController.Instance.HideAllUI();
|
|
|
|
// 1. Initiate smooth velocity transition to surfacing speed
|
|
float targetVelocityFactor = -1.0f * _settings.SurfacingSpeedFactor;
|
|
SetVelocityFactor(targetVelocityFactor);
|
|
|
|
// 2. Find and notify trench tile spawner about direction change (for spawning/despawning logic)
|
|
TrenchTileSpawner tileSpawner = FindFirstObjectByType<TrenchTileSpawner>();
|
|
if (tileSpawner != null)
|
|
{
|
|
// Subscribe to velocity changes if not already subscribed
|
|
OnVelocityFactorChanged -= tileSpawner.OnVelocityFactorChanged;
|
|
OnVelocityFactorChanged += tileSpawner.OnVelocityFactorChanged;
|
|
|
|
// Subscribe to the last tile event
|
|
tileSpawner.onLastTileLeft.RemoveListener(OnLastTileLeft);
|
|
tileSpawner.onLastTileLeft.AddListener(OnLastTileLeft);
|
|
|
|
// Tell spawner to reverse spawn/despawn logic
|
|
tileSpawner.StartSurfacing();
|
|
|
|
// Immediately send current velocity factor
|
|
tileSpawner.OnVelocityFactorChanged(_currentVelocityFactor);
|
|
}
|
|
|
|
// Handle the Rock object - disable components and animate it falling offscreen
|
|
GameObject rockObject = GameObject.FindGameObjectWithTag("Rock");
|
|
if (rockObject != null)
|
|
{
|
|
// Disable all components except Transform on the rock object (not its children)
|
|
foreach (Component component in rockObject.GetComponents<Component>())
|
|
{
|
|
if (!(component is Transform))
|
|
{
|
|
if (component is Behaviour behaviour)
|
|
{
|
|
behaviour.enabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start coroutine to animate the rock falling offscreen
|
|
StartCoroutine(MoveRockOffscreen(rockObject.transform));
|
|
|
|
Logging.Debug("[DivingGameManager] Disabled rock components and animating it offscreen");
|
|
}
|
|
|
|
// Handle the Player object - disable components and reset X position
|
|
GameObject playerObject = GameObject.FindGameObjectWithTag("Player");
|
|
if (playerObject != null)
|
|
{
|
|
// Disable all components except Transform, Animator, and PlayerBlinkBehavior on the player object
|
|
foreach (Component component in playerObject.GetComponents<Component>())
|
|
{
|
|
if (!(component is Transform) &&
|
|
!(component is Animator) &&
|
|
!(component is PlayerBlinkBehavior))
|
|
{
|
|
if (component is Behaviour behaviour)
|
|
{
|
|
behaviour.enabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start coroutine to reset X position to 0 over 1 second
|
|
StartCoroutine(ResetPlayerPosition(playerObject.transform));
|
|
|
|
Logging.Debug("[DivingGameManager] Disabled player components (keeping Animator and PlayerBlinkBehavior) and resetting position");
|
|
}
|
|
|
|
// 3. Find bubble spawner and slow down existing bubbles (no velocity management needed)
|
|
BubbleSpawner bubbleSpawner = FindFirstObjectByType<BubbleSpawner>();
|
|
if (bubbleSpawner != null)
|
|
{
|
|
bubbleSpawner.StartSurfacing();
|
|
}
|
|
|
|
// 4. Find obstacle spawner and set up for velocity changes
|
|
ObstacleSpawner obstacleSpawner = FindFirstObjectByType<ObstacleSpawner>();
|
|
if (obstacleSpawner != null)
|
|
{
|
|
// Subscribe to velocity changes
|
|
OnVelocityFactorChanged -= obstacleSpawner.OnVelocityFactorChanged;
|
|
OnVelocityFactorChanged += obstacleSpawner.OnVelocityFactorChanged;
|
|
|
|
// Tell spawner to reverse spawn/despawn logic
|
|
obstacleSpawner.StartSurfacing();
|
|
|
|
// Immediately send current velocity factor
|
|
obstacleSpawner.OnVelocityFactorChanged(_currentVelocityFactor);
|
|
}
|
|
|
|
// Start the surfacing sequence coroutine
|
|
if (_surfacingSequenceCoroutine != null)
|
|
{
|
|
StopCoroutine(_surfacingSequenceCoroutine);
|
|
}
|
|
_surfacingSequenceCoroutine = StartCoroutine(SurfacingSequence());
|
|
|
|
Logging.Debug($"[DivingGameManager] Started surfacing with target velocity factor: {targetVelocityFactor}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine to animate the rock falling below the screen
|
|
/// </summary>
|
|
private IEnumerator MoveRockOffscreen(Transform rockTransform)
|
|
{
|
|
Vector3 startPosition = rockTransform.position;
|
|
|
|
// Calculate position below the screen
|
|
UnityEngine.Camera mainCamera = UnityEngine.Camera.main;
|
|
if (mainCamera == null)
|
|
{
|
|
Logging.Warning("[DivingGameManager] Cannot find main camera to calculate offscreen position");
|
|
yield break;
|
|
}
|
|
|
|
// Get a position below the bottom of the screen
|
|
Vector3 offscreenPosition = mainCamera.ViewportToWorldPoint(new Vector3(0.5f, -0.2f, mainCamera.nearClipPlane));
|
|
Vector3 targetPosition = new Vector3(startPosition.x, offscreenPosition.y, startPosition.z);
|
|
|
|
float duration = 2.0f; // Animation duration in seconds
|
|
float elapsed = 0f;
|
|
|
|
while (elapsed < duration)
|
|
{
|
|
elapsed += Time.deltaTime;
|
|
float t = Mathf.Clamp01(elapsed / duration);
|
|
|
|
// Use an easing function that accelerates to simulate falling
|
|
float easedT = t * t; // Quadratic easing
|
|
|
|
rockTransform.position = Vector3.Lerp(startPosition, targetPosition, easedT);
|
|
yield return null;
|
|
}
|
|
|
|
// Ensure final position is exactly at target
|
|
rockTransform.position = targetPosition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine to reset the player's X position to 0 over time
|
|
/// </summary>
|
|
private IEnumerator ResetPlayerPosition(Transform playerTransform)
|
|
{
|
|
Vector3 startPosition = playerTransform.position;
|
|
Vector3 targetPosition = new Vector3(0f, startPosition.y, startPosition.z);
|
|
|
|
float duration = 1.0f; // Reset duration in seconds (as requested)
|
|
float elapsed = 0f;
|
|
|
|
while (elapsed < duration)
|
|
{
|
|
elapsed += Time.deltaTime;
|
|
float t = Mathf.Clamp01(elapsed / duration);
|
|
|
|
// Use smooth step for more natural movement
|
|
float smoothT = Mathf.SmoothStep(0f, 1f, t);
|
|
|
|
playerTransform.position = Vector3.Lerp(startPosition, targetPosition, smoothT);
|
|
yield return null;
|
|
}
|
|
|
|
// Ensure final position is exactly at target
|
|
playerTransform.position = targetPosition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine to handle the surfacing sequence timing
|
|
/// </summary>
|
|
private IEnumerator SurfacingSequence()
|
|
{
|
|
// Wait for the configured delay
|
|
yield return new WaitForSeconds(_settings.SurfacingSpawnDelay);
|
|
|
|
// Find tile spawner and tell it to stop spawning
|
|
TrenchTileSpawner tileSpawner = FindFirstObjectByType<TrenchTileSpawner>();
|
|
if (tileSpawner != null)
|
|
{
|
|
// Tell it to stop spawning new tiles
|
|
tileSpawner.StopSpawning();
|
|
Logging.Debug("[DivingGameManager] Stopped spawning new tiles after delay");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the last tile leaves the screen
|
|
/// </summary>
|
|
private void OnLastTileLeft()
|
|
{
|
|
// Play the timeline
|
|
if (surfacingTimeline != null)
|
|
{
|
|
//Instead of surfacingTimeline, play the outro cinematic
|
|
deathAudioPlayer.Stop();
|
|
CinematicsManager.Instance.LoadAndPlayCinematic("SurfacingCinematic",true);
|
|
CinematicsManager.Instance.ChangeCinematicBackgroundColour(new Color(0.5058824f, 0.7803922f, 0.8862746f, 1f));
|
|
//PlayerHudManager.Instance.ResizeCinematicPlayer();
|
|
//surfacingTimeline.Play();
|
|
//Logging.Debug("[DivingGameManager] Last tile left the screen, playing timeline");
|
|
|
|
}
|
|
else
|
|
{
|
|
Logging.Warning("[DivingGameManager] No surfacing timeline assigned!");
|
|
}
|
|
}
|
|
|
|
// Call this when the game ends
|
|
public void EndGame()
|
|
{
|
|
CinematicsManager.Instance.OnCinematicStopped -= EndGame;
|
|
// Start the end game sequence that grants a booster, waits for the UI animation, then shows Game Over.
|
|
StartCoroutine(EndGameSequence());
|
|
}
|
|
|
|
private IEnumerator EndGameSequence()
|
|
{
|
|
// Clean up active monsters
|
|
foreach (var monster in _activeMonsters.ToArray())
|
|
{
|
|
if (monster != null)
|
|
{
|
|
monster.DespawnMonster();
|
|
}
|
|
}
|
|
|
|
_activeMonsters.Clear();
|
|
|
|
// 1) Call the booster pack giver if available
|
|
bool completed = false;
|
|
var giver = UI.CardSystem.MinigameBoosterGiver.Instance;
|
|
if (giver != null)
|
|
{
|
|
UIPageController.Instance.ShowAllUI();
|
|
Logging.Debug("[DivingGameManager] Starting booster giver sequence");
|
|
giver.GiveBooster(() =>
|
|
{
|
|
completed = true;
|
|
Logging.Debug("[DivingGameManager] Booster giver completed callback received");
|
|
});
|
|
|
|
// 2) Wait for it to finish (NO timeout - wait indefinitely for user interaction)
|
|
while (!completed)
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
Logging.Debug("[DivingGameManager] Booster giver sequence finished, proceeding to game over");
|
|
}
|
|
else
|
|
{
|
|
// If no giver is present, proceed immediately
|
|
Logging.Debug("[DivingGameManager] MinigameBoosterGiver not found; skipping booster animation.");
|
|
}
|
|
|
|
// 3) Only then show the game over screen
|
|
CinematicsManager.Instance.ShowGameOverScreen();
|
|
|
|
// Final score could be saved to player prefs or other persistence
|
|
Logging.Debug($"Final Score: {_playerScore}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts a smooth transition to the new velocity factor
|
|
/// </summary>
|
|
/// <param name="targetFactor">Target velocity factor (e.g., -1.0 for surfacing speed)</param>
|
|
public void SetVelocityFactor(float targetFactor)
|
|
{
|
|
if (_velocityTransitionCoroutine != null)
|
|
{
|
|
StopCoroutine(_velocityTransitionCoroutine);
|
|
}
|
|
|
|
_velocityTransitionCoroutine = StartCoroutine(TransitionVelocityFactor(targetFactor));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine to smoothly transition the velocity factor over time
|
|
/// </summary>
|
|
private IEnumerator<WaitForEndOfFrame> TransitionVelocityFactor(float targetFactor)
|
|
{
|
|
float startFactor = _currentVelocityFactor;
|
|
float elapsed = 0f;
|
|
|
|
while (elapsed < _settings.SpeedTransitionDuration)
|
|
{
|
|
elapsed += Time.deltaTime;
|
|
float t = Mathf.Clamp01(elapsed / _settings.SpeedTransitionDuration);
|
|
|
|
// Smooth step interpolation
|
|
float smoothStep = t * t * (3f - 2f * t);
|
|
|
|
_currentVelocityFactor = Mathf.Lerp(startFactor, targetFactor, smoothStep);
|
|
|
|
// Notify listeners about the velocity factor change
|
|
OnVelocityFactorChanged?.Invoke(_currentVelocityFactor);
|
|
|
|
yield return null;
|
|
}
|
|
|
|
_currentVelocityFactor = targetFactor;
|
|
|
|
// Final assignment to ensure exact target value
|
|
OnVelocityFactorChanged?.Invoke(_currentVelocityFactor);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a component as pausable with this manager
|
|
/// </summary>
|
|
/// <param name="component">The pausable component to register</param>
|
|
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 (GameManager.Instance.IsPaused)
|
|
{
|
|
component.Pause();
|
|
}
|
|
|
|
Logging.Debug($"[DivingGameManager] Registered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregister a pausable component
|
|
/// </summary>
|
|
/// <param name="component">The pausable component to unregister</param>
|
|
public void UnregisterPausableComponent(IPausable component)
|
|
{
|
|
if (component != null && _pausableComponents.Contains(component))
|
|
{
|
|
_pausableComponents.Remove(component);
|
|
Logging.Debug($"[DivingGameManager] Unregistered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pause the game and all registered components
|
|
/// </summary>
|
|
public void Pause()
|
|
{
|
|
// Ignore pause requests once the game has reached Game Over
|
|
if (_isGameOver) return;
|
|
DoPause();
|
|
}
|
|
|
|
public void DoPause(bool turnOffGameInput = true)
|
|
{
|
|
// Ignore pause requests once the game has ended
|
|
if (_isGameOver) return;
|
|
// Pause all registered components
|
|
foreach (var component in _pausableComponents)
|
|
{
|
|
component.Pause();
|
|
}
|
|
|
|
// Change input mode to UI when menu is open
|
|
if(turnOffGameInput)
|
|
InputManager.Instance.SetInputMode(InputMode.GameAndUI);
|
|
|
|
Logging.Debug($"[DivingGameManager] Game paused. Paused {_pausableComponents.Count} components.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resume the game and all registered components
|
|
/// </summary>
|
|
public void DoResume()
|
|
{
|
|
// Ignore resume requests once the game has ended
|
|
if (_isGameOver) return;
|
|
// Resume all registered components
|
|
foreach (var component in _pausableComponents)
|
|
{
|
|
component.DoResume();
|
|
}
|
|
|
|
// Change input mode to UI when menu is open
|
|
InputManager.Instance.SetInputMode(InputMode.GameAndUI);
|
|
|
|
Logging.Debug($"[DivingGameManager] Game resumed. Resumed {_pausableComponents.Count} components.");
|
|
}
|
|
|
|
#region Photo Sequence Methods
|
|
|
|
/// <summary>
|
|
/// Called when the viewfinder animation phase changes to reverse (zoom out)
|
|
/// </summary>
|
|
private void OnReverseAnimationStarted()
|
|
{
|
|
Logging.Debug("[DivingGameManager] Viewfinder animation entering reverse (zoom-out) phase");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when proximity value is updated during animation
|
|
/// </summary>
|
|
private void OnProximityUpdated(float proximity)
|
|
{
|
|
// Store the current proximity value for potential use in scoring
|
|
_capturedProximity = proximity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the player taps during the viewfinder animation
|
|
/// </summary>
|
|
private void OnViewfinderTappedDuringAnimation(float proximity)
|
|
{
|
|
if (!_isPhotoSequenceActive || _currentPhotoTarget == null)
|
|
return;
|
|
|
|
// Store the proximity value at the time of tap for scoring
|
|
_capturedProximity = proximity;
|
|
|
|
Logging.Debug($"[DivingGameManager] Player tapped during animation! Proximity: {proximity:F2}");
|
|
|
|
// Take the picture at the current proximity
|
|
TakePicture();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes the picture with the current proximity value for scoring
|
|
/// </summary>
|
|
private void TakePicture()
|
|
{
|
|
if (!_isPhotoSequenceActive || _currentPhotoTarget == null)
|
|
return;
|
|
|
|
// Notify the monster that its picture was taken
|
|
_currentPhotoTarget.NotifyPictureTaken();
|
|
|
|
// Calculate score based on proximity and depth
|
|
CalculateScore(_currentPhotoTarget, _capturedProximity);
|
|
|
|
//Trigger the Flash Effect
|
|
if (flashRef != null)
|
|
{
|
|
|
|
var flash = flashRef.GetComponent<FlashBehaviour>();
|
|
if (flash != null)
|
|
{
|
|
|
|
flash.TriggerFlash();
|
|
cameraViewfinderManager.PlayShutterSound();
|
|
}
|
|
}
|
|
|
|
// Complete the sequence
|
|
CompletePhotoSequence();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the score for a picture based on proximity to target and monster depth
|
|
/// </summary>
|
|
private void CalculateScore(Monster monster, float proximity)
|
|
{
|
|
if (monster == null) return;
|
|
|
|
// Calculate base points from depth
|
|
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);
|
|
|
|
// Calculate total score
|
|
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;
|
|
|
|
// Add pictures taken
|
|
picturesTaken += 1;
|
|
|
|
// Fire events
|
|
OnScoreChanged?.Invoke(picturesTaken);
|
|
OnPictureTaken?.Invoke(monster, picturesTaken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles completion of the viewfinder animation
|
|
/// </summary>
|
|
private void OnViewfinderAnimationCompleted()
|
|
{
|
|
if (!_isPhotoSequenceActive || _currentPhotoTarget == null)
|
|
return;
|
|
|
|
// Take the picture at whatever the final proximity was
|
|
TakePicture();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up and completes the picture-taking sequence
|
|
/// </summary>
|
|
private void CompletePhotoSequence()
|
|
{
|
|
if (!_isPhotoSequenceActive)
|
|
return;
|
|
|
|
// Notify listeners that photo sequence has completed (with proximity score)
|
|
if (_currentPhotoTarget != null)
|
|
{
|
|
OnPhotoSequenceCompleted?.Invoke(_currentPhotoTarget, _capturedProximity);
|
|
|
|
// We no longer despawn the monster after taking a picture
|
|
// _currentPhotoTarget.Despawn(false);
|
|
|
|
// Just mark the photo sequence as no longer in progress
|
|
_currentPhotoTarget.NotifyPictureTaken();
|
|
}
|
|
|
|
DoResume();
|
|
|
|
// Reset state
|
|
_isPhotoSequenceActive = false;
|
|
_currentPhotoTarget = null;
|
|
|
|
Logging.Debug($"[DivingGameManager] Completed photo sequence with proximity score: {_capturedProximity:F2}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a component to be exempt from pausing during photo sequence
|
|
/// </summary>
|
|
public void RegisterExemptFromPhotoSequencePausing(IPausable component)
|
|
{
|
|
if (!_exemptFromPhotoSequencePausing.Contains(component))
|
|
{
|
|
_exemptFromPhotoSequencePausing.Add(component);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Game Initialization
|
|
|
|
/// <summary>
|
|
/// Initializes the game components once the orientation is correct.
|
|
/// This is called by SceneOrientationEnforcer when the device is properly oriented.
|
|
/// </summary>
|
|
public void InitializeGame()
|
|
{
|
|
if (_isGameInitialized)
|
|
{
|
|
DoResume();
|
|
return;
|
|
}
|
|
|
|
// Prevent double initialization
|
|
if (_isGameInitialized) return;
|
|
|
|
Logging.Debug("[DivingGameManager] Initializing game");
|
|
|
|
// Subscribe to tile spawned event
|
|
TrenchTileSpawner tileSpawner = FindFirstObjectByType<TrenchTileSpawner>();
|
|
if (tileSpawner != null)
|
|
{
|
|
tileSpawner.onTileSpawned.AddListener(OnTileSpawned);
|
|
}
|
|
else
|
|
{
|
|
Logging.Warning("No TrenchTileSpawner found in scene. Monster spawning won't work.");
|
|
}
|
|
|
|
// Mark as initialized
|
|
_isGameInitialized = true;
|
|
|
|
// Notify all listeners that the game is initialized
|
|
OnGameInitialized?.Invoke();
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void DoMonsterSpawned(Monster monster)
|
|
{
|
|
if (monster != null)
|
|
{
|
|
// Subscribe to monster enter/exit events
|
|
monster.OnPlayerEnterDetectionRange += OnPlayerEnterMonsterRange;
|
|
monster.OnPlayerExitDetectionRange += OnPlayerExitMonsterRange;
|
|
monster.OnMonsterDespawned += DoMonsterDespawned;
|
|
|
|
// Add to active monsters list
|
|
_activeMonsters.Add(monster);
|
|
}
|
|
}
|
|
|
|
private void DoMonsterDespawned(Monster monster)
|
|
{
|
|
// Remove from active list
|
|
_activeMonsters.Remove(monster);
|
|
|
|
// Unsubscribe from monster events
|
|
if (monster != null)
|
|
{
|
|
monster.OnPlayerEnterDetectionRange -= OnPlayerEnterMonsterRange;
|
|
monster.OnPlayerExitDetectionRange -= OnPlayerExitMonsterRange;
|
|
monster.OnMonsterDespawned -= DoMonsterDespawned;
|
|
}
|
|
}
|
|
|
|
// Handles player entering monster detection range
|
|
private void OnPlayerEnterMonsterRange(Monster monster)
|
|
{
|
|
if (monster != null && !_isPhotoSequenceActive)
|
|
{
|
|
// Store current target for later use
|
|
_currentPhotoTarget = monster;
|
|
|
|
// Show the full-screen viewfinder (first step in two-step process)
|
|
if (viewfinderManager != null)
|
|
{
|
|
viewfinderManager.ShowFullScreenViewfinder();
|
|
Logging.Debug($"[DivingGameManager] Player entered range of monster {monster.name}, showing full-screen viewfinder");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handles player exiting monster detection range
|
|
private void OnPlayerExitMonsterRange(Monster monster)
|
|
{
|
|
// Only hide viewfinder if we're not already in a photo sequence
|
|
if (!_isPhotoSequenceActive && monster == _currentPhotoTarget)
|
|
{
|
|
// Hide the viewfinder
|
|
if (viewfinderManager != null)
|
|
{
|
|
viewfinderManager.HideViewfinder();
|
|
Logging.Debug($"[DivingGameManager] Player exited range of monster {monster.name}, hiding viewfinder");
|
|
}
|
|
|
|
// Clear current target
|
|
_currentPhotoTarget = null;
|
|
}
|
|
}
|
|
|
|
// Called when the player taps on the viewfinder
|
|
private void OnViewfinderTapped()
|
|
{
|
|
// Only proceed if we have a valid target and not already in a sequence
|
|
if (_currentPhotoTarget != null && !_isPhotoSequenceActive && _currentPhotoTarget.IsPlayerInDetectionRange)
|
|
{
|
|
// Pause the game immediately
|
|
DoPause(false);
|
|
Logging.Debug($"[DivingGameManager] Pausing game before starting viewfinder animation");
|
|
|
|
// Mark the photo sequence as active
|
|
_isPhotoSequenceActive = true;
|
|
|
|
// Mark monster as in photo sequence
|
|
_currentPhotoTarget.SetPhotoSequenceInProgress();
|
|
|
|
// Reset captured proximity
|
|
_capturedProximity = 0f;
|
|
|
|
// Notify listeners that photo sequence has started
|
|
OnPhotoSequenceStarted?.Invoke(_currentPhotoTarget);
|
|
|
|
// Start the complete animation sequence to target monster
|
|
if (viewfinderManager != null)
|
|
{
|
|
viewfinderManager.StartViewfinderSequence(_currentPhotoTarget.transform);
|
|
Logging.Debug($"[DivingGameManager] Viewfinder tapped for monster {_currentPhotoTarget.name}, starting animation sequence");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("[DivingGameManager] No ViewfinderManager available!");
|
|
CompletePhotoSequence();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|