auth and rc purchase dummy
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class IntroSceneManager : MonoBehaviour
|
||||
{
|
||||
public void OnPlay(){
|
||||
LevelLoadManager.LoadLevel("MainMenu");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class LoginManager : MonoBehaviour
|
||||
{
|
||||
const string AuthBaseUrl = "http://vps.playpoolstudios.com:2612";
|
||||
const string PlayerPrefsAuthKey = "WalkInvest.AuthToken";
|
||||
|
||||
const int MIN_USERNAME_LENGTH = 3;
|
||||
const int MAX_USERNAME_LENGTH = 16;
|
||||
const int MIN_PASSWORD_LENGTH = 8;
|
||||
const int MAX_PASSWORD_LENGTH = 20;
|
||||
|
||||
[Header("Login")]
|
||||
[SerializeField] private TMP_InputField usernameInputLogin;
|
||||
[SerializeField] private TMP_InputField passwordInputLogin;
|
||||
[SerializeField] private Button btnLogin;
|
||||
[Header("Register")]
|
||||
[SerializeField] private TMP_InputField usernameInputRegister;
|
||||
[SerializeField] private TMP_InputField passwordInputRegister;
|
||||
[SerializeField] private Button btnRegister;
|
||||
|
||||
[Header("UI")]
|
||||
[SerializeField] private TMP_Text error_txt;
|
||||
|
||||
bool _authInProgress;
|
||||
|
||||
public static string AuthToken { get; private set; }
|
||||
|
||||
/// <summary>Latest user payload from the server. Reading this schedules a refresh (one in flight at a time).</summary>
|
||||
public static UserData CurrentUser
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance != null)
|
||||
instance.ScheduleUserDataRefreshOnRead();
|
||||
return _currentUser;
|
||||
}
|
||||
}
|
||||
|
||||
public static Action<UserData> OnUserDataUpdated;
|
||||
|
||||
static UserData _currentUser;
|
||||
bool _userDataReadRefreshPending;
|
||||
|
||||
[Serializable]
|
||||
class AuthApiResponse
|
||||
{
|
||||
public bool ok;
|
||||
public string error;
|
||||
public string token;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class AuthUserApiResponse
|
||||
{
|
||||
public bool ok;
|
||||
public string error;
|
||||
public UserData user;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class PurchaseRcApiResponse
|
||||
{
|
||||
public bool ok;
|
||||
public string error;
|
||||
}
|
||||
|
||||
public static LoginManager instance;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
error_txt.text = "";
|
||||
AuthToken = PlayerPrefs.GetString(PlayerPrefsAuthKey, "");
|
||||
|
||||
if(instance !=null){
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
btnLogin.onClick.AddListener(OnLogin);
|
||||
btnRegister.onClick.AddListener(OnRegister);
|
||||
|
||||
if (!string.IsNullOrEmpty(AuthToken))
|
||||
{
|
||||
SetBusy(true);
|
||||
StartCoroutine(ResumeSessionCoroutine());
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator ResumeSessionCoroutine()
|
||||
{
|
||||
yield return StartCoroutine(FetchUserDataCoroutine(null, true));
|
||||
ProceedToMainMenu();
|
||||
SetBusy(false);
|
||||
}
|
||||
|
||||
static void ProceedToMainMenu()
|
||||
{
|
||||
if (LevelLoadManager.instance != null)
|
||||
LevelLoadManager.LoadLevel("MainMenu");
|
||||
else
|
||||
SceneManager.LoadScene("MainMenu");
|
||||
}
|
||||
|
||||
void SetBusy(bool busy)
|
||||
{
|
||||
_authInProgress = busy;
|
||||
if (btnLogin != null) btnLogin.interactable = !busy;
|
||||
if (btnRegister != null) btnRegister.interactable = !busy;
|
||||
}
|
||||
|
||||
void OnLogin()
|
||||
{
|
||||
if (_authInProgress) return;
|
||||
|
||||
string username = usernameInputLogin.text;
|
||||
string password = passwordInputLogin.text;
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
||||
{
|
||||
error_txt.text = "Please fill in all fields";
|
||||
return;
|
||||
}
|
||||
|
||||
if (username.Length < MIN_USERNAME_LENGTH || username.Length > MAX_USERNAME_LENGTH)
|
||||
{
|
||||
error_txt.text = "Username must be between " + MIN_USERNAME_LENGTH + " and " + MAX_USERNAME_LENGTH + " characters";
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.Length < MIN_PASSWORD_LENGTH || password.Length > MAX_PASSWORD_LENGTH)
|
||||
{
|
||||
error_txt.text = "Password must be between " + MIN_PASSWORD_LENGTH + " and " + MAX_PASSWORD_LENGTH + " characters";
|
||||
return;
|
||||
}
|
||||
|
||||
string json = "{\"username\":\"" + EscapeJsonString(username) + "\",\"password\":\"" + EscapeJsonString(password) + "\"}";
|
||||
StartCoroutine(AuthPostCoroutine(AuthBaseUrl + "/auth/login", json, "Signed in."));
|
||||
}
|
||||
|
||||
void OnRegister()
|
||||
{
|
||||
if (_authInProgress) return;
|
||||
|
||||
string username = usernameInputRegister.text;
|
||||
string password = passwordInputRegister.text;
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
||||
{
|
||||
error_txt.text = "Please fill in all fields";
|
||||
return;
|
||||
}
|
||||
|
||||
if (username.Length < MIN_USERNAME_LENGTH || username.Length > MAX_USERNAME_LENGTH)
|
||||
{
|
||||
error_txt.text = "Username must be between " + MIN_USERNAME_LENGTH + " and " + MAX_USERNAME_LENGTH + " characters";
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.Length < MIN_PASSWORD_LENGTH || password.Length > MAX_PASSWORD_LENGTH)
|
||||
{
|
||||
error_txt.text = "Password must be between " + MIN_PASSWORD_LENGTH + " and " + MAX_PASSWORD_LENGTH + " characters";
|
||||
return;
|
||||
}
|
||||
|
||||
// Server register endpoint accepts username + password only (email field is optional UI-only).
|
||||
string json = "{\"username\":\"" + EscapeJsonString(username) + "\",\"password\":\"" + EscapeJsonString(password) + "\"}";
|
||||
StartCoroutine(AuthPostCoroutine(AuthBaseUrl + "/auth/register", json, "Account created."));
|
||||
}
|
||||
|
||||
static string EscapeJsonString(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return s;
|
||||
return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
IEnumerator AuthPostCoroutine(string url, string jsonBody, string successMessage)
|
||||
{
|
||||
SetBusy(true);
|
||||
error_txt.text = "";
|
||||
|
||||
using (var request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST))
|
||||
{
|
||||
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonBody);
|
||||
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
|
||||
request.downloadHandler = new DownloadHandlerBuffer();
|
||||
request.SetRequestHeader("Content-Type", "application/json");
|
||||
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
||||
|
||||
if (request.result != UnityWebRequest.Result.Success &&
|
||||
request.result != UnityWebRequest.Result.ProtocolError)
|
||||
{
|
||||
error_txt.text = string.IsNullOrEmpty(request.error) ? "Network error" : request.error;
|
||||
SetBusy(false);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
error_txt.text = "Empty response from server";
|
||||
SetBusy(false);
|
||||
yield break;
|
||||
}
|
||||
|
||||
AuthApiResponse resp = JsonUtility.FromJson<AuthApiResponse>(text);
|
||||
|
||||
if (resp.ok && !string.IsNullOrEmpty(resp.token))
|
||||
{
|
||||
AuthToken = resp.token;
|
||||
PlayerPrefs.SetString(PlayerPrefsAuthKey, resp.token);
|
||||
PlayerPrefs.Save();
|
||||
|
||||
yield return StartCoroutine(FetchUserDataCoroutine(null, true));
|
||||
ProceedToMainMenu();
|
||||
}
|
||||
else
|
||||
{
|
||||
error_txt.text = string.IsNullOrEmpty(resp.error) ? "Request failed" : resp.error;
|
||||
}
|
||||
}
|
||||
|
||||
SetBusy(false);
|
||||
}
|
||||
|
||||
static void ApplyUserData(UserData u)
|
||||
{
|
||||
_currentUser = u;
|
||||
OnUserDataUpdated?.Invoke(u);
|
||||
}
|
||||
|
||||
void ScheduleUserDataRefreshOnRead()
|
||||
{
|
||||
if (string.IsNullOrEmpty(AuthToken)) return;
|
||||
if (_userDataReadRefreshPending) return;
|
||||
_userDataReadRefreshPending = true;
|
||||
StartCoroutine(UserDataReadRefreshCoroutine());
|
||||
}
|
||||
|
||||
IEnumerator UserDataReadRefreshCoroutine()
|
||||
{
|
||||
try
|
||||
{
|
||||
yield return StartCoroutine(FetchUserDataCoroutine(null, true));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_userDataReadRefreshPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>GET /auth/user; updates <see cref="CurrentUser"/> when successful. Callback (success, errorOrNull, userOrNull).</summary>
|
||||
public void GetUserData(Action<bool, string, UserData> onComplete)
|
||||
{
|
||||
StartCoroutine(FetchUserDataCoroutine(onComplete, true));
|
||||
}
|
||||
|
||||
/// <summary>GET /auth/rc-packs (no auth). Callback (success, errorOrNull, jsonOrNull).</summary>
|
||||
public void GetRcPacks(Action<bool, string, string> onComplete)
|
||||
{
|
||||
StartCoroutine(GetRcPacksCoroutine(onComplete));
|
||||
}
|
||||
|
||||
IEnumerator GetRcPacksCoroutine(Action<bool, string, string> onComplete)
|
||||
{
|
||||
string url = AuthBaseUrl + "/auth/rc-packs";
|
||||
using (var request = UnityWebRequest.Get(url))
|
||||
{
|
||||
request.downloadHandler = new DownloadHandlerBuffer();
|
||||
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
||||
|
||||
if (request.result != UnityWebRequest.Result.Success &&
|
||||
request.result != UnityWebRequest.Result.ProtocolError)
|
||||
{
|
||||
onComplete?.Invoke(false, string.IsNullOrEmpty(request.error) ? "Network error" : request.error, null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (request.responseCode < 200 || request.responseCode >= 300)
|
||||
{
|
||||
onComplete?.Invoke(false, string.IsNullOrEmpty(text) ? "Request failed (" + request.responseCode + ")" : text, null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
onComplete?.Invoke(true, null, text);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>POST /auth/purchase-rc with <c>{"pack":"usd_5"|...}</c>. Refreshes profile on success. Callback (success, errorOrNull).</summary>
|
||||
public void PurchaseRcPack(string packId, Action<bool, string> onComplete)
|
||||
{
|
||||
StartCoroutine(PurchaseRcCoroutine(packId, onComplete));
|
||||
}
|
||||
|
||||
IEnumerator PurchaseRcCoroutine(string packId, Action<bool, string> onComplete)
|
||||
{
|
||||
if (string.IsNullOrEmpty(AuthToken))
|
||||
{
|
||||
onComplete?.Invoke(false, "Not signed in");
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!RcPack.IsValid(packId))
|
||||
{
|
||||
onComplete?.Invoke(false, "Invalid pack (use " + RcPack.Usd5 + ", " + RcPack.Usd20 + ", or " + RcPack.Usd50 + ")");
|
||||
yield break;
|
||||
}
|
||||
|
||||
string json = "{\"pack\":\"" + EscapeJsonString(packId) + "\"}";
|
||||
string url = AuthBaseUrl + "/auth/purchase-rc";
|
||||
|
||||
using (var request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST))
|
||||
{
|
||||
byte[] bodyRaw = Encoding.UTF8.GetBytes(json);
|
||||
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
|
||||
request.downloadHandler = new DownloadHandlerBuffer();
|
||||
request.SetRequestHeader("Content-Type", "application/json");
|
||||
request.SetRequestHeader("Authorization", "Bearer " + AuthToken);
|
||||
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
||||
|
||||
if (request.result != UnityWebRequest.Result.Success &&
|
||||
request.result != UnityWebRequest.Result.ProtocolError)
|
||||
{
|
||||
onComplete?.Invoke(false, string.IsNullOrEmpty(request.error) ? "Network error" : request.error);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (request.responseCode < 200 || request.responseCode >= 300)
|
||||
{
|
||||
onComplete?.Invoke(false, string.IsNullOrEmpty(text) ? "Request failed (" + request.responseCode + ")" : text);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
onComplete?.Invoke(false, "Empty response from server");
|
||||
yield break;
|
||||
}
|
||||
|
||||
PurchaseRcApiResponse resp = JsonUtility.FromJson<PurchaseRcApiResponse>(text);
|
||||
|
||||
if (resp != null && resp.ok)
|
||||
{
|
||||
bool refreshOk = false;
|
||||
string refreshErr = null;
|
||||
yield return StartCoroutine(FetchUserDataCoroutine((ok, err, u) =>
|
||||
{
|
||||
refreshOk = ok;
|
||||
refreshErr = err;
|
||||
}, true));
|
||||
|
||||
if (refreshOk)
|
||||
onComplete?.Invoke(true, null);
|
||||
else
|
||||
onComplete?.Invoke(false, string.IsNullOrEmpty(refreshErr) ? "Purchase ok but profile refresh failed" : refreshErr);
|
||||
}
|
||||
else
|
||||
{
|
||||
string err = resp != null && !string.IsNullOrEmpty(resp.error) ? resp.error : "Purchase failed";
|
||||
onComplete?.Invoke(false, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator FetchUserDataCoroutine(Action<bool, string, UserData> onComplete, bool applyToStatic)
|
||||
{
|
||||
if (string.IsNullOrEmpty(AuthToken))
|
||||
{
|
||||
onComplete?.Invoke(false, "Not signed in", null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
string url = AuthBaseUrl + "/auth/user";
|
||||
using (var request = UnityWebRequest.Get(url))
|
||||
{
|
||||
request.downloadHandler = new DownloadHandlerBuffer();
|
||||
request.SetRequestHeader("Authorization", "Bearer " + AuthToken);
|
||||
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
string text = request.downloadHandler != null ? request.downloadHandler.text : "";
|
||||
|
||||
if (request.result != UnityWebRequest.Result.Success &&
|
||||
request.result != UnityWebRequest.Result.ProtocolError)
|
||||
{
|
||||
onComplete?.Invoke(false, string.IsNullOrEmpty(request.error) ? "Network error" : request.error, null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (request.responseCode < 200 || request.responseCode >= 300)
|
||||
{
|
||||
onComplete?.Invoke(false, string.IsNullOrEmpty(text) ? "Request failed (" + request.responseCode + ")" : text, null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
onComplete?.Invoke(false, "Empty response from server", null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
AuthUserApiResponse resp = JsonUtility.FromJson<AuthUserApiResponse>(text);
|
||||
|
||||
if (resp != null && resp.ok && resp.user != null)
|
||||
{
|
||||
if (applyToStatic)
|
||||
ApplyUserData(resp.user);
|
||||
onComplete?.Invoke(true, null, resp.user);
|
||||
}
|
||||
else
|
||||
{
|
||||
string err = resp != null && !string.IsNullOrEmpty(resp.error) ? resp.error : "Request failed";
|
||||
onComplete?.Invoke(false, err, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class UserData
|
||||
{
|
||||
public int id;
|
||||
public string username;
|
||||
public int cc;
|
||||
public int rc;
|
||||
public string created_at;
|
||||
public string last_logged_at;
|
||||
}
|
||||
|
||||
/// <summary>Typical <c>pack</c> ids for <see cref="LoginManager.PurchaseRcPack"/>; prefer <see cref="LoginManager.GetRcPacks"/> for the live list.</summary>
|
||||
public static class RcPack
|
||||
{
|
||||
public const string Usd5 = "usd_5";
|
||||
public const string Usd20 = "usd_20";
|
||||
public const string Usd50 = "usd_50";
|
||||
|
||||
public static bool IsValid(string packId)
|
||||
{
|
||||
return packId == Usd5 || packId == Usd20 || packId == Usd50;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CoinHelper{
|
||||
public static float Format(int coins){
|
||||
int whole = coins / 4;
|
||||
int fraction = coins % 4;
|
||||
return float.Parse(whole + "." + fraction);
|
||||
}
|
||||
|
||||
public static string FormatString(int coins){
|
||||
return Format(coins).ToString("N2");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class MainMenuManager : MonoBehaviour
|
||||
{
|
||||
[Header("Stats")]
|
||||
[SerializeField] private GameObject statsPanel;
|
||||
[SerializeField] private TMP_Text usernameText;
|
||||
[SerializeField] private TMP_Text ccText;
|
||||
[SerializeField] private TMP_Text rcText;
|
||||
|
||||
[Header("Screens")]
|
||||
public GameObject mainMenuScreen;
|
||||
public GameObject gameModesScreen;
|
||||
public GameObject dummyBuyScreen;
|
||||
|
||||
[Header("Dummy Buy")]
|
||||
public Button btnBuyUsd5;
|
||||
public Button btnBuyUsd20;
|
||||
public Button btnBuyUsd50;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if(LoginManager.instance==null){
|
||||
SceneManager.LoadScene(0);
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
LoginManager.OnUserDataUpdated += OnUserUpdated;
|
||||
|
||||
usernameText.text = "";
|
||||
ccText.text = "";
|
||||
rcText.text = "";
|
||||
LoginManager.instance.GetUserData((success, error, user) => {
|
||||
if(success){
|
||||
OnUserUpdated(user);
|
||||
}
|
||||
});
|
||||
|
||||
btnBuyUsd5.onClick.AddListener(() => OnBuy(RcPack.Usd5));
|
||||
btnBuyUsd20.onClick.AddListener(() => OnBuy(RcPack.Usd20));
|
||||
btnBuyUsd50.onClick.AddListener(() => OnBuy(RcPack.Usd50));
|
||||
|
||||
}
|
||||
|
||||
void OnBuy(string packId){
|
||||
LoginManager.instance.PurchaseRcPack(packId, (success, error) => {
|
||||
if(success){
|
||||
OnUserUpdated(LoginManager.CurrentUser);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
LoginManager.OnUserDataUpdated -= OnUserUpdated;
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void OnUserUpdated(UserData user){
|
||||
usernameText.text = user.username;
|
||||
ccText.text = CoinHelper.FormatString(user.cc) + " CC";
|
||||
rcText.text = CoinHelper.FormatString(user.rc) + " RC";
|
||||
}
|
||||
|
||||
|
||||
public void ShowMainMenuScreen(){
|
||||
statsPanel.SetActive(true);
|
||||
mainMenuScreen.SetActive(true);
|
||||
gameModesScreen.SetActive(false);
|
||||
dummyBuyScreen.SetActive(false);
|
||||
}
|
||||
|
||||
public void ShowGameModesScreen(){
|
||||
statsPanel.SetActive(true);
|
||||
mainMenuScreen.SetActive(false);
|
||||
gameModesScreen.SetActive(true);
|
||||
dummyBuyScreen.SetActive(false);
|
||||
}
|
||||
|
||||
public void ShowDummyBuyScreen(){
|
||||
statsPanel.SetActive(false);
|
||||
mainMenuScreen.SetActive(false);
|
||||
gameModesScreen.SetActive(false);
|
||||
dummyBuyScreen.SetActive(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d216f08d4b95494ba7875cca68b440a
|
||||
@@ -0,0 +1,45 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class TabGroup : MonoBehaviour
|
||||
{
|
||||
public Button[] buttons;
|
||||
public GameObject[] contents;
|
||||
public int selectedIndex = 0;
|
||||
|
||||
|
||||
void OnValidate(){
|
||||
if(buttons == null || buttons.Length == 0){
|
||||
buttons=GetComponentsInChildren<Button>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Awake()
|
||||
{
|
||||
SetSelectedIndex(selectedIndex);
|
||||
|
||||
for(int i=0; i < buttons.Length; i++)
|
||||
{
|
||||
int index = i;
|
||||
buttons[i].onClick.AddListener(() => SetSelectedIndex(index));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetSelectedIndex(int index)
|
||||
{
|
||||
if(index < 0 || index >= buttons.Length) return;
|
||||
|
||||
selectedIndex = index;
|
||||
for(int i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
buttons[i].targetGraphic.color = i == index ? buttons[i].colors.normalColor : buttons[i].colors.disabledColor;
|
||||
try{
|
||||
contents[i].SetActive(i == index);
|
||||
}catch{
|
||||
Debug.LogError("Content not found for index: " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d961ea20492fa0f448fbedacd3495911
|
||||
Reference in New Issue
Block a user