Revert "game: overcomplicated interim 2"

This reverts commit 4e6c7db3f1.
This commit is contained in:
Mark Joshwel 2024-11-17 21:32:26 +08:00
parent 4e6c7db3f1
commit 27f42ffc55
11 changed files with 148 additions and 314 deletions

View file

@ -166,7 +166,7 @@ Transform:
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 133964670} m_GameObject: {fileID: 133964670}
serializedVersion: 2 serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
@ -459,8 +459,6 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 0cfc0b50e9003a0468ebdc186439c53b, type: 3} m_Script: {fileID: 11500000, guid: 0cfc0b50e9003a0468ebdc186439c53b, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
state: 0
uiGameObject: {fileID: 133964670}
--- !u!4 &1204483826 --- !u!4 &1204483826
Transform: Transform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -482,6 +480,6 @@ SceneRoots:
m_Roots: m_Roots:
- {fileID: 963194228} - {fileID: 963194228}
- {fileID: 705507995} - {fileID: 705507995}
- {fileID: 447905427}
- {fileID: 133964672} - {fileID: 133964672}
- {fileID: 447905427}
- {fileID: 1204483826} - {fileID: 1204483826}

View file

@ -78,20 +78,11 @@ public class AccountUI : MonoBehaviour
/// </summary> /// </summary>
private Button _usernameUpdateButton; private Button _usernameUpdateButton;
/// <summary> public void Start()
/// start function to initialise the account view
/// </summary>
/// <exception cref="Exception"></exception>
private void Start()
{ {
TransitionStateTo(State.NotSignedIn);
if (state == State.UnassociatedState) throw new Exception("unreachable state"); if (state == State.UnassociatedState) throw new Exception("unreachable state");
GameManager.Instance.Backend.RegisterOnSignInCallback(OnSignInCallback);
GameManager.Instance.RegisterOnLocalPlayerDataUpdate(data => // GameManager.Instance.Backend.RegisterOnSignInCallback(OnSignInCallback);
{
Debug.Log("local player data update callback, populating AccountView fields");
PopulateFields(data.LastKnownUsername, data.LastKnownEmail);
});
} }
/// <summary> /// <summary>
@ -123,11 +114,15 @@ public class AccountUI : MonoBehaviour
_secondaryActionButton = ui.Q<Button>("SecondaryActionButton"); _secondaryActionButton = ui.Q<Button>("SecondaryActionButton");
_secondaryActionButton.clicked += OnSecondaryActionButtonClick; _secondaryActionButton.clicked += OnSecondaryActionButtonClick;
TransitionStateTo(State.NotSignedIn);
GameManager.Instance.Backend.RegisterOnSignInCallback(OnSignInCallback);
} }
private void OnSignInCallback(FirebaseUser user) private void OnSignInCallback(FirebaseUser user)
{ {
Debug.Log("post-auth callback, updating account view text"); Debug.Log("sign in account ui callback");
var username = GameManager.Instance.Backend.GetUsername(); var username = GameManager.Instance.Backend.GetUsername();
_usernameField.value = username; _usernameField.value = username;
_emailField.value = GameManager.Instance.Backend.GetUser().Email; _emailField.value = GameManager.Instance.Backend.GetUser().Email;
@ -221,12 +216,12 @@ public class AccountUI : MonoBehaviour
state = newState; state = newState;
} }
private void ValidateUsername(TextField usernameField) private void ValidateUsername()
{ {
// just has to be min. 5 characters // just has to be min. 5 characters
usernameField.style.color = _defaultInputFieldValueTextColour; _usernameField.style.color = _defaultInputFieldValueTextColour;
if (usernameField.value.Length >= 5) return; if (_usernameField.value.Length >= 5) return;
usernameField.style.color = _errorInputFieldValueTextColour; _usernameField.style.color = _errorInputFieldValueTextColour;
throw new Exception("Username must be at least 5 characters long."); throw new Exception("Username must be at least 5 characters long.");
} }
@ -284,18 +279,18 @@ public class AccountUI : MonoBehaviour
try try
{ {
ValidateEmailField(emailField); ValidateEmailField(_emailField);
} }
catch (Exception) catch (Exception _)
{ {
invalidEmail = true; invalidEmail = true;
} }
try try
{ {
ValidatePasswordField(passwordField); ValidatePasswordField(_passwordField);
} }
catch (Exception) catch (Exception _)
{ {
invalidPassword = true; invalidPassword = true;
} }
@ -326,27 +321,27 @@ public class AccountUI : MonoBehaviour
try try
{ {
ValidateEmailField(emailField); ValidateEmailField(_emailField);
} }
catch (Exception) catch (Exception _)
{ {
invalidEmail = true; invalidEmail = true;
} }
try try
{ {
ValidatePasswordField(passwordField); ValidatePasswordField(_passwordField);
} }
catch (Exception) catch (Exception _)
{ {
invalidPassword = true; invalidPassword = true;
} }
try try
{ {
ValidateUsername(usernameField); ValidateUsername();
} }
catch (Exception) catch (Exception _)
{ {
invalidUsername = true; invalidUsername = true;
} }
@ -374,7 +369,7 @@ public class AccountUI : MonoBehaviour
{ {
try try
{ {
ValidateUsername(_usernameField); ValidateUsername();
} }
catch (Exception e) catch (Exception e)
{ {

View file

@ -57,6 +57,16 @@ public class Backend
Password Password
} }
/// <summary>
/// callback functions to be invoked when the user signs in
/// </summary>
private readonly List<Action<FirebaseUser>> _onSignInCallback = new();
/// <summary>
/// callback functions to be invoked when the user signs out
/// </summary>
private readonly List<Action<FirebaseUser>> _onSignOutCallback = new();
/// <summary> /// <summary>
/// the firebase authentication object /// the firebase authentication object
/// </summary> /// </summary>
@ -67,16 +77,6 @@ public class Backend
/// </summary> /// </summary>
private DatabaseReference _db; private DatabaseReference _db;
/// <summary>
/// callback functions to be invoked when the user signs in
/// </summary>
private readonly List<Action<FirebaseUser>> _onSignInCallbacks = new();
/// <summary>
/// callback functions to be invoked when the user signs out
/// </summary>
private readonly List<Action<FirebaseUser>> _onSignOutCallbacks = new();
/// <summary> /// <summary>
/// the current user object, if authenticated /// the current user object, if authenticated
/// </summary> /// </summary>
@ -95,9 +95,9 @@ public class Backend
/// <summary> /// <summary>
/// variable initialisation function /// variable initialisation function
/// </summary> /// </summary>
public void Initialise(Action<FirebaseConnectionStatus> callback) public void Initialise()
{ {
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWithOnMainThread(task => FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
{ {
// cher is this robust enough // cher is this robust enough
switch (task.Result) switch (task.Result)
@ -107,7 +107,6 @@ public class Backend
_auth.StateChanged += AuthStateChanged; _auth.StateChanged += AuthStateChanged;
_db = FirebaseDatabase.DefaultInstance.RootReference; _db = FirebaseDatabase.DefaultInstance.RootReference;
Status = FirebaseConnectionStatus.Connected; Status = FirebaseConnectionStatus.Connected;
callback(Status);
break; break;
case DependencyStatus.UnavailableDisabled: case DependencyStatus.UnavailableDisabled:
@ -115,25 +114,21 @@ public class Backend
case DependencyStatus.UnavilableMissing: case DependencyStatus.UnavilableMissing:
case DependencyStatus.UnavailablePermission: case DependencyStatus.UnavailablePermission:
Status = FirebaseConnectionStatus.ExternalError; Status = FirebaseConnectionStatus.ExternalError;
callback(Status);
break; break;
case DependencyStatus.UnavailableUpdating: case DependencyStatus.UnavailableUpdating:
Status = FirebaseConnectionStatus.Updating; Status = FirebaseConnectionStatus.Updating;
callback(Status); RetryInitialiseAfterDelay();
RetryInitialiseAfterDelay(callback);
break; break;
case DependencyStatus.UnavailableUpdaterequired: case DependencyStatus.UnavailableUpdaterequired:
Status = FirebaseConnectionStatus.UpdateRequired; Status = FirebaseConnectionStatus.UpdateRequired;
callback(Status);
break; break;
case DependencyStatus.UnavailableOther: case DependencyStatus.UnavailableOther:
default: default:
Status = FirebaseConnectionStatus.InternalError; Status = FirebaseConnectionStatus.InternalError;
Debug.LogError("firebase ??? blew up or something," + task.Result); Debug.LogError("firebase ??? blew up or something," + task.Result);
callback(Status);
break; break;
} }
@ -144,10 +139,10 @@ public class Backend
/// <summary> /// <summary>
/// async function to retry initialisation after a delay /// async function to retry initialisation after a delay
/// </summary> /// </summary>
private async void RetryInitialiseAfterDelay(Action<FirebaseConnectionStatus> callback) private async void RetryInitialiseAfterDelay()
{ {
await Task.Delay(TimeSpan.FromSeconds(10)); await Task.Delay(TimeSpan.FromSeconds(10));
Initialise(callback); Initialise();
} }
/// <summary> /// <summary>
@ -177,7 +172,7 @@ public class Backend
if (!signedIn && _user != null) if (!signedIn && _user != null)
{ {
Debug.Log($"signed out successfully as {_user.UserId}"); Debug.Log($"signed out successfully as {_user.UserId}");
foreach (var callback in _onSignOutCallbacks) foreach (var callback in _onSignOutCallback)
callback(_user); callback(_user);
} }
@ -188,29 +183,26 @@ public class Backend
Debug.Log($"signed in successfully as {_user.UserId}"); Debug.Log($"signed in successfully as {_user.UserId}");
RetrieveUsernameWithCallback((_, _) => RetrieveUsernameWithCallback((_, _) =>
{ {
Debug.Log($"retrieved username post-authentication, calling {_onSignInCallbacks.Count} callbacks"); foreach (var callback in _onSignInCallback) callback(_user);
foreach (var callback in _onSignInCallbacks) callback(_user);
}); });
} }
/// <summary> /// <summary>
/// function to register a callback for when the user signs in /// function to register a callback for when the user signs in
/// </summary> /// </summary>
/// <param name="callback">callback function that takes in a <c>FirebaseUser</c> object</param> /// <param name="callback">callback function that takes in a FirebaseUser argument</param>
public void RegisterOnSignInCallback(Action<FirebaseUser> callback) public void RegisterOnSignInCallback(Action<FirebaseUser> callback)
{ {
_onSignInCallbacks.Add(callback); _onSignInCallback.Add(callback);
Debug.Log($"registering on sign in callback, there are now {_onSignInCallbacks.Count} callbacks");
} }
/// <summary> /// <summary>
/// function to register a callback for when the user signs out /// function to register a callback for when the user signs out
/// </summary> /// </summary>
/// <param name="callback">callback function that takes in a <c>FirebaseUser</c> object</param> /// <param name="callback">callback function that takes in a FirebaseUser argument</param>
public void RegisterOnSignOutCallback(Action<FirebaseUser> callback) public void RegisterOnSignOutCallback(Action<FirebaseUser> callback)
{ {
Debug.Log($"registering on sign out callback, there are now {_onSignOutCallbacks.Count} callbacks"); _onSignOutCallback.Add(callback);
_onSignOutCallbacks.Add(callback);
} }
/// <summary> /// <summary>
@ -372,18 +364,21 @@ public class Backend
_db.Child("users").Child(_user.UserId).Child("username").GetValueAsync().ContinueWithOnMainThread(task => _db.Child("users").Child(_user.UserId).Child("username").GetValueAsync().ContinueWithOnMainThread(task =>
{ {
DatabaseTransactionResult result;
if (task.IsCompletedSuccessfully) if (task.IsCompletedSuccessfully)
{ {
result = DatabaseTransactionResult.Ok;
_username = task.Result.Value.ToString(); _username = task.Result.Value.ToString();
Debug.Log($"our username is {_username}"); Debug.Log($"our username is {_username}");
callback(DatabaseTransactionResult.Ok, _username);
} }
else else
{ {
result = DatabaseTransactionResult.Error;
_username = "Unknown"; _username = "Unknown";
Debug.LogError("failed to get username"); Debug.LogError("failed to get username");
callback(DatabaseTransactionResult.Error, _username);
} }
callback(result, _username);
}); });
} }
@ -434,8 +429,8 @@ public class Backend
/// abstraction function to get the user's recent scores from the database /// abstraction function to get the user's recent scores from the database
/// </summary> /// </summary>
/// <param name="callback"> /// <param name="callback">
/// callback function that takes in a <c>DatabaseTransactionResult</c> enum /// callback function that takes in a DatabaseTransactionResult and List of LocalPlayerData.Score
/// and a <c>List&lt;LocalPlayerData.Score&gt;</c>argument /// argument
/// </param> /// </param>
public void GetRecentScores(Action<DatabaseTransactionResult, List<LocalPlayerData.Score>> callback) public void GetRecentScores(Action<DatabaseTransactionResult, List<LocalPlayerData.Score>> callback)
{ {

View file

@ -62,7 +62,6 @@ public static class Colorimetry
/// a and b must be normalized so a^2 + b^2 == 1 /// a and b must be normalized so a^2 + b^2 == 1
/// </summary> /// </summary>
// https://bottosson.github.io/posts/gamutclipping/ (MIT) // https://bottosson.github.io/posts/gamutclipping/ (MIT)
// ReSharper disable once MemberCanBePrivate.Global
public static float find_gamut_intersection( public static float find_gamut_intersection(
float a, float a,
float b, float b,
@ -165,7 +164,6 @@ public static class Colorimetry
/// a and b must be normalized so a^2 + b^2 == 1 /// a and b must be normalized so a^2 + b^2 == 1
/// </summary> /// </summary>
// https://bottosson.github.io/posts/gamutclipping/ (MIT) // https://bottosson.github.io/posts/gamutclipping/ (MIT)
// ReSharper disable once MemberCanBePrivate.Global
public static LC find_cusp(float a, float b) public static LC find_cusp(float a, float b)
{ {
// First, find the maximum saturation (saturation S = C/L) // First, find the maximum saturation (saturation S = C/L)
@ -180,7 +178,7 @@ public static class Colorimetry
} }
// https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab (public domain) // https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab (public domain)
// ReSharper disable once MemberCanBePrivate.Global
public static Lab linear_srgb_to_oklab(RGB c) public static Lab linear_srgb_to_oklab(RGB c)
{ {
var l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b; var l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
@ -222,7 +220,6 @@ public static class Colorimetry
/// a and b must be normalized so a^2 + b^2 == 1 /// a and b must be normalized so a^2 + b^2 == 1
/// </summary> /// </summary>
// https://bottosson.github.io/posts/gamutclipping/ (MIT) // https://bottosson.github.io/posts/gamutclipping/ (MIT)
// ReSharper disable once MemberCanBePrivate.Global
public static float compute_max_saturation(float a, float b) public static float compute_max_saturation(float a, float b)
{ {
// Max saturation will be when one of r, g or b goes below zero. // Max saturation will be when one of r, g or b goes below zero.

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UIElements; using UnityEngine.UIElements;
/// <summary> /// <summary>
@ -13,45 +12,24 @@ public class GameManager : MonoBehaviour
/// </summary> /// </summary>
public enum DisplayState public enum DisplayState
{ {
UnassociatedState, // initial state, then we transition to Nothing to initialise the ui
Nothing, Nothing,
GameView, PlayView,
LeaderboardView, LeaderboardView,
AccountView AccountView,
} }
/// <summary> /// <summary>
/// singleton pattern: define instance field for accessing the singleton elsewhere /// singleton pattern: define instance field for accessing the singleton elsewhere
/// </summary> /// </summary>
public static GameManager Instance; public static GameManager Instance;
/// <summary> /// <summary>
/// the current display state of the game /// the current display state of the game
/// </summary> /// </summary>
[SerializeField] private DisplayState state = DisplayState.UnassociatedState; [SerializeField] private DisplayState state = DisplayState.Nothing;
/// <summary> /// <summary>
/// the game object for the ui /// the visual element object for game ui (hud/prompts/tooltips)
/// </summary>
[SerializeField] private GameObject uiGameObject;
// /// <summary>
// /// callback functions to be invoked when the display state changes
// /// </summary>
// private readonly List<Action<DisplayState>> _onDisplayStateChange = new();
/// <summary>
/// callback functions to be invoked when the local player data is updated
/// </summary>
private readonly List<Action<LocalPlayerData>> _onLocalPlayerDataUpdateCallbacks = new();
/// <summary>
/// the local player data object for storing player data
/// </summary>
private LocalPlayerData _data;
/// <summary>
/// the visual element for the ui
/// </summary> /// </summary>
private VisualElement _ui; private VisualElement _ui;
@ -59,6 +37,11 @@ public class GameManager : MonoBehaviour
/// backend object for handling communication with the firebase backend /// backend object for handling communication with the firebase backend
/// </summary> /// </summary>
public Backend Backend; public Backend Backend;
/// <summary>
/// the local player data object for storing player data
/// </summary>
private LocalPlayerData _localPlayerData;
/// <summary> /// <summary>
/// enforces singleton behaviour; sets doesn't destroy on load and checks for multiple instances /// enforces singleton behaviour; sets doesn't destroy on load and checks for multiple instances
@ -78,67 +61,32 @@ public class GameManager : MonoBehaviour
Debug.Log("awake as non-singleton instance, destroying self"); Debug.Log("awake as non-singleton instance, destroying self");
Destroy(gameObject); Destroy(gameObject);
} }
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");
} }
/// <summary>
/// called before the first frame update
/// </summary>
private void Start() private void Start()
{ {
// transition to the initial state SetDisplayState(DisplayState.PlayView);
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;
});
// load the local player data and refresh the ui // load the local player data and refresh the ui
_data = new LocalPlayerData(); _localPlayerData = new LocalPlayerData();
_data.LoadFromTheWorld(data => _localPlayerData.LoadFromTheWorld();
{
foreach (var callback in _onLocalPlayerDataUpdateCallbacks) callback(data);
});
// register a callback to refresh the ui when the player signs in // register a callback to refresh the ui when the player signs in
Backend.RegisterOnSignInCallback(_ => Backend.RegisterOnSignInCallback(_ =>
{ {
Debug.Log("post-auth callback, refreshing player data from the world"); _localPlayerData.LoadFromTheWorld();
_data.LoadFromTheWorld(data =>
{
Debug.Log("firing lpdata update callbacks");
foreach (var callback in _onLocalPlayerDataUpdateCallbacks) callback(data);
});
}); });
} }
/// <summary>
/// called when the game object is enabled
/// </summary>
private void OnEnable()
{
Backend = new Backend();
Backend.Initialise();
}
/// <summary> /// <summary>
/// called when the game object is disabled /// called when the game object is disabled
/// </summary> /// </summary>
@ -147,26 +95,6 @@ public class GameManager : MonoBehaviour
Backend.Cleanup(); Backend.Cleanup();
} }
// /// <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);
// }
/// <summary>
/// function to register a callback for when the local player data is updated
/// </summary>
/// <param name="callback">callback function that takes in a <c>LocalPlayerData</c> object</param>
public void RegisterOnLocalPlayerDataUpdate(Action<LocalPlayerData> callback)
{
_onLocalPlayerDataUpdateCallbacks.Add(callback);
Debug.Log(
$"registering on lpdata update callback, there are now {_onLocalPlayerDataUpdateCallbacks.Count} callbacks");
}
/// <summary> /// <summary>
/// function to show a menu based on the enum passed, /// function to show a menu based on the enum passed,
/// and any other necessary actions /// and any other necessary actions
@ -174,50 +102,7 @@ public class GameManager : MonoBehaviour
/// <param name="newDisplayState">the game menu to show</param> /// <param name="newDisplayState">the game menu to show</param>
public void SetDisplayState(DisplayState newDisplayState) public void SetDisplayState(DisplayState newDisplayState)
{ {
var currentDisplayState = state; var currentDisplayState = St;
// 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");
switch (newDisplayState) switch (newDisplayState)
{
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);
}
} }
} }

View file

@ -4,7 +4,7 @@ MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2
defaultReferences: [] defaultReferences: []
executionOrder: -10 executionOrder: 0
icon: {instanceID: 0} icon: {instanceID: 0}
userData: userData:
assetBundleName: assetBundleName:

View file

@ -1,44 +1,42 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Unity.VisualScripting;
using UnityEngine; using UnityEngine;
public class LocalPlayerData public class LocalPlayerData
{ {
/// <summary>
/// last known email used
/// </summary>
public string LastKnownEmail = "";
/// <summary>
/// last known username used
/// </summary>
public string LastKnownUsername = "Guest";
/// <summary>
/// queue of the 10 most recent local scores
/// </summary>
public Queue<Score> RecentLocalScores = new(10);
/// <summary>
/// queue of the 10 most recent online scores,
/// used in user rating calculation and accuracy display stats
/// </summary>
public Queue<Score> RecentOnlineScores = new(10);
/// <summary> /// <summary>
/// queue of the best online scores, /// queue of the best online scores,
/// used in user rating calculation and accuracy display stats /// used in user rating calculation and accuracy display stats
/// </summary> /// </summary>
public Queue<Score> BestOnlineScores = new(30); public Queue<Score> BestOnlineScores = new(30);
/// <summary>
/// last known email used
/// </summary>
public string LastKnownEmail = "";
/// <summary>
/// last known username used
/// </summary>
public string LastKnownUsername = "Guest";
/// <summary>
/// queue of the 10 most recent local scores
/// </summary>
public Queue<Score> RecentLocalScores = new(10);
/// <summary>
/// queue of the 10 most recent online scores,
/// used in user rating calculation and accuracy display stats
/// </summary>
public Queue<Score> RecentOnlineScores = new(10);
/// <summary> /// <summary>
/// loads player data from player prefs and database /// loads player data from player prefs and database
/// </summary> /// </summary>
public void LoadFromTheWorld(Action<LocalPlayerData> callback) public void LoadFromTheWorld()
{ {
// load user data, possibly from the backend // load user data, possibly from the backend
var possibleUser = GameManager.Instance.Backend.GetUser(); var possibleUser = GameManager.Instance.Backend.GetUser();
var currentKnownEmail = string.Empty; var currentKnownEmail = string.Empty;
@ -48,7 +46,6 @@ public class LocalPlayerData
currentKnownEmail = possibleUser.Email; currentKnownEmail = possibleUser.Email;
currentKnownUsername = GameManager.Instance.Backend.GetUsername(); currentKnownUsername = GameManager.Instance.Backend.GetUsername();
} }
var lastStoredEmail = PlayerPrefs.GetString("LastKnownEmail", ""); var lastStoredEmail = PlayerPrefs.GetString("LastKnownEmail", "");
var lastStoredUsername = PlayerPrefs.GetString("LastKnownUsername", "Guest"); var lastStoredUsername = PlayerPrefs.GetString("LastKnownUsername", "Guest");
LastKnownEmail = string.IsNullOrEmpty(currentKnownEmail) ? lastStoredEmail : currentKnownEmail; LastKnownEmail = string.IsNullOrEmpty(currentKnownEmail) ? lastStoredEmail : currentKnownEmail;
@ -78,24 +75,30 @@ public class LocalPlayerData
// if any of the values are invalid, don't add the score // if any of the values are invalid, don't add the score
if (noOfRounds < 0 || l < 0 || c < 0 || h < 0) continue; if (noOfRounds < 0 || l < 0 || c < 0 || h < 0) continue;
RecentLocalScores.Enqueue(new Score(timestamp, noOfRounds, l, c, h));
}
RegisterLocalScore(new Score(timestamp, noOfRounds, l, c, h));
}
// load online scores // load online scores
RecentOnlineScores.Clear(); RecentOnlineScores.Clear();
GameManager.Instance.Backend.GetRecentScores((_, recentOnlineScores) => GameManager.Instance.Backend.GetRecentScores((dtr, recentOnlineScores) =>
{ {
foreach (var onlineScore in recentOnlineScores) foreach (var onlineScore in recentOnlineScores)
{ {
if (RecentOnlineScores.Count > 10) RecentOnlineScores.Dequeue(); if (RecentOnlineScores.Count > 10) RecentOnlineScores.Dequeue();
RecentOnlineScores.Enqueue(onlineScore); RecentOnlineScores.Enqueue(onlineScore);
} }
Debug.Log("firing late post-load lpdata callback");
callback(this);
}); });
}
Debug.Log("loaded player data from the world");
/// <summary>
/// registers a score to the player's local data
/// </summary>
/// <param name="score">the score to register</param>
public void RegisterLocalScore(Score score)
{
if (RecentLocalScores.Count > 10) RecentLocalScores.Dequeue();
RecentLocalScores.Enqueue(score);
} }
/// <summary> /// <summary>
@ -117,22 +120,10 @@ public class LocalPlayerData
PlayerPrefs.SetFloat($"RecentLocalScores_{idx}_AvgHueAccuracy", score.AvgHueAccuracy); PlayerPrefs.SetFloat($"RecentLocalScores_{idx}_AvgHueAccuracy", score.AvgHueAccuracy);
idx++; idx++;
} }
Debug.Log("saved player data to the world");
// online scores are already saved in the backend // online scores are already saved in the backend
} }
/// <summary>
/// registers a score to the player's local data
/// </summary>
/// <param name="score">the score to register</param>
public void RegisterLocalScore(Score score)
{
if (RecentLocalScores.Count > 10) RecentLocalScores.Dequeue();
RecentLocalScores.Enqueue(score);
}
public struct Score public struct Score
{ {
/// <summary> /// <summary>
@ -178,12 +169,4 @@ public class LocalPlayerData
AvgHueAccuracy = h; AvgHueAccuracy = h;
} }
} }
/// <summary>
/// function to save the player data when the application quits
/// </summary>
private void OnApplicationQuit()
{
SaveToTheWorld();
}
} }

View file

@ -7,55 +7,45 @@ using UnityEngine.UIElements;
public class SideViewUI : MonoBehaviour public class SideViewUI : MonoBehaviour
{ {
/// <summary> /// <summary>
/// button to change to transition to the account view /// settings button for showing the settings menu
/// </summary> /// </summary>
private Button _accountButton; private Button _accountButton;
/// <summary> /// <summary>
/// text that displays the users stable chroma accuracy /// settings button for showing the settings menu
/// </summary> /// </summary>
private Label _chromaAccuracyText; private Label _chromaAccuracyText;
/// <summary> /// <summary>
/// text that displays the connection status to the backend /// settings button for showing the settings menu
/// </summary>
private Label _connectionStatusText;
/// <summary>
/// text that displays the users stable hue accuracy
/// </summary> /// </summary>
private Label _hueAccuracyText; private Label _hueAccuracyText;
/// <summary> /// <summary>
/// button to transition to the leaderboard view /// leaderboard button for showing the leaderboard
/// </summary> /// </summary>
private Button _leaderboardButton; private Button _leaderboardButton;
/// <summary> /// <summary>
/// text that displays the users stable lightness accuracy /// settings button for showing the settings menu
/// </summary> /// </summary>
private Label _lightnessAccuracyText; private Label _lightnessAccuracyText;
/// <summary> /// <summary>
/// button to transition to the game view /// play button for starting the game
/// </summary> /// </summary>
private Button _playButton; private Button _playButton;
/// <summary> /// <summary>
/// text that displays the username /// settings button for showing the settings menu
/// </summary> /// </summary>
private Label _playerNameText; private Label _playerNameText;
/// <summary> /// <summary>
/// text that displays the users' rating /// settings button for showing the settings menu
/// </summary> /// </summary>
private Label _playerRatingText; private Label _playerRatingText;
private void Start()
{
GameManager.Instance.RegisterOnLocalPlayerDataUpdate(RenderFromPlayerData);
}
/// <summary> /// <summary>
/// function to subscribe button events to their respective functions /// function to subscribe button events to their respective functions
/// </summary> /// </summary>
@ -77,15 +67,14 @@ public class SideViewUI : MonoBehaviour
_chromaAccuracyText = ui.Q<Label>("ChromaAccuracyText"); _chromaAccuracyText = ui.Q<Label>("ChromaAccuracyText");
} }
/// <summary> /// <summary>
/// function to update the ui with the latest data /// function to update the ui with the latest data
/// </summary> /// </summary>
/// <param name="localPlayerData">the local player data to render the ui from</param> /// <param name="localPlayerData"></param>
private void RenderFromPlayerData(LocalPlayerData localPlayerData) private void RenderFromPlayerData(LocalPlayerData localPlayerData)
{ {
// calculate averages from both recent local scores and online scores // calculate averages from both recent local scores and online scores
var totalLightness = 0f; var totalLightessAcc = 0f;
var totalChromaAcc = 0f; var totalChromaAcc = 0f;
var totalHueAcc = 0f; var totalHueAcc = 0f;
var totalRounds = 0; var totalRounds = 0;
@ -94,7 +83,7 @@ public class SideViewUI : MonoBehaviour
foreach (var localScore in localPlayerData.RecentLocalScores) foreach (var localScore in localPlayerData.RecentLocalScores)
{ {
totalLightness += localScore.AvgLightnessAccuracy; totalLightessAcc += localScore.AvgLightnessAccuracy;
totalChromaAcc += localScore.AvgChromaAccuracy; totalChromaAcc += localScore.AvgChromaAccuracy;
totalHueAcc += localScore.AvgHueAccuracy; totalHueAcc += localScore.AvgHueAccuracy;
totalRounds += localScore.NoOfRounds; totalRounds += localScore.NoOfRounds;
@ -102,26 +91,26 @@ public class SideViewUI : MonoBehaviour
foreach (var onlineScore in localPlayerData.RecentOnlineScores) foreach (var onlineScore in localPlayerData.RecentOnlineScores)
{ {
totalLightness += onlineScore.AvgLightnessAccuracy; totalLightessAcc += onlineScore.AvgLightnessAccuracy;
totalChromaAcc += onlineScore.AvgChromaAccuracy; totalChromaAcc += onlineScore.AvgChromaAccuracy;
totalHueAcc += onlineScore.AvgHueAccuracy; totalHueAcc += onlineScore.AvgHueAccuracy;
totalRounds += onlineScore.NoOfRounds; totalRounds += onlineScore.NoOfRounds;
} }
foreach (var onlineScore in localPlayerData.BestOnlineScores) foreach (var onlineScore in localPlayerData.BestOnlineScores)
{ {
totalLightness += onlineScore.AvgLightnessAccuracy; totalLightessAcc += onlineScore.AvgLightnessAccuracy;
totalChromaAcc += onlineScore.AvgChromaAccuracy; totalChromaAcc += onlineScore.AvgChromaAccuracy;
totalHueAcc += onlineScore.AvgHueAccuracy; totalHueAcc += onlineScore.AvgHueAccuracy;
totalRounds += onlineScore.NoOfRounds; totalRounds += onlineScore.NoOfRounds;
} }
// finally, set the labels // finally, set the labels
_playerNameText.text = GameManager.Instance.Backend.GetUsername(); _playerNameText.text = GameManager.Instance.Backend.GetUsername();
_lightnessAccuracyText.text = $"{totalLightness / totalRounds:F}"; _lightnessAccuracyText.text = $"{(totalLightessAcc / totalRounds):F}";
_hueAccuracyText.text = $"{totalHueAcc / totalRounds:F}"; _hueAccuracyText.text = $"{(totalHueAcc / totalRounds):F}";
_chromaAccuracyText.text = $"{totalChromaAcc / totalRounds:F}"; _chromaAccuracyText.text = $"{(totalChromaAcc / totalRounds):F}";
// and set the player rating, but after we get it from the backend // and set the player rating, but after we get it from the backend
// (god I LOVE async (I am LYING out of my teeth)) // (god I LOVE async (I am LYING out of my teeth))
GameManager.Instance.Backend.CalculateUserRating((dtr, rating) => GameManager.Instance.Backend.CalculateUserRating((dtr, rating) =>
@ -136,7 +125,7 @@ public class SideViewUI : MonoBehaviour
/// </summary> /// </summary>
private static void OnPlayButtonClicked() private static void OnPlayButtonClicked()
{ {
GameManager.Instance.SetDisplayState(GameManager.DisplayState.GameView); GameManager.Instance.SetDisplayState(GameManager.DisplayState.PlayView);
} }
/// <summary> /// <summary>

View file

@ -25,7 +25,7 @@
<ui:Button text="Account ↗" parse-escape-sequences="true" display-tooltip-when-elided="true" <ui:Button text="Account ↗" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="AccountButton"/> name="AccountButton"/>
<ui:VisualElement name="AccountSection" <ui:VisualElement name="AccountSection"
style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; border-top-width: 1px; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); padding-bottom: 12px;"> style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; border-top-width: 1px; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); border-bottom-width: 1px; padding-bottom: 12px;">
<ui:VisualElement name="PlayerDetails" <ui:VisualElement name="PlayerDetails"
style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch;"> style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch;">
<ui:VisualElement name="PlayerNameDetail" style="flex-grow: 1;"> <ui:VisualElement name="PlayerNameDetail" style="flex-grow: 1;">
@ -73,12 +73,6 @@
</ui:VisualElement> </ui:VisualElement>
</ui:VisualElement> </ui:VisualElement>
</ui:VisualElement> </ui:VisualElement>
<ui:VisualElement name="ConnectionStatusText"
style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; border-top-width: 1px; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); border-bottom-width: 1px; padding-bottom: 12px;">
<ui:Label tabindex="-1" text="Status: Unknown" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ConnectionStatusText"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left;"/>
</ui:VisualElement>
</ui:VisualElement> </ui:VisualElement>
</ui:VisualElement> </ui:VisualElement>
<ui:VisualElement name="MainView" <ui:VisualElement name="MainView"
@ -119,14 +113,16 @@
</ui:VisualElement> </ui:VisualElement>
</ui:VisualElement> </ui:VisualElement>
<ui:VisualElement name="LeaderboardView" <ui:VisualElement name="LeaderboardView"
style="flex-grow: 1; display: flex; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;"> style="flex-grow: 1; display: none; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
<ui:Label tabindex="-1" text="Leaderboard" parse-escape-sequences="true" <ui:VisualElement name="LeaderboardHeader" style="flex-grow: 0;">
display-tooltip-when-elided="true" name="RoundText" <ui:Label tabindex="-1" text="Leaderboard" parse-escape-sequences="true"
style="font-size: 58px; -unity-font-style: normal;"/> display-tooltip-when-elided="true" name="RoundText"
style="font-size: 58px; -unity-font-style: bold;"/>
</ui:VisualElement>
<ui:ListView name="LeaderboardListView"/> <ui:ListView name="LeaderboardListView"/>
</ui:VisualElement> </ui:VisualElement>
<ui:VisualElement name="AccountView" <ui:VisualElement name="AccountView"
style="flex-grow: 1; display: none; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;"> style="flex-grow: 1; display: flex; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
<ui:Label tabindex="-1" text="You are not signed in." parse-escape-sequences="true" <ui:Label tabindex="-1" text="You are not signed in." parse-escape-sequences="true"
display-tooltip-when-elided="true" name="AccountHeader" display-tooltip-when-elided="true" name="AccountHeader"
style="font-size: 58px; -unity-font-style: normal;"/> style="font-size: 58px; -unity-font-style: normal;"/>

View file

@ -4,8 +4,5 @@
EditorBuildSettings: EditorBuildSettings:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
serializedVersion: 2 serializedVersion: 2
m_Scenes: m_Scenes: []
- enabled: 1
path: Assets/Scenes/GameScene.unity
guid: 9fc0d4010bbf28b4594072e72b8655ab
m_configObjects: {} m_configObjects: {}

View file

@ -161,8 +161,7 @@ PlayerSettings:
resetResolutionOnWindowResize: 0 resetResolutionOnWindowResize: 0
androidSupportedAspectRatio: 1 androidSupportedAspectRatio: 1
androidMaxAspectRatio: 2.1 androidMaxAspectRatio: 2.1
applicationIdentifier: applicationIdentifier: {}
Standalone: com.DefaultCompany.ColourMeOKLABGame
buildNumber: buildNumber:
Standalone: 0 Standalone: 0
VisionOS: 0 VisionOS: 0