Added comments to everything

This commit is contained in:
djkellerman
2025-04-18 15:54:50 -04:00
parent a0305ea0e9
commit 213bb2d14b
39 changed files with 3166 additions and 1796 deletions

View File

@@ -1,69 +1,113 @@
using System.Collections.Generic;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Music
{
public class AudioManager : MonoBehaviour
{
public List<SoundEffect> soundEffects = new List<SoundEffect>();
public static AudioManager Instance;
private void Awake()
/// <summary>
/// This class manages the playback of sound effects in the game.
/// It provides functionality to play specific sounds by name and ensures a singleton instance for global access.
/// </summary>
public class AudioManager : MonoBehaviour
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
/// <summary>
/// A list of all sound effects managed by the AudioManager.
/// </summary>
public List<SoundEffect> soundEffects = new List<SoundEffect>();
foreach (Transform child in transform)
/// <summary>
/// The singleton instance of the <see cref="AudioManager"/> class.
/// </summary>
public static AudioManager Instance;
/// <summary>
/// Initializes the singleton instance and loads all child AudioSources as sound effects.
/// </summary>
private void Awake()
{
var soundEffect = new SoundEffect(child.name, child.GetComponent<AudioSource>());
if (soundEffect != null)
// Ensure only one instance of the AudioManager exists
if (Instance == null)
{
soundEffects.Add(soundEffect);
Instance = this;
DontDestroyOnLoad(gameObject); // Persist across scenes
}
else
{
Destroy(gameObject); // Destroy duplicate instances
}
// Load all child AudioSources into the soundEffects list
foreach (Transform child in transform)
{
var soundEffect = new SoundEffect(child.name, child.GetComponent<AudioSource>());
if (soundEffect != null)
{
soundEffects.Add(soundEffect);
}
}
}
}
public void PlaySound(string soundName)
{
if (soundName == "Punch")
/// <summary>
/// Plays a sound effect by its name.
/// </summary>
/// <param name="soundName">The name of the sound effect to play.</param>
/// <remarks>
/// If the sound name is "Punch," it plays multiple punch-related sound effects.
/// If the sound is not found, a warning is logged to the console.
/// </remarks>
public void PlaySound(string soundName)
{
soundEffects.Find(x => x.name == "Punch").audioSource.Play();
soundEffects.Find(x => x.name == "Punch 2").audioSource.Play();
soundEffects.Find(x => x.name == "Punch 3").audioSource.Play();
return;
}
foreach (var soundEffect in soundEffects)
{
if (soundEffect.name == soundName)
// Special case: Play multiple punch sound effects
if (soundName == "Punch")
{
soundEffect.audioSource.Play();
soundEffects.Find(x => x.name == "Punch").audioSource.Play();
soundEffects.Find(x => x.name == "Punch 2").audioSource.Play();
soundEffects.Find(x => x.name == "Punch 3").audioSource.Play();
return;
}
// Find and play the sound effect by name
foreach (var soundEffect in soundEffects)
{
if (soundEffect.name == soundName)
{
soundEffect.audioSource.Play();
return;
}
}
// Log a warning if the sound effect is not found
Debug.LogWarning($"Sound '{soundName}' not found!");
}
Debug.LogWarning($"Sound '{soundName}' not found!");
}
}
public class SoundEffect
{
public string name;
public AudioSource audioSource;
public SoundEffect(string name, AudioSource audioSource)
/// <summary>
/// Represents a sound effect, including its name and associated AudioSource.
/// </summary>
public class SoundEffect
{
this.name = name;
this.audioSource = audioSource;
/// <summary>
/// The name of the sound effect.
/// </summary>
public string name;
/// <summary>
/// The AudioSource component that plays the sound effect.
/// </summary>
public AudioSource audioSource;
/// <summary>
/// Initializes a new instance of the <see cref="SoundEffect"/> class.
/// </summary>
/// <param name="name">The name of the sound effect.</param>
/// <param name="audioSource">The AudioSource component for the sound effect.</param>
public SoundEffect(string name, AudioSource audioSource)
{
this.name = name;
this.audioSource = audioSource;
}
}
}
}

View File

@@ -1,35 +1,64 @@
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
using UnityEngine.InputSystem;
namespace Game
{
/// <summary>
/// This class is used to create cards for players when they join the game.
/// </summary>
public class PlayerCardCreator : MonoBehaviour
{
/// <summary>
/// A single instance of this class that can be accessed from anywhere.
/// </summary>
public static PlayerCardCreator Instance;
/// <summary>
/// The template used to create new player cards.
/// </summary>
public GameObject playerJoinCardPrefab;
private void Awake() // Ensures only one instance of PlayerCardCreator exists
/// <summary>
/// Makes sure there is only one PlayerCardCreator in the game.
/// </summary>
private void Awake()
{
if (Instance == null) Instance = this;
// If this is the first instance, set it as the main one.
if (Instance == null)
{
Instance = this;
}
else
{
// If another instance already exists, remove this one.
Destroy(gameObject);
}
}
public PlayerJoinCard CreateCard() // Creates a player join card
/// <summary>
/// Creates a new player card and returns it.
/// </summary>
/// <returns>The new player card, or nothing if it couldn't be created.</returns>
public PlayerJoinCard CreateCard()
{
try
{
// Make a new player card using the template.
GameObject card = Instantiate(playerJoinCardPrefab, transform);
// Return the player card so it can be used.
return card.GetComponent<PlayerJoinCard>();
}
catch
{
// If something goes wrong, return nothing.
return null;
}
}
}
}

View File

@@ -1,17 +1,30 @@
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
using UnityEngine.EventSystems;
namespace Game
{
public class EventSystemizer : MonoBehaviour
{
private void Update() // Ensures only one instance of EventSystem exists
/// <summary>
/// This class makes sure there is only one EventSystem in the game at any time.
/// </summary>
public class EventSystemizer : MonoBehaviour
{
foreach (EventSystem system in FindObjectsByType<EventSystem>(FindObjectsSortMode.None))
/// <summary>
/// Checks every frame to ensure there is only one EventSystem in the game.
/// </summary>
private void Update()
{
if (system == GetComponent<EventSystem>()) continue;
Destroy(system.gameObject);
// Find all EventSystem objects in the scene
foreach (EventSystem system in FindObjectsByType<EventSystem>(FindObjectsSortMode.None))
{
// Skip the EventSystem attached to this GameObject
if (system == GetComponent<EventSystem>()) continue;
// Remove any extra EventSystem objects
Destroy(system.gameObject);
}
}
}
}
}

View File

@@ -1,58 +1,108 @@
using System.Collections;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Game
{
public class FallPlatform : MonoBehaviour
{
public float fallDelay = 2f; // Delay before the platform falls
public float resetDelay = 4f; // Delay before the platform resets
bool falling;
Rigidbody2D rb;
Vector3 defposition;
void Start()
/// <summary>
/// This class controls platforms that fall when touched by a player or another platform.
/// The platform will fall after a delay and then reset to its original position.
/// </summary>
public class FallPlatform : MonoBehaviour
{
defposition = transform.parent.position;
rb = transform.parent.GetComponent<Rigidbody2D>();
}
private void OnTriggerEnter2D(Collider2D collision) // Makes platform fall when player or another platform touch it
{
try
/// <summary>
/// The time (in seconds) before the platform starts falling after being triggered.
/// </summary>
public float fallDelay = 2f;
/// <summary>
/// The time (in seconds) before the platform resets to its original position after falling.
/// </summary>
public float resetDelay = 4f;
/// <summary>
/// Indicates whether the platform is currently falling.
/// </summary>
private bool falling;
/// <summary>
/// Reference to the Rigidbody2D component of the platform's parent object.
/// </summary>
private Rigidbody2D rb;
/// <summary>
/// The original position of the platform's parent object.
/// </summary>
private Vector3 defposition;
/// <summary>
/// Initializes the platform's Rigidbody2D and stores its original position.
/// </summary>
private void Start()
{
if (collision.transform.childCount != 0 && !falling && (collision.gameObject.CompareTag("Player") || collision.transform.GetChild(0).TryGetComponent(out FallPlatform _)))
defposition = transform.parent.position;
rb = transform.parent.GetComponent<Rigidbody2D>();
}
/// <summary>
/// Triggers the platform to fall when a player or another platform touches it.
/// </summary>
/// <param name="collision">The object that collided with the platform.</param>
private void OnTriggerEnter2D(Collider2D collision)
{
try
{
StartCoroutine(FallAfterDelay());
// Check if the collision is caused by a player or another falling platform
if (collision.transform.childCount != 0 && !falling &&
(collision.gameObject.CompareTag("Player") || collision.transform.GetChild(0).TryGetComponent(out FallPlatform _)))
{
StartCoroutine(FallAfterDelay());
}
}
catch (System.Exception e)
{
Debug.LogError("Error in FallPlatform: " + e.Message);
}
}
catch (System.Exception e)
/// <summary>
/// Makes the platform fall after a delay and resets it after another delay.
/// </summary>
/// <returns>An IEnumerator for coroutine execution.</returns>
private IEnumerator FallAfterDelay()
{
Debug.LogError("Error in FallPlatform: " + e.Message);
falling = true;
// Wait for the fall delay before making the platform fall
yield return new WaitForSeconds(fallDelay);
rb.bodyType = RigidbodyType2D.Dynamic;
// Wait for the reset delay before resetting the platform
yield return new WaitForSeconds(resetDelay);
transform.parent.GetComponent<Animator>().SetTrigger("respawn");
// Wait briefly before resetting the platform
yield return new WaitForSeconds(0.5f);
Respawn();
}
}
private IEnumerator FallAfterDelay() // Sets platform to fall and respawn
{
falling = true;
yield return new WaitForSeconds(fallDelay);
rb.bodyType = RigidbodyType2D.Dynamic;
yield return new WaitForSeconds(resetDelay);
transform.parent.GetComponent<Animator>().SetTrigger("respawn");
yield return new WaitForSeconds(0.5f);
Respawn();
}
/// <summary>
/// Resets the platform to its original position and state.
/// </summary>
private void Respawn()
{
falling = false;
//only resets the object script is attached to, need to fix so platform will reset with fall trigger object
// Use transform.parent to get the object it's attatched to
private void Respawn() // Resets the platform position
{
falling = false;
rb.bodyType = RigidbodyType2D.Static;
transform.parent.position = defposition;
transform.parent.rotation = Quaternion.identity;
// Set the platform back to a static state
rb.bodyType = RigidbodyType2D.Static;
// Reset the platform's position and rotation
transform.parent.position = defposition;
transform.parent.rotation = Quaternion.identity;
}
}
}
}

View File

@@ -1,181 +1,267 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
using UnityEngine.Events;
using UnityEngine.InputSystem;
namespace Game
{
/// <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>
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
public float time = 180f;
public delegate void GameEvent();
public event GameEvent StartGameEvent;
public event GameEvent EndGameEvent;
public static List<GameObject> players = new List<GameObject>();
public static List<Color> playerColors = new List<Color>();
public float offset = 1f;
public static bool music = true;
public bool gameOver = false;
public GameTimer gameTimer;
public static Dictionary<GameObject, float> playerHoldTimes = new Dictionary<GameObject, float>();
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;
public Vector2 obstacleCourseSpawnPosition;
public List<Vector2> hatSpawnPositions = new List<Vector2>();
public Canvas LeaderboardCanvas;
public Canvas TimerCanvas;
public GameObject hatObject;
/// <summary>
/// Enum representing the different game modes.
/// This class controls the main game logic, like starting the game, keeping track of players,
/// handling game modes, and deciding when the game ends.
/// </summary>
public enum GameMode
public class GameManager : MonoBehaviour
{
freeForAll,
keepAway,
obstacleCourse
}
/// <summary>
/// The single instance of this class that can be accessed from anywhere in the game.
/// </summary>
public static GameManager Instance { get; private set; }
/// <summary>
/// Ensures only one instance of GameManager exists.
/// </summary>
private void Awake()
{
if (Instance == null)
/// <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
{
Instance = this;
/// <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
}
else
{
Destroy(gameObject);
}
}
/// <summary>
/// Starts the game and initializes music.
/// </summary>
private void Start()
{
if (MusicManager.Instance != null)
/// <summary>
/// Makes sure there is only one GameManager in the game. If another one exists, it gets deleted.
/// </summary>
private void Awake()
{
MusicManager.Instance.StartPlaylist();
}
StartGame();
}
/// <summary>
/// Continuously updates player hold times during the game.
/// </summary>
private void Update()
{
if (gameOver) return;
if (gameMode == GameMode.keepAway)
{
foreach (var player in players)
if (Instance == null)
{
float holdTime = GetPlayerHoldTime(player);
UpdatePlayerHoldTime(player, holdTime);
}
}
}
/// <summary>
/// Retrieves the hold time of a player.
/// </summary>
/// <param name="player">The player GameObject.</param>
/// <returns>The hold time of the player.</returns>
private float GetPlayerHoldTime(GameObject player)
{
UseItem useItem = player.GetComponent<UseItem>();
if (useItem != null)
{
return useItem.holdTime;
}
return 0f;
}
/// <summary>
/// Sets up the game based on the selected game mode.
/// </summary>
public void StartGame()
{
GameManager.playerHoldTimes.Clear();
if (GameManager.players.Count == 0) return;
StartGameEvent?.Invoke();
print("Starting game with mode: " + gameMode + " and map: " + map);
if (gameMode == GameMode.freeForAll)
{
foreach (GameObject player in players)
{
player.transform.position = spawnPosition + (offset * players.IndexOf(player) * Vector2.right);
player.GetComponent<Damageable>().lives = 5;
}
}
if (gameMode == GameMode.keepAway)
{
if (gameTimer != null)
{
gameTimer.startTime = time;
gameTimer.StartTimer();
}
foreach (GameObject player in players)
{
player.transform.position = spawnPosition + (offset * players.IndexOf(player) * Vector2.right);
player.GetComponent<Damageable>().lives = 0;
}
}
if (gameMode == GameMode.obstacleCourse)
{
foreach (GameObject player in players)
{
player.transform.position = spawnPosition;
player.GetComponent<Damageable>().lives = 0;
}
}
}
/// <summary>
/// Handles player deaths based on the current game mode.
/// </summary>
/// <param name="player">The player that died.</param>
public void PlayerDied(Damageable player)
{
UseItem useItem = player.GetComponent<UseItem>();
if (useItem != null)
{
if (gameOver == false)
{
useItem.DropItem();
Instance = this;
}
else
{
//return;
Destroy(gameObject);
}
}
if (gameMode == GameMode.freeForAll)
/// <summary>
/// Starts the game and plays background music if it's enabled.
/// </summary>
private void Start()
{
player.lives--;
if (player.lives <= 0 && !gameOver)
if (MusicManager.Instance != null)
{
player.gameObject.SetActive(false);
if (AlivePlayers().Count <= 1)
MusicManager.Instance.StartPlaylist();
}
StartGame();
}
/// <summary>
/// Updates the game every frame. In "keep-away" mode, it tracks how long each player holds the item.
/// </summary>
private void Update()
{
if (gameOver) return;
if (gameMode == GameMode.keepAway)
{
foreach (var player in players)
{
GameOver();
float holdTime = GetPlayerHoldTime(player);
UpdatePlayerHoldTime(player, holdTime);
}
}
}
/// <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)
{
UseItem useItem = player.GetComponent<UseItem>();
if (useItem != null)
{
return useItem.holdTime;
}
return 0f;
}
/// <summary>
/// Sets up the game based on the selected game mode. This includes spawning players and setting their lives.
/// </summary>
public void StartGame()
{
GameManager.playerHoldTimes.Clear();
if (GameManager.players.Count == 0) return;
StartGameEvent?.Invoke();
print("Starting game with mode: " + gameMode + " and map: " + map);
if (gameMode == GameMode.freeForAll)
{
foreach (GameObject player in players)
{
player.transform.position = spawnPosition + (offset * players.IndexOf(player) * Vector2.right);
player.GetComponent<Damageable>().lives = 5;
}
}
if (gameMode == GameMode.keepAway)
{
if (gameTimer != null)
{
gameTimer.startTime = time;
gameTimer.StartTimer();
}
foreach (GameObject player in players)
{
player.transform.position = spawnPosition + (offset * players.IndexOf(player) * Vector2.right);
player.GetComponent<Damageable>().lives = 0;
}
}
if (gameMode == GameMode.obstacleCourse)
{
foreach (GameObject player in players)
{
player.transform.position = spawnPosition;
player.GetComponent<Damageable>().lives = 0;
}
}
}
/// <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)
{
UseItem useItem = player.GetComponent<UseItem>();
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
@@ -183,155 +269,219 @@ public class GameManager : MonoBehaviour
RespawnPlayer(player.gameObject);
}
}
if (gameMode == GameMode.keepAway || gameMode == GameMode.obstacleCourse)
{
RespawnPlayer(player.gameObject);
}
}
/// <summary>
/// Respawns a player at their designated spawn point.
/// </summary>
/// <param name="player">The player GameObject to respawn.</param>
private void RespawnPlayer(GameObject player)
{
RespawnOnTriggerEnter respawnScript = player.GetComponent<RespawnOnTriggerEnter>();
if (respawnScript != null)
/// <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)
{
player.transform.position = respawnScript.spawnPoint;
player.GetComponent<Damageable>().ResetDamage();
player.GetComponent<Damageable>().Respawn();
}
}
/// <summary>
/// Ends the game and determines the winner based on the game mode.
/// </summary>
public void GameOver()
{
if (LeaderboardCanvas == null){}
gameOver = true;
EndGameEvent?.Invoke();
if (LeaderboardCanvas != null)
{
LeaderboardCanvas.gameObject.SetActive(false);
}
if (TimerCanvas != null)
{
TimerCanvas.gameObject.SetActive(false);
}
if (gameMode == GameMode.freeForAll)
{
GameObject winner = AlivePlayers()[0];
print(winner.name + " is the winner");
FindFirstObjectByType<PlayerCameraMovement>().WinScene(winner);
WinScreen.Instance.ShowWinScreen(players.IndexOf(winner) + 1);
FindFirstObjectByType<LifeDisplayManager>().HideLifeDisplay();
}
if (gameMode == GameMode.keepAway)
{
GameObject winner = null;
float maxHoldTime = -1f;
foreach (var player in GameManager.playerHoldTimes)
RespawnOnTriggerEnter respawnScript = player.GetComponent<RespawnOnTriggerEnter>();
if (respawnScript != null)
{
if (player.Value > maxHoldTime)
{
maxHoldTime = player.Value;
winner = player.Key;
}
player.transform.position = respawnScript.spawnPoint;
player.GetComponent<Damageable>().ResetDamage();
player.GetComponent<Damageable>().Respawn();
}
if (winner != null)
}
/// <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()
{
// Mark the game as over
gameOver = true;
// Trigger the end game event for any listeners
EndGameEvent?.Invoke();
// Hide the leaderboard and timer UI if they exist
if (LeaderboardCanvas != null)
{
print(winner.name + " is the winner with " + maxHoldTime + " seconds!");
var cameraMovement = FindFirstObjectByType<PlayerCameraMovement>();
if (cameraMovement != null)
{
cameraMovement.WinScene(winner);
}
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 = AlivePlayers()[0];
print(winner.name + " is the winner");
// Show the winner's scene and update the win screen
FindFirstObjectByType<PlayerCameraMovement>().WinScene(winner);
WinScreen.Instance.ShowWinScreen(players.IndexOf(winner) + 1);
var lifeDisplayManager = FindFirstObjectByType<LifeDisplayManager>();
if (lifeDisplayManager != null)
// 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)
{
lifeDisplayManager.HideLifeDisplay();
if (player.Value > maxHoldTime)
{
maxHoldTime = player.Value;
winner = player.Key;
}
}
StartCoroutine(MoveHatToWinner(winner));
if (hatObject != null)
if (winner != null)
{
hatObject.SetActive(true);
hatObject.GetComponent<Collider2D>().enabled = true;
hatObject.GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Dynamic;
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;
}
}
}
}
if (gameMode == GameMode.obstacleCourse)
{
GameObject winner = ObstacleCourse.playerWon;
print(winner.name + " is the winner!");
FindFirstObjectByType<PlayerCameraMovement>().WinScene(winner);
WinScreen.Instance.ShowWinScreen(players.IndexOf(winner) + 1);
FindFirstObjectByType<LifeDisplayManager>().HideLifeDisplay();
}
}
/// <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>
private IEnumerator MoveHatToWinner(GameObject winner)
{
while (!winner.GetComponent<UseItem>().IsHoldingItem())
{
hatObject.transform.position = winner.transform.position + Vector3.up * 3 / 2;
winner.GetComponent<UseItem>().PickUpItem(hatObject);
yield return null;
}
}
/// <summary>
/// Returns a list of all players that are currently alive.
/// </summary>
/// <returns>A list of alive player GameObjects.</returns>
public List<GameObject> AlivePlayers()
{
List<GameObject> alivePlayers = new();
foreach (GameObject player in players)
{
if (player.activeInHierarchy) alivePlayers.Add(player);
}
return alivePlayers;
}
/// <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)
{
bool shouldSort = false;
if (playerHoldTimes.ContainsKey(player))
{
if (holdTime > playerHoldTimes[player])
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<PlayerCameraMovement>().WinScene(winner);
WinScreen.Instance.ShowWinScreen(players.IndexOf(winner) + 1);
// Hide the life display UI
FindFirstObjectByType<LifeDisplayManager>().HideLifeDisplay();
}
}
/// <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)
{
while (!winner.GetComponent<UseItem>().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<UseItem>().PickUpItem(hatObject);
// Wait for the next frame before trying again
yield return null;
}
}
/// <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();
// Check each player to see if they are still active
foreach (GameObject player in players)
{
if (player.activeInHierarchy)
{
alivePlayers.Add(player);
}
}
return alivePlayers;
}
/// <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)
{
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;
}
playerHoldTimes[player] = holdTime;
}
else
{
playerHoldTimes.Add(player, holdTime);
shouldSort = true;
}
LeaderboardManager.Instance.UpdatePlayerHoldTimeText(player, holdTime);
if (shouldSort)
{
LeaderboardManager.Instance.UpdateLeaderboard();
// 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();
}
}
}
}
}

View File

@@ -1,26 +1,46 @@
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Game
{
[ExecuteAlways]
public class GameManagerHelper : MonoBehaviour
{
public bool addHatPosition;
public bool addSpawnPosition;
private void Update()
/// <summary>
/// This class helps manage positions for the GameManager during development.
/// It allows adding hat spawn positions and player spawn positions directly in the editor.
/// </summary>
[ExecuteAlways]
public class GameManagerHelper : MonoBehaviour
{
if (addHatPosition)
/// <summary>
/// If true, adds the current position of the "HELPER" object to the hat spawn positions in the GameManager.
/// </summary>
public bool addHatPosition;
/// <summary>
/// If true, sets the current position of the "HELPER" object as the player spawn position in the GameManager.
/// </summary>
public bool addSpawnPosition;
/// <summary>
/// Checks for changes to the addHatPosition and addSpawnPosition flags every frame.
/// Updates the GameManager with the corresponding positions when the flags are set.
/// </summary>
private void Update()
{
addHatPosition = false;
GetComponent<GameManager>().hatSpawnPositions.Add(GameObject.Find("HELPER").transform.position);
}
if (addSpawnPosition)
{
addSpawnPosition = false;
GetComponent<GameManager>().spawnPosition = GameObject.Find("HELPER").transform.position;
// Add the current position of the "HELPER" object to the hat spawn positions
if (addHatPosition)
{
addHatPosition = false; // Reset the flag
GetComponent<GameManager>().hatSpawnPositions.Add(GameObject.Find("HELPER").transform.position);
}
// Set the current position of the "HELPER" object as the player spawn position
if (addSpawnPosition)
{
addSpawnPosition = false; // Reset the flag
GetComponent<GameManager>().spawnPosition = GameObject.Find("HELPER").transform.position;
}
}
}
}
}

View File

@@ -1,61 +1,112 @@
using TMPro;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
using UnityEngine.UI;
namespace Game
{
public class GameTimer : MonoBehaviour
{
public float startTime = 180f;
private float timeRemaining;
private bool timerRunning = false;
public Text timerText;
[SerializeField] private TextMeshProUGUI timer;
private void Start()
/// <summary>
/// This class manages the game's countdown timer.
/// It starts, updates, and stops the timer, and ends the game when time runs out.
/// </summary>
public class GameTimer : MonoBehaviour
{
timeRemaining = startTime;
timer.text = "3:00.00";
UpdateTimerDisplay();
}
/// <summary>
/// The starting time for the timer, in seconds.
/// </summary>
public float startTime = 180f;
private void Update() // Updates the timer to show the time remaining
{
if (timerRunning)
/// <summary>
/// The time remaining on the timer, in seconds.
/// </summary>
private float timeRemaining;
/// <summary>
/// Indicates whether the timer is currently running.
/// </summary>
private bool timerRunning = false;
/// <summary>
/// The UI text element that displays the timer.
/// </summary>
public Text timerText;
/// <summary>
/// The TextMeshPro element that displays the timer.
/// </summary>
[SerializeField] private TextMeshProUGUI timer;
/// <summary>
/// Sets up the timer when the game starts.
/// </summary>
private void Start()
{
timeRemaining -= Time.deltaTime;
if (timeRemaining <= 0)
{
timeRemaining = 0;
timerRunning = false;
OnTimerEnd();
}
// Set the timer to the starting time and display the initial value
timeRemaining = startTime;
timer.text = "3:00.00";
UpdateTimerDisplay();
}
}
public void StartTimer() // Starts the timer
{
if (!timerRunning)
/// <summary>
/// Updates the timer every frame to show the time remaining.
/// </summary>
private void Update()
{
timeRemaining = startTime;
timerRunning = true;
if (timerRunning)
{
// Decrease the time remaining
timeRemaining -= Time.deltaTime;
// Stop the timer if time runs out
if (timeRemaining <= 0)
{
timeRemaining = 0;
timerRunning = false;
OnTimerEnd();
}
// Update the timer display
UpdateTimerDisplay();
}
}
/// <summary>
/// Starts the timer if it is not already running.
/// </summary>
public void StartTimer()
{
if (!timerRunning)
{
// Reset the timer and start it
timeRemaining = startTime;
timerRunning = true;
}
}
/// <summary>
/// Updates the timer display to show the current time remaining.
/// </summary>
private void UpdateTimerDisplay()
{
// Calculate minutes and seconds from the remaining time
int minutes = Mathf.FloorToInt(timeRemaining / 60);
int seconds = Mathf.FloorToInt(timeRemaining % 60);
// Format the time as "MM:SS" and update the UI
timer.text = string.Format("{0}:{1:D2}", minutes, seconds);
}
/// <summary>
/// Ends the game when the timer reaches zero.
/// </summary>
private void OnTimerEnd()
{
// Notify the GameManager that the game is over
GameManager.Instance.GameOver();
}
}
private void UpdateTimerDisplay() // Formats and sets the time remaining
{
int minutes = Mathf.FloorToInt(timeRemaining / 60);
int seconds = Mathf.FloorToInt(timeRemaining % 60);
timer.text = string.Format("{0}:{1:D2}", minutes, seconds);
}
private void OnTimerEnd() // Ends the game when the time runs out
{
GameManager.Instance.GameOver();
}
}
}

View File

@@ -3,45 +3,88 @@ using Game;
using Music;
using Player;
using System.Collections;
namespace Game
{
/// <summary>
/// This class manages the behavior of the hat in the game, including its respawn logic.
/// The hat can be picked up, dropped, and respawned after a certain amount of time.
/// </summary>
public class HatRespawn : MonoBehaviour
{
/// <summary>
/// The last time the hat was interacted with.
/// </summary>
private float lastInteractionTime;
/// <summary>
/// The amount of time (in seconds) before the hat respawns after being inactive.
/// </summary>
public const float respawnTime = 10f;
/// <summary>
/// Indicates whether the hat is currently dropped.
/// </summary>
private bool isDropped;
/// <summary>
/// The initial position of the subhat (if applicable).
/// </summary>
public Vector2 initialSubhatPosition;
/// <summary>
/// A flag to check if the hat can be picked up.
/// </summary>
public static bool canBePickedUp = true;
public static bool canBePickedUp = true; // Flag to check if the hat can be picked up
/// <summary>
/// The initial scale of the hat.
/// </summary>
public Vector2 initialScale;
/// <summary>
/// Saves the initial scale of the hat when the game starts.
/// </summary>
private void Awake()
{
initialScale = transform.lossyScale;
}
void Start()
/// <summary>
/// Sets up the hat's initial position and state when the game starts.
/// </summary>
private void Start()
{
//initialSubhatPosition = transform.GetChild(0).transform.localPosition;
lastInteractionTime = Time.time;
isDropped = false;
// Place the hat at a random spawn position
transform.position = GameManager.Instance.hatSpawnPositions[Random.Range(0, GameManager.Instance.hatSpawnPositions.Count - 1)];
}
void Update() // Checks if the hat has been inactive for too long
/// <summary>
/// Checks if the hat has been inactive for too long and respawns it if necessary.
/// </summary>
private void Update()
{
if (GameManager.Instance.gameOver) GetComponent<BoxCollider2D>().enabled = false;
// Disable the hat's collider if the game is over
if (GameManager.Instance.gameOver)
{
GetComponent<BoxCollider2D>().enabled = false;
}
// Respawn the hat if it has been dropped for too long
if (isDropped && Time.time - lastInteractionTime > respawnTime)
{
StartCoroutine(RespawnHat());
}
}
void OnTriggerEnter2D(Collider2D collision) // Respawns the hat if it falls out of bounds
/// <summary>
/// Respawns the hat if it falls out of bounds.
/// </summary>
/// <param name="collision">The object that collided with the hat.</param>
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Platformer Hazard"))
{
@@ -50,30 +93,46 @@ namespace Game
}
}
public void Interact() // Updates the player interaction time
/// <summary>
/// Updates the last interaction time when a player interacts with the hat.
/// </summary>
public void Interact()
{
lastInteractionTime = Time.time;
isDropped = false;
}
public void OnHatDropped() // Resets the timer when the hat is dropped
/// <summary>
/// Marks the hat as dropped and resets the timer.
/// </summary>
public void OnHatDropped()
{
lastInteractionTime = Time.time;
isDropped = true;
}
private IEnumerator RespawnHat() // Respawns the hat at the designated spawn position
/// <summary>
/// Respawns the hat at a random spawn position after a short delay.
/// </summary>
/// <returns>An IEnumerator for coroutine execution.</returns>
private IEnumerator RespawnHat()
{
// Play the respawn animation
GetComponentInChildren<Animator>().SetTrigger("respawn");
// Wait briefly before respawning the hat
yield return new WaitForSeconds(1f / 3f / 2f);
// Move the hat to a random spawn position and reset its state
transform.position = GameManager.Instance.hatSpawnPositions[Random.Range(0, GameManager.Instance.hatSpawnPositions.Count - 1)];
GetComponent<Rigidbody2D>().linearVelocity = Vector2.zero;
GetComponent<Rigidbody2D>().angularVelocity = 0f;
transform.rotation = Quaternion.identity;
lastInteractionTime = Time.time; // Reset the timer after respawning
lastInteractionTime = Time.time;
isDropped = false;
}
}
}
}

View File

@@ -1,95 +1,143 @@
using System.Collections.Generic;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Game
{
public class HealthBarManager : MonoBehaviour
{
public GameObject healthBarPrefab;
private Dictionary<GameObject, GameObject> playerHealthBars = new Dictionary<GameObject, GameObject>();
void Start()
/// <summary>
/// This class manages the health bars for all players in the game.
/// It creates, updates, and removes health bars as needed.
/// </summary>
public class HealthBarManager : MonoBehaviour
{
GameManager.Instance.StartGameEvent += OnGameStart;
GameManager.Instance.EndGameEvent += OnGameEnd;
}
/// <summary>
/// The template used to create new health bars.
/// </summary>
public GameObject healthBarPrefab;
void OnDestroy()
{
GameManager.Instance.StartGameEvent -= OnGameStart;
GameManager.Instance.EndGameEvent -= OnGameEnd;
}
/// <summary>
/// A dictionary that links each player to their health bar.
/// </summary>
private Dictionary<GameObject, GameObject> playerHealthBars = new Dictionary<GameObject, GameObject>();
void Update() // Updates position of health bars to follow each player
{
foreach (var kvp in playerHealthBars)
/// <summary>
/// Sets up event listeners for when the game starts and ends.
/// </summary>
private void Start()
{
GameObject player = kvp.Key;
if (player == null) continue;
GameObject healthBar = kvp.Value;
healthBar.transform.SetPositionAndRotation(new Vector3(player.transform.position.x, player.transform.position.y + 1.5f, player.transform.position.z), Quaternion.identity);
GameManager.Instance.StartGameEvent += OnGameStart;
GameManager.Instance.EndGameEvent += OnGameEnd;
}
}
private void OnGameStart() // Creates health bars for each player
{
foreach (GameObject player in GameManager.players)
/// <summary>
/// Removes event listeners when this object is destroyed.
/// </summary>
private void OnDestroy()
{
GameManager.Instance.StartGameEvent -= OnGameStart;
GameManager.Instance.EndGameEvent -= OnGameEnd;
}
/// <summary>
/// Updates the position of each health bar to follow its player.
/// </summary>
private void Update()
{
foreach (var kvp in playerHealthBars)
{
GameObject player = kvp.Key;
if (player == null) continue;
GameObject healthBar = kvp.Value;
// Position the health bar slightly above the player
healthBar.transform.SetPositionAndRotation(
new Vector3(player.transform.position.x, player.transform.position.y + 1.5f, player.transform.position.z),
Quaternion.identity
);
}
}
/// <summary>
/// Creates health bars for all players when the game starts.
/// </summary>
private void OnGameStart()
{
foreach (GameObject player in GameManager.players)
{
if (!playerHealthBars.ContainsKey(player))
{
CreateHealthBar(player);
// Listen for the player's death and respawn events
var damageable = player.GetComponent<Damageable>();
damageable.OnPlayerDeath += HandlePlayerDeath;
damageable.OnPlayerRespawn += HandlePlayerRespawn;
}
}
}
/// <summary>
/// Creates a health bar for a specific player.
/// </summary>
/// <param name="player">The player to create a health bar for.</param>
private void CreateHealthBar(GameObject player)
{
// Create a new health bar and link it to the player
GameObject healthBar = Instantiate(healthBarPrefab);
healthBar.transform.localScale *= 1.5f; // Make the health bar slightly larger
healthBar.GetComponent<TerribleHealthBarScript>().SetPlayer(player);
playerHealthBars[player] = healthBar;
}
/// <summary>
/// Handles the player's respawn by recreating their health bar if needed.
/// </summary>
/// <param name="player">The player who respawned.</param>
private void HandlePlayerRespawn(GameObject player)
{
if (!playerHealthBars.ContainsKey(player))
{
CreateHealthBar(player);
// Subscribe to the player's death and respawn events
var damageable = player.GetComponent<Damageable>();
damageable.OnPlayerDeath += HandlePlayerDeath;
damageable.OnPlayerRespawn += HandlePlayerRespawn;
}
}
}
private void HandlePlayerRespawn(GameObject player)
{
if (!playerHealthBars.ContainsKey(player))
/// <summary>
/// Handles the player's death by removing their health bar.
/// </summary>
/// <param name="player">The player who died.</param>
private void HandlePlayerDeath(GameObject player)
{
CreateHealthBar(player);
}
}
private void CreateHealthBar(GameObject player)
{
GameObject healthBar = Instantiate(healthBarPrefab);
healthBar.transform.localScale *= 1.5f;
healthBar.GetComponent<TerribleHealthBarScript>().SetPlayer(player);
playerHealthBars[player] = healthBar;
}
private void HandlePlayerDeath(GameObject player)
{
if (playerHealthBars.TryGetValue(player, out GameObject healthBar))
{
Destroy(healthBar);
playerHealthBars.Remove(player);
}
}
private void OnGameEnd()
{
foreach (var kvp in playerHealthBars)
{
Destroy(kvp.Value);
}
playerHealthBars.Clear();
// Unsubscribe from all player events
foreach (GameObject player in GameManager.players)
{
if (player != null && player.TryGetComponent<Damageable>(out var damageable))
if (playerHealthBars.TryGetValue(player, out GameObject healthBar))
{
damageable.OnPlayerDeath -= HandlePlayerDeath;
damageable.OnPlayerRespawn -= HandlePlayerRespawn;
Destroy(healthBar);
playerHealthBars.Remove(player);
}
}
/// <summary>
/// Cleans up all health bars and unsubscribes from player events when the game ends.
/// </summary>
private void OnGameEnd()
{
// Remove all health bars
foreach (var kvp in playerHealthBars)
{
Destroy(kvp.Value);
}
playerHealthBars.Clear();
// Unsubscribe from all player events
foreach (GameObject player in GameManager.players)
{
if (player != null && player.TryGetComponent<Damageable>(out var damageable))
{
damageable.OnPlayerDeath -= HandlePlayerDeath;
damageable.OnPlayerRespawn -= HandlePlayerRespawn;
}
}
}
}
}
}

View File

@@ -0,0 +1,178 @@
using System.Linq;
using UnityEngine;
using Game;
using Music;
using Player;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEngine.Events;
using System.Collections;
namespace Game
{
/// <summary>
/// This class manages the hub area of the game, including loading and unloading game scenes,
/// controlling the hub camera, and managing game buttons.
/// </summary>
public class HubManager : MonoBehaviour
{
/// <summary>
/// A single instance of this class that can be accessed from anywhere.
/// </summary>
public static HubManager Instance;
/// <summary>
/// The camera used in the hub area.
/// </summary>
public GameObject hubCamera;
/// <summary>
/// The parent object containing all game buttons in the hub.
/// </summary>
public GameObject gameButtonsParent;
/// <summary>
/// Sets up the hub manager and ensures only one instance exists.
/// </summary>
private void Start()
{
// Ensure there is only one HubManager in the game
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(this.gameObject);
}
// Add an AudioListener to the hub camera if it doesn't already have one
if (hubCamera.GetComponent<AudioListener>() == null)
{
hubCamera.AddComponent<AudioListener>();
}
// Activate the hub camera and start the music playlist
hubCamera.SetActive(true);
MusicManager.Instance.StartPlaylist();
print("Game started");
}
/// <summary>
/// Loads a new game scene and disables the hub camera.
/// </summary>
/// <param name="sceneName">The name of the scene to load.</param>
public void LoadScene(string sceneName)
{
// Unload the current game scene and disable the hub camera
UnloadGameScene();
hubCamera.SetActive(false);
// Load the new scene
SceneManager.LoadScene(sceneName, LoadSceneMode.Additive);
// Ensure the active camera has an AudioListener
var activeCamera = Camera.main;
if (activeCamera != null && activeCamera.GetComponent<AudioListener>() == null)
{
activeCamera.gameObject.AddComponent<AudioListener>();
}
// Start the music playlist for the new scene
MusicManager.Instance.StartPlaylist();
print("Loading scene: " + sceneName);
}
/// <summary>
/// Unloads the current game scene and reactivates the hub camera.
/// </summary>
public void UnloadGameScene()
{
// Reactivate the hub camera
hubCamera.SetActive(true);
try
{
// Unload the currently loaded game scene
SceneManager.UnloadSceneAsync(SceneManager.GetSceneAt(1));
}
catch
{
// Ignore errors if no additional scene is loaded
}
// Disable interaction with game buttons
ChangeGameButtonsInteractability(false);
}
/// <summary>
/// Handles input and resets the game when the escape key is pressed.
/// </summary>
private void Update()
{
if (InputSystem.GetDevice<Keyboard>().escapeKey.wasPressedThisFrame)
{
// Unload the current game scene and enable game buttons
UnloadGameScene();
ChangeGameButtonsInteractability(true);
// Remove all players and reset the game state
if (GameManager.players != null)
{
foreach (GameObject player in GameManager.players.ToList())
{
GameManager.players.Remove(player);
if (player != null)
{
Destroy(player);
}
}
}
// Restart the music playlist for the title screen
if (MusicManager.Instance != null)
{
MusicManager.Instance.StartPlaylist("Title Screen");
}
// Disable all cameras in the scene
var cameras = FindObjectsByType<Camera>(FindObjectsSortMode.None);
if (cameras != null)
{
foreach (Camera camera in cameras)
{
camera.enabled = false;
}
}
// Clear player data and reset the game state
GameManager.players?.Clear();
GameManager.playerColors?.Clear();
if (GameManager.Instance != null)
{
GameManager.Instance.gameOver = false;
}
// Load the title screen
SceneManager.LoadScene("Title Screen");
}
}
/// <summary>
/// Enables or disables interaction with the game buttons in the hub.
/// </summary>
/// <param name="interactable">True to enable interaction, false to disable it.</param>
private void ChangeGameButtonsInteractability(bool interactable)
{
// Show or hide the game buttons
gameButtonsParent.transform.parent.gameObject.SetActive(interactable);
// Enable or disable each button
foreach (Transform button in gameButtonsParent.transform)
{
button.GetComponent<Button>().interactable = interactable;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3fbcc366ef6e3480399963dee7cad1cd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: -20
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,24 +1,48 @@
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Game
{
public class InfiniteScroll : MonoBehaviour
{
public float speed;
public float start;
public float end;
private void Update() // Moves the background
/// <summary>
/// This class handles the infinite scrolling effect for the background.
/// </summary>
public class InfiniteScroll : MonoBehaviour
{
if (transform.position.x > end)
{
transform.position = new Vector3(start, transform.position.y, transform.position.z);
}
else if (transform.position.x < start)
{
transform.position = new Vector3(end, transform.position.y, transform.position.z);
}
/// <summary>
/// The speed at which the background scrolls.
/// </summary>
public float speed;
transform.position += speed * Time.deltaTime * Vector3.right;
/// <summary>
/// The starting position of the background.
/// </summary>
public float start;
/// <summary>
/// The ending position of the background.
/// </summary>
public float end;
/// <summary>
/// Updates the position of the background to create a scrolling effect.
/// </summary>
private void Update()
{
// If the background moves past the end position, reset it to the start position
if (transform.position.x > end)
{
transform.position = new Vector3(start, transform.position.y, transform.position.z);
}
// If the background moves past the start position, reset it to the end position
else if (transform.position.x < start)
{
transform.position = new Vector3(end, transform.position.y, transform.position.z);
}
// Move the background to the right based on the speed and time elapsed
transform.position += speed * Time.deltaTime * Vector3.right;
}
}
}
}

View File

@@ -1,90 +1,139 @@
using System.Collections.Generic;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
using TMPro;
using UnityEngine.UI;
namespace Game
{
public class LeaderboardManager : MonoBehaviour
{
public static LeaderboardManager Instance { get; private set; }
[SerializeField] private GameObject playersParent;
[SerializeField] private GameObject playerPrefab;
[SerializeField] private GameObject leaderboardIconPrefab;
private Dictionary<GameObject, GameObject> playerIcons = new Dictionary<GameObject, GameObject>();
private void Awake() // Ensures only one instance of LeaderboardManager exists
/// <summary>
/// This class manages the leaderboard, including initializing player icons,
/// updating player positions, and displaying hold times.
/// </summary>
public class LeaderboardManager : MonoBehaviour
{
if (Instance == null)
/// <summary>
/// A single instance of this class that can be accessed from anywhere.
/// </summary>
public static LeaderboardManager Instance { get; private set; }
/// <summary>
/// The parent object that contains all player icons on the leaderboard.
/// </summary>
[SerializeField] private GameObject playersParent;
/// <summary>
/// The prefab used to represent a player on the leaderboard.
/// </summary>
[SerializeField] private GameObject playerPrefab;
/// <summary>
/// The prefab used for the leaderboard icon of each player.
/// </summary>
[SerializeField] private GameObject leaderboardIconPrefab;
/// <summary>
/// A dictionary mapping each player to their corresponding leaderboard icon.
/// </summary>
private Dictionary<GameObject, GameObject> playerIcons = new Dictionary<GameObject, GameObject>();
/// <summary>
/// Ensures only one instance of the LeaderboardManager exists.
/// </summary>
private void Awake()
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
private void Start()
{
InitializeLeaderboard();
}
private void InitializeLeaderboard() // Creates the leaderboard icons for each player
{
RectTransform parentRectTransform = playersParent.GetComponent<RectTransform>();
parentRectTransform.anchoredPosition = new Vector2(-10f, 10f);
foreach (GameObject player in GameManager.players)
{
Transform parent = Instantiate(playerPrefab, playersParent.transform).transform;
GameObject leaderboardIcon = Instantiate(leaderboardIconPrefab, parent);
leaderboardIcon.GetComponentInChildren<Image>().color = GameManager.playerColors[GameManager.players.IndexOf(player)];
playerIcons[player] = parent.gameObject;
}
}
public void UpdateLeaderboard()
{
List<KeyValuePair<GameObject, float>> sortedList = new List<KeyValuePair<GameObject, float>>(GameManager.playerHoldTimes);
sortedList.Sort((pair1, pair2) => pair2.Value.CompareTo(pair1.Value));
for (int i = 0; i < sortedList.Count; i++)
{
var player = sortedList[i];
playerIcons[player.Key].transform.SetSiblingIndex(i);
// Update the number text
TextMeshProUGUI[] textComponents = playerIcons[player.Key].GetComponentsInChildren<TextMeshProUGUI>();
foreach (var textComponent in textComponents)
if (Instance == null)
{
if (textComponent.name == "Position Text")
Instance = this;
}
else
{
Destroy(gameObject);
}
}
/// <summary>
/// Initializes the leaderboard when the game starts.
/// </summary>
private void Start()
{
InitializeLeaderboard();
}
/// <summary>
/// Creates the leaderboard icons for each player and positions them.
/// </summary>
private void InitializeLeaderboard()
{
// Adjust the position of the parent container for player icons
RectTransform parentRectTransform = playersParent.GetComponent<RectTransform>();
parentRectTransform.anchoredPosition = new Vector2(-10f, 10f);
// Create a leaderboard icon for each player
foreach (GameObject player in GameManager.players)
{
Transform parent = Instantiate(playerPrefab, playersParent.transform).transform;
GameObject leaderboardIcon = Instantiate(leaderboardIconPrefab, parent);
// Set the color of the leaderboard icon based on the player's color
leaderboardIcon.GetComponentInChildren<Image>().color = GameManager.playerColors[GameManager.players.IndexOf(player)];
playerIcons[player] = parent.gameObject;
}
}
/// <summary>
/// Updates the leaderboard by sorting players based on their hold times
/// and adjusting their positions.
/// </summary>
public void UpdateLeaderboard()
{
// Sort players by their hold times in descending order
List<KeyValuePair<GameObject, float>> sortedList = new List<KeyValuePair<GameObject, float>>(GameManager.playerHoldTimes);
sortedList.Sort((pair1, pair2) => pair2.Value.CompareTo(pair1.Value));
// Update the position and rank of each player on the leaderboard
for (int i = 0; i < sortedList.Count; i++)
{
var player = sortedList[i];
playerIcons[player.Key].transform.SetSiblingIndex(i);
// Update the rank text for the player
TextMeshProUGUI[] textComponents = playerIcons[player.Key].GetComponentsInChildren<TextMeshProUGUI>();
foreach (var textComponent in textComponents)
{
textComponent.text = "#" + (i + 1).ToString();
break;
if (textComponent.name == "Position Text")
{
textComponent.text = "#" + (i + 1).ToString();
break;
}
}
}
}
/// <summary>
/// Updates the hold time text for a specific player on the leaderboard.
/// </summary>
/// <param name="player">The player whose hold time is being updated.</param>
/// <param name="holdTime">The new hold time to display.</param>
public void UpdatePlayerHoldTimeText(GameObject player, float holdTime)
{
if (playerIcons.ContainsKey(player))
{
// Find and update the hold time text for the player
TextMeshProUGUI[] textComponents = playerIcons[player].GetComponentsInChildren<TextMeshProUGUI>();
foreach (var textComponent in textComponents)
{
if (textComponent.name == "Text (TMP)")
{
int minutes = Mathf.FloorToInt(holdTime / 60F);
int seconds = Mathf.FloorToInt(holdTime % 60F);
textComponent.text = string.Format("{0:0}:{1:00}", minutes, seconds);
break;
}
}
}
}
}
public void UpdatePlayerHoldTimeText(GameObject player, float holdTime)
{
if (playerIcons.ContainsKey(player))
{
TextMeshProUGUI[] textComponents = playerIcons[player].GetComponentsInChildren<TextMeshProUGUI>();
foreach (var textComponent in textComponents)
{
if (textComponent.name == "Text (TMP)")
{
int minutes = Mathf.FloorToInt(holdTime / 60F);
int seconds = Mathf.FloorToInt(holdTime % 60F);
textComponent.text = string.Format("{0:0}:{1:00}", minutes, seconds);
break;
}
}
}
}
}}
}

View File

@@ -1,49 +1,90 @@
using System.Collections.Generic;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
using UnityEngine.UI;
namespace Game
{
public class LifeDisplayManager : MonoBehaviour
{
public GameObject players;
public GameObject playerPrefab;
public GameObject lifePrefab;
public Dictionary<Damageable, List<GameObject>> lifeDisplays = new Dictionary<Damageable, List<GameObject>>();
private void Start() // Creates life icons for each player
/// <summary>
/// This class manages the display of player lives, including creating life icons
/// and updating them based on the player's remaining lives.
/// </summary>
public class LifeDisplayManager : MonoBehaviour
{
if (GameManager.gameMode == GameManager.GameMode.freeForAll)
/// <summary>
/// The parent object that contains all player life displays.
/// </summary>
public GameObject players;
/// <summary>
/// The prefab used to represent a player in the life display.
/// </summary>
public GameObject playerPrefab;
/// <summary>
/// The prefab used to represent a single life icon.
/// </summary>
public GameObject lifePrefab;
/// <summary>
/// A dictionary mapping each player's <see cref="Damageable"/> component
/// to their corresponding list of life icons.
/// </summary>
public Dictionary<Damageable, List<GameObject>> lifeDisplays = new Dictionary<Damageable, List<GameObject>>();
/// <summary>
/// Initializes the life display by creating life icons for each player.
/// </summary>
private void Start()
{
foreach (GameObject player in GameManager.players)
// Only initialize life displays in free-for-all game mode
if (GameManager.gameMode == GameManager.GameMode.freeForAll)
{
Transform parent = Instantiate(playerPrefab, players.transform).transform;
List<GameObject> lives = new List<GameObject>();
for (int i = 0; i < player.GetComponent<Damageable>().lives; i++)
foreach (GameObject player in GameManager.players)
{
GameObject life = Instantiate(lifePrefab, parent);
life.transform.Find("LIFE").GetComponent<Image>().color = GameManager.playerColors[GameManager.players.IndexOf(player)];
lives.Add(life);
// Create a parent object for the player's life icons
Transform parent = Instantiate(playerPrefab, players.transform).transform;
// Create life icons based on the player's number of lives
List<GameObject> lives = new List<GameObject>();
for (int i = 0; i < player.GetComponent<Damageable>().lives; i++)
{
GameObject life = Instantiate(lifePrefab, parent);
// Set the color of the life icon to match the player's color
life.transform.Find("LIFE").GetComponent<Image>().color = GameManager.playerColors[GameManager.players.IndexOf(player)];
lives.Add(life);
}
// Map the player's Damageable component to their life icons
lifeDisplays.Add(player.GetComponent<Damageable>(), lives);
}
lifeDisplays.Add(player.GetComponent<Damageable>(), lives);
}
}
}
private void Update() // Updates the lives displayed based on player lives
{
foreach (Damageable damageable in lifeDisplays.Keys)
/// <summary>
/// Updates the life display to reflect the current number of lives for each player.
/// </summary>
private void Update()
{
foreach (GameObject life in lifeDisplays[damageable])
foreach (Damageable damageable in lifeDisplays.Keys)
{
life.SetActive(lifeDisplays[damageable].IndexOf(life) < damageable.lives);
// Enable or disable life icons based on the player's remaining lives
foreach (GameObject life in lifeDisplays[damageable])
{
life.SetActive(lifeDisplays[damageable].IndexOf(life) < damageable.lives);
}
}
}
}
public void HideLifeDisplay() // Hides life display
{
players.SetActive(false);
/// <summary>
/// Hides the life display by deactivating the parent object.
/// </summary>
public void HideLifeDisplay()
{
players.SetActive(false);
}
}
}
}

View File

@@ -1,21 +1,38 @@
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
using UnityEngine.UI;
namespace Game
{
public class MapSelect : MonoBehaviour
{
private ToggleGroup maps;
private void Start()
/// <summary>
/// This class manages the map selection process by setting the selected map
/// based on the active toggle in the toggle group.
/// </summary>
public class MapSelect : MonoBehaviour
{
maps = GetComponent<ToggleGroup>();
}
/// <summary>
/// The toggle group containing the map selection toggles.
/// </summary>
private ToggleGroup maps;
void Update() // Sets the map based on the selected toggle
{
Toggle toggle = maps.GetFirstActiveToggle();
GameManager.map = toggle.name;
/// <summary>
/// Initializes the toggle group for map selection.
/// </summary>
private void Start()
{
maps = GetComponent<ToggleGroup>();
}
/// <summary>
/// Updates the selected map in the game manager based on the active toggle.
/// </summary>
private void Update()
{
// Get the currently active toggle and set the selected map
Toggle toggle = maps.GetFirstActiveToggle();
GameManager.map = toggle.name;
}
}
}
}

View File

@@ -1,32 +1,49 @@
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
using UnityEngine.UI;
namespace Game
{
public class ModeSelect : MonoBehaviour
{
private ToggleGroup maps;
private void Start()
/// <summary>
/// This class manages the game mode selection process by setting the game mode
/// based on the active toggle in the toggle group.
/// </summary>
public class ModeSelect : MonoBehaviour
{
maps = GetComponent<ToggleGroup>();
}
/// <summary>
/// The toggle group containing the game mode selection toggles.
/// </summary>
private ToggleGroup maps;
void Update() // Updates the game mode based on the selected toggle
{
Toggle toggle = maps.GetFirstActiveToggle();
if (toggle.name == "Free-For-All")
/// <summary>
/// Initializes the toggle group for game mode selection.
/// </summary>
private void Start()
{
GameManager.gameMode = GameManager.GameMode.freeForAll;
maps = GetComponent<ToggleGroup>();
}
else if (toggle.name == "Keep-Away")
/// <summary>
/// Updates the selected game mode in the game manager based on the active toggle.
/// </summary>
private void Update()
{
GameManager.gameMode = GameManager.GameMode.keepAway;
}
else if (toggle.name == "Obstacle Course")
{
GameManager.gameMode = GameManager.GameMode.obstacleCourse;
// Get the currently active toggle and set the game mode
Toggle toggle = maps.GetFirstActiveToggle();
if (toggle.name == "Free-For-All")
{
GameManager.gameMode = GameManager.GameMode.freeForAll;
}
else if (toggle.name == "Keep-Away")
{
GameManager.gameMode = GameManager.GameMode.keepAway;
}
else if (toggle.name == "Obstacle Course")
{
GameManager.gameMode = GameManager.GameMode.obstacleCourse;
}
}
}
}
}

View File

@@ -1,33 +1,67 @@
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Game
{
public class MovingPlatform : MonoBehaviour
{
public Transform platform;
public int startPoint;
public Transform[] points;
public float speed;
private int i;
void Start() // Sets the initial position of the platform
/// <summary>
/// This class controls a platform that moves between specified points in a loop.
/// </summary>
public class MovingPlatform : MonoBehaviour
{
transform.position = points[startPoint].position;
}
/// <summary>
/// The platform object that will move.
/// </summary>
public Transform platform;
void FixedUpdate()
{
// If the platform is close to the target point, it starts moving to the next one
if (Vector2.Distance(transform.position, points[i].position) < 0.02f)
/// <summary>
/// The index of the starting point for the platform.
/// </summary>
public int startPoint;
/// <summary>
/// An array of points that the platform will move between.
/// </summary>
public Transform[] points;
/// <summary>
/// The speed at which the platform moves.
/// </summary>
public float speed;
/// <summary>
/// The current target point index.
/// </summary>
private int i;
/// <summary>
/// Sets the initial position of the platform to the starting point.
/// </summary>
private void Start()
{
i++;
if (i == points.Length)
{
i = 0;
}
transform.position = points[startPoint].position;
}
/// <summary>
/// Moves the platform between points in a loop.
/// </summary>
private void FixedUpdate()
{
// If the platform is close to the target point, update to the next point
if (Vector2.Distance(transform.position, points[i].position) < 0.02f)
{
i++;
if (i == points.Length)
{
i = 0; // Loop back to the first point
}
}
// Move the platform towards the current target point
GetComponent<Rigidbody2D>().MovePosition(
Vector2.MoveTowards(transform.position, points[i].position, speed * Time.fixedDeltaTime)
);
}
// Moves the platform towards the next point
// transform.position = Vector2.MoveTowards(transform.position, points[i].position, speed * Time.deltaTime);
GetComponent<Rigidbody2D>().MovePosition(Vector2.MoveTowards(transform.position, points[i].position, speed * Time.fixedDeltaTime));
}
}}
}

View File

@@ -1,28 +1,44 @@
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Game
{
public class ObjectVisibility : MonoBehaviour
{
void Start()
/// <summary>
/// This class controls the visibility of an object based on the current game mode.
/// </summary>
public class ObjectVisibility : MonoBehaviour
{
UpdateVisibility();
}
void Update()
{
UpdateVisibility();
}
private void UpdateVisibility() // Sets object visible if playing keep away mode
{
if (GameManager.gameMode == GameManager.GameMode.keepAway)
/// <summary>
/// Initializes the visibility of the object when the game starts.
/// </summary>
private void Start()
{
gameObject.SetActive(true);
UpdateVisibility();
}
else
/// <summary>
/// Updates the visibility of the object every frame.
/// </summary>
private void Update()
{
gameObject.SetActive(false);
UpdateVisibility();
}
/// <summary>
/// Sets the object to be visible only if the game mode is "Keep Away".
/// </summary>
private void UpdateVisibility()
{
if (GameManager.gameMode == GameManager.GameMode.keepAway)
{
gameObject.SetActive(true);
}
else
{
gameObject.SetActive(false);
}
}
}
}
}

View File

@@ -1,19 +1,33 @@
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Game
{
public class ObstacleCourse : MonoBehaviour
{
public static GameObject playerWon;
void OnTriggerEnter2D(Collider2D collision)
/// <summary>
/// This class handles the logic for detecting when a player completes the obstacle course.
/// </summary>
public class ObstacleCourse : MonoBehaviour
{
if (collision.gameObject.CompareTag("Player"))
/// <summary>
/// The player who successfully completes the obstacle course.
/// </summary>
public static GameObject playerWon;
/// <summary>
/// Detects when a player enters the trigger area and ends the game.
/// </summary>
/// <param name="collision">The collider of the object that entered the trigger.</param>
private void OnTriggerEnter2D(Collider2D collision)
{
playerWon = collision.gameObject;
GameManager.Instance.GameOver();
// Check if the object entering the trigger is a player
if (collision.gameObject.CompareTag("Player"))
{
// Set the player who won and trigger the game over logic
playerWon = collision.gameObject;
GameManager.Instance.GameOver();
}
}
}
}
}

View File

@@ -1,19 +1,31 @@
using Game;
using UnityEngine;
/// <summary>
/// This class controls the visibility of the obstacle end object based on the current game mode.
/// </summary>
public class ObstacleEnd : MonoBehaviour
{
void Start()
/// <summary>
/// Initializes the visibility of the object when the game starts.
/// </summary>
private void Start()
{
UpdateVisibility();
}
void Update()
/// <summary>
/// Updates the visibility of the object every frame.
/// </summary>
private void Update()
{
UpdateVisibility();
}
private void UpdateVisibility() // Sets object active if playing obstacle course
/// <summary>
/// Sets the object to be active only if the game mode is "Obstacle Course".
/// </summary>
private void UpdateVisibility()
{
if (GameManager.gameMode == GameManager.GameMode.obstacleCourse)
{

View File

@@ -1,17 +1,39 @@
using TMPro;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Game
{
public class PlayerJoinCard : MonoBehaviour
{
public GameObject playerPreview;
public int playerNumber;
public TextMeshProUGUI playerNumberText;
void Start() // Sets player number
/// <summary>
/// This class represents a player join card, displaying the player's number
/// and preview in the game lobby.
/// </summary>
public class PlayerJoinCard : MonoBehaviour
{
playerNumberText.text = playerNumber.ToString();
/// <summary>
/// The preview object representing the player.
/// </summary>
public GameObject playerPreview;
/// <summary>
/// The number assigned to the player.
/// </summary>
public int playerNumber;
/// <summary>
/// The text element displaying the player's number.
/// </summary>
public TextMeshProUGUI playerNumberText;
/// <summary>
/// Sets the player's number text when the game starts.
/// </summary>
private void Start()
{
// Display the player's number on the join card
playerNumberText.text = playerNumber.ToString();
}
}
}
}

View File

@@ -1,28 +1,57 @@
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Game
{
public class RespawnOnTriggerEnter : MonoBehaviour
{
public Vector2 spawnPoint;
public bool spawnPointIsInitialPosition = false;
public string respawnTag;
private void Start() // Set the spawn point to the initial maps spawn point
/// <summary>
/// This class handles respawning objects when they collide with a trigger tagged with a specific value.
/// </summary>
public class RespawnOnTriggerEnter : MonoBehaviour
{
if (spawnPointIsInitialPosition)
{
spawnPoint = transform.position;
}
}
/// <summary>
/// The spawn point where the object will respawn.
/// </summary>
public Vector2 spawnPoint;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag(respawnTag))
/// <summary>
/// If true, the spawn point is set to the object's initial position.
/// </summary>
public bool spawnPointIsInitialPosition = false;
/// <summary>
/// The tag of the trigger that causes the object to respawn.
/// </summary>
public string respawnTag;
/// <summary>
/// Sets the spawn point to the object's initial position if specified.
/// </summary>
private void Start()
{
if (TryGetComponent(out Damageable damageable))
if (spawnPointIsInitialPosition)
{
damageable.Damage(9999f);
// Set the spawn point to the object's initial position
spawnPoint = transform.position;
}
}
/// <summary>
/// Handles collisions with triggers and applies damage to the object if it has a <see cref="Damageable"/> component.
/// </summary>
/// <param name="other">The collider of the object that entered the trigger.</param>
private void OnTriggerEnter2D(Collider2D other)
{
// Check if the collider has the specified tag
if (other.CompareTag(respawnTag))
{
// Apply damage to the object if it has a Damageable component
if (TryGetComponent(out Damageable damageable))
{
damageable.Damage(9999f);
}
}
}
}
}}
}

View File

@@ -1,83 +1,170 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
using TMPro;
namespace Game
{
public class TerribleHealthBarScript : MonoBehaviour
{
public Color fullHealthColor;
public Color fullDeathColor;
public Color subtractionColor;
public GameObject healthVisual;
public GameObject actualHealthVisual;
public GameObject deathVisual;
public float smoothSpeed = 0.1f;
public TextMeshProUGUI text;
private Damageable healthScript;
private Vector3 initialScale;
private Vector3 initialPosition;
private Vector3 targetScale;
private Vector3 targetPosition;
private Color targetActualColor;
public GameObject player;
void Start()
/// <summary>
/// This class manages the health bar visuals for a player, including updating
/// the health bar's size, position, and color based on the player's current health.
/// </summary>
public class TerribleHealthBarScript : MonoBehaviour
{
InitializePlayer(player);
}
/// <summary>
/// The color of the health bar when the player is at full health.
/// </summary>
public Color fullHealthColor;
void Update() // Updates each player's health bar to display their current health
{
if (player == null || healthScript == null)
/// <summary>
/// The color of the health bar when the player is at zero health.
/// </summary>
public Color fullDeathColor;
/// <summary>
/// The color used to represent health subtraction.
/// </summary>
public Color subtractionColor;
/// <summary>
/// The visual representation of the health bar.
/// </summary>
public GameObject healthVisual;
/// <summary>
/// The actual health bar that reflects the player's current health.
/// </summary>
public GameObject actualHealthVisual;
/// <summary>
/// The visual representation of the player's death state.
/// </summary>
public GameObject deathVisual;
/// <summary>
/// The speed at which the health bar updates smoothly.
/// </summary>
public float smoothSpeed = 0.1f;
/// <summary>
/// The text element displaying the player's current and maximum health.
/// </summary>
public TextMeshProUGUI text;
/// <summary>
/// The <see cref="Damageable"/> component of the player, used to track health.
/// </summary>
private Damageable healthScript;
/// <summary>
/// The initial scale of the health bar.
/// </summary>
private Vector3 initialScale;
/// <summary>
/// The initial position of the health bar.
/// </summary>
private Vector3 initialPosition;
/// <summary>
/// The target scale of the health bar based on the player's current health.
/// </summary>
private Vector3 targetScale;
/// <summary>
/// The target position of the health bar based on the player's current health.
/// </summary>
private Vector3 targetPosition;
/// <summary>
/// The target color of the actual health bar based on the player's current health.
/// </summary>
private Color targetActualColor;
/// <summary>
/// The player associated with this health bar.
/// </summary>
public GameObject player;
/// <summary>
/// Initializes the health bar for the specified player.
/// </summary>
private void Start()
{
return;
InitializePlayer(player);
}
float healthRatio = (healthScript.maxDamage - healthScript.damage) / healthScript.maxDamage;
targetActualColor = Color.Lerp(fullDeathColor, fullHealthColor, healthRatio);
targetScale = new Vector3(Mathf.Lerp(0, 1, healthRatio) * initialScale.x, healthVisual.transform.localScale.y, healthVisual.transform.localScale.z);
targetPosition = new Vector3(Mathf.Lerp(-0.5f, 0, healthRatio), healthVisual.transform.localPosition.y, healthVisual.transform.localPosition.z);
text.text = (healthScript.maxDamage - healthScript.damage).ToString() + "/" + healthScript.maxDamage.ToString();
actualHealthVisual.transform.localScale = targetScale;
actualHealthVisual.transform.localPosition = targetPosition;
healthVisual.transform.localScale = Vector3.Lerp(healthVisual.transform.localScale, targetScale, smoothSpeed);
healthVisual.transform.localPosition = Vector3.Lerp(healthVisual.transform.localPosition, targetPosition, smoothSpeed);
actualHealthVisual.GetComponent<SpriteRenderer>().color = Color.Lerp(actualHealthVisual.GetComponent<SpriteRenderer>().color, targetActualColor, smoothSpeed);
deathVisual.GetComponent<SpriteRenderer>().color = Color.Lerp(deathVisual.GetComponent<SpriteRenderer>().color, targetActualColor * 0.5f, smoothSpeed);
healthVisual.GetComponent<SpriteRenderer>().color = subtractionColor;
}
public void SetPlayer(GameObject player)
{
InitializePlayer(player);
}
private void InitializePlayer(GameObject player) // Adds a health bar for each player
{
this.player = player;
if (this.player == null)
/// <summary>
/// Updates the health bar visuals to reflect the player's current health.
/// </summary>
private void Update()
{
return;
}
healthScript = player.GetComponent<Damageable>();
if (healthScript == null)
{
return;
}
Initialize();
}
if (player == null || healthScript == null)
{
return;
}
private void Initialize() // Sets up the health bars
{
initialScale = healthVisual.transform.localScale;
initialPosition = healthVisual.transform.position;
targetScale = initialScale;
targetPosition = initialPosition;
targetActualColor = actualHealthVisual.GetComponent<SpriteRenderer>().color;
// Calculate the health ratio and update the health bar visuals
float healthRatio = (healthScript.maxDamage - healthScript.damage) / healthScript.maxDamage;
targetActualColor = Color.Lerp(fullDeathColor, fullHealthColor, healthRatio);
targetScale = new Vector3(Mathf.Lerp(0, 1, healthRatio) * initialScale.x, healthVisual.transform.localScale.y, healthVisual.transform.localScale.z);
targetPosition = new Vector3(Mathf.Lerp(-0.5f, 0, healthRatio), healthVisual.transform.localPosition.y, healthVisual.transform.localPosition.z);
text.text = (healthScript.maxDamage - healthScript.damage).ToString() + "/" + healthScript.maxDamage.ToString();
// Smoothly update the health bar's scale, position, and color
actualHealthVisual.transform.localScale = targetScale;
actualHealthVisual.transform.localPosition = targetPosition;
healthVisual.transform.localScale = Vector3.Lerp(healthVisual.transform.localScale, targetScale, smoothSpeed);
healthVisual.transform.localPosition = Vector3.Lerp(healthVisual.transform.localPosition, targetPosition, smoothSpeed);
actualHealthVisual.GetComponent<SpriteRenderer>().color = Color.Lerp(actualHealthVisual.GetComponent<SpriteRenderer>().color, targetActualColor, smoothSpeed);
deathVisual.GetComponent<SpriteRenderer>().color = Color.Lerp(deathVisual.GetComponent<SpriteRenderer>().color, targetActualColor * 0.5f, smoothSpeed);
healthVisual.GetComponent<SpriteRenderer>().color = subtractionColor;
}
/// <summary>
/// Sets the player associated with this health bar.
/// </summary>
/// <param name="player">The player to associate with this health bar.</param>
public void SetPlayer(GameObject player)
{
InitializePlayer(player);
}
/// <summary>
/// Initializes the health bar for the specified player.
/// </summary>
/// <param name="player">The player to initialize the health bar for.</param>
private void InitializePlayer(GameObject player)
{
this.player = player;
if (this.player == null)
{
return;
}
// Get the Damageable component of the player
healthScript = player.GetComponent<Damageable>();
if (healthScript == null)
{
return;
}
Initialize();
}
/// <summary>
/// Sets up the initial state of the health bar.
/// </summary>
private void Initialize()
{
initialScale = healthVisual.transform.localScale;
initialPosition = healthVisual.transform.position;
targetScale = initialScale;
targetPosition = initialPosition;
targetActualColor = actualHealthVisual.GetComponent<SpriteRenderer>().color;
}
}
}
}

View File

@@ -1,14 +1,30 @@
using UnityEngine;
using TMPro;
/// <summary>
/// This class manages the user manual popup, including displaying instructions,
/// game mode descriptions, controls, and tips for players.
/// </summary>
public class UserManualPopup : MonoBehaviour
{
/// <summary>
/// The panel that contains the user manual popup.
/// </summary>
public GameObject popupPanel;
/// <summary>
/// The text element used to display the user manual content.
/// </summary>
public TextMeshProUGUI userManualText;
// Reference to the combined sprite asset
/// <summary>
/// The sprite asset used for embedding icons in the user manual text.
/// </summary>
public TMP_SpriteAsset combinedSpriteAsset;
/// <summary>
/// Initializes the user manual content and assigns the sprite asset.
/// </summary>
private void Start()
{
// Assign the combined sprite asset
@@ -61,17 +77,23 @@ public class UserManualPopup : MonoBehaviour
userManualText.text += "<size=30>The hat has physics too! Don't punch it too hard.</size>";
}
public void ShowPopup() // Show the popup
/// <summary>
/// Displays the user manual popup.
/// </summary>
public void ShowPopup()
{
popupPanel.SetActive(true);
gameObject.SetActive(true);
print("User manual opened");
}
public void HidePopup() // Hide the popup
/// <summary>
/// Hides the user manual popup.
/// </summary>
public void HidePopup()
{
popupPanel.SetActive(false);
gameObject.SetActive(false);
print("User manual closed");
}
}
}

View File

@@ -1,50 +1,76 @@
using System.Collections.Generic;
using TMPro;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Game
{
public class WinScreen : MonoBehaviour
{
public static WinScreen Instance;
public List<TextMeshProUGUI> playerTexts;
private void Awake() // Ensures only one instance of WinScreen exists
/// <summary>
/// Manages the win screen display for the game.
/// Displays the winning player's information and triggers the win screen animation.
/// </summary>
public class WinScreen : MonoBehaviour
{
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(this.gameObject);
}
}
public void ShowWinScreen(int player)
{
if (player - 1 < 0 || player - 1 >= GameManager.playerColors.Count)
{
return;
}
/// <summary>
/// A singleton instance of the <see cref="WinScreen"/> class.
/// Ensures only one instance of the WinScreen exists at a time.
/// </summary>
public static WinScreen Instance;
foreach (TextMeshProUGUI playerText in playerTexts)
/// <summary>
/// A list of text elements used to display player information on the win screen.
/// </summary>
public List<TextMeshProUGUI> playerTexts;
/// <summary>
/// Ensures only one instance of the WinScreen exists.
/// Destroys duplicate instances if they are created.
/// </summary>
private void Awake()
{
playerText.text = "Player " + player;
if (playerText.color != Color.black)
if (Instance == null)
{
playerText.color = GameManager.playerColors[player - 1];
Instance = this;
}
else
{
Destroy(this.gameObject);
}
}
Animator animator = GetComponent<Animator>();
if (animator == null)
/// <summary>
/// Displays the win screen for the specified player.
/// Updates the text and color of the win screen to reflect the winning player.
/// </summary>
/// <param name="player">The number of the winning player (1-based index).</param>
public void ShowWinScreen(int player)
{
return;
// Validate the player index
if (player - 1 < 0 || player - 1 >= GameManager.playerColors.Count)
{
return;
}
// Update the text and color for each player text element
foreach (TextMeshProUGUI playerText in playerTexts)
{
playerText.text = "Player " + player;
if (playerText.color != Color.black)
{
playerText.color = GameManager.playerColors[player - 1];
}
}
// Trigger the win screen animation
Animator animator = GetComponent<Animator>();
if (animator == null)
{
return;
}
animator.SetTrigger("win");
}
animator.SetTrigger("win");
}
}
}