using System.Collections; using System.Collections.Generic; using UnityEngine; using Game; using Music; using Player; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; namespace Game { /// /// This class controls the main game logic, like starting the game, keeping track of players, /// handling game modes, and deciding when the game ends. /// public class GameManager : MonoBehaviour { /// /// The single instance of this class that can be accessed from anywhere in the game. /// public static GameManager Instance { get; private set; } /// /// The total time (in seconds) for the game to run. /// public float time = 180f; /// /// A type of event that happens during the game, like when it starts or ends. /// public delegate void GameEvent(); /// /// Event triggered when the game starts. /// public event GameEvent StartGameEvent; /// /// Event triggered when the game ends. /// public event GameEvent EndGameEvent; /// /// A list of all the players in the game. /// public static List players = new List(); /// /// A list of colors assigned to each player. /// public static List playerColors = new List(); /// /// The distance between players when they spawn. /// public float offset = 1f; /// /// Whether the background music is turned on. /// public static bool music = true; /// /// Whether the game is currently over. /// public bool gameOver = false; /// /// A timer that counts down during the game. /// public GameTimer gameTimer; /// /// Tracks how long each player has held an item in "keep-away" mode. /// public static Dictionary playerHoldTimes = new Dictionary(); /// /// The current game mode (e.g., free-for-all, keep-away, or obstacle course). /// public static GameMode gameMode = GameMode.freeForAll; /// /// The name of the map being played. /// public static string map = "Platformer With Headroom"; /// /// The position where players spawn at the start of the game. /// public Vector2 spawnPosition; /// /// The position where players spawn in obstacle course mode. /// public Vector2 obstacleCourseSpawnPosition; /// /// Positions where the hat can spawn in "keep-away" mode. /// public List hatSpawnPositions = new List(); /// /// The canvas that shows the leaderboard during the game. /// public Canvas LeaderboardCanvas; /// /// The canvas that shows the timer during the game. /// public Canvas TimerCanvas; /// /// The hat object used in "keep-away" mode. /// public GameObject hatObject; /// /// The different game modes players can choose from. /// public enum GameMode { /// /// Players compete individually to be the last one standing. /// freeForAll, /// /// Players compete to hold an item (like a hat) for the longest time. /// keepAway, /// /// Players race to complete an obstacle course. /// obstacleCourse } /// /// Makes sure there is only one GameManager in the game. If another one exists, it gets deleted. /// private void Awake() { if (Instance == null) { Instance = this; } else { Destroy(gameObject); } } /// /// Starts the game and plays background music if it's enabled. /// private void Start() { if (MusicManager.Instance != null) { MusicManager.Instance.StartPlaylist(); } StartGame(); } /// /// Updates the game every frame. In "keep-away" mode, it tracks how long each player holds the item. /// private void Update() { if (gameOver) return; if (gameMode == GameMode.keepAway) { foreach (var player in players) { float holdTime = GetPlayerHoldTime(player); UpdatePlayerHoldTime(player, holdTime); } } } /// /// Gets how long a player has held the item in "keep-away" mode. /// /// The player to check. /// The time (in seconds) the player has held the item. private float GetPlayerHoldTime(GameObject player) { UseItem useItem = player.GetComponent(); if (useItem != null) { return useItem.holdTime; } return 0f; } /// /// Sets up the game based on the selected game mode. This includes spawning players and setting their lives. /// public void StartGame() { Instance = this; GameManager.playerHoldTimes.Clear(); if (GameManager.players.Count == 0) return; StartGameEvent?.Invoke(); print("invoked startgameevent"); print("Starting game with mode: " + gameMode + " and map: " + map); if (gameMode == GameMode.freeForAll) { foreach (GameObject player in players) { player.transform.position = spawnPosition + (offset * (int.Parse(player.name) - 1) * Vector2.right); player.GetComponent().lives = 5; } } if (gameMode == GameMode.keepAway) { if (gameTimer != null) { gameTimer.startTime = time; gameTimer.StartTimer(); } foreach (GameObject player in players) { player.transform.position = spawnPosition + (offset * (int.Parse(player.name) - 1) * Vector2.right); player.GetComponent().lives = 0; } } if (gameMode == GameMode.obstacleCourse) { foreach (GameObject player in players) { print("processing player " + player.name); if (obstacleCourseSpawnPosition == Vector2.zero) { player.transform.position = spawnPosition + (offset * (int.Parse(player.name) - 1) * Vector2.right); } else { player.transform.position = obstacleCourseSpawnPosition + (offset * (int.Parse(player.name) - 1) * Vector2.right); } player.GetComponent().lives = 0; } } } /// /// Handles what happens when a player dies, like respawning them or ending the game. /// /// The player who died. public void PlayerDied(Damageable player) { UseItem useItem = player.GetComponent(); if (useItem != null && !gameOver) { useItem.DropItem(); } if (gameMode == GameMode.freeForAll) { player.lives--; if (player.lives <= 0 && !gameOver) { player.gameObject.SetActive(false); if (AlivePlayers().Count <= 1) { GameOver(); } } else { RespawnPlayer(player.gameObject); } } else { RespawnPlayer(player.gameObject); } } /// /// Respawns a player at their starting position and resets their damage. /// /// The player to respawn. private void RespawnPlayer(GameObject player) { RespawnOnTriggerEnter respawnScript = player.GetComponent(); if (respawnScript != null) { //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); } player.GetComponent().ResetDamage(); player.GetComponent().Respawn(); } } /// /// Ends the game and determines the winner based on the current game mode. /// /// /// This method handles the end-of-game logic, such as stopping the timer, hiding UI elements, /// and determining the winner based on the . It also triggers the /// 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 logic. /// /// /// /// GameManager.Instance.GameOver(); /// /// public void GameOver() { // Mark the game as over gameOver = true; // Trigger the end game event for any listeners EndGameEvent?.Invoke(); print("invoked engameeveevt"); // Hide the leaderboard and timer UI if they exist if (LeaderboardCanvas != null) { LeaderboardCanvas.gameObject.SetActive(false); } if (TimerCanvas != null) { 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 GameObject winner = FindFirstObjectByType().gameObject;//AlivePlayers()[0]; print(winner.name + " is the winner"); // Show the winner's scene and update the win screen FindFirstObjectByType().WinScene(winner); print($"Player {int.Parse(winner.name)} won"); WinScreen.Instance.ShowWinScreen(int.Parse(winner.name)); // Hide the life display UI FindFirstObjectByType().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) { if (player.Value > maxHoldTime) { maxHoldTime = player.Value; winner = player.Key; } } if (winner != null) { print(winner.name + " is the winner with " + maxHoldTime + " seconds!"); // Show the winner's scene and update the win screen var cameraMovement = FindFirstObjectByType(); if (cameraMovement != null) { cameraMovement.WinScene(winner); } WinScreen.Instance.ShowWinScreen(players.IndexOf(winner) + 1); // Hide the life display UI var lifeDisplayManager = FindFirstObjectByType(); 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().enabled = true; hatObject.GetComponent().bodyType = RigidbodyType2D.Dynamic; } } } 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!"); // Show the winner's scene and update the win screen FindFirstObjectByType().WinScene(winner); WinScreen.Instance.ShowWinScreen(players.IndexOf(winner) + 1); // Hide the life display UI FindFirstObjectByType().HideLifeDisplay(); } } /// /// Moves the hat to the winner in "keep-away" mode. /// /// The player who won the game. /// An IEnumerator for coroutine execution. /// /// 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. /// private IEnumerator MoveHatToWinner(GameObject winner) { while (!winner.GetComponent().IsHoldingItem()) { // Position the hat above the winner hatObject.transform.position = winner.transform.position + Vector3.up * 1.5f; // Attempt to make the winner pick up the hat winner.GetComponent().PickUpItem(hatObject); // Wait for the next frame before trying again yield return null; } } /// /// Gets a list of all players who are still alive in the game. /// /// A list of players who are still active in the game. /// /// 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. /// public List AlivePlayers() { List alivePlayers = new(); // Check each player to see if they are still active foreach (GameObject player in players) { if (player.activeInHierarchy) { alivePlayers.Add(player); } } return alivePlayers; } /// /// Updates the hold time for a player and refreshes the leaderboard. /// /// The player whose hold time is being updated. /// The new hold time for the player. /// /// 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. /// public void UpdatePlayerHoldTime(GameObject player, float holdTime) { 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 { // Add the player to the dictionary if they are not already in it playerHoldTimes.Add(player, holdTime); shouldSort = true; } // 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(); } } } }