Files
AppleHillsProduction/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs

382 lines
13 KiB
C#
Raw Normal View History

using UnityEngine;
using System.Collections.Generic;
using System;
using UnityEngine.Events;
namespace Minigames.DivingForPictures
{
public class DivingGameManager : MonoBehaviour
{
[Header("Monster Prefabs")]
[Tooltip("Array of monster prefabs to spawn randomly")]
[SerializeField] private GameObject[] monsterPrefabs;
[Header("Spawn Probability")]
[Tooltip("Base chance (0-1) of spawning a monster on each tile")]
[SerializeField] private float baseSpawnProbability = 0.2f;
[Tooltip("Maximum chance (0-1) of spawning a monster")]
[SerializeField] private float maxSpawnProbability = 0.5f;
[Tooltip("How fast the probability increases per second")]
[SerializeField] private float probabilityIncreaseRate = 0.01f;
[Header("Spawn Timing")]
[Tooltip("Force a spawn after this many seconds without spawns")]
[SerializeField] private float guaranteedSpawnTime = 30f;
[Tooltip("Minimum time between monster spawns")]
[SerializeField] private float spawnCooldown = 5f;
[Header("Scoring")]
[Tooltip("Base points for taking a picture")]
[SerializeField] private int basePoints = 100;
[Tooltip("Additional points per depth unit")]
[SerializeField] private int depthMultiplier = 10;
2025-09-21 22:36:05 +02:00
[Header("Rope Damage System")]
[Tooltip("Ropes that will break one by one as player takes damage")]
[SerializeField] private RopeBreaker[] playerRopes;
// 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>();
// Public properties
public int PlayerScore => playerScore;
// Events
public event Action<int> OnScoreChanged;
public event Action<Monster> OnMonsterSpawned;
public event Action<Monster, int> OnPictureTaken;
public event Action<float> OnSpawnProbabilityChanged;
2025-09-21 22:36:05 +02:00
public event Action OnGameOver;
public event Action<int> OnRopeBroken; // Passes remaining ropes count
// Private state variables for rope system
private int currentRopeIndex = 0;
private bool isGameOver = false;
private bool _isSurfacing = false;
// Used to track if we're currently surfacing
public bool IsSurfacing => _isSurfacing;
private void Awake()
{
currentSpawnProbability = baseSpawnProbability;
}
private void Start()
{
// Subscribe to tile spawned event
2025-09-19 13:46:24 +02:00
TrenchTileSpawner tileSpawner = FindFirstObjectByType<TrenchTileSpawner>();
if (tileSpawner != null)
{
tileSpawner.onTileSpawned.AddListener(OnTileSpawned);
}
else
{
Debug.LogWarning("No TrenchTileSpawner found in scene. Monster spawning won't work.");
}
2025-09-21 22:36:05 +02:00
// Subscribe to player damage events
PlayerCollisionBehavior.OnDamageTaken += OnPlayerDamageTaken;
// Validate rope references
ValidateRopeReferences();
}
private void OnDestroy()
{
// Unsubscribe from events when the manager is destroyed
PlayerCollisionBehavior.OnDamageTaken -= OnPlayerDamageTaken;
}
private void Update()
{
timeSinceLastSpawn += Time.deltaTime;
// Gradually increase spawn probability over time
float previousProbability = currentSpawnProbability;
if (currentSpawnProbability < maxSpawnProbability)
{
currentSpawnProbability += probabilityIncreaseRate * Time.deltaTime;
currentSpawnProbability = Mathf.Min(currentSpawnProbability, 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 >= guaranteedSpawnTime;
bool onCooldown = timeSinceLastSpawn < 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)];
2025-09-19 13:46:24 +02:00
// Spawn the monster at the spawn point and parent it
SpawnMonster(spawnPoint.transform);
// Reset timer and adjust probability
lastSpawnTime = Time.time;
timeSinceLastSpawn = 0f;
currentSpawnProbability = baseSpawnProbability;
OnSpawnProbabilityChanged?.Invoke(currentSpawnProbability);
}
}
2025-09-19 13:46:24 +02:00
private void SpawnMonster(Transform spawnPoint)
{
if (monsterPrefabs.Length == 0)
{
Debug.LogWarning("No monster prefabs assigned to DivingGameManager.");
return;
}
// Select random monster prefab
GameObject prefab = monsterPrefabs[UnityEngine.Random.Range(0, monsterPrefabs.Length)];
2025-09-19 13:46:24 +02:00
// Instantiate monster at spawn point position
GameObject monsterObj = Instantiate(prefab, spawnPoint.position, Quaternion.identity);
Monster monster = monsterObj.GetComponent<Monster>();
if (monster != null)
{
2025-09-19 13:46:24 +02:00
// Parent the monster to the spawn point so it moves with the tile
monsterObj.transform.SetParent(spawnPoint);
// Subscribe to monster events
monster.OnPictureTaken += OnMonsterPictureTaken;
monster.OnMonsterDespawned += OnMonsterDespawned;
// Add to active monsters list
activeMonsters.Add(monster);
// Fire event
OnMonsterSpawned?.Invoke(monster);
}
else
{
Debug.LogError($"Monster prefab {prefab.name} does not have a Monster component!");
Destroy(monsterObj);
}
}
private void OnMonsterPictureTaken(Monster monster)
{
// Calculate points based on depth
int depthBonus = Mathf.FloorToInt(Mathf.Abs(monster.transform.position.y) * depthMultiplier);
int pointsAwarded = basePoints + depthBonus;
// Add score
playerScore += pointsAwarded;
// Fire events
OnScoreChanged?.Invoke(playerScore);
OnPictureTaken?.Invoke(monster, pointsAwarded);
}
private void OnMonsterDespawned(Monster monster)
{
// Remove from active list
activeMonsters.Remove(monster);
// Unsubscribe from events
monster.OnPictureTaken -= OnMonsterPictureTaken;
monster.OnMonsterDespawned -= OnMonsterDespawned;
}
2025-09-21 22:36:05 +02:00
/// <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();
}
else
{
// Notify listeners about rope break and remaining ropes
int remainingRopes = playerRopes.Length - currentRopeIndex;
OnRopeBroken?.Invoke(remainingRopes);
Debug.Log($"[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
{
Debug.LogWarning($"[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;
Debug.Log("[DivingGameManager] Game Over! All ropes broken.");
// Fire game over event
OnGameOver?.Invoke();
// Call the existing end game method
EndGame();
}
/// <summary>
/// Validates rope references and logs warnings if any are missing
/// </summary>
private void ValidateRopeReferences()
{
if (playerRopes == null || playerRopes.Length == 0)
{
Debug.LogWarning("[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)
{
Debug.LogWarning($"[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();
}
}
}
Debug.Log("[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;
// 1. Find and reverse trench tile spawner
TrenchTileSpawner tileSpawner = FindFirstObjectByType<TrenchTileSpawner>();
if (tileSpawner != null)
{
tileSpawner.StartSurfacing();
}
// 2. Find bubble spawner and slow down existing bubbles
BubbleSpawner bubbleSpawner = FindFirstObjectByType<BubbleSpawner>();
if (bubbleSpawner != null)
{
bubbleSpawner.StartSurfacing();
}
// 3. Find obstacle spawner and reverse existing obstacles
ObstacleSpawner obstacleSpawner = FindFirstObjectByType<ObstacleSpawner>();
if (obstacleSpawner != null)
{
obstacleSpawner.StartSurfacing();
}
// Note: Monster spawning is handled automatically through the Update and OnTileSpawned methods
// which will check the _isSurfacing flag
}
// Call this when the game ends
public void EndGame()
{
// Clean up active monsters
foreach (var monster in activeMonsters.ToArray())
{
if (monster != null)
{
monster.DespawnMonster();
}
}
activeMonsters.Clear();
// Final score could be saved to player prefs or other persistence
Debug.Log($"Final Score: {playerScore}");
}
}
}