Files
Crash-Course/Assets/Scripts/Game/GameManager.cs

507 lines
18 KiB
C#
Raw Normal View History

2025-04-04 12:09:40 -04:00
using System.Collections;
2025-02-19 20:11:57 -05:00
using System.Collections.Generic;
2025-04-18 15:54:50 -04:00
using UnityEngine;
using Game;
using Music;
using Player;
using UnityEngine.Events;
2025-03-07 11:56:19 -05:00
using UnityEngine.InputSystem;
2025-04-18 20:11:19 -04:00
using UnityEngine.InputSystem.Controls;
2025-04-16 19:57:54 -04:00
namespace Game
{
2025-04-14 18:47:17 -04:00
/// <summary>
2025-04-18 15:54:50 -04:00
/// This class controls the main game logic, like starting the game, keeping track of players,
/// handling game modes, and deciding when the game ends.
2025-04-14 18:47:17 -04:00
/// </summary>
2025-04-18 15:54:50 -04:00
public class GameManager : MonoBehaviour
{
2025-04-18 15:54:50 -04:00
/// <summary>
/// The single instance of this class that can be accessed from anywhere in the game.
/// </summary>
public static GameManager Instance { get; private set; }
2025-02-28 14:01:55 -05:00
2025-04-18 15:54:50 -04:00
/// <summary>
/// The total time (in seconds) for the game to run.
/// </summary>
public float time = 180f;
/// <summary>
/// A type of event that happens during the game, like when it starts or ends.
/// </summary>
public delegate void GameEvent();
/// <summary>
/// Event triggered when the game starts.
/// </summary>
public event GameEvent StartGameEvent;
/// <summary>
/// Event triggered when the game ends.
/// </summary>
public event GameEvent EndGameEvent;
/// <summary>
/// A list of all the players in the game.
/// </summary>
public static List<GameObject> players = new List<GameObject>();
/// <summary>
/// A list of colors assigned to each player.
/// </summary>
public static List<Color> playerColors = new List<Color>();
/// <summary>
/// The distance between players when they spawn.
/// </summary>
public float offset = 1f;
/// <summary>
/// Whether the background music is turned on.
/// </summary>
public static bool music = true;
/// <summary>
/// Whether the game is currently over.
/// </summary>
public bool gameOver = false;
/// <summary>
/// A timer that counts down during the game.
/// </summary>
public GameTimer gameTimer;
/// <summary>
/// Tracks how long each player has held an item in "keep-away" mode.
/// </summary>
public static Dictionary<GameObject, float> playerHoldTimes = new Dictionary<GameObject, float>();
/// <summary>
/// The current game mode (e.g., free-for-all, keep-away, or obstacle course).
/// </summary>
public static GameMode gameMode = GameMode.freeForAll;
/// <summary>
/// The name of the map being played.
/// </summary>
public static string map = "Platformer With Headroom";
/// <summary>
/// The position where players spawn at the start of the game.
/// </summary>
public Vector2 spawnPosition;
/// <summary>
/// The position where players spawn in obstacle course mode.
/// </summary>
public Vector2 obstacleCourseSpawnPosition;
/// <summary>
/// Positions where the hat can spawn in "keep-away" mode.
/// </summary>
public List<Vector2> hatSpawnPositions = new List<Vector2>();
/// <summary>
/// The canvas that shows the leaderboard during the game.
/// </summary>
public Canvas LeaderboardCanvas;
/// <summary>
/// The canvas that shows the timer during the game.
/// </summary>
public Canvas TimerCanvas;
/// <summary>
/// The hat object used in "keep-away" mode.
/// </summary>
public GameObject hatObject;
/// <summary>
/// The different game modes players can choose from.
/// </summary>
public enum GameMode
2025-03-04 20:10:28 -05:00
{
2025-04-18 15:54:50 -04:00
/// <summary>
/// Players compete individually to be the last one standing.
/// </summary>
freeForAll,
/// <summary>
/// Players compete to hold an item (like a hat) for the longest time.
/// </summary>
keepAway,
/// <summary>
/// Players race to complete an obstacle course.
/// </summary>
obstacleCourse
2025-03-04 20:10:28 -05:00
}
2025-04-18 15:54:50 -04:00
/// <summary>
/// Makes sure there is only one GameManager in the game. If another one exists, it gets deleted.
/// </summary>
private void Awake()
{
2025-04-18 15:54:50 -04:00
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
2025-02-28 13:17:06 -05:00
2025-04-18 15:54:50 -04:00
/// <summary>
/// Starts the game and plays background music if it's enabled.
/// </summary>
private void Start()
2025-03-28 21:27:09 -04:00
{
2025-04-18 15:54:50 -04:00
if (MusicManager.Instance != null)
2025-03-31 18:43:02 -04:00
{
2025-04-18 15:54:50 -04:00
MusicManager.Instance.StartPlaylist();
2025-03-31 18:43:02 -04:00
}
2025-04-18 15:54:50 -04:00
StartGame();
2025-03-28 21:27:09 -04:00
}
2025-04-18 15:54:50 -04:00
/// <summary>
/// Updates the game every frame. In "keep-away" mode, it tracks how long each player holds the item.
/// </summary>
private void Update()
2025-03-28 21:27:09 -04:00
{
2025-04-18 15:54:50 -04:00
if (gameOver) return;
2025-03-28 21:27:09 -04:00
2025-04-18 15:54:50 -04:00
if (gameMode == GameMode.keepAway)
{
foreach (var player in players)
{
float holdTime = GetPlayerHoldTime(player);
UpdatePlayerHoldTime(player, holdTime);
}
}
}
2025-03-07 16:50:04 -05:00
2025-04-18 15:54:50 -04:00
/// <summary>
/// Gets how long a player has held the item in "keep-away" mode.
/// </summary>
/// <param name="player">The player to check.</param>
/// <returns>The time (in seconds) the player has held the item.</returns>
private float GetPlayerHoldTime(GameObject player)
2025-02-26 18:16:51 -05:00
{
2025-04-18 15:54:50 -04:00
UseItem useItem = player.GetComponent<UseItem>();
if (useItem != null)
{
2025-04-18 15:54:50 -04:00
return useItem.holdTime;
}
2025-04-18 15:54:50 -04:00
return 0f;
2025-02-26 18:16:51 -05:00
}
2025-04-18 15:54:50 -04:00
/// <summary>
/// Sets up the game based on the selected game mode. This includes spawning players and setting their lives.
/// </summary>
public void StartGame()
2025-02-26 18:16:51 -05:00
{
2025-04-18 15:54:50 -04:00
GameManager.playerHoldTimes.Clear();
if (GameManager.players.Count == 0) return;
StartGameEvent?.Invoke();
2025-04-20 12:35:58 -04:00
print("invoked startgameevent");
2025-04-18 15:54:50 -04:00
print("Starting game with mode: " + gameMode + " and map: " + map);
if (gameMode == GameMode.freeForAll)
{
2025-04-18 15:54:50 -04:00
foreach (GameObject player in players)
{
2025-04-19 12:54:05 -04:00
player.transform.position = spawnPosition + (offset * (int.Parse(player.name) - 1) * Vector2.right);
2025-04-18 15:54:50 -04:00
player.GetComponent<Damageable>().lives = 5;
}
}
2025-04-18 15:54:50 -04:00
if (gameMode == GameMode.keepAway)
{
2025-04-18 15:54:50 -04:00
if (gameTimer != null)
{
gameTimer.startTime = time;
gameTimer.StartTimer();
}
foreach (GameObject player in players)
{
2025-04-19 12:54:05 -04:00
player.transform.position = spawnPosition + (offset * (int.Parse(player.name) - 1) * Vector2.right);
2025-04-18 15:54:50 -04:00
player.GetComponent<Damageable>().lives = 0;
}
}
2025-04-18 15:54:50 -04:00
if (gameMode == GameMode.obstacleCourse)
{
2025-04-18 15:54:50 -04:00
foreach (GameObject player in players)
{
2025-04-19 12:54:05 -04:00
print("processing player " + player.name);
2025-04-19 11:50:17 -04:00
if (obstacleCourseSpawnPosition == Vector2.zero)
{
2025-04-19 12:54:05 -04:00
player.transform.position = spawnPosition + (offset * (int.Parse(player.name) - 1) * Vector2.right);
2025-04-19 11:50:17 -04:00
}
else
{
2025-04-19 12:54:05 -04:00
player.transform.position = obstacleCourseSpawnPosition + (offset * (int.Parse(player.name) - 1) * Vector2.right);
2025-04-19 11:50:17 -04:00
}
2025-04-18 15:54:50 -04:00
player.GetComponent<Damageable>().lives = 0;
}
}
2025-02-26 18:16:51 -05:00
}
2025-04-18 15:54:50 -04:00
/// <summary>
/// Handles what happens when a player dies, like respawning them or ending the game.
/// </summary>
/// <param name="player">The player who died.</param>
public void PlayerDied(Damageable player)
{
2025-04-18 15:54:50 -04:00
UseItem useItem = player.GetComponent<UseItem>();
if (useItem != null && !gameOver)
2025-04-04 12:09:40 -04:00
{
useItem.DropItem();
}
2025-04-18 15:54:50 -04:00
if (gameMode == GameMode.freeForAll)
2025-02-28 14:01:55 -05:00
{
2025-04-18 15:54:50 -04:00
player.lives--;
if (player.lives <= 0 && !gameOver)
{
player.gameObject.SetActive(false);
if (AlivePlayers().Count <= 1)
{
GameOver();
}
}
else
2025-03-08 13:33:19 -05:00
{
2025-04-18 15:54:50 -04:00
RespawnPlayer(player.gameObject);
2025-03-08 13:33:19 -05:00
}
2025-02-28 14:01:55 -05:00
}
else
{
2025-03-07 10:03:16 -05:00
RespawnPlayer(player.gameObject);
2025-02-28 14:01:55 -05:00
}
}
2025-04-18 15:54:50 -04:00
/// <summary>
/// Respawns a player at their starting position and resets their damage.
/// </summary>
/// <param name="player">The player to respawn.</param>
private void RespawnPlayer(GameObject player)
2025-02-28 14:01:55 -05:00
{
2025-04-18 15:54:50 -04:00
RespawnOnTriggerEnter respawnScript = player.GetComponent<RespawnOnTriggerEnter>();
if (respawnScript != null)
{
2025-04-19 11:50:17 -04:00
//player.transform.position = respawnScript.spawnPoint;
if (GameManager.gameMode == GameMode.obstacleCourse)
{
player.transform.position = obstacleCourseSpawnPosition + (offset * players.IndexOf(player) * Vector2.right);
}
else
{
player.transform.position = spawnPosition + (offset * players.IndexOf(player) * Vector2.right);
}
2025-04-18 15:54:50 -04:00
player.GetComponent<Damageable>().ResetDamage();
player.GetComponent<Damageable>().Respawn();
}
2025-02-28 14:01:55 -05:00
}
2025-04-18 15:54:50 -04:00
/// <summary>
/// Ends the game and determines the winner based on the current game mode.
/// </summary>
/// <remarks>
/// This method handles the end-of-game logic, such as stopping the timer, hiding UI elements,
/// and determining the winner based on the <see cref="GameMode"/>. It also triggers the
/// <see cref="EndGameEvent"/> for any subscribed listeners.
///
/// In "free-for-all" mode, the last alive player is declared the winner. In "keep-away" mode,
/// the player with the longest hold time wins. In "obstacle course" mode, the winner is determined
/// by the <see cref="ObstacleCourse"/> logic.
/// </remarks>
/// <example>
/// <code>
/// GameManager.Instance.GameOver();
/// </code>
/// </example>
public void GameOver()
2025-03-20 14:45:29 -04:00
{
2025-04-18 15:54:50 -04:00
// Mark the game as over
gameOver = true;
// Trigger the end game event for any listeners
EndGameEvent?.Invoke();
2025-04-20 12:35:58 -04:00
print("invoked engameeveevt");
2025-04-18 15:54:50 -04:00
// Hide the leaderboard and timer UI if they exist
if (LeaderboardCanvas != null)
2025-03-20 14:45:29 -04:00
{
2025-04-18 15:54:50 -04:00
LeaderboardCanvas.gameObject.SetActive(false);
2025-03-20 14:45:29 -04:00
}
2025-04-18 15:54:50 -04:00
if (TimerCanvas != null)
2025-03-20 14:45:29 -04:00
{
2025-04-18 15:54:50 -04:00
TimerCanvas.gameObject.SetActive(false);
}
// Handle the winner logic based on the game mode
if (gameMode == GameMode.freeForAll)
{
// In free-for-all mode, the last alive player is the winner
2025-04-18 20:11:19 -04:00
GameObject winner = FindFirstObjectByType<PlayerMovement>().gameObject;//AlivePlayers()[0];
2025-04-18 15:54:50 -04:00
print(winner.name + " is the winner");
// Show the winner's scene and update the win screen
FindFirstObjectByType<PlayerCameraMovement>().WinScene(winner);
2025-04-18 20:11:19 -04:00
print($"Player {int.Parse(winner.name)} won");
WinScreen.Instance.ShowWinScreen(int.Parse(winner.name));
2025-04-18 15:54:50 -04:00
// Hide the life display UI
FindFirstObjectByType<LifeDisplayManager>().HideLifeDisplay();
}
else if (gameMode == GameMode.keepAway)
{
// In keep-away mode, the player with the longest hold time is the winner
GameObject winner = null;
float maxHoldTime = -1f;
foreach (var player in GameManager.playerHoldTimes)
{
2025-04-18 15:54:50 -04:00
if (player.Value > maxHoldTime)
{
maxHoldTime = player.Value;
winner = player.Key;
}
}
2025-04-18 15:54:50 -04:00
if (winner != null)
{
2025-04-18 15:54:50 -04:00
print(winner.name + " is the winner with " + maxHoldTime + " seconds!");
// Show the winner's scene and update the win screen
var cameraMovement = FindFirstObjectByType<PlayerCameraMovement>();
if (cameraMovement != null)
{
cameraMovement.WinScene(winner);
}
WinScreen.Instance.ShowWinScreen(players.IndexOf(winner) + 1);
// Hide the life display UI
var lifeDisplayManager = FindFirstObjectByType<LifeDisplayManager>();
if (lifeDisplayManager != null)
{
lifeDisplayManager.HideLifeDisplay();
}
// Move the hat to the winner
StartCoroutine(MoveHatToWinner(winner));
// Reactivate the hat object
if (hatObject != null)
{
hatObject.SetActive(true);
hatObject.GetComponent<Collider2D>().enabled = true;
hatObject.GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Dynamic;
}
}
2025-03-20 14:45:29 -04:00
}
2025-04-18 15:54:50 -04:00
else if (gameMode == GameMode.obstacleCourse)
{
// In obstacle course mode, the winner is determined by the ObstacleCourse logic
GameObject winner = ObstacleCourse.playerWon;
print(winner.name + " is the winner!");
2025-03-31 18:28:05 -04:00
2025-04-18 15:54:50 -04:00
// Show the winner's scene and update the win screen
FindFirstObjectByType<PlayerCameraMovement>().WinScene(winner);
WinScreen.Instance.ShowWinScreen(players.IndexOf(winner) + 1);
// Hide the life display UI
FindFirstObjectByType<LifeDisplayManager>().HideLifeDisplay();
}
2025-03-31 18:28:05 -04:00
}
2025-04-04 12:09:40 -04:00
2025-04-18 15:54:50 -04:00
/// <summary>
/// Moves the hat to the winner in "keep-away" mode.
/// </summary>
/// <param name="winner">The player who won the game.</param>
/// <returns>An IEnumerator for coroutine execution.</returns>
/// <remarks>
/// This coroutine ensures that the hat is moved to the winner and picked up by them.
/// It keeps trying until the winner successfully picks up the hat.
/// </remarks>
private IEnumerator MoveHatToWinner(GameObject winner)
2025-04-04 12:09:40 -04:00
{
2025-04-18 15:54:50 -04:00
while (!winner.GetComponent<UseItem>().IsHoldingItem())
{
// Position the hat above the winner
hatObject.transform.position = winner.transform.position + Vector3.up * 1.5f;
2025-03-07 10:03:16 -05:00
2025-04-18 15:54:50 -04:00
// Attempt to make the winner pick up the hat
winner.GetComponent<UseItem>().PickUpItem(hatObject);
2025-03-07 10:03:16 -05:00
2025-04-18 15:54:50 -04:00
// Wait for the next frame before trying again
yield return null;
}
2025-03-07 10:03:16 -05:00
}
2025-04-18 15:54:50 -04:00
/// <summary>
/// Gets a list of all players who are still alive in the game.
/// </summary>
/// <returns>A list of players who are still active in the game.</returns>
/// <remarks>
/// This method checks all players in the game and returns only those who are still active.
/// A player is considered "alive" if their GameObject is active in the scene.
/// </remarks>
public List<GameObject> AlivePlayers()
{
List<GameObject> alivePlayers = new();
2025-03-20 14:45:29 -04:00
2025-04-18 15:54:50 -04:00
// Check each player to see if they are still active
foreach (GameObject player in players)
{
if (player.activeInHierarchy)
{
alivePlayers.Add(player);
}
}
return alivePlayers;
}
2025-03-28 21:27:09 -04:00
2025-04-18 15:54:50 -04:00
/// <summary>
/// Updates the hold time for a player and refreshes the leaderboard.
/// </summary>
/// <param name="player">The player whose hold time is being updated.</param>
/// <param name="holdTime">The new hold time for the player.</param>
/// <remarks>
/// This method updates the player's hold time in the dictionary and refreshes the leaderboard UI.
/// If the player's hold time is higher than before, the leaderboard is re-sorted.
/// </remarks>
public void UpdatePlayerHoldTime(GameObject player, float holdTime)
2025-03-25 22:21:21 -04:00
{
2025-04-18 15:54:50 -04:00
bool shouldSort = false;
// Check if the player already has a recorded hold time
if (playerHoldTimes.ContainsKey(player))
{
// Update the hold time if the new time is greater
if (holdTime > playerHoldTimes[player])
{
shouldSort = true;
}
playerHoldTimes[player] = holdTime;
}
else
2025-03-28 21:27:09 -04:00
{
2025-04-18 15:54:50 -04:00
// Add the player to the dictionary if they are not already in it
playerHoldTimes.Add(player, holdTime);
2025-03-28 21:27:09 -04:00
shouldSort = true;
}
2025-04-18 15:54:50 -04:00
// Update the leaderboard UI with the new hold time
LeaderboardManager.Instance.UpdatePlayerHoldTimeText(player, holdTime);
// Re-sort the leaderboard if necessary
if (shouldSort)
{
LeaderboardManager.Instance.UpdateLeaderboard();
}
2025-03-25 22:21:21 -04:00
}
2025-03-20 14:45:29 -04:00
}
2025-04-16 19:57:54 -04:00
}