From cc79a5b1a0b5646d92b9dd814d0953d95c3a5eaf Mon Sep 17 00:00:00 2001 From: Mark Joshwel Date: Mon, 18 Nov 2024 09:16:03 +0800 Subject: [PATCH] game: gameplay + database interim --- ColourMeOKGame/Assets/Scenes/GameScene.unity | 38 ++- ColourMeOKGame/Assets/Scripts/AccountUI.cs | 4 +- ColourMeOKGame/Assets/Scripts/Backend.cs | 77 ++++- ColourMeOKGame/Assets/Scripts/Colorimetry.cs | 94 +++++- ColourMeOKGame/Assets/Scripts/GameManager.cs | 125 +++++++- ColourMeOKGame/Assets/Scripts/Gameplay.cs | 153 ++++++++++ .../Assets/Scripts/Gameplay.cs.meta | 11 + .../Assets/Scripts/LocalPlayerData.cs | 112 +++++++- ColourMeOKGame/Assets/Scripts/UIManager.cs | 20 +- ColourMeOKGame/Assets/UI/GameUI.uxml | 270 +++++++++++++----- 10 files changed, 789 insertions(+), 115 deletions(-) create mode 100644 ColourMeOKGame/Assets/Scripts/Gameplay.cs create mode 100644 ColourMeOKGame/Assets/Scripts/Gameplay.cs.meta diff --git a/ColourMeOKGame/Assets/Scenes/GameScene.unity b/ColourMeOKGame/Assets/Scenes/GameScene.unity index 87fe1f5..c80fb31 100644 --- a/ColourMeOKGame/Assets/Scenes/GameScene.unity +++ b/ColourMeOKGame/Assets/Scenes/GameScene.unity @@ -143,7 +143,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 1 + m_IsActive: 0 --- !u!114 &133964671 MonoBehaviour: m_ObjectHideFlags: 0 @@ -258,7 +258,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 1 + m_IsActive: 0 --- !u!4 &447905427 Transform: m_ObjectHideFlags: 0 @@ -476,7 +476,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 1 + m_IsActive: 0 --- !u!114 &1204483825 MonoBehaviour: m_ObjectHideFlags: 0 @@ -505,6 +505,37 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1680304394 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1680304395} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1680304395 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1680304394} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 @@ -514,3 +545,4 @@ SceneRoots: - {fileID: 133964672} - {fileID: 447905427} - {fileID: 1204483826} + - {fileID: 1680304395} diff --git a/ColourMeOKGame/Assets/Scripts/AccountUI.cs b/ColourMeOKGame/Assets/Scripts/AccountUI.cs index 1b737a0..98a9e85 100644 --- a/ColourMeOKGame/Assets/Scripts/AccountUI.cs +++ b/ColourMeOKGame/Assets/Scripts/AccountUI.cs @@ -18,12 +18,12 @@ public class AccountUI : MonoBehaviour /// /// default text colour /// - private readonly Color _defaultInputFieldValueTextColour = new(5.88f, 5.1f, 10.59f); + private readonly StyleColor _defaultInputFieldValueTextColour = new(new Color(0.0588f, 0.051f, 0.1059f)); /// /// error text colour /// - private readonly Color _errorInputFieldValueTextColour = new(1f, 50.59f, 50.2f); + private readonly StyleColor _errorInputFieldValueTextColour = new Color(1f, 0.5059f, 0.502f); /// /// accompanying text for the account input fields, used when an error/notice is needed diff --git a/ColourMeOKGame/Assets/Scripts/Backend.cs b/ColourMeOKGame/Assets/Scripts/Backend.cs index 0f2431d..cc99b08 100644 --- a/ColourMeOKGame/Assets/Scripts/Backend.cs +++ b/ColourMeOKGame/Assets/Scripts/Backend.cs @@ -175,7 +175,7 @@ public void Cleanup() _auth.StateChanged -= AuthStateChanged; _auth = null; } - + /// /// function to register a callback for when the user signs in /// @@ -195,7 +195,7 @@ public void RegisterOnSignOutCallback(Action callback) _onSignOutCallbacks.Add(callback); Debug.Log($"registering OnSignOutCallback ({_onSignOutCallbacks.Count})"); } - + /// /// function to register a callback for when the connection status changes /// @@ -205,7 +205,7 @@ public void RegisterOnConnectionStatusChangedCallback(Action /// function to fire all on sign in callbacks /// @@ -239,7 +239,7 @@ private void FireOnSignOutCallbacks() Debug.LogError($"error invoking OnSignOutCallback: {e.Message}"); } } - + /// /// function to fire all on connection status changed callbacks /// @@ -519,6 +519,19 @@ public void GetRecentScores(Action(0)); } + /// + /// abstraction function to get the user's best scores from the database + /// + /// + /// callback function that takes in a DatabaseTransactionResult enum and a + /// List<LocalPlayerData.Score> + /// + public void GetBestScores(Action> callback) + { + // TODO: implement this + callback(DatabaseTransactionResult.Error, new List(0)); + } + /// /// abstraction function to submit a score to the database /// @@ -528,7 +541,29 @@ public void GetRecentScores(Action callback) { - throw new NotImplementedException(); + if (!Status.Equals(FirebaseConnectionStatus.Connected)) return; + + if (_user == null) + { + callback(DatabaseTransactionResult.Unauthenticated); + return; + } + + _db.Child("scores") + .Push() + .SetValueAsync(score.ToDictionary()) + .ContinueWithOnMainThread(task => + { + if (task.IsCompletedSuccessfully) + { + callback(DatabaseTransactionResult.Ok); + } + else + { + Debug.LogError(task.Exception); + callback(DatabaseTransactionResult.Error); + } + }); } /// @@ -542,16 +577,42 @@ public void GetRecentScores(Action callback) { - throw new NotImplementedException(); + GetRecentScores((recentRes, recentScores) => + { + if (recentRes == DatabaseTransactionResult.Error) + { + Debug.Log("failed to get recent scores"); + callback(DatabaseTransactionResult.Error, 0f); + return; + } + + var recentScoreQueue = GameManager.Instance.Data.RecentOnlineScores; + foreach (var score in recentScores) recentScoreQueue.Enqueue(score); + while (recentScoreQueue.Count > LocalPlayerData.MaxBestOnlineScores) recentScoreQueue.Dequeue(); + + GetBestScores((bestRes, bestScores) => + { + if (bestRes == DatabaseTransactionResult.Error) + { + Debug.Log("failed to get recent scores"); + callback(DatabaseTransactionResult.Error, 0f); + return; + } + + var bestScoreQueue = GameManager.Instance.Data.BestOnlineScores; + foreach (var score in recentScores) bestScoreQueue.Enqueue(score); + while (bestScoreQueue.Count > LocalPlayerData.MaxBestOnlineScores) bestScoreQueue.Dequeue(); + + callback(DatabaseTransactionResult.Ok, GameManager.Instance.Data.CalculateUserRating()); + }); + }); } /// /// abstraction function to update the user's rating in the database /// - /// new user rating value as a float /// callback function that takes in a DatabaseTransactionResult enum public void UpdateUserRating( - float newRating, Action callback) { throw new NotImplementedException(); diff --git a/ColourMeOKGame/Assets/Scripts/Colorimetry.cs b/ColourMeOKGame/Assets/Scripts/Colorimetry.cs index 8b19f80..2819747 100644 --- a/ColourMeOKGame/Assets/Scripts/Colorimetry.cs +++ b/ColourMeOKGame/Assets/Scripts/Colorimetry.cs @@ -3,10 +3,58 @@ public static class Colorimetry { + /// + /// calculate a 0-100% distance/accuracy between two unity rgba colour objects + /// + /// the template colour to compare against + /// the response colour to compare + /// a DeltaLabCHE struct + public static DeltaLabCHE CalculateDistance(Color template, Color response) + { + // rgb to oklab + var templateOklab = linear_srgb_to_oklab(new RGB( + (float)srgb_nonlinear_transform_f_inv(template.r), + (float)srgb_nonlinear_transform_f_inv(template.g), + (float)srgb_nonlinear_transform_f_inv(template.b))); + + var responseOklab = linear_srgb_to_oklab(new RGB( + (float)srgb_nonlinear_transform_f_inv(response.r), + (float)srgb_nonlinear_transform_f_inv(response.g), + (float)srgb_nonlinear_transform_f_inv(response.b))); + + // https://en.wikipedia.org/wiki/Oklab_color_space#Color_differences + // ... "The perceptual color difference in Oklab is calculated as the Euclidean + // ... distance between the (L, a, b) coordinates." + // https://github.com/svgeesus/svgeesus.github.io/blob/master/Color/OKLab-notes.md#color-difference-metric + // ... ΔL = L1 - L2 + // ... C1 = √(a1² + b1²) -> chroma values + // ... C2 = √(a2² + b2²) -> chroma values + // ... ΔC = C1 - C2 -> chroma difference + // ... Δa = a1 - a2 + // ... Δb = b1 - b2 + // ... ΔH = √(Δa² + Δb² - ΔC²) -> hue difference + // ... ΔE = √(ΔL² + ΔC² + ΔH²) -> final difference + + float l1, a1, b1, l2, a2, b2; + (l1, a1, b1) = (templateOklab.L, templateOklab.a, templateOklab.b); + (l2, a2, b2) = (responseOklab.L, responseOklab.a, responseOklab.b); + + var deltaL = l1 - l2; + var c1 = Math.Sqrt(a1 * a1 + b1 * b1); + var c2 = Math.Sqrt(a2 * a2 + b2 * b2); + var deltaC = c1 - c2; + var deltaA = a1 - a2; + var deltaB = b1 - b2; + var deltaH = Math.Max(0d, Math.Sqrt(deltaA * deltaA + deltaB * deltaB - deltaC * deltaC)); + var deltaE = Math.Sqrt(deltaL * deltaL + deltaC * deltaC + deltaH * deltaH); + + return new DeltaLabCHE(deltaL, deltaA, deltaB, deltaC, deltaH, deltaE); + } + /// /// convert the oklch colour to a unity rgba colour object /// - /// a unity rgba color object + /// a unity rgba Color object public static Color RawLchToColor(double lightness, double chroma, double hue) { // clamp values @@ -55,7 +103,7 @@ public static double srgb_nonlinear_transform_f_inv(double x) } /// - /// clips a color to the sRGB gamut while preserving chroma + /// clips a colour to the sRGB gamut while preserving chroma /// // https://bottosson.github.io/posts/gamutclipping/ (MIT) public static RGB gamut_clip_preserve_chroma(RGB rgb) @@ -331,6 +379,48 @@ public static float compute_max_saturation(float a, float b) return maxSaturation; } + // ReSharper disable once InconsistentNaming + public struct DeltaLabCHE + { + // ReSharper disable once InconsistentNaming + public double dL; + + // ReSharper disable once InconsistentNaming + public double da; + + // ReSharper disable once InconsistentNaming + public double db; + + // ReSharper disable once InconsistentNaming + public double dC; + + // ReSharper disable once InconsistentNaming + public double dH; + + // ReSharper disable once InconsistentNaming + public double dE; + + public DeltaLabCHE( + // ReSharper disable once InconsistentNaming + double L, + double a, + double b, + // ReSharper disable once InconsistentNaming + double C, + // ReSharper disable once InconsistentNaming + double H, + // ReSharper disable once InconsistentNaming + double E) + { + dL = L; + da = a; + db = b; + dC = C; + dH = H; + dE = E; + } + } + public readonly struct Lab { public readonly float L; diff --git a/ColourMeOKGame/Assets/Scripts/GameManager.cs b/ColourMeOKGame/Assets/Scripts/GameManager.cs index 5518b5c..d702e8a 100644 --- a/ColourMeOKGame/Assets/Scripts/GameManager.cs +++ b/ColourMeOKGame/Assets/Scripts/GameManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using UnityEngine; using UnityEngine.UIElements; @@ -34,7 +35,7 @@ public class GameManager : MonoBehaviour public Backend Backend; /// - /// read-only property for accessing the local player data outside of this class + /// read-only property for accessing the local player data outside this class /// public LocalPlayerData Data => _data; @@ -107,16 +108,16 @@ private void OnEnable() Debug.Log("sign in callback, refreshing GameManager-controlled SideView UI"); _data.LoadFromTheWorld(FireLocalPlayerDataChangeCallbacks); }); - + Backend.RegisterOnConnectionStatusChangedCallback(status => { Debug.Log($"post-fcStatus change, deciding to show/hide buttons based on new status: {status}"); ui.UI.Q