2025-04-04 12:09:40 -04:00
|
|
|
using System.Collections;
|
2025-02-19 20:11:57 -05:00
|
|
|
using System.Collections.Generic;
|
2025-02-17 18:23:05 -05:00
|
|
|
using UnityEngine;
|
2025-03-07 23:32:42 -05:00
|
|
|
using UnityEngine.Events;
|
2025-03-07 11:56:19 -05:00
|
|
|
using UnityEngine.InputSystem;
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// The GameManager class manages the overall game logic, including game modes, player states,
|
|
|
|
|
/// game events, and game-over conditions. It ensures a single instance exists and provides
|
|
|
|
|
/// functionality for starting, updating, and ending the game.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Manages the overall game logic, including game modes, player states, game events, and game-over conditions.
|
|
|
|
|
/// </summary>
|
2025-02-17 18:23:05 -05:00
|
|
|
public class GameManager : MonoBehaviour
|
|
|
|
|
{
|
2025-02-28 14:01:55 -05:00
|
|
|
public static GameManager Instance { get; private set; }
|
2025-03-28 13:00:37 -04:00
|
|
|
public float time = 180f;
|
2025-03-06 01:27:42 -05:00
|
|
|
public delegate void GameEvent();
|
|
|
|
|
public event GameEvent StartGameEvent;
|
|
|
|
|
public event GameEvent EndGameEvent;
|
|
|
|
|
public static List<GameObject> players = new List<GameObject>();
|
2025-03-07 16:25:59 -05:00
|
|
|
public static List<Color> playerColors = new List<Color>();
|
2025-03-07 16:50:04 -05:00
|
|
|
public float offset = 1f;
|
2025-03-07 23:32:42 -05:00
|
|
|
public static bool music = true;
|
2025-03-07 16:50:04 -05:00
|
|
|
public bool gameOver = false;
|
2025-03-20 14:45:29 -04:00
|
|
|
public GameTimer gameTimer;
|
|
|
|
|
public static Dictionary<GameObject, float> playerHoldTimes = new Dictionary<GameObject, float>();
|
2025-03-28 17:39:07 -04:00
|
|
|
public static GameMode gameMode = GameMode.freeForAll; // loads a default gamemode as a safety net
|
|
|
|
|
public static string map = "Platformer With Headroom"; // loads a default map as a safety net
|
|
|
|
|
public Vector2 spawnPosition;
|
2025-04-07 18:30:35 -04:00
|
|
|
public Vector2 obstacleCourseSpawnPosition;
|
2025-03-29 15:18:27 -04:00
|
|
|
public List<Vector2> hatSpawnPositions = new List<Vector2>();
|
2025-03-28 22:50:33 -04:00
|
|
|
public Canvas LeaderboardCanvas;
|
|
|
|
|
public Canvas TimerCanvas;
|
|
|
|
|
public GameObject hatObject;
|
2025-04-14 18:47:17 -04:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Enum representing the different game modes.
|
|
|
|
|
/// </summary>
|
2025-03-28 17:39:07 -04:00
|
|
|
public enum GameMode
|
|
|
|
|
{
|
|
|
|
|
freeForAll,
|
|
|
|
|
keepAway,
|
|
|
|
|
obstacleCourse
|
|
|
|
|
}
|
2025-02-28 14:01:55 -05:00
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Ensures only one instance of GameManager exists.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void Awake()
|
2025-03-04 20:10:28 -05:00
|
|
|
{
|
|
|
|
|
if (Instance == null)
|
|
|
|
|
{
|
|
|
|
|
Instance = this;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Destroy(gameObject);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Starts the game and initializes music.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void Start()
|
2025-02-28 13:17:06 -05:00
|
|
|
{
|
2025-03-07 23:32:42 -05:00
|
|
|
MusicManager.Instance.StartPlaylist();
|
2025-02-28 13:17:06 -05:00
|
|
|
StartGame();
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Continuously updates player hold times during the game.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void Update()
|
2025-03-28 21:27:09 -04:00
|
|
|
{
|
2025-03-28 22:50:33 -04:00
|
|
|
if (gameOver) return;
|
|
|
|
|
|
2025-03-31 18:43:02 -04:00
|
|
|
if (gameMode == GameMode.keepAway)
|
2025-03-28 21:27:09 -04:00
|
|
|
{
|
2025-03-31 18:43:02 -04:00
|
|
|
foreach (var player in players)
|
|
|
|
|
{
|
|
|
|
|
float holdTime = GetPlayerHoldTime(player);
|
|
|
|
|
UpdatePlayerHoldTime(player, holdTime);
|
|
|
|
|
}
|
2025-03-28 21:27:09 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves the hold time of a player.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="player">The player GameObject.</param>
|
|
|
|
|
/// <returns>The hold time of the player.</returns>
|
2025-03-28 21:27:09 -04:00
|
|
|
private float GetPlayerHoldTime(GameObject player)
|
|
|
|
|
{
|
|
|
|
|
UseItem useItem = player.GetComponent<UseItem>();
|
|
|
|
|
if (useItem != null)
|
|
|
|
|
{
|
|
|
|
|
return useItem.holdTime;
|
|
|
|
|
}
|
|
|
|
|
return 0f;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Sets up the game based on the selected game mode.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void StartGame()
|
2025-02-26 18:16:51 -05:00
|
|
|
{
|
2025-03-20 14:45:29 -04:00
|
|
|
GameManager.playerHoldTimes.Clear();
|
2025-03-07 16:50:04 -05:00
|
|
|
if (GameManager.players.Count == 0) return;
|
|
|
|
|
|
2025-03-06 01:27:42 -05:00
|
|
|
StartGameEvent?.Invoke();
|
2025-03-03 18:23:54 -05:00
|
|
|
print("Starting game with mode: " + gameMode + " and map: " + map);
|
2025-04-14 18:47:17 -04:00
|
|
|
if (gameMode == GameMode.freeForAll)
|
2025-02-26 18:16:51 -05:00
|
|
|
{
|
2025-03-06 01:27:42 -05:00
|
|
|
foreach (GameObject player in players)
|
|
|
|
|
{
|
2025-03-25 11:56:06 -04:00
|
|
|
player.transform.position = spawnPosition + (offset * players.IndexOf(player) * Vector2.right);
|
2025-03-17 19:59:41 -04:00
|
|
|
player.GetComponent<Damageable>().lives = 5;
|
2025-03-06 01:27:42 -05:00
|
|
|
}
|
2025-02-26 18:16:51 -05:00
|
|
|
}
|
2025-04-14 18:47:17 -04:00
|
|
|
if (gameMode == GameMode.keepAway)
|
2025-02-26 18:16:51 -05:00
|
|
|
{
|
2025-03-28 13:00:37 -04:00
|
|
|
gameTimer.startTime = time;
|
2025-03-20 14:45:29 -04:00
|
|
|
gameTimer.StartTimer();
|
2025-03-06 01:27:42 -05:00
|
|
|
foreach (GameObject player in players)
|
|
|
|
|
{
|
2025-03-25 11:56:06 -04:00
|
|
|
player.transform.position = spawnPosition + (offset * players.IndexOf(player) * Vector2.right);
|
2025-03-17 19:59:41 -04:00
|
|
|
player.GetComponent<Damageable>().lives = 0;
|
2025-03-06 01:27:42 -05:00
|
|
|
}
|
2025-02-26 18:16:51 -05:00
|
|
|
}
|
2025-04-14 18:47:17 -04:00
|
|
|
if (gameMode == GameMode.obstacleCourse)
|
2025-02-26 18:16:51 -05:00
|
|
|
{
|
2025-03-06 01:27:42 -05:00
|
|
|
foreach (GameObject player in players)
|
|
|
|
|
{
|
|
|
|
|
player.transform.position = spawnPosition;
|
2025-03-31 18:28:05 -04:00
|
|
|
player.GetComponent<Damageable>().lives = 0;
|
2025-03-06 01:27:42 -05:00
|
|
|
}
|
2025-02-26 18:16:51 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Handles player deaths based on the current game mode.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="player">The player that died.</param>
|
|
|
|
|
public void PlayerDied(Damageable player)
|
2025-02-28 14:01:55 -05:00
|
|
|
{
|
2025-04-14 18:47:17 -04:00
|
|
|
UseItem useItem = player.GetComponent<UseItem>();
|
2025-03-28 22:50:33 -04:00
|
|
|
if (useItem != null)
|
|
|
|
|
{
|
2025-04-04 12:09:40 -04:00
|
|
|
if (gameOver == false)
|
|
|
|
|
{
|
|
|
|
|
useItem.DropItem();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-04-14 18:47:17 -04:00
|
|
|
return;
|
2025-04-04 12:09:40 -04:00
|
|
|
}
|
2025-03-28 22:50:33 -04:00
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
if (gameMode == GameMode.freeForAll)
|
2025-02-28 14:01:55 -05:00
|
|
|
{
|
2025-03-07 10:03:16 -05:00
|
|
|
player.lives--;
|
2025-03-08 13:33:19 -05:00
|
|
|
if (player.lives <= 0 && !gameOver)
|
2025-02-28 14:01:55 -05:00
|
|
|
{
|
2025-03-08 13:33:19 -05:00
|
|
|
player.gameObject.SetActive(false);
|
|
|
|
|
if (AlivePlayers().Count <= 1)
|
|
|
|
|
{
|
2025-04-14 18:47:17 -04:00
|
|
|
GameOver();
|
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-14 18:47:17 -04:00
|
|
|
if (gameMode == GameMode.keepAway || gameMode == GameMode.obstacleCourse)
|
2025-02-28 14:01:55 -05:00
|
|
|
{
|
2025-03-31 18:43:02 -04:00
|
|
|
RespawnPlayer(player.gameObject);
|
2025-02-28 14:01:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Respawns a player at their designated spawn point.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="player">The player GameObject to respawn.</param>
|
|
|
|
|
private void RespawnPlayer(GameObject player)
|
2025-02-28 14:01:55 -05:00
|
|
|
{
|
|
|
|
|
RespawnOnTriggerEnter respawnScript = player.GetComponent<RespawnOnTriggerEnter>();
|
|
|
|
|
if (respawnScript != null)
|
|
|
|
|
{
|
|
|
|
|
player.transform.position = respawnScript.spawnPoint;
|
|
|
|
|
player.GetComponent<Damageable>().ResetDamage();
|
2025-03-07 10:03:16 -05:00
|
|
|
player.GetComponent<Damageable>().Respawn();
|
2025-02-28 14:01:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Ends the game and determines the winner based on the game mode.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void GameOver()
|
2025-02-28 14:01:55 -05:00
|
|
|
{
|
2025-03-08 13:33:19 -05:00
|
|
|
gameOver = true;
|
|
|
|
|
EndGameEvent?.Invoke();
|
2025-03-28 22:50:33 -04:00
|
|
|
LeaderboardCanvas.gameObject.SetActive(false);
|
|
|
|
|
TimerCanvas.gameObject.SetActive(false);
|
|
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
if (gameMode == GameMode.freeForAll)
|
2025-03-20 14:45:29 -04:00
|
|
|
{
|
2025-03-28 17:39:07 -04:00
|
|
|
GameObject winner = AlivePlayers()[0];
|
|
|
|
|
print(winner.name + " is the winner");
|
|
|
|
|
FindFirstObjectByType<PlayerCameraMovement>().WinScene(winner);
|
|
|
|
|
WinScreen.Instance.ShowWinScreen(players.IndexOf(winner) + 1);
|
2025-03-20 14:45:29 -04:00
|
|
|
FindFirstObjectByType<LifeDisplayManager>().HideLifeDisplay();
|
|
|
|
|
}
|
2025-04-14 18:47:17 -04:00
|
|
|
if (gameMode == GameMode.keepAway)
|
2025-03-20 14:45:29 -04:00
|
|
|
{
|
|
|
|
|
GameObject winner = null;
|
2025-03-31 18:28:05 -04:00
|
|
|
float maxHoldTime = -1f;
|
2025-03-20 14:45:29 -04:00
|
|
|
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!");
|
|
|
|
|
FindFirstObjectByType<PlayerCameraMovement>().WinScene(winner);
|
|
|
|
|
WinScreen.Instance.ShowWinScreen(players.IndexOf(winner) + 1);
|
|
|
|
|
FindFirstObjectByType<LifeDisplayManager>().HideLifeDisplay();
|
2025-04-04 12:09:40 -04:00
|
|
|
StartCoroutine(MoveHatToWinner(winner));
|
|
|
|
|
hatObject.SetActive(true);
|
|
|
|
|
hatObject.GetComponent<Collider2D>().enabled = true;
|
|
|
|
|
hatObject.GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Dynamic;
|
2025-03-20 14:45:29 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-04-14 18:47:17 -04:00
|
|
|
if (gameMode == GameMode.obstacleCourse)
|
2025-03-31 18:28:05 -04:00
|
|
|
{
|
|
|
|
|
GameObject winner = ObstacleCourse.playerWon;
|
|
|
|
|
|
|
|
|
|
print(winner.name + " is the winner!");
|
|
|
|
|
FindFirstObjectByType<PlayerCameraMovement>().WinScene(winner);
|
|
|
|
|
WinScreen.Instance.ShowWinScreen(players.IndexOf(winner) + 1);
|
|
|
|
|
FindFirstObjectByType<LifeDisplayManager>().HideLifeDisplay();
|
|
|
|
|
}
|
2025-03-07 10:03:16 -05:00
|
|
|
}
|
2025-04-04 12:09:40 -04:00
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Moves the hat to the winner in keep-away mode.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="winner">The winning player GameObject.</param>
|
|
|
|
|
/// <returns>An IEnumerator for coroutine execution.</returns>
|
2025-04-04 12:09:40 -04:00
|
|
|
private IEnumerator MoveHatToWinner(GameObject winner)
|
|
|
|
|
{
|
|
|
|
|
while (!winner.GetComponent<UseItem>().IsHoldingItem())
|
|
|
|
|
{
|
2025-04-14 18:47:17 -04:00
|
|
|
hatObject.transform.position = winner.transform.position + Vector3.up * 3 / 2;
|
2025-04-12 17:24:51 -04:00
|
|
|
winner.GetComponent<UseItem>().PickUpItem(hatObject);
|
2025-04-04 12:09:40 -04:00
|
|
|
yield return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-07 10:03:16 -05:00
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Returns a list of all players that are currently alive.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>A list of alive player GameObjects.</returns>
|
|
|
|
|
public List<GameObject> AlivePlayers()
|
2025-03-07 10:03:16 -05:00
|
|
|
{
|
|
|
|
|
List<GameObject> alivePlayers = new();
|
|
|
|
|
|
|
|
|
|
foreach (GameObject player in players)
|
|
|
|
|
{
|
|
|
|
|
if (player.activeInHierarchy) alivePlayers.Add(player);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return alivePlayers;
|
2025-02-28 14:01:55 -05:00
|
|
|
}
|
2025-03-20 14:45:29 -04:00
|
|
|
|
2025-04-14 18:47:17 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Updates the player's hold time and updates the leaderboard.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="player">The player GameObject.</param>
|
|
|
|
|
/// <param name="holdTime">The hold time to update.</param>
|
|
|
|
|
public void UpdatePlayerHoldTime(GameObject player, float holdTime)
|
2025-03-20 14:45:29 -04:00
|
|
|
{
|
2025-03-28 21:27:09 -04:00
|
|
|
bool shouldSort = false;
|
|
|
|
|
|
2025-03-25 22:21:21 -04:00
|
|
|
if (playerHoldTimes.ContainsKey(player))
|
|
|
|
|
{
|
2025-03-28 21:27:09 -04:00
|
|
|
if (holdTime > playerHoldTimes[player])
|
|
|
|
|
{
|
|
|
|
|
shouldSort = true;
|
|
|
|
|
}
|
2025-03-25 22:21:21 -04:00
|
|
|
playerHoldTimes[player] = holdTime;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
playerHoldTimes.Add(player, holdTime);
|
2025-03-28 21:27:09 -04:00
|
|
|
shouldSort = true;
|
|
|
|
|
}
|
|
|
|
|
LeaderboardManager.Instance.UpdatePlayerHoldTimeText(player, holdTime);
|
|
|
|
|
if (shouldSort)
|
|
|
|
|
{
|
|
|
|
|
LeaderboardManager.Instance.UpdateLeaderboard();
|
2025-03-25 22:21:21 -04:00
|
|
|
}
|
2025-03-20 14:45:29 -04:00
|
|
|
}
|
2025-02-17 18:23:05 -05:00
|
|
|
}
|