2024-11-17 21:32:14 +08:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
2024-11-15 07:54:43 +08:00
|
|
|
using UnityEngine;
|
2024-11-17 07:29:22 +08:00
|
|
|
using UnityEngine.UIElements;
|
2024-11-15 07:54:43 +08:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// singleton for a single source of truth game state and flow management
|
|
|
|
/// </summary>
|
|
|
|
public class GameManager : MonoBehaviour
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// enum for available menus in the game, for use with <c>ShowMenu()</c>
|
|
|
|
/// </summary>
|
|
|
|
public enum DisplayState
|
|
|
|
{
|
2024-11-17 21:32:14 +08:00
|
|
|
UnassociatedState, // initial state, then we transition to Nothing to initialise the ui
|
2024-11-17 19:10:01 +08:00
|
|
|
Nothing,
|
2024-11-17 21:32:14 +08:00
|
|
|
GameView,
|
2024-11-17 19:10:01 +08:00
|
|
|
LeaderboardView,
|
2024-11-17 21:32:14 +08:00
|
|
|
AccountView
|
2024-11-15 07:54:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// singleton pattern: define instance field for accessing the singleton elsewhere
|
|
|
|
/// </summary>
|
|
|
|
public static GameManager Instance;
|
2024-11-17 21:32:14 +08:00
|
|
|
|
2024-11-17 19:10:01 +08:00
|
|
|
/// <summary>
|
|
|
|
/// the current display state of the game
|
|
|
|
/// </summary>
|
2024-11-17 21:32:14 +08:00
|
|
|
[SerializeField] private DisplayState state = DisplayState.UnassociatedState;
|
2024-11-15 07:54:43 +08:00
|
|
|
|
2024-11-17 07:29:22 +08:00
|
|
|
/// <summary>
|
2024-11-17 21:32:14 +08:00
|
|
|
/// the game object for the ui
|
2024-11-17 07:29:22 +08:00
|
|
|
/// </summary>
|
2024-11-17 21:32:14 +08:00
|
|
|
[SerializeField] private GameObject uiGameObject;
|
|
|
|
|
|
|
|
// /// <summary>
|
|
|
|
// /// callback functions to be invoked when the display state changes
|
|
|
|
// /// </summary>
|
|
|
|
// private readonly List<Action<DisplayState>> _onDisplayStateChange = new();
|
2024-11-17 07:29:22 +08:00
|
|
|
|
|
|
|
/// <summary>
|
2024-11-17 21:32:14 +08:00
|
|
|
/// callback functions to be invoked when the local player data is updated
|
2024-11-17 07:29:22 +08:00
|
|
|
/// </summary>
|
2024-11-17 21:32:14 +08:00
|
|
|
private readonly List<Action<LocalPlayerData>> _onLocalPlayerDataUpdateCallbacks = new();
|
|
|
|
|
2024-11-17 19:10:01 +08:00
|
|
|
/// <summary>
|
|
|
|
/// the local player data object for storing player data
|
|
|
|
/// </summary>
|
2024-11-17 21:32:14 +08:00
|
|
|
private LocalPlayerData _data;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// the visual element for the ui
|
|
|
|
/// </summary>
|
|
|
|
private VisualElement _ui;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// backend object for handling communication with the firebase backend
|
|
|
|
/// </summary>
|
|
|
|
public Backend Backend;
|
2024-11-17 07:29:22 +08:00
|
|
|
|
2024-11-15 07:54:43 +08:00
|
|
|
/// <summary>
|
|
|
|
/// enforces singleton behaviour; sets doesn't destroy on load and checks for multiple instances
|
|
|
|
/// </summary>
|
|
|
|
private void Awake()
|
|
|
|
{
|
|
|
|
// check if instance hasn't been set yet
|
|
|
|
if (Instance == null)
|
|
|
|
{
|
|
|
|
Debug.Log("awake as singleton instance, setting self as the forever-alive instance");
|
|
|
|
Instance = this;
|
|
|
|
DontDestroyOnLoad(gameObject);
|
|
|
|
}
|
|
|
|
// check if instance is already set and it's not this instance
|
|
|
|
else if (Instance != null && Instance != this)
|
|
|
|
{
|
|
|
|
Debug.Log("awake as non-singleton instance, destroying self");
|
|
|
|
Destroy(gameObject);
|
|
|
|
}
|
2024-11-17 21:32:14 +08:00
|
|
|
|
|
|
|
if (uiGameObject == null)
|
|
|
|
throw new NullReferenceException("a reference UI GameObject is not set in the inspector");
|
|
|
|
|
|
|
|
_ui = uiGameObject.GetComponent<UIDocument>()?.rootVisualElement;
|
|
|
|
if (_ui == null)
|
|
|
|
throw new NullReferenceException("could not grab the UIDocument in the reference UI GameObject");
|
2024-11-15 07:54:43 +08:00
|
|
|
}
|
2024-11-17 07:29:22 +08:00
|
|
|
|
2024-11-17 21:32:14 +08:00
|
|
|
/// <summary>
|
|
|
|
/// called before the first frame update
|
|
|
|
/// </summary>
|
2024-11-17 19:10:01 +08:00
|
|
|
private void Start()
|
|
|
|
{
|
2024-11-17 21:32:14 +08:00
|
|
|
// transition to the initial state
|
|
|
|
SetDisplayState(DisplayState.Nothing);
|
|
|
|
|
|
|
|
// initialise the backend
|
|
|
|
Backend = new Backend();
|
|
|
|
Backend.Initialise(status =>
|
|
|
|
{
|
|
|
|
Debug.Log("initialised backend, setting connection status text");
|
|
|
|
|
|
|
|
_ui.Q<Label>("ConnectionStatusText").text = status switch
|
|
|
|
{
|
|
|
|
Backend.FirebaseConnectionStatus.Connected => "Status: Connected",
|
|
|
|
Backend.FirebaseConnectionStatus.Updating => "Status: Updating... (Retrying in a bit!)",
|
|
|
|
Backend.FirebaseConnectionStatus.NotConnected => "Status: Disconnected",
|
|
|
|
Backend.FirebaseConnectionStatus.UpdateRequired =>
|
|
|
|
"Status: Disconnected (Device Component Update Required)",
|
|
|
|
Backend.FirebaseConnectionStatus.ExternalError => "Status: Disconnected (External/Device Error)",
|
|
|
|
Backend.FirebaseConnectionStatus.InternalError => "Status: Disconnected (Internal Error)",
|
|
|
|
_ => "Status: Disconnected (unknown fcs state, this is unreachable and a bug)"
|
|
|
|
};
|
|
|
|
|
|
|
|
if (status == Backend.FirebaseConnectionStatus.Connected) return;
|
|
|
|
|
|
|
|
// if we're not connected, hide any online 'features'
|
|
|
|
_ui.Q<Button>("LeaderboardButton").style.display = DisplayStyle.None;
|
|
|
|
_ui.Q<Button>("AccountButton").style.display = DisplayStyle.None;
|
|
|
|
});
|
|
|
|
|
2024-11-17 19:10:01 +08:00
|
|
|
// load the local player data and refresh the ui
|
2024-11-17 21:32:14 +08:00
|
|
|
_data = new LocalPlayerData();
|
|
|
|
_data.LoadFromTheWorld(data =>
|
|
|
|
{
|
|
|
|
foreach (var callback in _onLocalPlayerDataUpdateCallbacks) callback(data);
|
|
|
|
});
|
|
|
|
|
2024-11-17 19:10:01 +08:00
|
|
|
// register a callback to refresh the ui when the player signs in
|
|
|
|
Backend.RegisterOnSignInCallback(_ =>
|
|
|
|
{
|
2024-11-17 21:32:14 +08:00
|
|
|
Debug.Log("post-auth callback, refreshing player data from the world");
|
|
|
|
_data.LoadFromTheWorld(data =>
|
|
|
|
{
|
|
|
|
Debug.Log("firing lpdata update callbacks");
|
|
|
|
foreach (var callback in _onLocalPlayerDataUpdateCallbacks) callback(data);
|
|
|
|
});
|
2024-11-17 19:10:01 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-11-17 07:29:22 +08:00
|
|
|
/// <summary>
|
2024-11-17 21:32:14 +08:00
|
|
|
/// called when the game object is disabled
|
2024-11-17 07:29:22 +08:00
|
|
|
/// </summary>
|
2024-11-17 21:32:14 +08:00
|
|
|
private void OnDestroy()
|
2024-11-17 07:29:22 +08:00
|
|
|
{
|
2024-11-17 21:32:14 +08:00
|
|
|
Backend.Cleanup();
|
2024-11-17 07:29:22 +08:00
|
|
|
}
|
|
|
|
|
2024-11-17 21:32:14 +08:00
|
|
|
// /// <summary>
|
|
|
|
// /// function to register a callback for when the display state changes
|
|
|
|
// /// </summary>
|
|
|
|
// /// <param name="callback">callback function that takes in a <c>DisplayState</c> enum</param>
|
|
|
|
// public void RegisterOnDisplayStateChange(Action<DisplayState> callback)
|
|
|
|
// {
|
|
|
|
// _onDisplayStateChange.Add(callback);
|
|
|
|
// }
|
|
|
|
|
2024-11-17 07:29:22 +08:00
|
|
|
/// <summary>
|
2024-11-17 21:32:14 +08:00
|
|
|
/// function to register a callback for when the local player data is updated
|
2024-11-17 07:29:22 +08:00
|
|
|
/// </summary>
|
2024-11-17 21:32:14 +08:00
|
|
|
/// <param name="callback">callback function that takes in a <c>LocalPlayerData</c> object</param>
|
|
|
|
public void RegisterOnLocalPlayerDataUpdate(Action<LocalPlayerData> callback)
|
2024-11-17 07:29:22 +08:00
|
|
|
{
|
2024-11-17 21:32:14 +08:00
|
|
|
_onLocalPlayerDataUpdateCallbacks.Add(callback);
|
|
|
|
Debug.Log(
|
|
|
|
$"registering on lpdata update callback, there are now {_onLocalPlayerDataUpdateCallbacks.Count} callbacks");
|
2024-11-17 07:29:22 +08:00
|
|
|
}
|
2024-11-17 19:10:01 +08:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// function to show a menu based on the enum passed,
|
|
|
|
/// and any other necessary actions
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="newDisplayState">the game menu to show</param>
|
|
|
|
public void SetDisplayState(DisplayState newDisplayState)
|
|
|
|
{
|
2024-11-17 21:32:14 +08:00
|
|
|
var currentDisplayState = state;
|
|
|
|
|
|
|
|
// if the new state is the same as the current state, do nothing
|
|
|
|
if (currentDisplayState == newDisplayState)
|
|
|
|
{
|
|
|
|
Debug.Log($"staying at {currentDisplayState} (illogical transition)");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Debug.Log($"switching from {currentDisplayState} to {newDisplayState}");
|
|
|
|
|
|
|
|
var gameView = _ui.Q<VisualElement>("GameView");
|
|
|
|
var leaderboardView = _ui.Q<VisualElement>("LeaderboardView");
|
|
|
|
var accountView = _ui.Q<VisualElement>("AccountView");
|
|
|
|
|
2024-11-17 19:10:01 +08:00
|
|
|
switch (newDisplayState)
|
2024-11-17 21:32:14 +08:00
|
|
|
{
|
|
|
|
case DisplayState.Nothing:
|
|
|
|
gameView.style.display = DisplayStyle.None;
|
|
|
|
leaderboardView.style.display = DisplayStyle.None;
|
|
|
|
accountView.style.display = DisplayStyle.None;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DisplayState.GameView:
|
|
|
|
gameView.style.display = DisplayStyle.Flex;
|
|
|
|
leaderboardView.style.display = DisplayStyle.None;
|
|
|
|
accountView.style.display = DisplayStyle.None;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DisplayState.LeaderboardView:
|
|
|
|
gameView.style.display = DisplayStyle.None;
|
|
|
|
leaderboardView.style.display = DisplayStyle.Flex;
|
|
|
|
accountView.style.display = DisplayStyle.None;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DisplayState.AccountView:
|
|
|
|
gameView.style.display = DisplayStyle.None;
|
|
|
|
leaderboardView.style.display = DisplayStyle.None;
|
|
|
|
accountView.style.display = DisplayStyle.Flex;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DisplayState.UnassociatedState:
|
|
|
|
default:
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(newDisplayState), newDisplayState, null);
|
|
|
|
}
|
2024-11-17 19:10:01 +08:00
|
|
|
}
|
2024-11-15 07:54:43 +08:00
|
|
|
}
|