/* * author: mark joshwel, sai puay * date: 11/8/2024 * description: game manager singleton for a single source of truth state management */ using System; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.UIElements; using Cursor = UnityEngine.Cursor; /// /// singleton class for managing the game state as a single source of truth /// public class GameManager : MonoBehaviour { /// /// enum for available menus in the game, for use with ShowMenu() /// public enum DisplayState { Game, ScreenMainMenu, ScreenOptionsMenu, OverlayPauseMenu, OverlayCompleteUnderTimeMenu, OverlayFailedOverTimeMenu, UnassociatedState } /// /// singleton pattern: define instance field for accessing the singleton elsewhere /// public static GameManager Instance; /// /// game object for the interaction prompt /// [SerializeField] private GameObject guiInteractionPromptObject; /// /// game object for the heads-up display /// [SerializeField] private GameObject guiHudObject; /// /// game object for the completion menu /// [SerializeField] private GameObject guiCompletedMenuObject; /// /// float to keep track of the elapsed play/run/speeder time /// private float _elapsedRunTime; /// /// checked when the scene is loaded to restart the game and skip the main menu /// private bool _restarting; /// /// the current state of the game /// private DisplayState _state = DisplayState.UnassociatedState; /// /// the visual element object for game ui (hud/prompts/tooltips) /// private VisualElement _ui; /// /// hud ui label for an interaction prompt/tooltip /// private Label _uiLabelInteractionPrompt; /// /// hud ui label for the speed-run stopwatch /// private Label _uiLabelStopwatch; /// /// property to check if the game is paused based on the current DisplayState /// public bool Paused => _state != DisplayState.Game; /// /// function to set doesn't destroy on load and checks for multiple instances /// /// thrown if a Heads-Up Display game object isn't set in the properties private void Awake() { // check if instance hasn't been set yet if (Instance == null) { // set this instance as the singleton instance Instance = this; // don't destroy this instance on a scene load DontDestroyOnLoad(gameObject); Debug.Log("GameManager: Awake as singleton instance"); } // check if instance is already set and it's not this instance else if (Instance != null && Instance != this) { Debug.Log("GameManager: Awake as non-singleton instance, destroying self"); // destroy the new instance if it's not the singleton instance Destroy(gameObject); } } /// /// called when the game starts, sets state to the main menu /// // /// generic exception it couldn't verify a safe state when starting the game private void Start() { if (guiInteractionPromptObject == null) throw new NullReferenceException("GameManager: guiInteractionPromptObject not set"); if (guiHudObject == null) throw new NullReferenceException("GameManager: guiHudObject not set"); // if we're restarting, skip the main menu if (_restarting) { Debug.Log("GameManager.Start: inherited _restarting, honouring it rn"); NewGame(); } else { Debug.Log("GameManager.Start: setting to main menu and pausing"); SetDisplayState(DisplayState.ScreenMainMenu); PauseGameHelper(DisplayState.ScreenMainMenu); } } /// /// game run speed run stopwatch logic /// private void Update() { if (Paused) return; _elapsedRunTime += Time.deltaTime; var minutes = _elapsedRunTime / 60; var seconds = _elapsedRunTime % 60; var milliseconds = _elapsedRunTime * 1000 % 1000; _uiLabelStopwatch.text = $"{minutes:00}:{seconds:00}.{milliseconds:000}"; } /// /// called when the game object is enabled /// private void OnEnable() { InitialiseInterfaceElements(); } /// /// initialise ui elements used by the game[ manager] /// /// > private void InitialiseInterfaceElements() { _ui = guiInteractionPromptObject.GetComponent()?.rootVisualElement; _uiLabelInteractionPrompt = _ui.Q