game: interim
This commit is contained in:
parent
0d57d2b3a9
commit
c5402178f0
|
@ -134,6 +134,7 @@ GameObject:
|
||||||
- component: {fileID: 133964671}
|
- component: {fileID: 133964671}
|
||||||
- component: {fileID: 133964673}
|
- component: {fileID: 133964673}
|
||||||
- component: {fileID: 133964674}
|
- component: {fileID: 133964674}
|
||||||
|
- component: {fileID: 133964675}
|
||||||
m_Layer: 5
|
m_Layer: 5
|
||||||
m_Name: UI
|
m_Name: UI
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
|
@ -200,6 +201,18 @@ MonoBehaviour:
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
state: 0
|
state: 0
|
||||||
|
--- !u!114 &133964675
|
||||||
|
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: 6351b7620d84e2d43bc4f59c5f3f8b5c, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
--- !u!1 &447905425
|
--- !u!1 &447905425
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|
|
@ -1,82 +1,90 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Mail;
|
using System.Net.Mail;
|
||||||
|
using Firebase.Auth;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Assertions;
|
using UnityEngine.Assertions;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
|
using Button = UnityEngine.UIElements.Button;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// class to handle the account view ui
|
/// class to handle the account view ui
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AccountUI : MonoBehaviour
|
public class AccountUI : MonoBehaviour
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// state of the account view
|
|
||||||
/// </summary>
|
|
||||||
private enum State
|
|
||||||
{
|
|
||||||
UnassociatedState, // (start)
|
|
||||||
NotSignedIn, // initial
|
|
||||||
AfterContinue, // after
|
|
||||||
SignedIn // post
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// current state of the account view
|
/// current state of the account view
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeField] private State state = State.UnassociatedState;
|
[SerializeField] private State state = State.UnassociatedState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// account view header text
|
/// default text colour
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Label _header;
|
private readonly Color _defaultInputFieldValueTextColour = new(5.88f, 5.1f, 10.59f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// username text field
|
/// error text colour
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private TextField _usernameField;
|
private readonly Color _errorInputFieldValueTextColour = new(1f, 50.59f, 50.2f);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// email text field
|
|
||||||
/// </summary>
|
|
||||||
private TextField _emailField;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// password text field
|
|
||||||
/// </summary>
|
|
||||||
private TextField _passwordField;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// button to update the username
|
|
||||||
/// </summary>
|
|
||||||
private Button _usernameUpdateButton;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// button to update the email
|
|
||||||
/// </summary>
|
|
||||||
private Button _emailUpdateButton;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// button to update the password
|
|
||||||
/// </summary>
|
|
||||||
private Button _passwordUpdateButton;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// accompanying text for the account input fields, used when an error/notice is needed
|
/// accompanying text for the account input fields, used when an error/notice is needed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Label _accompanyingText;
|
private Label _accompanyingText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// email text field
|
||||||
|
/// </summary>
|
||||||
|
private TextField _emailField;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// button to update the email
|
||||||
|
/// </summary>
|
||||||
|
private Button _emailUpdateButton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// account view header text
|
||||||
|
/// </summary>
|
||||||
|
private Label _header;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// password text field
|
||||||
|
/// </summary>
|
||||||
|
private TextField _passwordField;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// button to update the password
|
||||||
|
/// </summary>
|
||||||
|
private Button _passwordUpdateButton;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// either 'continue', 'log in', or 'sign out' button
|
/// either 'continue', 'log in', or 'sign out' button
|
||||||
/// (in order of 'initial', 'after', and 'post' states)
|
/// (in order of 'initial', 'after', and 'post' states)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Button _primaryActionButton;
|
private Button _primaryActionButton;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// either 'forgot password' or 'create an account'
|
/// either 'forgot password' or 'create an account'
|
||||||
/// (in order of 'initial' and 'after' states, is hidden in 'post' state)
|
/// (in order of 'initial' and 'after' states, is hidden in 'post' state)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Button _secondaryActionButton;
|
private Button _secondaryActionButton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// username text field
|
||||||
|
/// </summary>
|
||||||
|
private TextField _usernameField;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// button to update the username
|
||||||
|
/// </summary>
|
||||||
|
private Button _usernameUpdateButton;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
if (state == State.UnassociatedState) throw new Exception("unreachable state");
|
||||||
|
|
||||||
|
// GameManager.Instance.Backend.RegisterOnSignInCallback(OnSignInCallback);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// function to subscribe button events to their respective functions
|
/// function to subscribe button events to their respective functions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -85,44 +93,57 @@ public void OnEnable()
|
||||||
var ui = GetComponent<UIDocument>().rootVisualElement;
|
var ui = GetComponent<UIDocument>().rootVisualElement;
|
||||||
|
|
||||||
_header = ui.Q<Label>("AccountHeader");
|
_header = ui.Q<Label>("AccountHeader");
|
||||||
|
|
||||||
_usernameField = ui.Q<TextField>("UsernameField");
|
_usernameField = ui.Q<TextField>("UsernameField");
|
||||||
_emailField = ui.Q<TextField>("EmailField");
|
_emailField = ui.Q<TextField>("EmailField");
|
||||||
_passwordField = ui.Q<TextField>("PasswordField");
|
_passwordField = ui.Q<TextField>("PasswordField");
|
||||||
|
|
||||||
_usernameUpdateButton = ui.Q<Button>("UsernameUpdateButton");
|
_usernameUpdateButton = ui.Q<Button>("UsernameUpdateButton");
|
||||||
_usernameUpdateButton.clicked += OnUsernameUpdateButtonClick;
|
_usernameUpdateButton.clicked += OnUsernameUpdateButtonClick;
|
||||||
|
|
||||||
_emailUpdateButton = ui.Q<Button>("EmailUpdateButton");
|
_emailUpdateButton = ui.Q<Button>("EmailUpdateButton");
|
||||||
_emailUpdateButton.clicked += OnEmailUpdateButtonClick;
|
_emailUpdateButton.clicked += OnEmailUpdateButtonClick;
|
||||||
|
|
||||||
_passwordUpdateButton = ui.Q<Button>("PasswordUpdateButton");
|
_passwordUpdateButton = ui.Q<Button>("PasswordUpdateButton");
|
||||||
_passwordUpdateButton.clicked += OnPasswordUpdateButtonClick;
|
_passwordUpdateButton.clicked += OnPasswordUpdateButtonClick;
|
||||||
|
|
||||||
_accompanyingText = ui.Q<Label>("AccompanyingText");
|
_accompanyingText = ui.Q<Label>("AccompanyingText");
|
||||||
|
|
||||||
_primaryActionButton = ui.Q<Button>("PrimaryActionButton");
|
_primaryActionButton = ui.Q<Button>("PrimaryActionButton");
|
||||||
_primaryActionButton.clicked += OnPrimaryActionButtonClick;
|
_primaryActionButton.clicked += OnPrimaryActionButtonClick;
|
||||||
|
|
||||||
_secondaryActionButton = ui.Q<Button>("SecondaryActionButton");
|
_secondaryActionButton = ui.Q<Button>("SecondaryActionButton");
|
||||||
_secondaryActionButton.clicked += OnSecondaryActionButtonClick;
|
_secondaryActionButton.clicked += OnSecondaryActionButtonClick;
|
||||||
|
|
||||||
TransitionStateTo(State.NotSignedIn);
|
TransitionStateTo(State.NotSignedIn);
|
||||||
|
GameManager.Instance.Backend.RegisterOnSignInCallback(OnSignInCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
private void OnSignInCallback(FirebaseUser user)
|
||||||
{
|
{
|
||||||
if (state == State.UnassociatedState)
|
Debug.Log("sign in account ui callback");
|
||||||
{
|
|
||||||
throw new Exception("unreachable state");
|
var username = GameManager.Instance.Backend.GetUsername();
|
||||||
}
|
_usernameField.value = username;
|
||||||
|
_emailField.value = GameManager.Instance.Backend.GetUser().Email;
|
||||||
|
_passwordField.value = "";
|
||||||
|
_header.text = $"Signed in as {username}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// populate the fields with the given username and email, used by GameManager after local player data is loaded
|
||||||
|
/// </summary>
|
||||||
|
public void PopulateFields(string username, string email)
|
||||||
|
{
|
||||||
|
_usernameField.value = username;
|
||||||
|
_emailField.value = email;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TransitionStateTo(State newState, bool keepAccompanyingText = false)
|
private void TransitionStateTo(State newState, bool keepAccompanyingText = false)
|
||||||
{
|
{
|
||||||
// if we're transitioning to the same state, do nothing
|
// if we're transitioning to the same state, do nothing
|
||||||
if (state == newState) return;
|
if (state == newState) return;
|
||||||
|
|
||||||
// else, hide the accompanying text
|
// else, hide the accompanying text
|
||||||
if (!keepAccompanyingText) _accompanyingText.style.display = DisplayStyle.None;
|
if (!keepAccompanyingText) _accompanyingText.style.display = DisplayStyle.None;
|
||||||
|
|
||||||
|
@ -139,78 +160,79 @@ private void TransitionStateTo(State newState, bool keepAccompanyingText = false
|
||||||
_passwordUpdateButton.style.display = DisplayStyle.Flex;
|
_passwordUpdateButton.style.display = DisplayStyle.Flex;
|
||||||
_emailUpdateButton.style.display = DisplayStyle.Flex;
|
_emailUpdateButton.style.display = DisplayStyle.Flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Log($"transitioning to {newState}");
|
|
||||||
|
|
||||||
// set primary/secondary buttons
|
// set primary/secondary buttons
|
||||||
switch (newState)
|
switch (newState)
|
||||||
{
|
{
|
||||||
case State.NotSignedIn:
|
case State.NotSignedIn:
|
||||||
_header.text = "You are not signed in.";
|
_header.text = "You are not signed in.";
|
||||||
|
|
||||||
_usernameField.style.display = DisplayStyle.None;
|
_usernameField.style.display = DisplayStyle.None;
|
||||||
_emailField.style.display = DisplayStyle.Flex;
|
_emailField.style.display = DisplayStyle.Flex;
|
||||||
_passwordField.style.display = DisplayStyle.Flex;
|
_passwordField.style.display = DisplayStyle.Flex;
|
||||||
|
|
||||||
_primaryActionButton.style.display = DisplayStyle.Flex;
|
_primaryActionButton.style.display = DisplayStyle.Flex;
|
||||||
_secondaryActionButton.style.display = DisplayStyle.Flex;
|
_secondaryActionButton.style.display = DisplayStyle.Flex;
|
||||||
|
|
||||||
_primaryActionButton.text = "Continue \u2192";
|
_primaryActionButton.text = "Continue \u2192";
|
||||||
_secondaryActionButton.text = "Forgot Password \u2192";
|
_secondaryActionButton.text = "Forgot Password \u2192";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case State.AfterContinue:
|
case State.AfterContinue:
|
||||||
_header.text = "You are not signed in.";
|
_header.text = "You are not signed in.";
|
||||||
|
|
||||||
_usernameField.style.display = DisplayStyle.None;
|
_usernameField.style.display = DisplayStyle.None;
|
||||||
_emailField.style.display = DisplayStyle.Flex;
|
_emailField.style.display = DisplayStyle.Flex;
|
||||||
_passwordField.style.display = DisplayStyle.Flex;
|
_passwordField.style.display = DisplayStyle.Flex;
|
||||||
|
|
||||||
_primaryActionButton.style.display = DisplayStyle.Flex;
|
_primaryActionButton.style.display = DisplayStyle.Flex;
|
||||||
_secondaryActionButton.style.display = DisplayStyle.Flex;
|
_secondaryActionButton.style.display = DisplayStyle.Flex;
|
||||||
|
|
||||||
_primaryActionButton.text = "Retry Log In \u2192";
|
_primaryActionButton.text = "Retry Log In \u2192";
|
||||||
_secondaryActionButton.text = "Create an Account \u2192";
|
_secondaryActionButton.text = "Create an Account \u2192";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case State.SignedIn:
|
case State.SignedIn:
|
||||||
|
Debug.Log("transitioning to signed in state");
|
||||||
|
|
||||||
var username = GameManager.Instance.Backend.GetUsername();
|
var username = GameManager.Instance.Backend.GetUsername();
|
||||||
_header.text = string.IsNullOrEmpty(username) ? "You are signed in." : $"Signed in as {username}";
|
_header.text = string.IsNullOrEmpty(username) ? "You are signed in." : $"Signed in as {username}";
|
||||||
|
|
||||||
_usernameField.style.display = DisplayStyle.Flex;
|
_usernameField.style.display = DisplayStyle.Flex;
|
||||||
_emailField.style.display = DisplayStyle.Flex;
|
_emailField.style.display = DisplayStyle.Flex;
|
||||||
_passwordField.style.display = DisplayStyle.Flex;
|
_passwordField.style.display = DisplayStyle.Flex;
|
||||||
|
|
||||||
_usernameField.value = GameManager.Instance.Backend.GetUsername();
|
|
||||||
_emailField.value = GameManager.Instance.Backend.GetUser().Email;
|
|
||||||
_passwordField.value = "";
|
|
||||||
|
|
||||||
_primaryActionButton.style.display = DisplayStyle.Flex;
|
_primaryActionButton.style.display = DisplayStyle.Flex;
|
||||||
_secondaryActionButton.style.display = DisplayStyle.None;
|
_secondaryActionButton.style.display = DisplayStyle.None;
|
||||||
|
|
||||||
_primaryActionButton.text = "Sign Out \u2192";
|
_primaryActionButton.text = "Sign Out \u2192";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case State.UnassociatedState:
|
case State.UnassociatedState:
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
state = newState;
|
state = newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ValidateUsername()
|
||||||
|
{
|
||||||
|
// just has to be min. 5 characters
|
||||||
|
_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.");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// validate the email and password fields
|
/// validate the email field
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="emailField">the email field to validate</param>
|
/// <param name="emailField">the email field to validate</param>
|
||||||
/// <param name="passwordField">the password field to validate</param>
|
/// <exception cref="Exception">if the email field is invalid</exception>
|
||||||
/// <exception cref="Exception">if the email or password fields are invalid</exception>
|
private void ValidateEmailField(TextField emailField)
|
||||||
private void ValidateFields(TextField emailField, TextField passwordField)
|
|
||||||
{
|
{
|
||||||
emailField.style.color = Color.black;
|
emailField.style.color = _defaultInputFieldValueTextColour;
|
||||||
passwordField.style.color = Color.black;
|
|
||||||
|
|
||||||
// validate email
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var dot = emailField.value.LastIndexOf(".", StringComparison.Ordinal);
|
var dot = emailField.value.LastIndexOf(".", StringComparison.Ordinal);
|
||||||
|
@ -226,34 +248,217 @@ private void ValidateFields(TextField emailField, TextField passwordField)
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
emailField.style.color = Color.red;
|
emailField.style.color = _errorInputFieldValueTextColour;
|
||||||
throw new Exception("Invalid email.");
|
throw new Exception("Invalid email.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate password
|
|
||||||
if (passwordField.value.Length >= 10) return;
|
|
||||||
passwordField.style.color = Color.red;
|
|
||||||
throw new Exception("Invalid password.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// validate the password field
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="passwordField">the password field to validate</param>
|
||||||
|
/// <exception cref="Exception">if the password field is invalid</exception>
|
||||||
|
private void ValidatePasswordField(TextField passwordField)
|
||||||
|
{
|
||||||
|
passwordField.style.color = _defaultInputFieldValueTextColour;
|
||||||
|
if (passwordField.value.Length >= 10) return;
|
||||||
|
passwordField.style.color = _errorInputFieldValueTextColour;
|
||||||
|
throw new Exception("Password must be at least 10 characters long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// validate both the email and password fields
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="emailField">the email field to validate</param>
|
||||||
|
/// <param name="passwordField">the password field to validate</param>
|
||||||
|
/// <exception cref="Exception">if either the email or password field is invalid</exception>
|
||||||
|
private void ValidateFields(TextField emailField, TextField passwordField)
|
||||||
|
{
|
||||||
|
var invalidEmail = false;
|
||||||
|
var invalidPassword = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ValidateEmailField(_emailField);
|
||||||
|
}
|
||||||
|
catch (Exception _)
|
||||||
|
{
|
||||||
|
invalidEmail = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ValidatePasswordField(_passwordField);
|
||||||
|
}
|
||||||
|
catch (Exception _)
|
||||||
|
{
|
||||||
|
invalidPassword = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!invalidEmail && !invalidPassword) return;
|
||||||
|
var errorMessage = (invalidEmail, invalidPassword) switch
|
||||||
|
{
|
||||||
|
(true, true) => "Invalid email and password is too short.",
|
||||||
|
(true, false) => "Invalid email.",
|
||||||
|
(false, true) => "Passwords must be at least 10 characters long.",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
throw new Exception(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// validate both the email and password fields
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="emailField">the email field to validate</param>
|
||||||
|
/// <param name="passwordField">the password field to validate</param>
|
||||||
|
/// <param name="usernameField">the username field to validate</param>
|
||||||
|
/// <exception cref="Exception">if either the email or password field is invalid</exception>
|
||||||
|
private void ValidateFields(TextField emailField, TextField passwordField, TextField usernameField)
|
||||||
|
{
|
||||||
|
var invalidEmail = false;
|
||||||
|
var invalidPassword = false;
|
||||||
|
var invalidUsername = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ValidateEmailField(_emailField);
|
||||||
|
}
|
||||||
|
catch (Exception _)
|
||||||
|
{
|
||||||
|
invalidEmail = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ValidatePasswordField(_passwordField);
|
||||||
|
}
|
||||||
|
catch (Exception _)
|
||||||
|
{
|
||||||
|
invalidPassword = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ValidateUsername();
|
||||||
|
}
|
||||||
|
catch (Exception _)
|
||||||
|
{
|
||||||
|
invalidUsername = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!invalidEmail && !invalidPassword && !invalidUsername) return;
|
||||||
|
var errorMessage = (invalidEmail, invalidPassword, invalidUsername) switch
|
||||||
|
{
|
||||||
|
(true, true, true) => "Invalid email, password is too short, and username is too short.",
|
||||||
|
(true, true, false) => "Invalid email, and password is too short.",
|
||||||
|
(true, false, true) => "Invalid email, and username is too short.",
|
||||||
|
(true, false, false) => "Invalid email.",
|
||||||
|
(false, true, true) => "Password is too short, and username is too short.",
|
||||||
|
(false, true, false) => "Password is too short.",
|
||||||
|
(false, false, true) => "Username is too short.",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
throw new Exception(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to handle the username update button click
|
||||||
|
/// </summary>
|
||||||
private void OnUsernameUpdateButtonClick()
|
private void OnUsernameUpdateButtonClick()
|
||||||
{
|
{
|
||||||
// TODO
|
try
|
||||||
throw new NotImplementedException();
|
{
|
||||||
|
ValidateUsername();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = e.Message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameManager.Instance.Backend.UpdateUserAccountDetail(Backend.UserAccountDetailTargetEnum.Username,
|
||||||
|
_usernameField.value,
|
||||||
|
dtr =>
|
||||||
|
{
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = dtr switch
|
||||||
|
{
|
||||||
|
Backend.DatabaseTransactionResult.Ok => "Username updated!",
|
||||||
|
Backend.DatabaseTransactionResult.Unauthenticated =>
|
||||||
|
"You are not signed in. Please sign in to update your username.",
|
||||||
|
_ => "An error occurred updating the username. Please try again."
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to handle the email update button click
|
||||||
|
/// </summary>
|
||||||
private void OnEmailUpdateButtonClick()
|
private void OnEmailUpdateButtonClick()
|
||||||
{
|
{
|
||||||
// TODO
|
try
|
||||||
throw new NotImplementedException();
|
{
|
||||||
|
ValidateEmailField(_emailField);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = e.Message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameManager.Instance.Backend.UpdateUserAccountDetail(Backend.UserAccountDetailTargetEnum.Email,
|
||||||
|
_emailField.value,
|
||||||
|
callback =>
|
||||||
|
{
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = callback switch
|
||||||
|
{
|
||||||
|
Backend.DatabaseTransactionResult.Ok =>
|
||||||
|
$"Verification email sent to {_emailField.value}! You may want to sign in again to see the changes.",
|
||||||
|
Backend.DatabaseTransactionResult.Unauthenticated =>
|
||||||
|
"You are not signed in. Please sign in to update your email.",
|
||||||
|
_ => "An error occurred updating the email. Please try again."
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to handle the password update button click
|
||||||
|
/// </summary>
|
||||||
private void OnPasswordUpdateButtonClick()
|
private void OnPasswordUpdateButtonClick()
|
||||||
{
|
{
|
||||||
// TODO
|
try
|
||||||
throw new NotImplementedException();
|
{
|
||||||
|
ValidatePasswordField(_passwordField);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = e.Message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameManager.Instance.Backend.UpdateUserAccountDetail(Backend.UserAccountDetailTargetEnum.Password,
|
||||||
|
_passwordField.value,
|
||||||
|
callback =>
|
||||||
|
{
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = callback switch
|
||||||
|
{
|
||||||
|
Backend.DatabaseTransactionResult.Ok => "Password updated!",
|
||||||
|
Backend.DatabaseTransactionResult.Unauthenticated =>
|
||||||
|
"You are not signed in. Please sign in to update your password.",
|
||||||
|
_ => "An error occurred updating the password. Please try again."
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to handle the primary action button click
|
||||||
|
/// </summary>
|
||||||
private void OnPrimaryActionButtonClick()
|
private void OnPrimaryActionButtonClick()
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
|
@ -273,11 +478,11 @@ private void OnPrimaryActionButtonClick()
|
||||||
_accompanyingText.text = e.Message;
|
_accompanyingText.text = e.Message;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameManager.Instance.Backend.AuthenticateUser(
|
GameManager.Instance.Backend.AuthenticateUser(
|
||||||
_emailField.value,
|
_emailField.value,
|
||||||
_passwordField.value,
|
_passwordField.value,
|
||||||
(result) =>
|
result =>
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
|
@ -289,7 +494,7 @@ private void OnPrimaryActionButtonClick()
|
||||||
case Backend.AuthenticationResult.NonExistentUser:
|
case Backend.AuthenticationResult.NonExistentUser:
|
||||||
TransitionStateTo(State.AfterContinue);
|
TransitionStateTo(State.AfterContinue);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Backend.AuthenticationResult.InvalidEmail:
|
case Backend.AuthenticationResult.InvalidEmail:
|
||||||
_accompanyingText.style.display = DisplayStyle.Flex;
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
_accompanyingText.text = "Invalid email. Please try again.";
|
_accompanyingText.text = "Invalid email. Please try again.";
|
||||||
|
@ -299,44 +504,43 @@ private void OnPrimaryActionButtonClick()
|
||||||
_accompanyingText.style.display = DisplayStyle.Flex;
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
_accompanyingText.text = "Invalid credentials. Please try again.";
|
_accompanyingText.text = "Invalid credentials. Please try again.";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Backend.AuthenticationResult.AlreadyExistingUser:
|
case Backend.AuthenticationResult.AlreadyExistingUser:
|
||||||
case Backend.AuthenticationResult.GenericError:
|
case Backend.AuthenticationResult.GenericError:
|
||||||
default:
|
default:
|
||||||
TransitionStateTo(State.AfterContinue);
|
TransitionStateTo(State.AfterContinue);
|
||||||
_accompanyingText.style.display = DisplayStyle.Flex;
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
_accompanyingText.text = "Either the account does not exist, or there was an internal error. Try again.";
|
_accompanyingText.text =
|
||||||
|
"There was an error. Either the account does not exist, or the credentials are invalid. Try again.";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
// sign out button
|
// sign out button
|
||||||
case State.SignedIn:
|
case State.SignedIn:
|
||||||
GameManager.Instance.Backend.SignOutUser();
|
GameManager.Instance.Backend.SignOutUser();
|
||||||
TransitionStateTo(State.NotSignedIn);
|
TransitionStateTo(State.NotSignedIn);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case State.UnassociatedState:
|
case State.UnassociatedState:
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to handle the secondary action button click
|
||||||
|
/// </summary>
|
||||||
private void OnSecondaryActionButtonClick()
|
private void OnSecondaryActionButtonClick()
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
// forgot password button
|
// forgot password button
|
||||||
case State.NotSignedIn:
|
case State.NotSignedIn:
|
||||||
throw new NotImplementedException();
|
|
||||||
|
|
||||||
// create an account button
|
|
||||||
case State.AfterContinue:
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ValidateFields(_emailField, _passwordField);
|
ValidateEmailField(_emailField);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -344,7 +548,30 @@ private void OnSecondaryActionButtonClick()
|
||||||
_accompanyingText.text = e.Message;
|
_accompanyingText.text = e.Message;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GameManager.Instance.Backend.ForgotPassword(_emailField.value, result =>
|
||||||
|
{
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text =
|
||||||
|
result
|
||||||
|
? "Password reset email sent. Check your inbox! :D"
|
||||||
|
: "An error occurred sending the email. Please try again.";
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
// create an account button
|
||||||
|
case State.AfterContinue:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ValidateFields(_emailField, _passwordField, _usernameField);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = e.Message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// if the username field is hidden, show it and then immediately return
|
// if the username field is hidden, show it and then immediately return
|
||||||
if (_usernameField.style.display == DisplayStyle.None)
|
if (_usernameField.style.display == DisplayStyle.None)
|
||||||
{
|
{
|
||||||
|
@ -353,11 +580,11 @@ private void OnSecondaryActionButtonClick()
|
||||||
_accompanyingText.text = "Set a username for your account.";
|
_accompanyingText.text = "Set a username for your account.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameManager.Instance.Backend.AuthenticateUser(
|
GameManager.Instance.Backend.AuthenticateUser(
|
||||||
_emailField.value,
|
_emailField.value,
|
||||||
_passwordField.value,
|
_passwordField.value,
|
||||||
(result) =>
|
result =>
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
|
@ -365,7 +592,7 @@ private void OnSecondaryActionButtonClick()
|
||||||
case Backend.AuthenticationResult.AlreadyAuthenticated:
|
case Backend.AuthenticationResult.AlreadyAuthenticated:
|
||||||
TransitionStateTo(State.SignedIn);
|
TransitionStateTo(State.SignedIn);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Backend.AuthenticationResult.InvalidEmail:
|
case Backend.AuthenticationResult.InvalidEmail:
|
||||||
_accompanyingText.style.display = DisplayStyle.Flex;
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
_accompanyingText.text = "Invalid email. Please try again.";
|
_accompanyingText.text = "Invalid email. Please try again.";
|
||||||
|
@ -375,7 +602,7 @@ private void OnSecondaryActionButtonClick()
|
||||||
_accompanyingText.style.display = DisplayStyle.Flex;
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
_accompanyingText.text = "Invalid credentials. Please try again.";
|
_accompanyingText.text = "Invalid credentials. Please try again.";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Backend.AuthenticationResult.NonExistentUser:
|
case Backend.AuthenticationResult.NonExistentUser:
|
||||||
case Backend.AuthenticationResult.AlreadyExistingUser:
|
case Backend.AuthenticationResult.AlreadyExistingUser:
|
||||||
case Backend.AuthenticationResult.GenericError:
|
case Backend.AuthenticationResult.GenericError:
|
||||||
|
@ -389,11 +616,22 @@ private void OnSecondaryActionButtonClick()
|
||||||
true,
|
true,
|
||||||
_usernameField.value);
|
_usernameField.value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case State.SignedIn:
|
case State.SignedIn:
|
||||||
case State.UnassociatedState:
|
case State.UnassociatedState:
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
|
/// state of the account view
|
||||||
|
/// </summary>
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
UnassociatedState, // (start)
|
||||||
|
NotSignedIn, // initial
|
||||||
|
AfterContinue, // after
|
||||||
|
SignedIn // post
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,163 +0,0 @@
|
||||||
/*
|
|
||||||
* author: mark joshwel
|
|
||||||
* date: 29/5/2024
|
|
||||||
* description: audio manager for handling audio in the game
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// singleton class for handling audio in the game
|
|
||||||
/// </summary>
|
|
||||||
public class AudioManager : MonoBehaviour
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// enum for available audio channels in the game
|
|
||||||
/// </summary>
|
|
||||||
public enum AudioChannel
|
|
||||||
{
|
|
||||||
Music,
|
|
||||||
SoundEffects
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// singleton pattern: define instance field for accessing the singleton elsewhere
|
|
||||||
/// </summary>
|
|
||||||
public static AudioManager Instance;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// music audio source
|
|
||||||
/// </summary>
|
|
||||||
[SerializeField] private AudioSource musicSource;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// sound effects (sfx) audio source
|
|
||||||
/// </summary>
|
|
||||||
[SerializeField] private AudioSource sfxSource;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// music source default volume
|
|
||||||
/// </summary>
|
|
||||||
[SerializeField] private float musicSourceDefaultVolume = 0.6f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// sound effects (sfx) source default volume
|
|
||||||
/// </summary>
|
|
||||||
[SerializeField] private float sfxSourceDefaultVolume = 0.6f;
|
|
||||||
|
|
||||||
/// <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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// function to set default volumes for the audio sources
|
|
||||||
/// </summary>
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
// set the default volume for the music source
|
|
||||||
musicSource.volume = musicSourceDefaultVolume;
|
|
||||||
|
|
||||||
// set the default volume for the sfx source
|
|
||||||
sfxSource.volume = sfxSourceDefaultVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// plays the audio clip once on a given channel
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="clip">the audio clip to play</param>
|
|
||||||
/// <param name="channel">the audio channel to play the clip on</param>
|
|
||||||
public void PlayClipOnChannel(AudioClip clip, AudioChannel channel)
|
|
||||||
{
|
|
||||||
switch (channel)
|
|
||||||
{
|
|
||||||
case AudioChannel.Music:
|
|
||||||
musicSource.PlayOneShot(clip);
|
|
||||||
break;
|
|
||||||
case AudioChannel.SoundEffects:
|
|
||||||
sfxSource.PlayOneShot(clip);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Debug.LogError($"invalid channel '{channel}'");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// gets the volume of a given audio channel
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="channel">the AudioManager.AudioChannel to get the volume of</param>
|
|
||||||
/// <returns>volume float value of the channel, from 0.0f-1.0f</returns>
|
|
||||||
public float GetChannelVolume(AudioChannel channel)
|
|
||||||
{
|
|
||||||
switch (channel)
|
|
||||||
{
|
|
||||||
case AudioChannel.Music:
|
|
||||||
return musicSource.volume;
|
|
||||||
case AudioChannel.SoundEffects:
|
|
||||||
return sfxSource.volume;
|
|
||||||
default:
|
|
||||||
Debug.LogError($"invalid channel '{channel}'");
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// sets the pure volume value of a given audio channel
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="volume">volume float value for the channel, from 0.0f-1.0f</param>
|
|
||||||
/// <param name="channel">the AudioManager.AudioChannel to set the volume of</param>
|
|
||||||
public void SetChannelVolumeReal(float volume, AudioChannel channel)
|
|
||||||
{
|
|
||||||
switch (channel)
|
|
||||||
{
|
|
||||||
case AudioChannel.Music:
|
|
||||||
musicSource.volume = Math.Min(volume, 1.0f);
|
|
||||||
break;
|
|
||||||
case AudioChannel.SoundEffects:
|
|
||||||
sfxSource.volume = Math.Min(volume, 1.0f);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Debug.LogError($"invalid channel '{channel}'");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// sets the volume value of a given audio channel based on a logarithmic scale
|
|
||||||
/// for human perception (e.g. 0.5f is half volume, 0.1f is very quiet)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="volume">volume float value for the channel, from 0.0f-1.0f</param>
|
|
||||||
/// <param name="channel">the AudioManager.AudioChannel to set the volume of</param>
|
|
||||||
public void SetChannelVolumeLog(float volume, AudioChannel channel)
|
|
||||||
{
|
|
||||||
switch (channel)
|
|
||||||
{
|
|
||||||
case AudioChannel.Music:
|
|
||||||
musicSource.volume = Mathf.Log10(Mathf.Max(volume, 0.0001f)) * 20;
|
|
||||||
break;
|
|
||||||
case AudioChannel.SoundEffects:
|
|
||||||
sfxSource.volume = Mathf.Log10(Mathf.Max(volume, 0.0001f)) * 20;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Debug.LogError($"invalid channel '{channel}'");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -50,6 +50,13 @@ public enum FirebaseConnectionStatus
|
||||||
InternalError // "an unknown error occurred"
|
InternalError // "an unknown error occurred"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum UserAccountDetailTargetEnum
|
||||||
|
{
|
||||||
|
Username,
|
||||||
|
Email,
|
||||||
|
Password
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// callback functions to be invoked when the user signs in
|
/// callback functions to be invoked when the user signs in
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -171,12 +178,13 @@ private void AuthStateChanged(object sender, EventArgs eventArgs)
|
||||||
|
|
||||||
// they have signed in, update _user
|
// they have signed in, update _user
|
||||||
_user = _auth.CurrentUser;
|
_user = _auth.CurrentUser;
|
||||||
if (signedIn)
|
if (!signedIn) return;
|
||||||
|
|
||||||
|
Debug.Log($"signed in successfully as {_user.UserId}");
|
||||||
|
RetrieveUsernameWithCallback((_, _) =>
|
||||||
{
|
{
|
||||||
Debug.Log($"signed in successfully as {_user.UserId}");
|
|
||||||
RetrieveUsername();
|
|
||||||
foreach (var callback in _onSignInCallback) callback(_user);
|
foreach (var callback in _onSignInCallback) callback(_user);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -292,6 +300,7 @@ public void RegisterOnSignOutCallback(Action<FirebaseUser> callback)
|
||||||
{
|
{
|
||||||
if (signInTask.IsCompletedSuccessfully)
|
if (signInTask.IsCompletedSuccessfully)
|
||||||
{
|
{
|
||||||
|
RetrieveUsername();
|
||||||
callback(AuthenticationResult.Ok);
|
callback(AuthenticationResult.Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -332,23 +341,44 @@ public void RegisterOnSignOutCallback(Action<FirebaseUser> callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// function to retrieve the user's username from the database
|
/// helper function to run RetrieveUsername with no callback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void RetrieveUsername()
|
private void RetrieveUsername()
|
||||||
|
{
|
||||||
|
RetrieveUsernameWithCallback((result, username) => { });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to retrieve the user's username from the database
|
||||||
|
/// </summary>
|
||||||
|
private void RetrieveUsernameWithCallback(Action<DatabaseTransactionResult, string> callback)
|
||||||
{
|
{
|
||||||
if (!Status.Equals(FirebaseConnectionStatus.Connected)) return;
|
if (!Status.Equals(FirebaseConnectionStatus.Connected)) return;
|
||||||
_db.Child("users").Child(_user.UserId).Child("username").GetValueAsync().ContinueWith(task =>
|
|
||||||
|
if (_user == null)
|
||||||
{
|
{
|
||||||
|
Debug.LogError("receiving username post-authentication but user is null (should be unreachable)");
|
||||||
|
callback(DatabaseTransactionResult.Unauthenticated, "Unknown");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_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}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_username = "???";
|
result = DatabaseTransactionResult.Error;
|
||||||
|
_username = "Unknown";
|
||||||
Debug.LogError("failed to get username");
|
Debug.LogError("failed to get username");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callback(result, _username);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,12 +405,46 @@ public void SignOutUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// abstraction function to submit a play to the database
|
/// abstraction function for the user to reset their password
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="playData">play data</param>
|
/// <param name="email">the forgetful user's email lol</param>
|
||||||
|
/// <param name="callback">callback function to be invoked after the password reset email is sent</param>
|
||||||
|
public void ForgotPassword(string email, Action<bool> callback)
|
||||||
|
{
|
||||||
|
_auth.SendPasswordResetEmailAsync(email).ContinueWithOnMainThread(resetTask =>
|
||||||
|
{
|
||||||
|
if (resetTask.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
callback(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError(resetTask.Exception);
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// abstraction function to get the user's recent scores from the database
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">
|
||||||
|
/// callback function that takes in a DatabaseTransactionResult and List of LocalPlayerData.Score
|
||||||
|
/// argument
|
||||||
|
/// </param>
|
||||||
|
public void GetRecentScores(Action<DatabaseTransactionResult, List<LocalPlayerData.Score>> callback)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
callback(DatabaseTransactionResult.Error, new List<LocalPlayerData.Score>(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// abstraction function to submit a score to the database
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="score">score</param>
|
||||||
/// <param name="callback">callback function that takes in one DatabaseTransactionResult argument</param>
|
/// <param name="callback">callback function that takes in one DatabaseTransactionResult argument</param>
|
||||||
public void SubmitPlay(
|
public void SubmitScore(
|
||||||
PlayData playData,
|
LocalPlayerData.Score score,
|
||||||
Action<DatabaseTransactionResult> callback)
|
Action<DatabaseTransactionResult> callback)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
@ -421,20 +485,79 @@ public void SignOutUser()
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// private void Playground()
|
/// <summary>
|
||||||
// {
|
/// abstraction function to update the user's account details in the database
|
||||||
// // update username
|
/// </summary>
|
||||||
// _db.Child("users").Child(_user.UserId).Child("username").SetValueAsync("newusername").ContinueWith(task => { });
|
/// <param name="target">the target account detail to update</param>
|
||||||
//
|
/// <param name="newValue">the new value for the target account detail</param>
|
||||||
// // update email
|
/// <param name="callback">callback function that takes in one DatabaseTransactionResult argument</param>
|
||||||
// _user.SendEmailVerificationBeforeUpdatingEmailAsync("name@example.com").ContinueWith(task => { });
|
/// <exception cref="ArgumentOutOfRangeException">thrown when the target is not a valid UserAccountDetailTargetEnum</exception>
|
||||||
//
|
public void UpdateUserAccountDetail(
|
||||||
// // update password
|
UserAccountDetailTargetEnum target,
|
||||||
// _user.UpdatePasswordAsync("password").ContinueWith(task => { });
|
string newValue,
|
||||||
//
|
Action<DatabaseTransactionResult> callback)
|
||||||
// // reset password
|
{
|
||||||
// _auth.SendPasswordResetEmailAsync("name@example.com").ContinueWith(task => { });
|
if (!Status.Equals(FirebaseConnectionStatus.Connected)) callback(DatabaseTransactionResult.Unauthenticated);
|
||||||
// }
|
|
||||||
|
if (_user == null)
|
||||||
|
{
|
||||||
|
callback(DatabaseTransactionResult.Unauthenticated);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (target)
|
||||||
|
{
|
||||||
|
case UserAccountDetailTargetEnum.Email:
|
||||||
|
_user.SendEmailVerificationBeforeUpdatingEmailAsync(newValue).ContinueWithOnMainThread(task =>
|
||||||
|
{
|
||||||
|
if (task.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
callback(DatabaseTransactionResult.Ok);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError(task.Exception);
|
||||||
|
callback(DatabaseTransactionResult.Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UserAccountDetailTargetEnum.Username:
|
||||||
|
_db.Child("users").Child(_user.UserId).Child("username").SetValueAsync(newValue)
|
||||||
|
.ContinueWithOnMainThread(task =>
|
||||||
|
{
|
||||||
|
if (task.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
_username = newValue;
|
||||||
|
callback(DatabaseTransactionResult.Ok);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError(task.Exception);
|
||||||
|
callback(DatabaseTransactionResult.Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UserAccountDetailTargetEnum.Password:
|
||||||
|
_user.UpdatePasswordAsync(newValue).ContinueWithOnMainThread(task =>
|
||||||
|
{
|
||||||
|
if (task.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
callback(DatabaseTransactionResult.Ok);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError(task.Exception);
|
||||||
|
callback(DatabaseTransactionResult.Error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(target), target, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// struct for a leaderboard entry
|
/// struct for a leaderboard entry
|
||||||
|
@ -452,16 +575,4 @@ public LeaderboardEntry(string username, float rating, int playCount)
|
||||||
PlayCount = playCount;
|
PlayCount = playCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// struct for play data
|
|
||||||
/// </summary>
|
|
||||||
public struct PlayData
|
|
||||||
{
|
|
||||||
public int RoundsPlayed;
|
|
||||||
public float AverageOverallAccuracy;
|
|
||||||
public float AverageLightnessAccuracy;
|
|
||||||
public float AverageChromaAccuracy;
|
|
||||||
public float AverageHueAccuracy;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,122 +0,0 @@
|
||||||
/*
|
|
||||||
* author: mark joshwel
|
|
||||||
* date: 29/5/2024
|
|
||||||
* description: common menu script for hover and click sound effects on ui toolkit buttons
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UIElements;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <para>
|
|
||||||
/// common menu class for hover and click sound effects on ui toolkit buttons
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// override <c>OnEnable()</c> with the first call to <c>base.OnEnable()</c>
|
|
||||||
/// or <c>PostEnable()</c>, and set the variable
|
|
||||||
/// <c>GameManager.DisplayState.associatedState</c> to the respective menu state
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
|
||||||
public class CommonMenu : MonoBehaviour
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// associated display state with the menu for the game manager to filter out menus in a scene
|
|
||||||
/// </summary>
|
|
||||||
public GameManager.DisplayState associatedState = GameManager.DisplayState.UnassociatedState;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// audio clip for menu button click
|
|
||||||
/// </summary>
|
|
||||||
[SerializeField] public AudioClip menuButtonClick;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// audio clip for menu button hover
|
|
||||||
/// </summary>
|
|
||||||
[SerializeField] public AudioClip menuButtonHover;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// manager for the game state
|
|
||||||
/// </summary>
|
|
||||||
protected GameManager Game;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// the visual element object for the menu
|
|
||||||
/// </summary>
|
|
||||||
protected VisualElement UI;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// checks if The Menu (2022) was set up correctly
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="Exception">throws an exception if UI, Game and Audio are not set</exception>
|
|
||||||
private void Start()
|
|
||||||
{
|
|
||||||
if (associatedState == GameManager.DisplayState.UnassociatedState)
|
|
||||||
throw new Exception("associatedState not set");
|
|
||||||
|
|
||||||
if (Game == null)
|
|
||||||
throw new Exception("Game not set (was base.OnEnable() or PostEnable() called?)");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// override this class but call <c>base.OnEnable()</c> first.
|
|
||||||
/// also set the <c>associatedState</c> variable to the respective menu state
|
|
||||||
/// </summary>
|
|
||||||
public virtual void OnEnable()
|
|
||||||
{
|
|
||||||
PostEnable();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// function to subscribe to mouse events and assign managers
|
|
||||||
/// </summary>
|
|
||||||
public void PostEnable()
|
|
||||||
{
|
|
||||||
// get audio manager singleton instance from the world
|
|
||||||
UI = GetComponent<UIDocument>().rootVisualElement;
|
|
||||||
Game = GameManager.Instance;
|
|
||||||
|
|
||||||
// subscribe to hover events
|
|
||||||
UI.RegisterCallback<PointerOverEvent>(HoverListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// function listener for <c>PointerOverEvents</c> and plays a hover sound if it's a button
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="evt">event from UIE callback</param>
|
|
||||||
public virtual void HoverListener(PointerOverEvent evt)
|
|
||||||
{
|
|
||||||
// check for button
|
|
||||||
if (evt.target is Button)
|
|
||||||
// play hover sound
|
|
||||||
PlayHover();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// function listener for <c>ClickEvents</c> and plays a click sound if it's a button
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="evt">event from UIE callback</param>
|
|
||||||
public virtual void ClickListener(ClickEvent evt)
|
|
||||||
{
|
|
||||||
// check for button
|
|
||||||
if (evt.target is Button)
|
|
||||||
// play click sound
|
|
||||||
PlayClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// generic decoupled function to play click sound
|
|
||||||
/// </summary>
|
|
||||||
public virtual void PlayClick()
|
|
||||||
{
|
|
||||||
AudioManager.Instance.PlayClipOnChannel(menuButtonClick, AudioManager.AudioChannel.SoundEffects);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// generic decoupled function to play hover sound
|
|
||||||
/// </summary>
|
|
||||||
public virtual void PlayHover()
|
|
||||||
{
|
|
||||||
AudioManager.Instance.PlayClipOnChannel(menuButtonHover, AudioManager.AudioChannel.SoundEffects);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 26a2a7f124fc93c47aae6ca6569f68b9
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,4 +1,5 @@
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Serialization;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -11,18 +12,21 @@ public class GameManager : MonoBehaviour
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum DisplayState
|
public enum DisplayState
|
||||||
{
|
{
|
||||||
ScreenStart,
|
Nothing,
|
||||||
OverlayAccountManagement,
|
PlayView,
|
||||||
OverlaySettings,
|
LeaderboardView,
|
||||||
OverlayLeaderboard,
|
AccountView,
|
||||||
Game,
|
|
||||||
UnassociatedState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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>
|
||||||
|
/// the current display state of the game
|
||||||
|
/// </summary>
|
||||||
|
[SerializeField] private DisplayState state = DisplayState.Nothing;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// the visual element object for game ui (hud/prompts/tooltips)
|
/// the visual element object for game ui (hud/prompts/tooltips)
|
||||||
|
@ -33,6 +37,11 @@ public enum DisplayState
|
||||||
/// 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
|
||||||
|
@ -54,6 +63,21 @@ private void Awake()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// called when the game object is enabled
|
/// called when the game object is enabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -70,4 +94,15 @@ private void OnDestroy()
|
||||||
{
|
{
|
||||||
Backend.Cleanup();
|
Backend.Cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
var currentDisplayState = St;
|
||||||
|
switch (newDisplayState)
|
||||||
|
}
|
||||||
}
|
}
|
172
ColourMeOKGame/Assets/Scripts/LocalPlayerData.cs
Normal file
172
ColourMeOKGame/Assets/Scripts/LocalPlayerData.cs
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
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>
|
||||||
|
/// queue of the best online scores,
|
||||||
|
/// used in user rating calculation and accuracy display stats
|
||||||
|
/// </summary>
|
||||||
|
public Queue<Score> BestOnlineScores = new(30);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// loads player data from player prefs and database
|
||||||
|
/// </summary>
|
||||||
|
public void LoadFromTheWorld()
|
||||||
|
{
|
||||||
|
// load user data, possibly from the backend
|
||||||
|
var possibleUser = GameManager.Instance.Backend.GetUser();
|
||||||
|
var currentKnownEmail = string.Empty;
|
||||||
|
var currentKnownUsername = string.Empty;
|
||||||
|
if (possibleUser != null)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
LastKnownUsername = string.IsNullOrEmpty(currentKnownUsername) ? lastStoredUsername : currentKnownUsername;
|
||||||
|
|
||||||
|
// load local scores
|
||||||
|
RecentLocalScores.Clear();
|
||||||
|
for (var idx = 0; idx < 10; idx++)
|
||||||
|
{
|
||||||
|
var timestampRaw = PlayerPrefs.GetString($"RecentLocalScores_{idx}_Timestamp", "");
|
||||||
|
if (timestampRaw == "") continue;
|
||||||
|
|
||||||
|
var timestamp = DateTime.TryParseExact(timestampRaw,
|
||||||
|
"s",
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
DateTimeStyles.None,
|
||||||
|
out var t)
|
||||||
|
? t
|
||||||
|
: DateTime.MinValue;
|
||||||
|
|
||||||
|
if (timestamp == DateTime.MinValue) continue;
|
||||||
|
|
||||||
|
var noOfRounds = PlayerPrefs.GetInt($"RecentLocalScores_{idx}_NoOfRounds", -1);
|
||||||
|
var l = PlayerPrefs.GetFloat($"RecentLocalScores_{idx}_AvgLightnessAccuracy", -1f);
|
||||||
|
var c = PlayerPrefs.GetFloat($"RecentLocalScores_{idx}_AvgChromaAccuracy", -1f);
|
||||||
|
var h = PlayerPrefs.GetFloat($"RecentLocalScores_{idx}_AvgHueAccuracy", -1f);
|
||||||
|
|
||||||
|
// if any of the values are invalid, don't add the score
|
||||||
|
if (noOfRounds < 0 || l < 0 || c < 0 || h < 0) continue;
|
||||||
|
|
||||||
|
RegisterLocalScore(new Score(timestamp, noOfRounds, l, c, h));
|
||||||
|
}
|
||||||
|
|
||||||
|
// load online scores
|
||||||
|
RecentOnlineScores.Clear();
|
||||||
|
GameManager.Instance.Backend.GetRecentScores((dtr, recentOnlineScores) =>
|
||||||
|
{
|
||||||
|
foreach (var onlineScore in recentOnlineScores)
|
||||||
|
{
|
||||||
|
if (RecentOnlineScores.Count > 10) RecentOnlineScores.Dequeue();
|
||||||
|
RecentOnlineScores.Enqueue(onlineScore);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// saves player data to player prefs
|
||||||
|
/// </summary>
|
||||||
|
public void SaveToTheWorld()
|
||||||
|
{
|
||||||
|
PlayerPrefs.SetString("LastKnownEmail", LastKnownEmail);
|
||||||
|
PlayerPrefs.SetString("LastKnownUsername", LastKnownUsername);
|
||||||
|
|
||||||
|
var idx = 0;
|
||||||
|
foreach (var score in RecentLocalScores)
|
||||||
|
{
|
||||||
|
PlayerPrefs.SetString($"RecentLocalScores_{idx}_Timestamp",
|
||||||
|
score.Timestamp.ToString("s", CultureInfo.InvariantCulture));
|
||||||
|
PlayerPrefs.SetInt($"RecentLocalScores_{idx}_NoOfRounds", score.NoOfRounds);
|
||||||
|
PlayerPrefs.SetFloat($"RecentLocalScores_{idx}_AvgLightnessAccuracy", score.AvgLightnessAccuracy);
|
||||||
|
PlayerPrefs.SetFloat($"RecentLocalScores_{idx}_AvgChromaAccuracy", score.AvgChromaAccuracy);
|
||||||
|
PlayerPrefs.SetFloat($"RecentLocalScores_{idx}_AvgHueAccuracy", score.AvgHueAccuracy);
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// online scores are already saved in the backend
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Score
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// timestamp of the score
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Timestamp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// number of rounds played (0-100)
|
||||||
|
/// </summary>
|
||||||
|
public int NoOfRounds;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// average lightness accuracy across all rounds (0-100)
|
||||||
|
/// </summary>
|
||||||
|
public float AvgLightnessAccuracy;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// average chroma accuracy across all rounds (0-100)
|
||||||
|
/// </summary>
|
||||||
|
public float AvgChromaAccuracy;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// average hue accuracy across all rounds (0-100)
|
||||||
|
/// </summary>
|
||||||
|
public float AvgHueAccuracy;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// constructor for the score struct
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timestamp">timestamp of the score</param>
|
||||||
|
/// <param name="noOfRounds">number of rounds played (0-100)</param>
|
||||||
|
/// <param name="l">average lightness accuracy across all rounds (0-100)</param>
|
||||||
|
/// <param name="c">average chroma accuracy across all rounds (0-100)</param>
|
||||||
|
/// <param name="h">average hue accuracy across all rounds (0-100)</param>
|
||||||
|
public Score(DateTime timestamp = new(), int noOfRounds = 1, float l = 100.0f, float c = 100.0f,
|
||||||
|
float h = 100.0f)
|
||||||
|
{
|
||||||
|
Timestamp = timestamp;
|
||||||
|
NoOfRounds = noOfRounds;
|
||||||
|
AvgLightnessAccuracy = l;
|
||||||
|
AvgChromaAccuracy = c;
|
||||||
|
AvgHueAccuracy = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
ColourMeOKGame/Assets/Scripts/LocalPlayerData.cs.meta
Normal file
3
ColourMeOKGame/Assets/Scripts/LocalPlayerData.cs.meta
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 61389b17ec0645a6a0a9fe10bdaef72a
|
||||||
|
timeCreated: 1731833904
|
18
ColourMeOKGame/Assets/Scripts/Playground.cs
Normal file
18
ColourMeOKGame/Assets/Scripts/Playground.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class Playground
|
||||||
|
{
|
||||||
|
private float _score;
|
||||||
|
|
||||||
|
private void SomethingSomethingScore()
|
||||||
|
{
|
||||||
|
var score = 0.0f;
|
||||||
|
|
||||||
|
_score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowTheScore()
|
||||||
|
{
|
||||||
|
Debug.Log(_score);
|
||||||
|
}
|
||||||
|
}
|
3
ColourMeOKGame/Assets/Scripts/Playground.cs.meta
Normal file
3
ColourMeOKGame/Assets/Scripts/Playground.cs.meta
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 662d126b54a54d9ebe6a1dc716d199e3
|
||||||
|
timeCreated: 1731830473
|
146
ColourMeOKGame/Assets/Scripts/SideViewUI.cs
Normal file
146
ColourMeOKGame/Assets/Scripts/SideViewUI.cs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// singleton for a single source of truth game state and flow management
|
||||||
|
/// </summary>
|
||||||
|
public class SideViewUI : MonoBehaviour
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// settings button for showing the settings menu
|
||||||
|
/// </summary>
|
||||||
|
private Button _accountButton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// settings button for showing the settings menu
|
||||||
|
/// </summary>
|
||||||
|
private Label _chromaAccuracyText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// settings button for showing the settings menu
|
||||||
|
/// </summary>
|
||||||
|
private Label _hueAccuracyText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// leaderboard button for showing the leaderboard
|
||||||
|
/// </summary>
|
||||||
|
private Button _leaderboardButton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// settings button for showing the settings menu
|
||||||
|
/// </summary>
|
||||||
|
private Label _lightnessAccuracyText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// play button for starting the game
|
||||||
|
/// </summary>
|
||||||
|
private Button _playButton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// settings button for showing the settings menu
|
||||||
|
/// </summary>
|
||||||
|
private Label _playerNameText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// settings button for showing the settings menu
|
||||||
|
/// </summary>
|
||||||
|
private Label _playerRatingText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to subscribe button events to their respective functions
|
||||||
|
/// </summary>
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
var ui = GetComponent<UIDocument>().rootVisualElement;
|
||||||
|
|
||||||
|
_playButton = ui.Q<Button>("PlayButton");
|
||||||
|
_playButton.clicked += OnPlayButtonClicked;
|
||||||
|
|
||||||
|
_leaderboardButton = ui.Q<Button>("LeaderboardButton");
|
||||||
|
_leaderboardButton.clicked += OnLeaderboardButtonClicked;
|
||||||
|
|
||||||
|
_accountButton = ui.Q<Button>("AccountButton");
|
||||||
|
_accountButton.clicked += OnAccountButtonClicked;
|
||||||
|
|
||||||
|
_lightnessAccuracyText = ui.Q<Label>("LightnessAccuracyText");
|
||||||
|
_hueAccuracyText = ui.Q<Label>("HueAccuracyText");
|
||||||
|
_chromaAccuracyText = ui.Q<Label>("ChromaAccuracyText");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to update the ui with the latest data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="localPlayerData"></param>
|
||||||
|
private void RenderFromPlayerData(LocalPlayerData localPlayerData)
|
||||||
|
{
|
||||||
|
// calculate averages from both recent local scores and online scores
|
||||||
|
var totalLightessAcc = 0f;
|
||||||
|
var totalChromaAcc = 0f;
|
||||||
|
var totalHueAcc = 0f;
|
||||||
|
var totalRounds = 0;
|
||||||
|
|
||||||
|
// average out all the scores we have to get a stable-ish average
|
||||||
|
|
||||||
|
foreach (var localScore in localPlayerData.RecentLocalScores)
|
||||||
|
{
|
||||||
|
totalLightessAcc += localScore.AvgLightnessAccuracy;
|
||||||
|
totalChromaAcc += localScore.AvgChromaAccuracy;
|
||||||
|
totalHueAcc += localScore.AvgHueAccuracy;
|
||||||
|
totalRounds += localScore.NoOfRounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var onlineScore in localPlayerData.RecentOnlineScores)
|
||||||
|
{
|
||||||
|
totalLightessAcc += onlineScore.AvgLightnessAccuracy;
|
||||||
|
totalChromaAcc += onlineScore.AvgChromaAccuracy;
|
||||||
|
totalHueAcc += onlineScore.AvgHueAccuracy;
|
||||||
|
totalRounds += onlineScore.NoOfRounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var onlineScore in localPlayerData.BestOnlineScores)
|
||||||
|
{
|
||||||
|
totalLightessAcc += 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}";
|
||||||
|
|
||||||
|
// 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) =>
|
||||||
|
{
|
||||||
|
if (dtr != Backend.DatabaseTransactionResult.Ok) return;
|
||||||
|
_playerRatingText.text = $"{rating:F}";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to show the play view
|
||||||
|
/// </summary>
|
||||||
|
private static void OnPlayButtonClicked()
|
||||||
|
{
|
||||||
|
GameManager.Instance.SetDisplayState(GameManager.DisplayState.PlayView);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to show the leaderboard view
|
||||||
|
/// </summary>
|
||||||
|
private static void OnLeaderboardButtonClicked()
|
||||||
|
{
|
||||||
|
GameManager.Instance.SetDisplayState(GameManager.DisplayState.LeaderboardView);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to show the account view
|
||||||
|
/// </summary>
|
||||||
|
private static void OnAccountButtonClicked()
|
||||||
|
{
|
||||||
|
GameManager.Instance.SetDisplayState(GameManager.DisplayState.AccountView);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 17a7c29fa9f16244682562d58604fdba
|
guid: 6351b7620d84e2d43bc4f59c5f3f8b5c
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
|
@ -100,6 +100,7 @@ #AccountFields TextField #unity-text-input {
|
||||||
border-top-color: rgba(0, 0, 0, 0);
|
border-top-color: rgba(0, 0, 0, 0);
|
||||||
border-bottom-color: rgba(0, 0, 0, 0);
|
border-bottom-color: rgba(0, 0, 0, 0);
|
||||||
background-color: rgb(255, 232, 249);
|
background-color: rgb(255, 232, 249);
|
||||||
|
color: rgb(15, 13, 27);
|
||||||
}
|
}
|
||||||
|
|
||||||
#AccountFields Button {
|
#AccountFields Button {
|
||||||
|
|
Reference in a new issue