diff --git a/ColourMeOKGame/Assets/Scenes/GameScene.unity b/ColourMeOKGame/Assets/Scenes/GameScene.unity index 486551b..1298f4d 100644 --- a/ColourMeOKGame/Assets/Scenes/GameScene.unity +++ b/ColourMeOKGame/Assets/Scenes/GameScene.unity @@ -132,6 +132,7 @@ GameObject: m_Component: - component: {fileID: 133964672} - component: {fileID: 133964671} + - component: {fileID: 133964676} - component: {fileID: 133964673} - component: {fileID: 133964674} - component: {fileID: 133964675} @@ -213,6 +214,19 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 6351b7620d84e2d43bc4f59c5f3f8b5c, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!114 &133964676 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 133964670} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ac64716d16f44bd596798277ddc5eaaf, type: 3} + m_Name: + m_EditorClassIdentifier: + state: 0 --- !u!1 &447905425 GameObject: m_ObjectHideFlags: 0 @@ -459,6 +473,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 0cfc0b50e9003a0468ebdc186439c53b, type: 3} m_Name: m_EditorClassIdentifier: + ui: {fileID: 0} --- !u!4 &1204483826 Transform: m_ObjectHideFlags: 0 diff --git a/ColourMeOKGame/Assets/Scripts/AccountUI.cs b/ColourMeOKGame/Assets/Scripts/AccountUI.cs index 977f0ff..7e28a50 100644 --- a/ColourMeOKGame/Assets/Scripts/AccountUI.cs +++ b/ColourMeOKGame/Assets/Scripts/AccountUI.cs @@ -116,6 +116,10 @@ public void OnEnable() _secondaryActionButton.clicked += OnSecondaryActionButtonClick; TransitionStateTo(State.NotSignedIn); + } + + private void Awake() + { GameManager.Instance.Backend.RegisterOnSignInCallback(OnSignInCallback); } @@ -216,12 +220,12 @@ private void TransitionStateTo(State newState, bool keepAccompanyingText = false state = newState; } - private void ValidateUsername() + private void ValidateUsername(TextField usernameField) { // just has to be min. 5 characters - _usernameField.style.color = _defaultInputFieldValueTextColour; - if (_usernameField.value.Length >= 5) return; - _usernameField.style.color = _errorInputFieldValueTextColour; + usernameField.style.color = _defaultInputFieldValueTextColour; + if (usernameField.value.Length >= 5) return; + usernameField.style.color = _errorInputFieldValueTextColour; throw new Exception("Username must be at least 5 characters long."); } @@ -279,18 +283,18 @@ private void ValidateFields(TextField emailField, TextField passwordField) try { - ValidateEmailField(_emailField); + ValidateEmailField(emailField); } - catch (Exception _) + catch (Exception) { invalidEmail = true; } try { - ValidatePasswordField(_passwordField); + ValidatePasswordField(passwordField); } - catch (Exception _) + catch (Exception) { invalidPassword = true; } @@ -321,27 +325,27 @@ private void ValidateFields(TextField emailField, TextField passwordField, TextF try { - ValidateEmailField(_emailField); + ValidateEmailField(emailField); } - catch (Exception _) + catch (Exception) { invalidEmail = true; } try { - ValidatePasswordField(_passwordField); + ValidatePasswordField(passwordField); } - catch (Exception _) + catch (Exception) { invalidPassword = true; } try { - ValidateUsername(); + ValidateUsername(usernameField); } - catch (Exception _) + catch (Exception) { invalidUsername = true; } @@ -369,7 +373,7 @@ private void OnUsernameUpdateButtonClick() { try { - ValidateUsername(); + ValidateUsername(_usernameField); } catch (Exception e) { diff --git a/ColourMeOKGame/Assets/Scripts/Colorimetry.cs b/ColourMeOKGame/Assets/Scripts/Colorimetry.cs index 72c98b6..bf5ce52 100644 --- a/ColourMeOKGame/Assets/Scripts/Colorimetry.cs +++ b/ColourMeOKGame/Assets/Scripts/Colorimetry.cs @@ -62,6 +62,7 @@ public static RGB gamut_clip_preserve_chroma(RGB rgb) /// a and b must be normalized so a^2 + b^2 == 1 /// // https://bottosson.github.io/posts/gamutclipping/ (MIT) + // ReSharper disable once MemberCanBePrivate.Global public static float find_gamut_intersection( float a, float b, @@ -164,6 +165,7 @@ public static RGB gamut_clip_preserve_chroma(RGB rgb) /// a and b must be normalized so a^2 + b^2 == 1 /// // https://bottosson.github.io/posts/gamutclipping/ (MIT) + // ReSharper disable once MemberCanBePrivate.Global public static LC find_cusp(float a, float b) { // First, find the maximum saturation (saturation S = C/L) @@ -178,7 +180,7 @@ public static LC find_cusp(float a, float b) } // 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) { var l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b; @@ -220,6 +222,7 @@ public static RGB oklab_to_linear_srgb(Lab c) /// a and b must be normalized so a^2 + b^2 == 1 /// // https://bottosson.github.io/posts/gamutclipping/ (MIT) + // ReSharper disable once MemberCanBePrivate.Global public static float compute_max_saturation(float a, float b) { // Max saturation will be when one of r, g or b goes below zero. diff --git a/ColourMeOKGame/Assets/Scripts/GameManager.cs b/ColourMeOKGame/Assets/Scripts/GameManager.cs index 6e4c080..c58f431 100644 --- a/ColourMeOKGame/Assets/Scripts/GameManager.cs +++ b/ColourMeOKGame/Assets/Scripts/GameManager.cs @@ -1,47 +1,29 @@ using UnityEngine; -using UnityEngine.Serialization; -using UnityEngine.UIElements; /// /// singleton for a single source of truth game state and flow management /// public class GameManager : MonoBehaviour { - /// - /// enum for available menus in the game, for use with ShowMenu() - /// - public enum DisplayState - { - Nothing, - PlayView, - LeaderboardView, - AccountView, - } - /// /// singleton pattern: define instance field for accessing the singleton elsewhere /// public static GameManager Instance; - - /// - /// the current display state of the game - /// - [SerializeField] private DisplayState state = DisplayState.Nothing; /// - /// the visual element object for game ui (hud/prompts/tooltips) + /// the local player data object for storing player data /// - private VisualElement _ui; + private LocalPlayerData _localPlayerData; /// /// backend object for handling communication with the firebase backend /// public Backend Backend; - + /// - /// the local player data object for storing player data + /// ui manager object for handling ui state and flow /// - private LocalPlayerData _localPlayerData; + public UIManager ui; /// /// enforces singleton behaviour; sets doesn't destroy on load and checks for multiple instances @@ -61,30 +43,27 @@ private void Awake() Debug.Log("awake as non-singleton instance, destroying self"); Destroy(gameObject); } - } - private void Start() - { - SetDisplayState(DisplayState.PlayView); - - // load the local player data and refresh the ui - _localPlayerData = new LocalPlayerData(); - _localPlayerData.LoadFromTheWorld(); - - // register a callback to refresh the ui when the player signs in - Backend.RegisterOnSignInCallback(_ => - { - _localPlayerData.LoadFromTheWorld(); - }); } /// - /// called when the game object is enabled + /// called when the game object is enabled, initialises variables /// private void OnEnable() { Backend = new Backend(); Backend.Initialise(); + ui = UIManager.Instance; + } + + private void Start() + { + // load the local player data and refresh the ui + _localPlayerData = new LocalPlayerData(); + _localPlayerData.LoadFromTheWorld(); + + // register a callback to refresh the ui when the player signs in + Backend.RegisterOnSignInCallback(_ => { _localPlayerData.LoadFromTheWorld(); }); } /// @@ -94,13 +73,4 @@ private void OnDestroy() { Backend.Cleanup(); } - - /// - /// function to show a menu based on the enum passed, - /// and any other necessary actions - /// - /// the game menu to show - public void SetDisplayState(DisplayState newDisplayState) - { - } } \ No newline at end of file diff --git a/ColourMeOKGame/Assets/Scripts/GameManager.cs.meta b/ColourMeOKGame/Assets/Scripts/GameManager.cs.meta index 100b3aa..04fbdcd 100644 --- a/ColourMeOKGame/Assets/Scripts/GameManager.cs.meta +++ b/ColourMeOKGame/Assets/Scripts/GameManager.cs.meta @@ -4,7 +4,7 @@ MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] - executionOrder: 0 + executionOrder: -10 icon: {instanceID: 0} userData: assetBundleName: diff --git a/ColourMeOKGame/Assets/Scripts/LocalPlayerData.cs b/ColourMeOKGame/Assets/Scripts/LocalPlayerData.cs index dc6a8e7..7d44cca 100644 --- a/ColourMeOKGame/Assets/Scripts/LocalPlayerData.cs +++ b/ColourMeOKGame/Assets/Scripts/LocalPlayerData.cs @@ -5,32 +5,32 @@ public class LocalPlayerData { + /// + /// queue of the best online scores, + /// used in user rating calculation and accuracy display stats + /// + public Queue BestOnlineScores = new(30); + /// /// last known email used /// public string LastKnownEmail = ""; - + /// /// last known username used /// public string LastKnownUsername = "Guest"; - + /// /// queue of the 10 most recent local scores /// public Queue RecentLocalScores = new(10); - + /// /// queue of the 10 most recent online scores, /// used in user rating calculation and accuracy display stats /// public Queue RecentOnlineScores = new(10); - - /// - /// queue of the best online scores, - /// used in user rating calculation and accuracy display stats - /// - public Queue BestOnlineScores = new(30); /// /// loads player data from player prefs and database @@ -46,6 +46,7 @@ public void LoadFromTheWorld() currentKnownEmail = possibleUser.Email; currentKnownUsername = GameManager.Instance.Backend.GetUsername(); } + var lastStoredEmail = PlayerPrefs.GetString("LastKnownEmail", ""); var lastStoredUsername = PlayerPrefs.GetString("LastKnownUsername", "Guest"); LastKnownEmail = string.IsNullOrEmpty(currentKnownEmail) ? lastStoredEmail : currentKnownEmail; @@ -78,27 +79,19 @@ public void LoadFromTheWorld() RegisterLocalScore(new Score(timestamp, noOfRounds, l, c, h)); } - + // load online scores RecentOnlineScores.Clear(); - GameManager.Instance.Backend.GetRecentScores((dtr, recentOnlineScores) => + GameManager.Instance.Backend.GetRecentScores((_, recentOnlineScores) => { foreach (var onlineScore in recentOnlineScores) { if (RecentOnlineScores.Count > 10) RecentOnlineScores.Dequeue(); RecentOnlineScores.Enqueue(onlineScore); } - }); - } - /// - /// registers a score to the player's local data - /// - /// the score to register - public void RegisterLocalScore(Score score) - { - if (RecentLocalScores.Count > 10) RecentLocalScores.Dequeue(); - RecentLocalScores.Enqueue(score); + Debug.Log("loaded online scores from backend"); + }); } /// @@ -121,9 +114,21 @@ public void SaveToTheWorld() idx++; } + Debug.Log("saved lpdata to playerprefs"); // online scores are already saved in the backend } + /// + /// registers a score to the player's local data + /// + /// the score to register + public void RegisterLocalScore(Score score) + { + if (RecentLocalScores.Count > 10) RecentLocalScores.Dequeue(); + RecentLocalScores.Enqueue(score); + } + + public struct Score { /// diff --git a/ColourMeOKGame/Assets/Scripts/SideViewUI.cs b/ColourMeOKGame/Assets/Scripts/SideViewUI.cs index 5e27f07..f662a40 100644 --- a/ColourMeOKGame/Assets/Scripts/SideViewUI.cs +++ b/ColourMeOKGame/Assets/Scripts/SideViewUI.cs @@ -74,7 +74,7 @@ private void OnEnable() private void RenderFromPlayerData(LocalPlayerData localPlayerData) { // calculate averages from both recent local scores and online scores - var totalLightessAcc = 0f; + var totalLightnessAcc = 0f; var totalChromaAcc = 0f; var totalHueAcc = 0f; var totalRounds = 0; @@ -83,7 +83,7 @@ private void RenderFromPlayerData(LocalPlayerData localPlayerData) foreach (var localScore in localPlayerData.RecentLocalScores) { - totalLightessAcc += localScore.AvgLightnessAccuracy; + totalLightnessAcc += localScore.AvgLightnessAccuracy; totalChromaAcc += localScore.AvgChromaAccuracy; totalHueAcc += localScore.AvgHueAccuracy; totalRounds += localScore.NoOfRounds; @@ -91,26 +91,26 @@ private void RenderFromPlayerData(LocalPlayerData localPlayerData) foreach (var onlineScore in localPlayerData.RecentOnlineScores) { - totalLightessAcc += onlineScore.AvgLightnessAccuracy; + totalLightnessAcc += onlineScore.AvgLightnessAccuracy; totalChromaAcc += onlineScore.AvgChromaAccuracy; totalHueAcc += onlineScore.AvgHueAccuracy; totalRounds += onlineScore.NoOfRounds; } - + foreach (var onlineScore in localPlayerData.BestOnlineScores) { - totalLightessAcc += onlineScore.AvgLightnessAccuracy; + totalLightnessAcc += onlineScore.AvgLightnessAccuracy; totalChromaAcc += onlineScore.AvgChromaAccuracy; totalHueAcc += onlineScore.AvgHueAccuracy; totalRounds += onlineScore.NoOfRounds; } - + // finally, set the labels _playerNameText.text = GameManager.Instance.Backend.GetUsername(); - _lightnessAccuracyText.text = $"{(totalLightessAcc / totalRounds):F}"; - _hueAccuracyText.text = $"{(totalHueAcc / totalRounds):F}"; - _chromaAccuracyText.text = $"{(totalChromaAcc / totalRounds):F}"; - + _lightnessAccuracyText.text = $"{totalLightnessAcc / totalRounds:F}"; + _hueAccuracyText.text = $"{totalHueAcc / totalRounds:F}"; + _chromaAccuracyText.text = $"{totalChromaAcc / totalRounds:F}"; + // and set the player rating, but after we get it from the backend // (god I LOVE async (I am LYING out of my teeth)) GameManager.Instance.Backend.CalculateUserRating((dtr, rating) => @@ -125,7 +125,7 @@ private void RenderFromPlayerData(LocalPlayerData localPlayerData) /// private static void OnPlayButtonClicked() { - GameManager.Instance.SetDisplayState(GameManager.DisplayState.PlayView); + GameManager.Instance.ui.SetDisplayState(UIManager.DisplayState.PlayView); } /// @@ -133,7 +133,7 @@ private static void OnPlayButtonClicked() /// private static void OnLeaderboardButtonClicked() { - GameManager.Instance.SetDisplayState(GameManager.DisplayState.LeaderboardView); + GameManager.Instance.ui.SetDisplayState(UIManager.DisplayState.LeaderboardView); } /// @@ -141,6 +141,6 @@ private static void OnLeaderboardButtonClicked() /// private static void OnAccountButtonClicked() { - GameManager.Instance.SetDisplayState(GameManager.DisplayState.AccountView); + GameManager.Instance.ui.SetDisplayState(UIManager.DisplayState.AccountView); } } \ No newline at end of file diff --git a/ColourMeOKGame/Assets/Scripts/UIManager.cs b/ColourMeOKGame/Assets/Scripts/UIManager.cs new file mode 100644 index 0000000..a2e4d2b --- /dev/null +++ b/ColourMeOKGame/Assets/Scripts/UIManager.cs @@ -0,0 +1,113 @@ +using System; +using UnityEngine; +using UnityEngine.UIElements; + +public class UIManager : MonoBehaviour +{ + /// + /// singleton pattern: define instance field for accessing the singleton elsewhere + /// + public static UIManager Instance; + + /// + /// enum for available menus in the game, for use with ShowMenu() + /// + public enum DisplayState + { + UnassociatedState, + Nothing, + PlayView, + LeaderboardView, + AccountView + } + + /// + /// the current display state of the game + /// + [SerializeField] private DisplayState state = DisplayState.UnassociatedState; + + /// + /// the visual element object for game ui + /// + public VisualElement UI; + + /// + /// enforces singleton behaviour; sets doesn't destroy on load and checks for multiple instances + /// + 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); + } + } + + private void OnEnable() + { + UI = GetComponent().rootVisualElement; + SetDisplayState(DisplayState.Nothing); + } + + /// + /// function to show a menu based on the enum passed, + /// and any other necessary actions + /// + /// the game menu to show + public void SetDisplayState(DisplayState newDisplayState) + { + 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("GameView"); + var leaderboardView = UI.Q("LeaderboardView"); + var accountView = UI.Q("AccountView"); + + switch (newDisplayState) + { + case DisplayState.Nothing: + gameView.style.display = DisplayStyle.None; + leaderboardView.style.display = DisplayStyle.None; + accountView.style.display = DisplayStyle.None; + break; + + case DisplayState.PlayView: + 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); + } + } +} \ No newline at end of file diff --git a/ColourMeOKGame/Assets/Scripts/UIManager.cs.meta b/ColourMeOKGame/Assets/Scripts/UIManager.cs.meta new file mode 100644 index 0000000..b9d6172 --- /dev/null +++ b/ColourMeOKGame/Assets/Scripts/UIManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ac64716d16f44bd596798277ddc5eaaf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: -15 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/ColourMeOKGame/Assets/UI/GameUI.uxml b/ColourMeOKGame/Assets/UI/GameUI.uxml index d080856..7245dbc 100644 --- a/ColourMeOKGame/Assets/UI/GameUI.uxml +++ b/ColourMeOKGame/Assets/UI/GameUI.uxml @@ -1,162 +1,92 @@ - -