Keep local version of files

This commit is contained in:
2026-10-08 00:49:10 +05:30
parent 7fa0621ff8
commit 053665c690
7343 changed files with 5470578 additions and 5470528 deletions
+8 -8
View File
@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 07c0cc0c5a337f24986822f356e2f9f2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 07c0cc0c5a337f24986822f356e2f9f2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+39 -39
View File
@@ -1,39 +1,39 @@
using UnityEngine;
[CreateAssetMenu(fileName = "AudioDirectory", menuName = "Scriptable Objects/AudioDirectory")]
public class AudioDirectory : ScriptableObject
{
[Header("Scenes")]
public string introSceneName = "Intro";
public string menuSceneName = "MainMenu";
public string gameSceneName = "Game";
[Header("Music")]
public AudioClip introMusic;
public AudioClip menuMusic;
public AudioClip gameMusic;
[Header("SFX")]
public AudioClip[] coinsSfx;
[Header("Ball")]
public AudioClip[] ballHits;
public AudioClip[] puckHits;
public AudioClip[] wallHits;
public AudioClip[] ballHitNet;
[Header("Game")]
public AudioClip[] timerBeep;
public AudioClip[] matchMakingBeep;
public AudioClip[] matchFound;
[Header("Referee")]
public AudioClip[] refereeGoal;
public AudioClip[] refereeWhistle;
[Header("Crowd")]
public AudioClip[] crowdAmbientNoise;
public AudioClip[] crowdAmbientNoiseNearGoal;
public AudioClip[] crowdCheering;
public AudioClip[] crowdNoiseMatchStart;
}
using UnityEngine;
[CreateAssetMenu(fileName = "AudioDirectory", menuName = "Scriptable Objects/AudioDirectory")]
public class AudioDirectory : ScriptableObject
{
[Header("Scenes")]
public string introSceneName = "Intro";
public string menuSceneName = "MainMenu";
public string gameSceneName = "Game";
[Header("Music")]
public AudioClip introMusic;
public AudioClip menuMusic;
public AudioClip gameMusic;
[Header("SFX")]
public AudioClip[] coinsSfx;
[Header("Ball")]
public AudioClip[] ballHits;
public AudioClip[] puckHits;
public AudioClip[] wallHits;
public AudioClip[] ballHitNet;
[Header("Game")]
public AudioClip[] timerBeep;
public AudioClip[] matchMakingBeep;
public AudioClip[] matchFound;
[Header("Referee")]
public AudioClip[] refereeGoal;
public AudioClip[] refereeWhistle;
[Header("Crowd")]
public AudioClip[] crowdAmbientNoise;
public AudioClip[] crowdAmbientNoiseNearGoal;
public AudioClip[] crowdCheering;
public AudioClip[] crowdNoiseMatchStart;
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 776cfba92ccdba54d8129b1338fbcc6c
File diff suppressed because it is too large Load Diff
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 6aae5977db663834f86b46979365fa92
+21 -21
View File
@@ -1,21 +1,21 @@
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 int RcToCoins(float coins){
string s = coins.ToString("N1");
int whole = int.Parse(s.Split('.')[0]);
int fraction = int.Parse(s.Split('.')[1]);
return (whole * 4) + fraction;
}
public static string FormatString(int coins)
{
return Format(coins).ToString("N1");
}
}
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 int RcToCoins(float coins){
string s = coins.ToString("N1");
int whole = int.Parse(s.Split('.')[0]);
int fraction = int.Parse(s.Split('.')[1]);
return (whole * 4) + fraction;
}
public static string FormatString(int coins)
{
return Format(coins).ToString("N1");
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 5bf3c59f7c1a7e640a05edc5f9828abd
+19 -19
View File
@@ -1,19 +1,19 @@
using System.Net;
using System.Net.Sockets;
public static class GetAvailablePort
{
public static int GetAvailableUdpPort()
{
using var socket = new Socket(
AddressFamily.InterNetwork,
SocketType.Dgram,
ProtocolType.Udp
);
socket.Bind(new IPEndPoint(IPAddress.Any, 0));
return ((IPEndPoint)socket.LocalEndPoint).Port;
}
}
using System.Net;
using System.Net.Sockets;
public static class GetAvailablePort
{
public static int GetAvailableUdpPort()
{
using var socket = new Socket(
AddressFamily.InterNetwork,
SocketType.Dgram,
ProtocolType.Udp
);
socket.Bind(new IPEndPoint(IPAddress.Any, 0));
return ((IPEndPoint)socket.LocalEndPoint).Port;
}
}
+229 -229
View File
@@ -1,229 +1,229 @@
using System;
using DG.Tweening;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class MessageBoxDialog : MonoBehaviour
{
public const float ANIMATION_DURATION = 0.25f;
/// <summary>Above game/menu/loader canvases; below reconnect (500).</summary>
const int OverlaySortingOrder = 400;
public static MessageBoxDialog instance;
public static bool IsAvailable => instance != null;
public RectTransform dimmer;
public RectTransform basicMessageBox;
[Header("MessageBox")]
public RectTransform messageBox;
public TMP_Text txtTitle, txtMessage;
public Button btnOk, btnYes, btnNo;
Image dimmerImg;
TMP_Text basicMessageText;
Action<bool> onYesOrNo;
Sequence showHideSequence;
Canvas overlayCanvas;
void Awake()
{
if (instance != null && instance != this)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(gameObject);
overlayCanvas = GetComponent<Canvas>();
if (overlayCanvas != null)
overlayCanvas.sortingOrder = OverlaySortingOrder;
if (dimmer != null)
dimmerImg = dimmer.GetComponent<Image>();
if (basicMessageBox != null)
basicMessageText = basicMessageBox.GetComponentInChildren<TMP_Text>();
}
void OnEnable()
{
if (instance != this)
return;
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnDisable()
{
if (instance != this)
return;
SceneManager.sceneLoaded -= OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
transform.SetAsLastSibling();
}
public static void ShowBasic(string title)
{
if (instance == null)
{
Debug.LogWarning("MessageBoxDialog missing: " + title);
return;
}
instance.ShowMessageBox(title);
}
public static void Show(string title, string message, Action<bool> onResult = null)
{
if (instance == null)
{
Debug.LogWarning("MessageBoxDialog missing: " + title);
return;
}
instance.ShowMessageBox(title, message, onResult);
}
void OnDestroy()
{
KillTweens();
if (instance == this)
instance = null;
}
void Start()
{
Button basicButton = basicMessageBox != null ? basicMessageBox.GetComponent<Button>() : null;
if (basicButton != null)
basicButton.onClick.AddListener(HideMessageBox);
if (btnOk != null)
btnOk.onClick.AddListener(HideMessageBox);
if (btnYes != null)
btnYes.onClick.AddListener(() => OnQuestionDialog(true));
if (btnNo != null)
btnNo.onClick.AddListener(() => OnQuestionDialog(false));
}
void OnQuestionDialog(bool val)
{
Action<bool> callback = onYesOrNo;
onYesOrNo = null;
HideMessageBox();
callback?.Invoke(val);
}
public void ShowMessageBox(string title)
{
PrepareShow();
HideBoxImmediate(messageBox);
if (basicMessageText != null)
basicMessageText.text = title;
AnimateIn(basicMessageBox);
}
public void ShowMessageBox(string title, string message, Action<bool> onResult = null)
{
PrepareShow();
HideBoxImmediate(basicMessageBox);
txtTitle.text = title;
txtMessage.text = message;
bool isQuestion = onResult != null;
btnNo.gameObject.SetActive(isQuestion);
btnYes.gameObject.SetActive(isQuestion);
btnOk.gameObject.SetActive(!isQuestion);
onYesOrNo = onResult;
AnimateIn(messageBox);
}
public void HideMessageBox()
{
bool basicActive = basicMessageBox != null && basicMessageBox.gameObject.activeSelf;
bool fullActive = messageBox != null && messageBox.gameObject.activeSelf;
if (!basicActive && !fullActive)
return;
if (basicActive && fullActive)
HideBoxImmediate(basicMessageBox);
AnimateOut(basicActive && !fullActive ? basicMessageBox : messageBox);
}
void PrepareShow()
{
KillTweens();
onYesOrNo = null;
transform.SetAsLastSibling();
dimmer.gameObject.SetActive(true);
if (dimmerImg != null)
{
Color c = dimmerImg.color;
dimmerImg.color = new Color(c.r, c.g, c.b, 0f);
}
}
void HideBoxImmediate(RectTransform box)
{
if (box == null)
return;
box.DOKill();
box.localScale = Vector3.zero;
box.gameObject.SetActive(false);
}
void AnimateIn(RectTransform box)
{
box.localScale = Vector3.zero;
box.gameObject.SetActive(true);
showHideSequence = DOTween.Sequence().SetUpdate(true);
if (dimmerImg != null)
showHideSequence.Join(dimmerImg.DOFade(0.8f, ANIMATION_DURATION));
showHideSequence.Join(box.DOScale(1f, ANIMATION_DURATION).SetEase(Ease.OutBack));
}
void AnimateOut(RectTransform box)
{
KillTweens();
RectTransform targetBox = box;
showHideSequence = DOTween.Sequence().SetUpdate(true);
if (dimmerImg != null)
showHideSequence.Join(dimmerImg.DOFade(0f, ANIMATION_DURATION));
showHideSequence.Join(targetBox.DOScale(0f, ANIMATION_DURATION).SetEase(Ease.InBack));
showHideSequence.OnComplete(() =>
{
if (targetBox != null)
targetBox.gameObject.SetActive(false);
if (dimmer != null)
dimmer.gameObject.SetActive(false);
});
}
void KillTweens()
{
if (showHideSequence != null && showHideSequence.IsActive())
showHideSequence.Kill();
showHideSequence = null;
if (dimmerImg != null)
dimmerImg.DOKill();
if (basicMessageBox != null)
basicMessageBox.DOKill();
if (messageBox != null)
messageBox.DOKill();
}
}
using System;
using DG.Tweening;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class MessageBoxDialog : MonoBehaviour
{
public const float ANIMATION_DURATION = 0.25f;
/// <summary>Above game/menu/loader canvases; below reconnect (500).</summary>
const int OverlaySortingOrder = 400;
public static MessageBoxDialog instance;
public static bool IsAvailable => instance != null;
public RectTransform dimmer;
public RectTransform basicMessageBox;
[Header("MessageBox")]
public RectTransform messageBox;
public TMP_Text txtTitle, txtMessage;
public Button btnOk, btnYes, btnNo;
Image dimmerImg;
TMP_Text basicMessageText;
Action<bool> onYesOrNo;
Sequence showHideSequence;
Canvas overlayCanvas;
void Awake()
{
if (instance != null && instance != this)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(gameObject);
overlayCanvas = GetComponent<Canvas>();
if (overlayCanvas != null)
overlayCanvas.sortingOrder = OverlaySortingOrder;
if (dimmer != null)
dimmerImg = dimmer.GetComponent<Image>();
if (basicMessageBox != null)
basicMessageText = basicMessageBox.GetComponentInChildren<TMP_Text>();
}
void OnEnable()
{
if (instance != this)
return;
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnDisable()
{
if (instance != this)
return;
SceneManager.sceneLoaded -= OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
transform.SetAsLastSibling();
}
public static void ShowBasic(string title)
{
if (instance == null)
{
Debug.LogWarning("MessageBoxDialog missing: " + title);
return;
}
instance.ShowMessageBox(title);
}
public static void Show(string title, string message, Action<bool> onResult = null)
{
if (instance == null)
{
Debug.LogWarning("MessageBoxDialog missing: " + title);
return;
}
instance.ShowMessageBox(title, message, onResult);
}
void OnDestroy()
{
KillTweens();
if (instance == this)
instance = null;
}
void Start()
{
Button basicButton = basicMessageBox != null ? basicMessageBox.GetComponent<Button>() : null;
if (basicButton != null)
basicButton.onClick.AddListener(HideMessageBox);
if (btnOk != null)
btnOk.onClick.AddListener(HideMessageBox);
if (btnYes != null)
btnYes.onClick.AddListener(() => OnQuestionDialog(true));
if (btnNo != null)
btnNo.onClick.AddListener(() => OnQuestionDialog(false));
}
void OnQuestionDialog(bool val)
{
Action<bool> callback = onYesOrNo;
onYesOrNo = null;
HideMessageBox();
callback?.Invoke(val);
}
public void ShowMessageBox(string title)
{
PrepareShow();
HideBoxImmediate(messageBox);
if (basicMessageText != null)
basicMessageText.text = title;
AnimateIn(basicMessageBox);
}
public void ShowMessageBox(string title, string message, Action<bool> onResult = null)
{
PrepareShow();
HideBoxImmediate(basicMessageBox);
txtTitle.text = title;
txtMessage.text = message;
bool isQuestion = onResult != null;
btnNo.gameObject.SetActive(isQuestion);
btnYes.gameObject.SetActive(isQuestion);
btnOk.gameObject.SetActive(!isQuestion);
onYesOrNo = onResult;
AnimateIn(messageBox);
}
public void HideMessageBox()
{
bool basicActive = basicMessageBox != null && basicMessageBox.gameObject.activeSelf;
bool fullActive = messageBox != null && messageBox.gameObject.activeSelf;
if (!basicActive && !fullActive)
return;
if (basicActive && fullActive)
HideBoxImmediate(basicMessageBox);
AnimateOut(basicActive && !fullActive ? basicMessageBox : messageBox);
}
void PrepareShow()
{
KillTweens();
onYesOrNo = null;
transform.SetAsLastSibling();
dimmer.gameObject.SetActive(true);
if (dimmerImg != null)
{
Color c = dimmerImg.color;
dimmerImg.color = new Color(c.r, c.g, c.b, 0f);
}
}
void HideBoxImmediate(RectTransform box)
{
if (box == null)
return;
box.DOKill();
box.localScale = Vector3.zero;
box.gameObject.SetActive(false);
}
void AnimateIn(RectTransform box)
{
box.localScale = Vector3.zero;
box.gameObject.SetActive(true);
showHideSequence = DOTween.Sequence().SetUpdate(true);
if (dimmerImg != null)
showHideSequence.Join(dimmerImg.DOFade(0.8f, ANIMATION_DURATION));
showHideSequence.Join(box.DOScale(1f, ANIMATION_DURATION).SetEase(Ease.OutBack));
}
void AnimateOut(RectTransform box)
{
KillTweens();
RectTransform targetBox = box;
showHideSequence = DOTween.Sequence().SetUpdate(true);
if (dimmerImg != null)
showHideSequence.Join(dimmerImg.DOFade(0f, ANIMATION_DURATION));
showHideSequence.Join(targetBox.DOScale(0f, ANIMATION_DURATION).SetEase(Ease.InBack));
showHideSequence.OnComplete(() =>
{
if (targetBox != null)
targetBox.gameObject.SetActive(false);
if (dimmer != null)
dimmer.gameObject.SetActive(false);
});
}
void KillTweens()
{
if (showHideSequence != null && showHideSequence.IsActive())
showHideSequence.Kill();
showHideSequence = null;
if (dimmerImg != null)
dimmerImg.DOKill();
if (basicMessageBox != null)
basicMessageBox.DOKill();
if (messageBox != null)
messageBox.DOKill();
}
}
+10 -10
View File
@@ -1,10 +1,10 @@
using UnityEngine;
public class OpenLink : MonoBehaviour
{
public string url = "https://www.google.com";
public void Open(){
Application.OpenURL(url);
}
}
using UnityEngine;
public class OpenLink : MonoBehaviour
{
public string url = "https://www.google.com";
public void Open(){
Application.OpenURL(url);
}
}
+12 -12
View File
@@ -1,12 +1,12 @@
using UnityEngine;
public class RotateAround : MonoBehaviour
{
public Vector3 axis = Vector3.up;
public float speed = 10f;
public Vector3 center = Vector3.zero;
void Update()
{
transform.RotateAround(transform.position + center, axis, speed * Time.deltaTime);
}
}
using UnityEngine;
public class RotateAround : MonoBehaviour
{
public Vector3 axis = Vector3.up;
public float speed = 10f;
public Vector3 center = Vector3.zero;
void Update()
{
transform.RotateAround(transform.position + center, axis, speed * Time.deltaTime);
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: f67c475e52308cd42a56f95fd38c86c4
+8 -8
View File
@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 0b498645b8402b742a93101fc04df79f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 0b498645b8402b742a93101fc04df79f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+236 -236
View File
@@ -1,236 +1,236 @@
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
using UnityEngine.SceneManagement;
using System.Collections;
using TMPro;
using Mirror;
public class LevelLoadManager : MonoBehaviour
{
public static LevelLoadManager instance;
public Image contentImage;
public float transitionDuration = 0.5f;
CanvasGroup canvasGroup;
public bool isShowing => canvasGroup.alpha == 1;
[Header("Matchmade UI")]
public GameObject matchmadeUI;
public GameObject p1_red,p2_red,p1_blue,p2_blue;
public Image p1_icon, p2_icon;
public TMP_Text p1_name, p2_name;
public TMP_Text p1_l10, p2_l10;
public Sprite redIcon, blueIcon;
public TMP_Text txtMiddle;
public TMP_Text txtRcPrize;
[Header("Game Over UI")]
public GameObject p1_winner;
public GameObject p2_winner;
void Awake()
{
if(instance != null){
Destroy(gameObject);
return;
}
canvasGroup = GetComponent<CanvasGroup>();
instance = this;
DontDestroyOnLoad(gameObject);
Hide();
matchmadeUI.SetActive(false);
}
public static void LoadLevel(string levelName){
instance.loadLevel(levelName);
}
void loadLevel(string levelName){
StartCoroutine(CoroutineLoadLevel(levelName));
}
/// <summary>When true, Main Menu bootstrap (matchmaker settings) owns the overlay; suppress duplicate work in <see cref="MainMenuManager"/>.</summary>
public static bool BlocksMainMenuSettingsBootstrap { get; private set; }
IEnumerator CoroutineLoadLevel(string levelName){
instance.Show();
yield return new WaitForSeconds(transitionDuration);
if(matchmadeUI.activeSelf){
yield return new WaitForSeconds(3f);
}
bool mainMenuDestination = levelName == MainMenuManager.MainMenuSceneName;
if (mainMenuDestination)
BlocksMainMenuSettingsBootstrap = true;
yield return AddressableSceneLoader.LoadRoutine(levelName);
if (mainMenuDestination)
{
try
{
MainMenuManager menu = UnityEngine.Object.FindObjectOfType<MainMenuManager>();
if (menu != null)
yield return menu.FinishLoadingScreenAndApplySettings();
}
finally
{
BlocksMainMenuSettingsBootstrap = false;
}
}
instance.Hide();
}
public void Show(){
if(isShowing){return;} //already is showing
canvasGroup.blocksRaycasts=true;
canvasGroup.interactable=true;
contentImage.fillOrigin = 0;
canvasGroup.DOFade(1, transitionDuration).SetEase(Ease.OutSine);
contentImage.DOFillAmount(1, transitionDuration).SetEase(Ease.OutSine);
}
public void Hide(){
canvasGroup.blocksRaycasts=false;
canvasGroup.interactable=false;
contentImage.fillOrigin = 1;
canvasGroup.DOFade(0, transitionDuration).SetEase(Ease.InSine);
contentImage.DOFillAmount(0, transitionDuration).SetEase(Ease.InSine).OnComplete(() => {
if(gameOver){
gameOver = false;
matchmadeUI.SetActive(false);
}
});
}
/// <summary>
/// Drops the last match from the loading screen. This object survives scene loads, so logout must call this
/// or the next login shows the previous matchup while the overlay is up.
/// </summary>
public void ResetSession()
{
StopAllCoroutines();
BlocksMainMenuSettingsBootstrap = false;
gameOver = false;
myPlayer = null;
opponentPlayer = null;
canvasGroup.DOKill();
contentImage.DOKill();
canvasGroup.alpha = 0f;
canvasGroup.blocksRaycasts = false;
canvasGroup.interactable = false;
contentImage.fillAmount = 0f;
matchmadeUI.SetActive(false);
p1_winner.SetActive(false);
p2_winner.SetActive(false);
txtMiddle.text = "VS";
p1_name.text = "";
p2_name.text = "";
p1_l10.text = "";
p2_l10.text = "";
txtRcPrize.text = "";
}
Team myTeam = Team.Red;
public void SetupRematch(int rc_prize, int port, int gameId){
txtRcPrize.text = CoinHelper.FormatString(rc_prize) + " RC";
}
public void SetupMatchMade(Team myTeam_, string opponentName, string myName, string myL10, string opponentL10, string rcPrize){
myTeam = myTeam_;
matchmadeUI.SetActive(true);
p1_winner.SetActive(false);
p2_winner.SetActive(false);
txtMiddle.text = "VS";
if(myTeam == Team.Red){
p1_red.SetActive(true);
p1_blue.SetActive(false);
p2_red.SetActive(false);
p2_blue.SetActive(true);
p1_icon.sprite = redIcon;
p2_icon.sprite = blueIcon;
}else{
p1_red.SetActive(false);
p1_blue.SetActive(true);
p2_red.SetActive(true);
p2_blue.SetActive(false);
p1_icon.sprite = blueIcon;
p2_icon.sprite = redIcon;
}
p1_name.text = opponentName;
p2_name.text = myName;
p1_l10.text = myL10;
p2_l10.text = opponentL10;
txtRcPrize.text = rcPrize;
}
public static MatchmadePlayer myPlayer;
public static MatchmadePlayer opponentPlayer;
public void SetupMatchMade(Team myTeam_, MatchmadePlayer myPlayer_, MatchmadePlayer opponentPlayer_, string rcPrize){
myTeam = myTeam_;
myPlayer = myPlayer_;
opponentPlayer = opponentPlayer_;
string opponentName = opponentPlayer_.Name;
string myName = myPlayer_.Name;
string myL10 = $"L10 {myPlayer.l10_wins} -{myPlayer.l10_losses}";
string opponentL10 = $"L10 {opponentPlayer.l10_wins} -{opponentPlayer.l10_losses}";
SetupMatchMade(myTeam, myName, opponentName, myL10, opponentL10, rcPrize);
}
bool gameOver = false;
public void SetupGameOver(Team winningTeam, int redScore, int blueScore){
gameOver = true;
txtMiddle.text = $"{(myTeam == Team.Red ? redScore : blueScore)} FINAL {(myTeam == Team.Red ? blueScore : redScore)}";
if(myTeam == winningTeam){
p1_winner.SetActive(true);
p2_winner.SetActive(false);
}else{
p1_winner.SetActive(false);
p2_winner.SetActive(true);
}
p1_l10.text = $"L10 {myPlayer.l10_wins}-{myPlayer.l10_losses}";
p2_l10.text = $"L10 {opponentPlayer.l10_wins}-{opponentPlayer.l10_losses}";
}
public void Leave(string levelName = "MainMenu"){
NetManager.StopNetworkingForSceneChange();
StartCoroutine(CoroutineWaitTillDisconnection(levelName));
}
IEnumerator CoroutineWaitTillDisconnection(string levelName){
Show(); //Load the UI until the level is loaded
const float timeoutSeconds = 5f;
float elapsed = 0f;
while ((NetworkClient.isConnected || NetworkClient.active || NetworkServer.active) && elapsed < timeoutSeconds)
{
elapsed += Time.unscaledDeltaTime;
yield return null;
}
yield return new WaitForSeconds(0.25f);
LoadLevel(levelName);
}
}
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
using UnityEngine.SceneManagement;
using System.Collections;
using TMPro;
using Mirror;
public class LevelLoadManager : MonoBehaviour
{
public static LevelLoadManager instance;
public Image contentImage;
public float transitionDuration = 0.5f;
CanvasGroup canvasGroup;
public bool isShowing => canvasGroup.alpha == 1;
[Header("Matchmade UI")]
public GameObject matchmadeUI;
public GameObject p1_red,p2_red,p1_blue,p2_blue;
public Image p1_icon, p2_icon;
public TMP_Text p1_name, p2_name;
public TMP_Text p1_l10, p2_l10;
public Sprite redIcon, blueIcon;
public TMP_Text txtMiddle;
public TMP_Text txtRcPrize;
[Header("Game Over UI")]
public GameObject p1_winner;
public GameObject p2_winner;
void Awake()
{
if(instance != null){
Destroy(gameObject);
return;
}
canvasGroup = GetComponent<CanvasGroup>();
instance = this;
DontDestroyOnLoad(gameObject);
Hide();
matchmadeUI.SetActive(false);
}
public static void LoadLevel(string levelName){
instance.loadLevel(levelName);
}
void loadLevel(string levelName){
StartCoroutine(CoroutineLoadLevel(levelName));
}
/// <summary>When true, Main Menu bootstrap (matchmaker settings) owns the overlay; suppress duplicate work in <see cref="MainMenuManager"/>.</summary>
public static bool BlocksMainMenuSettingsBootstrap { get; private set; }
IEnumerator CoroutineLoadLevel(string levelName){
instance.Show();
yield return new WaitForSeconds(transitionDuration);
if(matchmadeUI.activeSelf){
yield return new WaitForSeconds(3f);
}
bool mainMenuDestination = levelName == MainMenuManager.MainMenuSceneName;
if (mainMenuDestination)
BlocksMainMenuSettingsBootstrap = true;
yield return AddressableSceneLoader.LoadRoutine(levelName);
if (mainMenuDestination)
{
try
{
MainMenuManager menu = UnityEngine.Object.FindObjectOfType<MainMenuManager>();
if (menu != null)
yield return menu.FinishLoadingScreenAndApplySettings();
}
finally
{
BlocksMainMenuSettingsBootstrap = false;
}
}
instance.Hide();
}
public void Show(){
if(isShowing){return;} //already is showing
canvasGroup.blocksRaycasts=true;
canvasGroup.interactable=true;
contentImage.fillOrigin = 0;
canvasGroup.DOFade(1, transitionDuration).SetEase(Ease.OutSine);
contentImage.DOFillAmount(1, transitionDuration).SetEase(Ease.OutSine);
}
public void Hide(){
canvasGroup.blocksRaycasts=false;
canvasGroup.interactable=false;
contentImage.fillOrigin = 1;
canvasGroup.DOFade(0, transitionDuration).SetEase(Ease.InSine);
contentImage.DOFillAmount(0, transitionDuration).SetEase(Ease.InSine).OnComplete(() => {
if(gameOver){
gameOver = false;
matchmadeUI.SetActive(false);
}
});
}
/// <summary>
/// Drops the last match from the loading screen. This object survives scene loads, so logout must call this
/// or the next login shows the previous matchup while the overlay is up.
/// </summary>
public void ResetSession()
{
StopAllCoroutines();
BlocksMainMenuSettingsBootstrap = false;
gameOver = false;
myPlayer = null;
opponentPlayer = null;
canvasGroup.DOKill();
contentImage.DOKill();
canvasGroup.alpha = 0f;
canvasGroup.blocksRaycasts = false;
canvasGroup.interactable = false;
contentImage.fillAmount = 0f;
matchmadeUI.SetActive(false);
p1_winner.SetActive(false);
p2_winner.SetActive(false);
txtMiddle.text = "VS";
p1_name.text = "";
p2_name.text = "";
p1_l10.text = "";
p2_l10.text = "";
txtRcPrize.text = "";
}
Team myTeam = Team.Red;
public void SetupRematch(int rc_prize, int port, int gameId){
txtRcPrize.text = CoinHelper.FormatString(rc_prize) + " RC";
}
public void SetupMatchMade(Team myTeam_, string opponentName, string myName, string myL10, string opponentL10, string rcPrize){
myTeam = myTeam_;
matchmadeUI.SetActive(true);
p1_winner.SetActive(false);
p2_winner.SetActive(false);
txtMiddle.text = "VS";
if(myTeam == Team.Red){
p1_red.SetActive(true);
p1_blue.SetActive(false);
p2_red.SetActive(false);
p2_blue.SetActive(true);
p1_icon.sprite = redIcon;
p2_icon.sprite = blueIcon;
}else{
p1_red.SetActive(false);
p1_blue.SetActive(true);
p2_red.SetActive(true);
p2_blue.SetActive(false);
p1_icon.sprite = blueIcon;
p2_icon.sprite = redIcon;
}
p1_name.text = opponentName;
p2_name.text = myName;
p1_l10.text = myL10;
p2_l10.text = opponentL10;
txtRcPrize.text = rcPrize;
}
public static MatchmadePlayer myPlayer;
public static MatchmadePlayer opponentPlayer;
public void SetupMatchMade(Team myTeam_, MatchmadePlayer myPlayer_, MatchmadePlayer opponentPlayer_, string rcPrize){
myTeam = myTeam_;
myPlayer = myPlayer_;
opponentPlayer = opponentPlayer_;
string opponentName = opponentPlayer_.Name;
string myName = myPlayer_.Name;
string myL10 = $"L10 {myPlayer.l10_wins} -{myPlayer.l10_losses}";
string opponentL10 = $"L10 {opponentPlayer.l10_wins} -{opponentPlayer.l10_losses}";
SetupMatchMade(myTeam, myName, opponentName, myL10, opponentL10, rcPrize);
}
bool gameOver = false;
public void SetupGameOver(Team winningTeam, int redScore, int blueScore){
gameOver = true;
txtMiddle.text = $"{(myTeam == Team.Red ? redScore : blueScore)} FINAL {(myTeam == Team.Red ? blueScore : redScore)}";
if(myTeam == winningTeam){
p1_winner.SetActive(true);
p2_winner.SetActive(false);
}else{
p1_winner.SetActive(false);
p2_winner.SetActive(true);
}
p1_l10.text = $"L10 {myPlayer.l10_wins}-{myPlayer.l10_losses}";
p2_l10.text = $"L10 {opponentPlayer.l10_wins}-{opponentPlayer.l10_losses}";
}
public void Leave(string levelName = "MainMenu"){
NetManager.StopNetworkingForSceneChange();
StartCoroutine(CoroutineWaitTillDisconnection(levelName));
}
IEnumerator CoroutineWaitTillDisconnection(string levelName){
Show(); //Load the UI until the level is loaded
const float timeoutSeconds = 5f;
float elapsed = 0f;
while ((NetworkClient.isConnected || NetworkClient.active || NetworkServer.active) && elapsed < timeoutSeconds)
{
elapsed += Time.unscaledDeltaTime;
yield return null;
}
yield return new WaitForSeconds(0.25f);
LoadLevel(levelName);
}
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: be7470bea6c16b9439ddf2a399e94cfc
+30 -30
View File
@@ -1,30 +1,30 @@
using UnityEngine;
public class SetActiveOnEditor : MonoBehaviour
{
public Component[] components;
public GameObject[] gameObjects;
public bool activeOnEditor = true;
void Start()
{
#if UNITY_EDITOR
foreach(var component in components)
{
component.gameObject.SetActive(activeOnEditor);
}
foreach(var gameObject in gameObjects)
{
gameObject.SetActive(activeOnEditor);
}
#else
foreach(var component in components)
{
component.gameObject.SetActive(!activeOnEditor);
}
foreach(var gameObject in gameObjects)
{
gameObject.SetActive(!activeOnEditor);
}
#endif
}
}
using UnityEngine;
public class SetActiveOnEditor : MonoBehaviour
{
public Component[] components;
public GameObject[] gameObjects;
public bool activeOnEditor = true;
void Start()
{
#if UNITY_EDITOR
foreach(var component in components)
{
component.gameObject.SetActive(activeOnEditor);
}
foreach(var gameObject in gameObjects)
{
gameObject.SetActive(activeOnEditor);
}
#else
foreach(var component in components)
{
component.gameObject.SetActive(!activeOnEditor);
}
foreach(var gameObject in gameObjects)
{
gameObject.SetActive(!activeOnEditor);
}
#endif
}
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 333a4e464b877d44abda3fce877fa3be
+8 -8
View File
@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: fd2602c01ff00f047aab3e93a95bb230
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: fd2602c01ff00f047aab3e93a95bb230
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+12 -12
View File
@@ -1,12 +1,12 @@
/// <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;
}
}
/// <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;
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 241f412d7db597748af9c1138e640e0e
+14 -14
View File
@@ -1,14 +1,14 @@
using System;
using UnityEngine;
[Serializable]
public class UserData
{
public int id;
public string username;
public string email;
public int cc;
public int rc;
public string created_at;
public string last_logged_at;
}
using System;
using UnityEngine;
[Serializable]
public class UserData
{
public int id;
public string username;
public string email;
public int cc;
public int rc;
public string created_at;
public string last_logged_at;
}
+1 -1
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 7fa72c32d6a0d1e4abc0b724b1988d8c
+8 -8
View File
@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 031adef9a13d67c48b0969916c4e2160
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 031adef9a13d67c48b0969916c4e2160
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,12 +1,12 @@
using System;
using UnityEngine;
[Serializable]
public class AuthApiResponse
{
public bool ok;
public string error;
public string code;
public string token;
public UserData user;
}
using System;
using UnityEngine;
[Serializable]
public class AuthApiResponse
{
public bool ok;
public string error;
public string code;
public string token;
public UserData user;
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 91e26300ebc95df47949bbd3744bf975
@@ -1,10 +1,10 @@
using System;
using UnityEngine;
[Serializable]
public class AuthUserApiResponse
{
public bool ok;
public string error;
public UserData user;
}
using System;
using UnityEngine;
[Serializable]
public class AuthUserApiResponse
{
public bool ok;
public string error;
public UserData user;
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: d82de2425d5fb6448bc038942fb1aa88
@@ -1,254 +1,254 @@
using System;
using UnityEngine;
[Serializable]
public class MatchmadePlayer
{
public string Name;
public long LastSeen;
public int l10_wins;
public int l10_losses;
public int UserId;
/// <summary>Rocket Credits from server (e.g. GET /players/:id/rc). Cached client-side.</summary>
public float rc;
}
/// <summary>Per-team block from matchmaker GET / when a side is in queue or match is ready.</summary>
[Serializable]
public class MatchmadeTeamPayload
{
public MatchmadePlayer[] Players;
public string GameName;
public int Port;
public long InitTime;
public bool instance_started;
public int match_id;
public int entry_fee;
public int rc_prize;
public string your_team;
}
/// <summary>Envelope from matchmaker fill / rematch success (and in-progress queue state).</summary>
[Serializable]
public class MatchmadeResponse
{
public bool ok;
public int entry_fee;
public int rc_prize;
public MatchmadeTeamPayload for_red;
public MatchmadeTeamPayload for_blue;
/// <summary>Optional root field: which nested block is for this client when both <see cref="for_red"/> and <see cref="for_blue"/> list the same players.</summary>
public string your_team;
public static bool TeamPayloadHasPort(MatchmadeTeamPayload p) =>
p != null && p.Port > 0;
/// <summary>True when both sides are present on the same dedicated instance (2v2-style duplicate rosters).</summary>
public static bool IsMatchReadyDuplicateRosters(MatchmadeResponse e)
{
if (e == null || !e.ok)
return false;
if (!TeamPayloadHasPort(e.for_red) || !TeamPayloadHasPort(e.for_blue))
return false;
if (e.for_red.Players == null || e.for_blue.Players == null)
return false;
if (e.for_red.match_id != e.for_blue.match_id || e.for_red.Port != e.for_blue.Port)
return false;
return e.for_red.Players.Length >= 2 && e.for_blue.Players.Length >= 2;
}
/// <summary>True when each side lists at least one player and together they form a full 1v1 (disjoint user sets, same instance).</summary>
public static bool IsMatchReadySplitRosters(MatchmadeResponse e)
{
if (e == null || !e.ok)
return false;
if (!TeamPayloadHasPort(e.for_red) || !TeamPayloadHasPort(e.for_blue))
return false;
if (e.for_red.Players == null || e.for_blue.Players == null)
return false;
if (e.for_red.match_id != e.for_blue.match_id || e.for_red.Port != e.for_blue.Port)
return false;
if (e.for_red.Players.Length < 1 || e.for_blue.Players.Length < 1)
return false;
int a = e.for_red.Players[0].UserId;
int b = e.for_blue.Players[0].UserId;
if (a == b)
return false;
if (e.for_red.Players.Length == 1 && e.for_blue.Players.Length == 1)
return true;
// Wider payloads: require no user id appearing on both sides
foreach (var pr in e.for_red.Players)
{
foreach (var pb in e.for_blue.Players)
{
if (pr.UserId == pb.UserId)
return false;
}
}
return true;
}
public static bool IsMatchReady(MatchmadeResponse e) =>
IsMatchReadyDuplicateRosters(e) || IsMatchReadySplitRosters(e);
static bool PayloadContainsUserId(MatchmadeTeamPayload p, int userId)
{
if (p?.Players == null || userId == 0)
return false;
for (int i = 0; i < p.Players.Length; i++)
{
if (p.Players[i].UserId == userId)
return true;
}
return false;
}
static int IndexOfUserId(MatchmadePlayer[] players, int userId)
{
if (players == null || userId == 0)
return -1;
for (int i = 0; i < players.Length; i++)
{
if (players[i].UserId == userId)
return i;
}
return -1;
}
/// <summary>Duplicate roster payloads: same users in the same order on red and blue blocks.</summary>
static bool SamePlayerOrder(MatchmadePlayer[] a, MatchmadePlayer[] b)
{
if (a == null || b == null || a.Length != b.Length)
return false;
for (int i = 0; i < a.Length; i++)
{
if (a[i].UserId != b[i].UserId)
return false;
}
return true;
}
/// <summary>
/// When <see cref="for_red"/> and <see cref="for_blue"/> list the same roster in the same order (1v1),
/// matchmaker convention: index 0 = red team, index 1 = blue team.
/// </summary>
static MatchmadeTeamPayload SelectDuplicateRosterBySlot(MatchmadeResponse env, int userId)
{
MatchmadePlayer[] r = env.for_red?.Players;
MatchmadePlayer[] b = env.for_blue?.Players;
if (r == null || b == null || r.Length != 2 || b.Length != 2)
return null;
if (!SamePlayerOrder(r, b))
return null;
int idx = IndexOfUserId(r, userId);
if (idx == 0)
return env.for_red;
if (idx == 1)
return env.for_blue;
return null;
}
/// <summary>Pick the nested block for this client (port, your_team, players for UI).</summary>
public static MatchmadeTeamPayload SelectTeamPayload(MatchmadeResponse env, int userId, Team? teamDisambiguation = null)
{
if (env == null)
return null;
if (!string.IsNullOrEmpty(env.your_team))
{
string y = env.your_team.Trim().ToLowerInvariant();
if (y == "red")
return env.for_red;
if (y == "blue")
return env.for_blue;
}
bool onRed = PayloadContainsUserId(env.for_red, userId);
bool onBlue = PayloadContainsUserId(env.for_blue, userId);
if (onRed && !onBlue)
return env.for_red;
if (onBlue && !onRed)
return env.for_blue;
if (onRed && onBlue)
{
MatchmadeTeamPayload bySlot = SelectDuplicateRosterBySlot(env, userId);
if (bySlot != null)
return bySlot;
if (teamDisambiguation.HasValue)
{
if (teamDisambiguation.Value == Team.Red)
return env.for_red;
if (teamDisambiguation.Value == Team.Blue)
return env.for_blue;
}
Debug.LogWarning("MatchmadeResponse: both sides list this user but roster is not 1v1 duplicate order; set root your_team on the server or pass team disambiguation.");
}
return env.for_red;
}
/// <summary>Resolve self and opponent from the selected branch (duplicate full roster) or from both sides (split 1v1 rosters).</summary>
public static bool TryResolveMyAndOpponent(MatchmadeResponse env, MatchmadeTeamPayload branch, int userId, out MatchmadePlayer myPlayer, out MatchmadePlayer opponentPlayer)
{
myPlayer = null;
opponentPlayer = null;
if (env == null || branch == null || branch.Players == null || userId == 0)
return false;
MatchmadeTeamPayload other = ReferenceEquals(branch, env.for_blue) ? env.for_red : env.for_blue;
if (branch.Players.Length >= 2)
{
for (int i = 0; i < branch.Players.Length; i++)
{
if (branch.Players[i].UserId == userId)
{
myPlayer = branch.Players[i];
break;
}
}
if (myPlayer == null)
return false;
for (int i = 0; i < branch.Players.Length; i++)
{
if (branch.Players[i].UserId != myPlayer.UserId)
{
opponentPlayer = branch.Players[i];
break;
}
}
return opponentPlayer != null;
}
for (int i = 0; i < branch.Players.Length; i++)
{
if (branch.Players[i].UserId == userId)
{
myPlayer = branch.Players[i];
break;
}
}
if (myPlayer == null && branch.Players.Length == 1 && branch.Players[0].UserId == userId)
myPlayer = branch.Players[0];
if (other?.Players == null)
return false;
for (int i = 0; i < other.Players.Length; i++)
{
if (other.Players[i].UserId != userId)
{
opponentPlayer = other.Players[i];
break;
}
}
if (opponentPlayer == null && other.Players.Length == 1)
opponentPlayer = other.Players[0];
return myPlayer != null && opponentPlayer != null && myPlayer.UserId != opponentPlayer.UserId;
}
}
using System;
using UnityEngine;
[Serializable]
public class MatchmadePlayer
{
public string Name;
public long LastSeen;
public int l10_wins;
public int l10_losses;
public int UserId;
/// <summary>Rocket Credits from server (e.g. GET /players/:id/rc). Cached client-side.</summary>
public float rc;
}
/// <summary>Per-team block from matchmaker GET / when a side is in queue or match is ready.</summary>
[Serializable]
public class MatchmadeTeamPayload
{
public MatchmadePlayer[] Players;
public string GameName;
public int Port;
public long InitTime;
public bool instance_started;
public int match_id;
public int entry_fee;
public int rc_prize;
public string your_team;
}
/// <summary>Envelope from matchmaker fill / rematch success (and in-progress queue state).</summary>
[Serializable]
public class MatchmadeResponse
{
public bool ok;
public int entry_fee;
public int rc_prize;
public MatchmadeTeamPayload for_red;
public MatchmadeTeamPayload for_blue;
/// <summary>Optional root field: which nested block is for this client when both <see cref="for_red"/> and <see cref="for_blue"/> list the same players.</summary>
public string your_team;
public static bool TeamPayloadHasPort(MatchmadeTeamPayload p) =>
p != null && p.Port > 0;
/// <summary>True when both sides are present on the same dedicated instance (2v2-style duplicate rosters).</summary>
public static bool IsMatchReadyDuplicateRosters(MatchmadeResponse e)
{
if (e == null || !e.ok)
return false;
if (!TeamPayloadHasPort(e.for_red) || !TeamPayloadHasPort(e.for_blue))
return false;
if (e.for_red.Players == null || e.for_blue.Players == null)
return false;
if (e.for_red.match_id != e.for_blue.match_id || e.for_red.Port != e.for_blue.Port)
return false;
return e.for_red.Players.Length >= 2 && e.for_blue.Players.Length >= 2;
}
/// <summary>True when each side lists at least one player and together they form a full 1v1 (disjoint user sets, same instance).</summary>
public static bool IsMatchReadySplitRosters(MatchmadeResponse e)
{
if (e == null || !e.ok)
return false;
if (!TeamPayloadHasPort(e.for_red) || !TeamPayloadHasPort(e.for_blue))
return false;
if (e.for_red.Players == null || e.for_blue.Players == null)
return false;
if (e.for_red.match_id != e.for_blue.match_id || e.for_red.Port != e.for_blue.Port)
return false;
if (e.for_red.Players.Length < 1 || e.for_blue.Players.Length < 1)
return false;
int a = e.for_red.Players[0].UserId;
int b = e.for_blue.Players[0].UserId;
if (a == b)
return false;
if (e.for_red.Players.Length == 1 && e.for_blue.Players.Length == 1)
return true;
// Wider payloads: require no user id appearing on both sides
foreach (var pr in e.for_red.Players)
{
foreach (var pb in e.for_blue.Players)
{
if (pr.UserId == pb.UserId)
return false;
}
}
return true;
}
public static bool IsMatchReady(MatchmadeResponse e) =>
IsMatchReadyDuplicateRosters(e) || IsMatchReadySplitRosters(e);
static bool PayloadContainsUserId(MatchmadeTeamPayload p, int userId)
{
if (p?.Players == null || userId == 0)
return false;
for (int i = 0; i < p.Players.Length; i++)
{
if (p.Players[i].UserId == userId)
return true;
}
return false;
}
static int IndexOfUserId(MatchmadePlayer[] players, int userId)
{
if (players == null || userId == 0)
return -1;
for (int i = 0; i < players.Length; i++)
{
if (players[i].UserId == userId)
return i;
}
return -1;
}
/// <summary>Duplicate roster payloads: same users in the same order on red and blue blocks.</summary>
static bool SamePlayerOrder(MatchmadePlayer[] a, MatchmadePlayer[] b)
{
if (a == null || b == null || a.Length != b.Length)
return false;
for (int i = 0; i < a.Length; i++)
{
if (a[i].UserId != b[i].UserId)
return false;
}
return true;
}
/// <summary>
/// When <see cref="for_red"/> and <see cref="for_blue"/> list the same roster in the same order (1v1),
/// matchmaker convention: index 0 = red team, index 1 = blue team.
/// </summary>
static MatchmadeTeamPayload SelectDuplicateRosterBySlot(MatchmadeResponse env, int userId)
{
MatchmadePlayer[] r = env.for_red?.Players;
MatchmadePlayer[] b = env.for_blue?.Players;
if (r == null || b == null || r.Length != 2 || b.Length != 2)
return null;
if (!SamePlayerOrder(r, b))
return null;
int idx = IndexOfUserId(r, userId);
if (idx == 0)
return env.for_red;
if (idx == 1)
return env.for_blue;
return null;
}
/// <summary>Pick the nested block for this client (port, your_team, players for UI).</summary>
public static MatchmadeTeamPayload SelectTeamPayload(MatchmadeResponse env, int userId, Team? teamDisambiguation = null)
{
if (env == null)
return null;
if (!string.IsNullOrEmpty(env.your_team))
{
string y = env.your_team.Trim().ToLowerInvariant();
if (y == "red")
return env.for_red;
if (y == "blue")
return env.for_blue;
}
bool onRed = PayloadContainsUserId(env.for_red, userId);
bool onBlue = PayloadContainsUserId(env.for_blue, userId);
if (onRed && !onBlue)
return env.for_red;
if (onBlue && !onRed)
return env.for_blue;
if (onRed && onBlue)
{
MatchmadeTeamPayload bySlot = SelectDuplicateRosterBySlot(env, userId);
if (bySlot != null)
return bySlot;
if (teamDisambiguation.HasValue)
{
if (teamDisambiguation.Value == Team.Red)
return env.for_red;
if (teamDisambiguation.Value == Team.Blue)
return env.for_blue;
}
Debug.LogWarning("MatchmadeResponse: both sides list this user but roster is not 1v1 duplicate order; set root your_team on the server or pass team disambiguation.");
}
return env.for_red;
}
/// <summary>Resolve self and opponent from the selected branch (duplicate full roster) or from both sides (split 1v1 rosters).</summary>
public static bool TryResolveMyAndOpponent(MatchmadeResponse env, MatchmadeTeamPayload branch, int userId, out MatchmadePlayer myPlayer, out MatchmadePlayer opponentPlayer)
{
myPlayer = null;
opponentPlayer = null;
if (env == null || branch == null || branch.Players == null || userId == 0)
return false;
MatchmadeTeamPayload other = ReferenceEquals(branch, env.for_blue) ? env.for_red : env.for_blue;
if (branch.Players.Length >= 2)
{
for (int i = 0; i < branch.Players.Length; i++)
{
if (branch.Players[i].UserId == userId)
{
myPlayer = branch.Players[i];
break;
}
}
if (myPlayer == null)
return false;
for (int i = 0; i < branch.Players.Length; i++)
{
if (branch.Players[i].UserId != myPlayer.UserId)
{
opponentPlayer = branch.Players[i];
break;
}
}
return opponentPlayer != null;
}
for (int i = 0; i < branch.Players.Length; i++)
{
if (branch.Players[i].UserId == userId)
{
myPlayer = branch.Players[i];
break;
}
}
if (myPlayer == null && branch.Players.Length == 1 && branch.Players[0].UserId == userId)
myPlayer = branch.Players[0];
if (other?.Players == null)
return false;
for (int i = 0; i < other.Players.Length; i++)
{
if (other.Players[i].UserId != userId)
{
opponentPlayer = other.Players[i];
break;
}
}
if (opponentPlayer == null && other.Players.Length == 1)
opponentPlayer = other.Players[0];
return myPlayer != null && opponentPlayer != null && myPlayer.UserId != opponentPlayer.UserId;
}
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 9f3314f8ed9de9c4babeefda34b3026c
@@ -1,10 +1,10 @@
using System;
using UnityEngine;
[Serializable]
public class PingReportApiResponse
{
public bool ok;
public string error;
public PingReportData report;
}
using System;
using UnityEngine;
[Serializable]
public class PingReportApiResponse
{
public bool ok;
public string error;
public PingReportData report;
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 76e4e69960816204d8a01d0e534e0bf4
@@ -1,13 +1,13 @@
using System;
using UnityEngine;
[Serializable]
public class PingReportData
{
public int id;
public int user_id;
public int match_id;
public int ping;
public string ip_address;
public string created_at;
}
using System;
using UnityEngine;
[Serializable]
public class PingReportData
{
public int id;
public int user_id;
public int match_id;
public int ping;
public string ip_address;
public string created_at;
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: b3f6089d3b18cb34482d7de0cd8bca51
@@ -1,14 +1,14 @@
using System;
[Serializable]
public class PlayerProfileApiResponse
{
public bool ok;
public string error;
public int player_id;
public string username;
public string email;
public int days_since_registration;
public int matches_played;
public float win_ratio;
}
using System;
[Serializable]
public class PlayerProfileApiResponse
{
public bool ok;
public string error;
public int player_id;
public string username;
public string email;
public int days_since_registration;
public int matches_played;
public float win_ratio;
}
@@ -1,9 +1,9 @@
using System;
using UnityEngine;
[Serializable]
public class PurchaseRcApiResponse
{
public bool ok;
public string error;
}
using System;
using UnityEngine;
[Serializable]
public class PurchaseRcApiResponse
{
public bool ok;
public string error;
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 4aef938f509352a43920aa7bc5eb593d
+8 -8
View File
@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 3dd42b9e0f78cdb4eb43c211cc4097c3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 3dd42b9e0f78cdb4eb43c211cc4097c3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+31 -31
View File
@@ -1,31 +1,31 @@
using UnityEngine;
public class CanvasGroupUtils : MonoBehaviour
{
public UnityEngine.UI.Graphic[] graphics;
public bool useOverrideColor = false;
public Color overrideColor = Color.white;
void OnValidate()
{
RefreshColor();
}
void Update()
{
RefreshColor();
}
void RefreshColor(){
if(useOverrideColor)
{
foreach(var graphic in graphics)
{
graphic.color = new Color(overrideColor.r, overrideColor.g, overrideColor.b, overrideColor.a);
}
}
}
}
using UnityEngine;
public class CanvasGroupUtils : MonoBehaviour
{
public UnityEngine.UI.Graphic[] graphics;
public bool useOverrideColor = false;
public Color overrideColor = Color.white;
void OnValidate()
{
RefreshColor();
}
void Update()
{
RefreshColor();
}
void RefreshColor(){
if(useOverrideColor)
{
foreach(var graphic in graphics)
{
graphic.color = new Color(overrideColor.r, overrideColor.g, overrideColor.b, overrideColor.a);
}
}
}
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: c6e9b80351cd150478194c9959e87012
+456 -456
View File
@@ -1,456 +1,456 @@
using System;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[RequireComponent(typeof(EventTrigger))]
public class CardCarousel : MonoBehaviour
{
[SerializeField] RectTransform[] items;
[SerializeField] float spacing = 40f;
[SerializeField] float swipeThreshold = 60f;
[SerializeField] float snapDuration = 0.25f;
[SerializeField] Ease snapEase = Ease.OutCubic;
[Tooltip("Horizontal half-width of the center tap zone, in local units of this RectTransform.")]
[SerializeField] float centerTapHalfWidth = 260f;
[SerializeField] bool looped = true;
[SerializeField] int selectedIndex;
[Header("Focus Animation")]
[SerializeField] float activeScale = 1.05f;
[SerializeField] float inactiveScale = 0.88f;
[SerializeField] float activePullDown = 35f;
public UnityEvent<int> onSelected = new UnityEvent<int>();
EventTrigger eventTrigger;
RectTransform rectTransform;
RectTransform dragSpace;
RectTransform duplicateClone;
readonly Dictionary<RectTransform, Vector3> baseScales = new();
float slotStep;
float focusX;
float focusY;
float focusPosition;
float dragStartPosition;
float pointerDownLocalX;
bool isDragging;
bool isAnimating;
Tween snapTween;
public int SelectedIndex => selectedIndex;
void Awake()
{
rectTransform = (RectTransform)transform;
eventTrigger = GetComponent<EventTrigger>();
RegisterTrigger(EventTriggerType.PointerDown, OnPointerDown);
RegisterTrigger(EventTriggerType.Drag, OnDrag);
RegisterTrigger(EventTriggerType.PointerUp, OnPointerUp);
}
void Start()
{
if (items == null || items.Length < 2)
{
Debug.LogWarning($"{nameof(CardCarousel)} on {name} needs at least 2 items.", this);
return;
}
selectedIndex = Mathf.Clamp(selectedIndex, 0, items.Length - 1);
dragSpace = items[0].parent as RectTransform;
slotStep = items[0].rect.width + spacing;
focusX = items[0].anchoredPosition.x;
focusY = items[0].anchoredPosition.y;
duplicateClone = CreateClone(items[0], "_carouselClone_Dup");
foreach (var item in items)
CacheBaseScale(item);
focusPosition = selectedIndex;
ApplyLayout();
}
void OnDestroy()
{
snapTween?.Kill();
}
void RegisterTrigger(EventTriggerType type, Action<BaseEventData> handler)
{
var entry = new EventTrigger.Entry { eventID = type };
entry.callback.AddListener(handler.Invoke);
eventTrigger.triggers.Add(entry);
}
RectTransform CreateClone(RectTransform source, string suffix)
{
var clone = Instantiate(source, source.parent);
clone.name = source.name + suffix;
DisableInteractions(clone);
return clone;
}
static void DisableInteractions(RectTransform target)
{
foreach (var graphic in target.GetComponentsInChildren<Graphic>(true))
graphic.raycastTarget = false;
foreach (var button in target.GetComponentsInChildren<Button>(true))
button.interactable = false;
}
void OnPointerDown(BaseEventData eventData)
{
if (!TryGetPointer(eventData, out var pointer) || isAnimating || items == null || items.Length < 2)
return;
snapTween?.Kill();
isAnimating = false;
isDragging = false;
dragStartPosition = focusPosition;
pointerDownLocalX = GetLocalDragX(pointer);
}
void OnDrag(BaseEventData eventData)
{
if (!TryGetPointer(eventData, out var pointer) || items == null || items.Length < 2)
return;
isDragging = true;
float delta = GetLocalDragX(pointer) - pointerDownLocalX;
focusPosition = dragStartPosition - delta / slotStep;
if (!looped)
focusPosition = Mathf.Clamp(focusPosition, 0f, items.Length - 1);
ApplyLayout();
}
void OnPointerUp(BaseEventData eventData)
{
if (!TryGetPointer(eventData, out var pointer) || isAnimating || items == null || items.Length < 2)
return;
float pointerDelta = GetLocalDragX(pointer) - pointerDownLocalX;
float dragDelta = focusPosition - dragStartPosition;
bool passedSwipeThreshold = Mathf.Abs(pointerDelta) >= swipeThreshold
|| Mathf.Abs(dragDelta * slotStep) >= swipeThreshold;
if (!passedSwipeThreshold)
{
if (TryGetTapZone(pointer, out int tapDirection))
{
if (tapDirection == 0)
SelectCurrent();
else if (CanMoveBy(tapDirection))
AnimateToDirection(tapDirection);
else
SnapTo(selectedIndex, null);
}
else
SnapTo(selectedIndex, null);
isDragging = false;
return;
}
isDragging = false;
int direction = dragDelta > 0f ? 1 : -1;
if (Mathf.Abs(focusPosition - selectedIndex) > 0.5f)
{
int nearest = Mathf.RoundToInt(focusPosition);
if (!looped)
nearest = Mathf.Clamp(nearest, 0, items.Length - 1);
if (nearest != selectedIndex && CanMoveBy(nearest > selectedIndex ? 1 : -1))
{
AnimateTo(nearest);
return;
}
}
if (!CanMoveBy(direction))
{
SnapTo(selectedIndex, null);
return;
}
AnimateToDirection(direction);
}
float GetLocalDragX(PointerEventData pointer)
{
if (dragSpace == null)
return pointer.position.x;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
dragSpace, pointer.position, pointer.pressEventCamera, out var local);
return local.x;
}
bool TryGetTapZone(PointerEventData pointer, out int direction)
{
direction = 0;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
rectTransform, pointer.position, pointer.pressEventCamera, out var local))
return false;
if (Mathf.Abs(local.x) <= centerTapHalfWidth)
return true;
direction = local.x < 0f ? -1 : 1;
return true;
}
void SelectCurrent()
{
if (Mathf.Approximately(focusPosition, selectedIndex))
onSelected.Invoke(selectedIndex);
else
SnapTo(selectedIndex, () => onSelected.Invoke(selectedIndex));
}
void AnimateToDirection(int direction)
{
int newIndex = GetAdjacentIndex(direction, items.Length);
if (newIndex < 0)
return;
AnimateTo(newIndex);
}
void AnimateTo(int newIndex)
{
isAnimating = true;
snapTween?.Kill();
snapTween = DOTween.To(
() => focusPosition,
value =>
{
focusPosition = value;
ApplyLayout();
},
newIndex,
snapDuration)
.SetEase(snapEase)
.OnComplete(() => CompleteSlide(newIndex));
}
void CompleteSlide(int newIndex)
{
selectedIndex = newIndex;
focusPosition = newIndex;
isAnimating = false;
ApplyLayout();
}
void SnapTo(float targetPosition, Action onComplete)
{
isAnimating = true;
snapTween?.Kill();
snapTween = DOTween.To(
() => focusPosition,
value =>
{
focusPosition = value;
ApplyLayout();
},
targetPosition,
snapDuration)
.SetEase(snapEase)
.OnComplete(() =>
{
isAnimating = false;
onComplete?.Invoke();
});
}
float GetItemOffset(int itemIndex)
{
return (itemIndex - focusPosition) * slotStep;
}
bool IsItemVisible(float offset)
{
return Mathf.Abs(offset) <= slotStep * 1.5f;
}
void ApplyLayout()
{
int count = items.Length;
bool useDuplicate = looped && count == 2;
for (int i = 0; i < count; i++)
{
float offset = GetItemOffset(i);
if (IsItemVisible(offset))
{
SetItemTransform(items[i], offset);
items[i].gameObject.SetActive(true);
}
else
items[i].gameObject.SetActive(false);
}
if (useDuplicate && Mathf.Approximately(focusPosition, Mathf.Round(focusPosition)))
{
int restIndex = Mathf.RoundToInt(focusPosition);
int otherIndex = 1 - restIndex;
float otherOffset = GetItemOffset(otherIndex);
float duplicateOffset = -otherOffset;
if (IsItemVisible(duplicateOffset))
{
SyncDuplicateClone(items[otherIndex]);
SetItemTransform(duplicateClone, duplicateOffset);
duplicateClone.gameObject.SetActive(true);
}
else if (duplicateClone != null)
duplicateClone.gameObject.SetActive(false);
}
else if (duplicateClone != null)
{
duplicateClone.gameObject.SetActive(false);
}
int topIndex = 0;
float bestFocus = float.MaxValue;
for (int i = 0; i < count; i++)
{
if (!items[i].gameObject.activeSelf)
continue;
float focus = GetFocusT(GetItemOffset(i));
if (focus < bestFocus)
{
bestFocus = focus;
topIndex = i;
}
}
items[topIndex].SetAsLastSibling();
}
void SyncDuplicateClone(RectTransform source)
{
string expectedName = source.name + "_carouselClone_Dup";
if (duplicateClone == null || duplicateClone.name != expectedName)
{
var parent = source.parent;
int sibling = duplicateClone != null ? duplicateClone.GetSiblingIndex() : source.GetSiblingIndex();
if (duplicateClone != null)
{
baseScales.Remove(duplicateClone);
Destroy(duplicateClone.gameObject);
}
duplicateClone = CreateClone(source, "_carouselClone_Dup");
duplicateClone.SetParent(parent, false);
duplicateClone.SetSiblingIndex(sibling);
}
CacheBaseScale(duplicateClone, source);
}
void CacheBaseScale(RectTransform item, RectTransform source = null)
{
if (baseScales.ContainsKey(item))
return;
baseScales[item] = source != null ? source.localScale : item.localScale;
}
void SetItemTransform(RectTransform item, float xOffset)
{
CacheBaseScale(item);
float focusT = GetFocusT(xOffset);
float scale = Mathf.Lerp(activeScale, inactiveScale, focusT);
item.localScale = baseScales[item] * scale;
var pos = item.anchoredPosition;
pos.x = focusX + xOffset;
pos.y = focusY - Mathf.Lerp(activePullDown, 0f, focusT);
item.anchoredPosition = pos;
}
float GetFocusT(float xOffset)
{
if (slotStep <= 0f)
return 0f;
float t = Mathf.Clamp01(Mathf.Abs(xOffset) / slotStep);
return t * t * (3f - 2f * t);
}
static int WrapIndex(int index, int count)
{
if (count <= 0)
return 0;
return ((index % count) + count) % count;
}
int GetAdjacentIndex(int direction, int count)
{
int index = selectedIndex + direction;
if (looped)
return WrapIndex(index, count);
return index >= 0 && index < count ? index : -1;
}
bool CanMoveBy(int direction)
{
if (items == null || items.Length == 0)
return false;
return GetAdjacentIndex(direction, items.Length) >= 0;
}
int NormalizeIndex(int index)
{
if (items == null || items.Length == 0)
return 0;
return looped ? WrapIndex(index, items.Length) : Mathf.Clamp(index, 0, items.Length - 1);
}
static bool TryGetPointer(BaseEventData eventData, out PointerEventData pointer)
{
pointer = eventData as PointerEventData;
return pointer != null;
}
public void SetSelectedIndex(int index, bool animate = false)
{
if (items == null || items.Length == 0)
return;
int target = NormalizeIndex(index);
if (target == selectedIndex)
return;
if (!animate || !isActiveAndEnabled)
{
snapTween?.Kill();
isAnimating = false;
selectedIndex = target;
focusPosition = target;
ApplyLayout();
return;
}
int direction = target > selectedIndex ? 1 : -1;
if (!CanMoveBy(direction) || Mathf.Abs(target - selectedIndex) != 1)
{
selectedIndex = target;
focusPosition = target;
ApplyLayout();
return;
}
AnimateToDirection(direction);
}
}
using System;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[RequireComponent(typeof(EventTrigger))]
public class CardCarousel : MonoBehaviour
{
[SerializeField] RectTransform[] items;
[SerializeField] float spacing = 40f;
[SerializeField] float swipeThreshold = 60f;
[SerializeField] float snapDuration = 0.25f;
[SerializeField] Ease snapEase = Ease.OutCubic;
[Tooltip("Horizontal half-width of the center tap zone, in local units of this RectTransform.")]
[SerializeField] float centerTapHalfWidth = 260f;
[SerializeField] bool looped = true;
[SerializeField] int selectedIndex;
[Header("Focus Animation")]
[SerializeField] float activeScale = 1.05f;
[SerializeField] float inactiveScale = 0.88f;
[SerializeField] float activePullDown = 35f;
public UnityEvent<int> onSelected = new UnityEvent<int>();
EventTrigger eventTrigger;
RectTransform rectTransform;
RectTransform dragSpace;
RectTransform duplicateClone;
readonly Dictionary<RectTransform, Vector3> baseScales = new();
float slotStep;
float focusX;
float focusY;
float focusPosition;
float dragStartPosition;
float pointerDownLocalX;
bool isDragging;
bool isAnimating;
Tween snapTween;
public int SelectedIndex => selectedIndex;
void Awake()
{
rectTransform = (RectTransform)transform;
eventTrigger = GetComponent<EventTrigger>();
RegisterTrigger(EventTriggerType.PointerDown, OnPointerDown);
RegisterTrigger(EventTriggerType.Drag, OnDrag);
RegisterTrigger(EventTriggerType.PointerUp, OnPointerUp);
}
void Start()
{
if (items == null || items.Length < 2)
{
Debug.LogWarning($"{nameof(CardCarousel)} on {name} needs at least 2 items.", this);
return;
}
selectedIndex = Mathf.Clamp(selectedIndex, 0, items.Length - 1);
dragSpace = items[0].parent as RectTransform;
slotStep = items[0].rect.width + spacing;
focusX = items[0].anchoredPosition.x;
focusY = items[0].anchoredPosition.y;
duplicateClone = CreateClone(items[0], "_carouselClone_Dup");
foreach (var item in items)
CacheBaseScale(item);
focusPosition = selectedIndex;
ApplyLayout();
}
void OnDestroy()
{
snapTween?.Kill();
}
void RegisterTrigger(EventTriggerType type, Action<BaseEventData> handler)
{
var entry = new EventTrigger.Entry { eventID = type };
entry.callback.AddListener(handler.Invoke);
eventTrigger.triggers.Add(entry);
}
RectTransform CreateClone(RectTransform source, string suffix)
{
var clone = Instantiate(source, source.parent);
clone.name = source.name + suffix;
DisableInteractions(clone);
return clone;
}
static void DisableInteractions(RectTransform target)
{
foreach (var graphic in target.GetComponentsInChildren<Graphic>(true))
graphic.raycastTarget = false;
foreach (var button in target.GetComponentsInChildren<Button>(true))
button.interactable = false;
}
void OnPointerDown(BaseEventData eventData)
{
if (!TryGetPointer(eventData, out var pointer) || isAnimating || items == null || items.Length < 2)
return;
snapTween?.Kill();
isAnimating = false;
isDragging = false;
dragStartPosition = focusPosition;
pointerDownLocalX = GetLocalDragX(pointer);
}
void OnDrag(BaseEventData eventData)
{
if (!TryGetPointer(eventData, out var pointer) || items == null || items.Length < 2)
return;
isDragging = true;
float delta = GetLocalDragX(pointer) - pointerDownLocalX;
focusPosition = dragStartPosition - delta / slotStep;
if (!looped)
focusPosition = Mathf.Clamp(focusPosition, 0f, items.Length - 1);
ApplyLayout();
}
void OnPointerUp(BaseEventData eventData)
{
if (!TryGetPointer(eventData, out var pointer) || isAnimating || items == null || items.Length < 2)
return;
float pointerDelta = GetLocalDragX(pointer) - pointerDownLocalX;
float dragDelta = focusPosition - dragStartPosition;
bool passedSwipeThreshold = Mathf.Abs(pointerDelta) >= swipeThreshold
|| Mathf.Abs(dragDelta * slotStep) >= swipeThreshold;
if (!passedSwipeThreshold)
{
if (TryGetTapZone(pointer, out int tapDirection))
{
if (tapDirection == 0)
SelectCurrent();
else if (CanMoveBy(tapDirection))
AnimateToDirection(tapDirection);
else
SnapTo(selectedIndex, null);
}
else
SnapTo(selectedIndex, null);
isDragging = false;
return;
}
isDragging = false;
int direction = dragDelta > 0f ? 1 : -1;
if (Mathf.Abs(focusPosition - selectedIndex) > 0.5f)
{
int nearest = Mathf.RoundToInt(focusPosition);
if (!looped)
nearest = Mathf.Clamp(nearest, 0, items.Length - 1);
if (nearest != selectedIndex && CanMoveBy(nearest > selectedIndex ? 1 : -1))
{
AnimateTo(nearest);
return;
}
}
if (!CanMoveBy(direction))
{
SnapTo(selectedIndex, null);
return;
}
AnimateToDirection(direction);
}
float GetLocalDragX(PointerEventData pointer)
{
if (dragSpace == null)
return pointer.position.x;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
dragSpace, pointer.position, pointer.pressEventCamera, out var local);
return local.x;
}
bool TryGetTapZone(PointerEventData pointer, out int direction)
{
direction = 0;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
rectTransform, pointer.position, pointer.pressEventCamera, out var local))
return false;
if (Mathf.Abs(local.x) <= centerTapHalfWidth)
return true;
direction = local.x < 0f ? -1 : 1;
return true;
}
void SelectCurrent()
{
if (Mathf.Approximately(focusPosition, selectedIndex))
onSelected.Invoke(selectedIndex);
else
SnapTo(selectedIndex, () => onSelected.Invoke(selectedIndex));
}
void AnimateToDirection(int direction)
{
int newIndex = GetAdjacentIndex(direction, items.Length);
if (newIndex < 0)
return;
AnimateTo(newIndex);
}
void AnimateTo(int newIndex)
{
isAnimating = true;
snapTween?.Kill();
snapTween = DOTween.To(
() => focusPosition,
value =>
{
focusPosition = value;
ApplyLayout();
},
newIndex,
snapDuration)
.SetEase(snapEase)
.OnComplete(() => CompleteSlide(newIndex));
}
void CompleteSlide(int newIndex)
{
selectedIndex = newIndex;
focusPosition = newIndex;
isAnimating = false;
ApplyLayout();
}
void SnapTo(float targetPosition, Action onComplete)
{
isAnimating = true;
snapTween?.Kill();
snapTween = DOTween.To(
() => focusPosition,
value =>
{
focusPosition = value;
ApplyLayout();
},
targetPosition,
snapDuration)
.SetEase(snapEase)
.OnComplete(() =>
{
isAnimating = false;
onComplete?.Invoke();
});
}
float GetItemOffset(int itemIndex)
{
return (itemIndex - focusPosition) * slotStep;
}
bool IsItemVisible(float offset)
{
return Mathf.Abs(offset) <= slotStep * 1.5f;
}
void ApplyLayout()
{
int count = items.Length;
bool useDuplicate = looped && count == 2;
for (int i = 0; i < count; i++)
{
float offset = GetItemOffset(i);
if (IsItemVisible(offset))
{
SetItemTransform(items[i], offset);
items[i].gameObject.SetActive(true);
}
else
items[i].gameObject.SetActive(false);
}
if (useDuplicate && Mathf.Approximately(focusPosition, Mathf.Round(focusPosition)))
{
int restIndex = Mathf.RoundToInt(focusPosition);
int otherIndex = 1 - restIndex;
float otherOffset = GetItemOffset(otherIndex);
float duplicateOffset = -otherOffset;
if (IsItemVisible(duplicateOffset))
{
SyncDuplicateClone(items[otherIndex]);
SetItemTransform(duplicateClone, duplicateOffset);
duplicateClone.gameObject.SetActive(true);
}
else if (duplicateClone != null)
duplicateClone.gameObject.SetActive(false);
}
else if (duplicateClone != null)
{
duplicateClone.gameObject.SetActive(false);
}
int topIndex = 0;
float bestFocus = float.MaxValue;
for (int i = 0; i < count; i++)
{
if (!items[i].gameObject.activeSelf)
continue;
float focus = GetFocusT(GetItemOffset(i));
if (focus < bestFocus)
{
bestFocus = focus;
topIndex = i;
}
}
items[topIndex].SetAsLastSibling();
}
void SyncDuplicateClone(RectTransform source)
{
string expectedName = source.name + "_carouselClone_Dup";
if (duplicateClone == null || duplicateClone.name != expectedName)
{
var parent = source.parent;
int sibling = duplicateClone != null ? duplicateClone.GetSiblingIndex() : source.GetSiblingIndex();
if (duplicateClone != null)
{
baseScales.Remove(duplicateClone);
Destroy(duplicateClone.gameObject);
}
duplicateClone = CreateClone(source, "_carouselClone_Dup");
duplicateClone.SetParent(parent, false);
duplicateClone.SetSiblingIndex(sibling);
}
CacheBaseScale(duplicateClone, source);
}
void CacheBaseScale(RectTransform item, RectTransform source = null)
{
if (baseScales.ContainsKey(item))
return;
baseScales[item] = source != null ? source.localScale : item.localScale;
}
void SetItemTransform(RectTransform item, float xOffset)
{
CacheBaseScale(item);
float focusT = GetFocusT(xOffset);
float scale = Mathf.Lerp(activeScale, inactiveScale, focusT);
item.localScale = baseScales[item] * scale;
var pos = item.anchoredPosition;
pos.x = focusX + xOffset;
pos.y = focusY - Mathf.Lerp(activePullDown, 0f, focusT);
item.anchoredPosition = pos;
}
float GetFocusT(float xOffset)
{
if (slotStep <= 0f)
return 0f;
float t = Mathf.Clamp01(Mathf.Abs(xOffset) / slotStep);
return t * t * (3f - 2f * t);
}
static int WrapIndex(int index, int count)
{
if (count <= 0)
return 0;
return ((index % count) + count) % count;
}
int GetAdjacentIndex(int direction, int count)
{
int index = selectedIndex + direction;
if (looped)
return WrapIndex(index, count);
return index >= 0 && index < count ? index : -1;
}
bool CanMoveBy(int direction)
{
if (items == null || items.Length == 0)
return false;
return GetAdjacentIndex(direction, items.Length) >= 0;
}
int NormalizeIndex(int index)
{
if (items == null || items.Length == 0)
return 0;
return looped ? WrapIndex(index, items.Length) : Mathf.Clamp(index, 0, items.Length - 1);
}
static bool TryGetPointer(BaseEventData eventData, out PointerEventData pointer)
{
pointer = eventData as PointerEventData;
return pointer != null;
}
public void SetSelectedIndex(int index, bool animate = false)
{
if (items == null || items.Length == 0)
return;
int target = NormalizeIndex(index);
if (target == selectedIndex)
return;
if (!animate || !isActiveAndEnabled)
{
snapTween?.Kill();
isAnimating = false;
selectedIndex = target;
focusPosition = target;
ApplyLayout();
return;
}
int direction = target > selectedIndex ? 1 : -1;
if (!CanMoveBy(direction) || Mathf.Abs(target - selectedIndex) != 1)
{
selectedIndex = target;
focusPosition = target;
ApplyLayout();
return;
}
AnimateToDirection(direction);
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: a4951ec6b41c5d14cb8f5a8803dfa0c2
+20 -20
View File
@@ -1,20 +1,20 @@
using TMPro;
using UnityEngine;
[RequireComponent(typeof(TMP_Text))]
public class CopyTextTMP : MonoBehaviour
{
public TMP_Text origin;
TMP_Text _text;
void Awake()
{
_text = GetComponent<TMP_Text>();
}
void Update()
{
_text.text = origin.text;
}
}
using TMPro;
using UnityEngine;
[RequireComponent(typeof(TMP_Text))]
public class CopyTextTMP : MonoBehaviour
{
public TMP_Text origin;
TMP_Text _text;
void Awake()
{
_text = GetComponent<TMP_Text>();
}
void Update()
{
_text.text = origin.text;
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 981262cae0f1178448cf21d0273828dc
+105 -105
View File
@@ -1,105 +1,105 @@
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
/// <summary>
/// Inverted stencil mask (see materialForRendering). Raycast options live on
/// <see cref="CutoutMaskUISettings"/> on the same GameObject (Unity's Image
/// inspector does not show fields on Image subclasses).
///
/// Parent <see cref="Mask"/> also implements <see cref="ICanvasRaycastFilter"/> and only
/// allows points inside its RectTransform. A fullscreen child under a small mask would
/// otherwise fail the raycast outside that rect (dimmed area lets clicks through) while
/// still blocking inside the mask bounds. We skip only Mask/RectMask2D in the raycast
/// walk (<see cref="Graphic.Raycast(UnityEngine.Vector2,UnityEngine.Camera,bool)"/> with
/// ignoreMasks) so hit testing matches the fullscreen Image; rendering still uses stencil
/// because <see cref="MaskableGraphic.maskable"/> stays true.
/// </summary>
[RequireComponent(typeof(CutoutMaskUISettings))]
public class CutoutMaskUI : Image
{
CutoutMaskUISettings settings;
Mask cachedParentMask;
Graphic cachedMaskGraphic;
bool storedMaskGraphicRaycast;
bool storedMaskGraphicRaycastValid;
public override bool Raycast(Vector2 sp, Camera eventCamera)
{
return Raycast(sp, eventCamera, ignoreMasks: true);
}
public override Material materialForRendering
{
get
{
Material material = new Material(base.materialForRendering);
material.SetInt("_StencilComp", (int)CompareFunction.NotEqual);
return material;
}
}
protected override void OnEnable()
{
settings = GetComponent<CutoutMaskUISettings>();
base.OnEnable();
if (settings == null || !settings.disableRaycastOnParentMaskGraphic)
return;
cachedParentMask = GetComponentInParent<Mask>();
if (cachedParentMask == null)
return;
cachedMaskGraphic = cachedParentMask.graphic;
if (cachedMaskGraphic == null || cachedMaskGraphic == this)
return;
storedMaskGraphicRaycastValid = true;
storedMaskGraphicRaycast = cachedMaskGraphic.raycastTarget;
cachedMaskGraphic.raycastTarget = false;
}
protected override void OnDisable()
{
if (storedMaskGraphicRaycastValid && cachedMaskGraphic != null)
{
cachedMaskGraphic.raycastTarget = storedMaskGraphicRaycast;
storedMaskGraphicRaycastValid = false;
}
cachedMaskGraphic = null;
cachedParentMask = null;
base.OnDisable();
}
public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
if (IsInsidePassThroughHole(screenPoint, eventCamera))
return settings != null && settings.blockRaycastThroughCutout;
return base.IsRaycastLocationValid(screenPoint, eventCamera);
}
bool IsInsidePassThroughHole(Vector2 screenPoint, Camera eventCamera)
{
RectTransform[] passThroughRects = settings != null ? settings.raycastPassThroughRects : null;
if (passThroughRects != null)
{
for (int i = 0; i < passThroughRects.Length; i++)
{
RectTransform rect = passThroughRects[i];
if (rect != null && RectTransformUtility.RectangleContainsScreenPoint(rect, screenPoint, eventCamera))
return true;
}
}
Mask mask = cachedParentMask != null ? cachedParentMask : GetComponentInParent<Mask>();
if (mask != null && mask.rectTransform != null &&
RectTransformUtility.RectangleContainsScreenPoint(mask.rectTransform, screenPoint, eventCamera))
return true;
return false;
}
}
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
/// <summary>
/// Inverted stencil mask (see materialForRendering). Raycast options live on
/// <see cref="CutoutMaskUISettings"/> on the same GameObject (Unity's Image
/// inspector does not show fields on Image subclasses).
///
/// Parent <see cref="Mask"/> also implements <see cref="ICanvasRaycastFilter"/> and only
/// allows points inside its RectTransform. A fullscreen child under a small mask would
/// otherwise fail the raycast outside that rect (dimmed area lets clicks through) while
/// still blocking inside the mask bounds. We skip only Mask/RectMask2D in the raycast
/// walk (<see cref="Graphic.Raycast(UnityEngine.Vector2,UnityEngine.Camera,bool)"/> with
/// ignoreMasks) so hit testing matches the fullscreen Image; rendering still uses stencil
/// because <see cref="MaskableGraphic.maskable"/> stays true.
/// </summary>
[RequireComponent(typeof(CutoutMaskUISettings))]
public class CutoutMaskUI : Image
{
CutoutMaskUISettings settings;
Mask cachedParentMask;
Graphic cachedMaskGraphic;
bool storedMaskGraphicRaycast;
bool storedMaskGraphicRaycastValid;
public override bool Raycast(Vector2 sp, Camera eventCamera)
{
return Raycast(sp, eventCamera, ignoreMasks: true);
}
public override Material materialForRendering
{
get
{
Material material = new Material(base.materialForRendering);
material.SetInt("_StencilComp", (int)CompareFunction.NotEqual);
return material;
}
}
protected override void OnEnable()
{
settings = GetComponent<CutoutMaskUISettings>();
base.OnEnable();
if (settings == null || !settings.disableRaycastOnParentMaskGraphic)
return;
cachedParentMask = GetComponentInParent<Mask>();
if (cachedParentMask == null)
return;
cachedMaskGraphic = cachedParentMask.graphic;
if (cachedMaskGraphic == null || cachedMaskGraphic == this)
return;
storedMaskGraphicRaycastValid = true;
storedMaskGraphicRaycast = cachedMaskGraphic.raycastTarget;
cachedMaskGraphic.raycastTarget = false;
}
protected override void OnDisable()
{
if (storedMaskGraphicRaycastValid && cachedMaskGraphic != null)
{
cachedMaskGraphic.raycastTarget = storedMaskGraphicRaycast;
storedMaskGraphicRaycastValid = false;
}
cachedMaskGraphic = null;
cachedParentMask = null;
base.OnDisable();
}
public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
if (IsInsidePassThroughHole(screenPoint, eventCamera))
return settings != null && settings.blockRaycastThroughCutout;
return base.IsRaycastLocationValid(screenPoint, eventCamera);
}
bool IsInsidePassThroughHole(Vector2 screenPoint, Camera eventCamera)
{
RectTransform[] passThroughRects = settings != null ? settings.raycastPassThroughRects : null;
if (passThroughRects != null)
{
for (int i = 0; i < passThroughRects.Length; i++)
{
RectTransform rect = passThroughRects[i];
if (rect != null && RectTransformUtility.RectangleContainsScreenPoint(rect, screenPoint, eventCamera))
return true;
}
}
Mask mask = cachedParentMask != null ? cachedParentMask : GetComponentInParent<Mask>();
if (mask != null && mask.rectTransform != null &&
RectTransformUtility.RectangleContainsScreenPoint(mask.rectTransform, screenPoint, eventCamera))
return true;
return false;
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 721d1e0343adc3c42b4508f55c455957
+15 -15
View File
@@ -1,15 +1,15 @@
using UnityEngine;
[DisallowMultipleComponent]
[RequireComponent(typeof(CutoutMaskUI))]
public class CutoutMaskUISettings : MonoBehaviour
{
[Tooltip("Rects matching cutout/hole areas. Clicks inside are not consumed by this Image. Leave empty to use the parent Mask's RectTransform.")]
public RectTransform[] raycastPassThroughRects;
[Tooltip("The Image on the same object as Mask is usually the cutout shape and still receives raycasts. When this is on, that graphic's Raycast Target is turned off while this component is enabled so the hole reaches UI behind the overlay.")]
public bool disableRaycastOnParentMaskGraphic = true;
[Tooltip("When enabled, clicks inside cutout/hole areas are consumed by this Image instead of passing through to UI behind.")]
public bool blockRaycastThroughCutout;
}
using UnityEngine;
[DisallowMultipleComponent]
[RequireComponent(typeof(CutoutMaskUI))]
public class CutoutMaskUISettings : MonoBehaviour
{
[Tooltip("Rects matching cutout/hole areas. Clicks inside are not consumed by this Image. Leave empty to use the parent Mask's RectTransform.")]
public RectTransform[] raycastPassThroughRects;
[Tooltip("The Image on the same object as Mask is usually the cutout shape and still receives raycasts. When this is on, that graphic's Raycast Target is turned off while this component is enabled so the hole reaches UI behind the overlay.")]
public bool disableRaycastOnParentMaskGraphic = true;
[Tooltip("When enabled, clicks inside cutout/hole areas are consumed by this Image instead of passing through to UI behind.")]
public bool blockRaycastThroughCutout;
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 29b95265513fc314995c59cb90745214
+8 -8
View File
@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 05aa9e67ef3520047a8c20a6f96165d8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 05aa9e67ef3520047a8c20a6f96165d8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+271 -271
View File
@@ -1,271 +1,271 @@
using UnityEngine.UI;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteAlways]
public class FrameAnimator : MonoBehaviour
{
public Sprite[] sprites;
public float frameRate = 10f;
public bool preview;
public bool isPlaying = false;
public bool loop = true;
public int currentFrame = 0;
float m_frameTimer = 0f;
bool m_previewPlaying = false;
#if UNITY_EDITOR
double m_lastEditorTime = 0d;
bool m_editorHooked;
#endif
Image m_img;
Image img {
get{
if(m_img==null){
m_img= GetComponent<Image>();
}
return m_img;
}
}
void OnValidate()
{
if(preview)
{
preview=false;
StartPreviewOnce();
}
}
void OnEnable()
{
#if UNITY_EDITOR
// Never hook editor updates during play-mode enter/exit — QueuePlayerLoopUpdate
// + isPlaying serialized true can infinite-reload Temp/__Backupscenes.
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
TryHookEditorUpdate();
#endif
}
void OnDisable()
{
#if UNITY_EDITOR
UnhookEditorUpdate();
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
#endif
}
void Start()
{
if (sprites == null || sprites.Length == 0)
{
return;
}
currentFrame = Mathf.Clamp(currentFrame, 0, sprites.Length - 1);
ApplyCurrentFrame();
}
// Update is called once per frame
void Update()
{
if (!Application.isPlaying)
{
return;
}
TickAnimation(Time.deltaTime);
}
#if UNITY_EDITOR
void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.ExitingEditMode ||
state == PlayModeStateChange.EnteredPlayMode ||
state == PlayModeStateChange.ExitingPlayMode)
{
m_previewPlaying = false;
UnhookEditorUpdate();
}
else if (state == PlayModeStateChange.EnteredEditMode)
{
TryHookEditorUpdate();
}
}
void TryHookEditorUpdate()
{
if (Application.isPlaying || EditorApplication.isPlayingOrWillChangePlaymode)
{
UnhookEditorUpdate();
return;
}
if (m_editorHooked)
return;
m_lastEditorTime = EditorApplication.timeSinceStartup;
EditorApplication.update += OnEditorUpdate;
m_editorHooked = true;
}
void UnhookEditorUpdate()
{
if (!m_editorHooked)
return;
EditorApplication.update -= OnEditorUpdate;
m_editorHooked = false;
}
void OnEditorUpdate()
{
if (Application.isPlaying ||
EditorApplication.isPlayingOrWillChangePlaymode ||
this == null ||
!isActiveAndEnabled)
{
UnhookEditorUpdate();
return;
}
// Edit-mode animation is preview-only. Ignoring serialized isPlaying prevents
// permanent editor loops when isPlaying was left true in the scene.
if (!m_previewPlaying)
return;
double now = EditorApplication.timeSinceStartup;
float dt = (float)(now - m_lastEditorTime);
m_lastEditorTime = now;
if (dt <= 0f)
return;
int frameBefore = currentFrame;
TickAnimation(dt);
if (currentFrame != frameBefore || m_previewPlaying)
EditorApplication.QueuePlayerLoopUpdate();
}
#endif
void TickAnimation(float deltaTime)
{
if (frameRate <= 0f || sprites == null || sprites.Length == 0)
{
return;
}
bool shouldAnimate = Application.isPlaying
? (m_previewPlaying || isPlaying)
: m_previewPlaying;
if (!shouldAnimate)
{
return;
}
m_frameTimer += deltaTime;
float frameDuration = 1f / frameRate;
while (m_frameTimer >= frameDuration)
{
m_frameTimer -= frameDuration;
if (m_previewPlaying)
{
AdvancePreviewFrame();
}
else
{
AdvanceFrame();
}
}
}
public void Play(bool restart = false)
{
if (sprites == null || sprites.Length == 0)
{
return;
}
if (restart)
{
currentFrame = 0;
m_frameTimer = 0f;
ApplyCurrentFrame();
}
isPlaying = true;
}
public void Stop()
{
isPlaying = false;
m_frameTimer = 0f;
}
public void ResetToFirstFrame()
{
currentFrame = 0;
m_frameTimer = 0f;
ApplyCurrentFrame();
}
void StartPreviewOnce()
{
if (sprites == null || sprites.Length == 0)
{
return;
}
isPlaying = false;
m_previewPlaying = true;
m_frameTimer = 0f;
currentFrame = 0;
ApplyCurrentFrame();
}
void AdvancePreviewFrame()
{
currentFrame++;
if (currentFrame >= sprites.Length)
{
currentFrame = sprites.Length - 1;
m_previewPlaying = false;
}
ApplyCurrentFrame();
}
void AdvanceFrame()
{
currentFrame++;
if (currentFrame >= sprites.Length)
{
if (loop)
{
currentFrame = 0;
}
else
{
currentFrame = sprites.Length - 1;
isPlaying = false;
}
}
ApplyCurrentFrame();
}
void ApplyCurrentFrame()
{
if (img == null || sprites == null || sprites.Length == 0)
{
return;
}
currentFrame = Mathf.Clamp(currentFrame, 0, sprites.Length - 1);
img.sprite = sprites[currentFrame];
}
}
using UnityEngine.UI;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteAlways]
public class FrameAnimator : MonoBehaviour
{
public Sprite[] sprites;
public float frameRate = 10f;
public bool preview;
public bool isPlaying = false;
public bool loop = true;
public int currentFrame = 0;
float m_frameTimer = 0f;
bool m_previewPlaying = false;
#if UNITY_EDITOR
double m_lastEditorTime = 0d;
bool m_editorHooked;
#endif
Image m_img;
Image img {
get{
if(m_img==null){
m_img= GetComponent<Image>();
}
return m_img;
}
}
void OnValidate()
{
if(preview)
{
preview=false;
StartPreviewOnce();
}
}
void OnEnable()
{
#if UNITY_EDITOR
// Never hook editor updates during play-mode enter/exit — QueuePlayerLoopUpdate
// + isPlaying serialized true can infinite-reload Temp/__Backupscenes.
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
TryHookEditorUpdate();
#endif
}
void OnDisable()
{
#if UNITY_EDITOR
UnhookEditorUpdate();
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
#endif
}
void Start()
{
if (sprites == null || sprites.Length == 0)
{
return;
}
currentFrame = Mathf.Clamp(currentFrame, 0, sprites.Length - 1);
ApplyCurrentFrame();
}
// Update is called once per frame
void Update()
{
if (!Application.isPlaying)
{
return;
}
TickAnimation(Time.deltaTime);
}
#if UNITY_EDITOR
void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.ExitingEditMode ||
state == PlayModeStateChange.EnteredPlayMode ||
state == PlayModeStateChange.ExitingPlayMode)
{
m_previewPlaying = false;
UnhookEditorUpdate();
}
else if (state == PlayModeStateChange.EnteredEditMode)
{
TryHookEditorUpdate();
}
}
void TryHookEditorUpdate()
{
if (Application.isPlaying || EditorApplication.isPlayingOrWillChangePlaymode)
{
UnhookEditorUpdate();
return;
}
if (m_editorHooked)
return;
m_lastEditorTime = EditorApplication.timeSinceStartup;
EditorApplication.update += OnEditorUpdate;
m_editorHooked = true;
}
void UnhookEditorUpdate()
{
if (!m_editorHooked)
return;
EditorApplication.update -= OnEditorUpdate;
m_editorHooked = false;
}
void OnEditorUpdate()
{
if (Application.isPlaying ||
EditorApplication.isPlayingOrWillChangePlaymode ||
this == null ||
!isActiveAndEnabled)
{
UnhookEditorUpdate();
return;
}
// Edit-mode animation is preview-only. Ignoring serialized isPlaying prevents
// permanent editor loops when isPlaying was left true in the scene.
if (!m_previewPlaying)
return;
double now = EditorApplication.timeSinceStartup;
float dt = (float)(now - m_lastEditorTime);
m_lastEditorTime = now;
if (dt <= 0f)
return;
int frameBefore = currentFrame;
TickAnimation(dt);
if (currentFrame != frameBefore || m_previewPlaying)
EditorApplication.QueuePlayerLoopUpdate();
}
#endif
void TickAnimation(float deltaTime)
{
if (frameRate <= 0f || sprites == null || sprites.Length == 0)
{
return;
}
bool shouldAnimate = Application.isPlaying
? (m_previewPlaying || isPlaying)
: m_previewPlaying;
if (!shouldAnimate)
{
return;
}
m_frameTimer += deltaTime;
float frameDuration = 1f / frameRate;
while (m_frameTimer >= frameDuration)
{
m_frameTimer -= frameDuration;
if (m_previewPlaying)
{
AdvancePreviewFrame();
}
else
{
AdvanceFrame();
}
}
}
public void Play(bool restart = false)
{
if (sprites == null || sprites.Length == 0)
{
return;
}
if (restart)
{
currentFrame = 0;
m_frameTimer = 0f;
ApplyCurrentFrame();
}
isPlaying = true;
}
public void Stop()
{
isPlaying = false;
m_frameTimer = 0f;
}
public void ResetToFirstFrame()
{
currentFrame = 0;
m_frameTimer = 0f;
ApplyCurrentFrame();
}
void StartPreviewOnce()
{
if (sprites == null || sprites.Length == 0)
{
return;
}
isPlaying = false;
m_previewPlaying = true;
m_frameTimer = 0f;
currentFrame = 0;
ApplyCurrentFrame();
}
void AdvancePreviewFrame()
{
currentFrame++;
if (currentFrame >= sprites.Length)
{
currentFrame = sprites.Length - 1;
m_previewPlaying = false;
}
ApplyCurrentFrame();
}
void AdvanceFrame()
{
currentFrame++;
if (currentFrame >= sprites.Length)
{
if (loop)
{
currentFrame = 0;
}
else
{
currentFrame = sprites.Length - 1;
isPlaying = false;
}
}
ApplyCurrentFrame();
}
void ApplyCurrentFrame()
{
if (img == null || sprites == null || sprites.Length == 0)
{
return;
}
currentFrame = Mathf.Clamp(currentFrame, 0, sprites.Length - 1);
img.sprite = sprites[currentFrame];
}
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: b861af9d256faea40b17dd89fad46ee6
+22 -22
View File
@@ -1,22 +1,22 @@
using UnityEngine;
using DG.Tweening;
using UnityEngine.UI;
[RequireComponent(typeof(Image))]
public class ImageBlinker : MonoBehaviour
{
public float duration = 0.5f;
public Ease ease = Ease.InOutSine;
public float from = 0f;
public float to = 1f;
public int loops = -1;
public LoopType loopType = LoopType.Yoyo;
void Start()
{
var image = GetComponent<Image>();
var c = image.color;
c.a = from;
image.color = c;
image.DOFade(to, duration).SetEase(ease).SetLoops(loops, loopType);
}
}
using UnityEngine;
using DG.Tweening;
using UnityEngine.UI;
[RequireComponent(typeof(Image))]
public class ImageBlinker : MonoBehaviour
{
public float duration = 0.5f;
public Ease ease = Ease.InOutSine;
public float from = 0f;
public float to = 1f;
public int loops = -1;
public LoopType loopType = LoopType.Yoyo;
void Start()
{
var image = GetComponent<Image>();
var c = image.color;
c.a = from;
image.color = c;
image.DOFade(to, duration).SetEase(ease).SetLoops(loops, loopType);
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: ee65e1f8cf52cc24b989d032d572c1fa
+29 -29
View File
@@ -1,29 +1,29 @@
using TMPro;
using UnityEngine;
[RequireComponent(typeof(TMP_Text))]
public class LoadingTextTMP : MonoBehaviour
{
public string baseText = "Loading";
public char dotChar = '.';
public int dotCount =3;
public float interval = 1f;
TMP_Text txt;
void Start()
{
txt = GetComponent<TMP_Text>();
}
float timer = 0;
void Update(){
timer += Time.deltaTime;
if(timer > interval * dotCount){
timer =0;
}
int dots = (int)(timer / interval);
txt.text = baseText + new string(dotChar, dots);
}
}
using TMPro;
using UnityEngine;
[RequireComponent(typeof(TMP_Text))]
public class LoadingTextTMP : MonoBehaviour
{
public string baseText = "Loading";
public char dotChar = '.';
public int dotCount =3;
public float interval = 1f;
TMP_Text txt;
void Start()
{
txt = GetComponent<TMP_Text>();
}
float timer = 0;
void Update(){
timer += Time.deltaTime;
if(timer > interval * dotCount){
timer =0;
}
int dots = (int)(timer / interval);
txt.text = baseText + new string(dotChar, dots);
}
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 093b12f4aa5fb0446a9c1aa544025970
+34 -34
View File
@@ -1,34 +1,34 @@
using UnityEngine;
using DG.Tweening;
using UnityEngine.EventSystems;
[RequireComponent(typeof(EventTrigger))]
public class ScaleOnHover : MonoBehaviour
{
public float scaleFactor = 1.1f;
public float duration = 0.1f;
public Ease ease = Ease.OutBack;
void Start()
{
EventTrigger eventTrigger = GetComponent<EventTrigger>();
EventTrigger.Entry entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.PointerEnter;
entry.callback.AddListener((data) => { OnPointerEnter((PointerEventData)data); });
eventTrigger.triggers.Add(entry);
entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.PointerExit;
entry.callback.AddListener((data) => { OnPointerExit((PointerEventData)data); });
eventTrigger.triggers.Add(entry);
}
public void OnPointerEnter(PointerEventData eventData)
{
transform.DOScale(scaleFactor, duration).SetEase(ease);
}
public void OnPointerExit(PointerEventData eventData)
{
transform.DOKill();
transform.DOScale(1f, duration).SetEase(ease);
}
}
using UnityEngine;
using DG.Tweening;
using UnityEngine.EventSystems;
[RequireComponent(typeof(EventTrigger))]
public class ScaleOnHover : MonoBehaviour
{
public float scaleFactor = 1.1f;
public float duration = 0.1f;
public Ease ease = Ease.OutBack;
void Start()
{
EventTrigger eventTrigger = GetComponent<EventTrigger>();
EventTrigger.Entry entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.PointerEnter;
entry.callback.AddListener((data) => { OnPointerEnter((PointerEventData)data); });
eventTrigger.triggers.Add(entry);
entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.PointerExit;
entry.callback.AddListener((data) => { OnPointerExit((PointerEventData)data); });
eventTrigger.triggers.Add(entry);
}
public void OnPointerEnter(PointerEventData eventData)
{
transform.DOScale(scaleFactor, duration).SetEase(ease);
}
public void OnPointerExit(PointerEventData eventData)
{
transform.DOKill();
transform.DOScale(1f, duration).SetEase(ease);
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 4e4c65221cd965e45853c892a9facd82
+45 -45
View File
@@ -1,45 +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);
}
}
}
}
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);
}
}
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: d961ea20492fa0f448fbedacd3495911
+23 -23
View File
@@ -1,23 +1,23 @@
using UnityEngine;
using DG.Tweening;
using UnityEngine.UI;
using TMPro;
[RequireComponent(typeof(TMP_Text))]
public class TextBlinker : MonoBehaviour
{
public float duration = 0.5f;
public Ease ease = Ease.InOutSine;
public float from = 0f;
public float to = 1f;
public int loops = -1;
public LoopType loopType = LoopType.Yoyo;
void Start()
{
var txt = GetComponent<TMP_Text>();
var c = txt.color;
c.a = from;
txt.color = c;
txt.DOFade(to, duration).SetEase(ease).SetLoops(loops, loopType);
}
}
using UnityEngine;
using DG.Tweening;
using UnityEngine.UI;
using TMPro;
[RequireComponent(typeof(TMP_Text))]
public class TextBlinker : MonoBehaviour
{
public float duration = 0.5f;
public Ease ease = Ease.InOutSine;
public float from = 0f;
public float to = 1f;
public int loops = -1;
public LoopType loopType = LoopType.Yoyo;
void Start()
{
var txt = GetComponent<TMP_Text>();
var c = txt.color;
c.a = from;
txt.color = c;
txt.DOFade(to, duration).SetEase(ease).SetLoops(loops, loopType);
}
}
+2 -2
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
guid: 430ada29524e05f41b887541b79adc2c
fileFormatVersion: 2
guid: 430ada29524e05f41b887541b79adc2c
+30 -30
View File
@@ -1,30 +1,30 @@
using TMPro;
using UnityEngine;
[RequireComponent(typeof(TMP_Text))]
public class TextColorFlash : MonoBehaviour
{
public Color colorA = Color.white;
public Color colorB = Color.red;
public float duration = 1f;
float a =0;
public float t;
TMP_Text txt;
void Start()
{
txt = GetComponent<TMP_Text>();
}
void Update()
{
if(duration <= 0){return;}
float sinA = Mathf.Sin(a);
t = sinA * 0.5f + 0.5f;
Color color = Color.Lerp(colorA, colorB, t);
txt.color = color;
a += Time.deltaTime * duration;
}
}
using TMPro;
using UnityEngine;
[RequireComponent(typeof(TMP_Text))]
public class TextColorFlash : MonoBehaviour
{
public Color colorA = Color.white;
public Color colorB = Color.red;
public float duration = 1f;
float a =0;
public float t;
TMP_Text txt;
void Start()
{
txt = GetComponent<TMP_Text>();
}
void Update()
{
if(duration <= 0){return;}
float sinA = Mathf.Sin(a);
t = sinA * 0.5f + 0.5f;
Color color = Color.Lerp(colorA, colorB, t);
txt.color = color;
a += Time.deltaTime * duration;
}
}
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 0d2075bb87ea2e44ebc4a00fcdac4ff2
+44 -44
View File
@@ -1,44 +1,44 @@
using System;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
[RequireComponent(typeof(Button))]
public class ToggleButton : MonoBehaviour
{
[SerializeField]
public ToggleSpriteData[] toggleSpriteData;
public bool isOn = false;
Button btn;
public UnityEvent<bool> onToggleValueChanged = new UnityEvent<bool>();
void Start()
{
btn = GetComponent<Button>();
btn.onClick.AddListener(OnClick);
}
void OnClick()
{
SetIsOn(!isOn);
}
public void SetIsOn(bool isOn)
{
this.isOn = isOn;
foreach (var spriteData in toggleSpriteData)
{
spriteData.target.sprite = isOn ? spriteData.onSprite : spriteData.offSprite;
}
onToggleValueChanged.Invoke(isOn);
}
}
[Serializable]
public class ToggleSpriteData{
public Image target;
public Sprite onSprite;
public Sprite offSprite;
}
using System;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
[RequireComponent(typeof(Button))]
public class ToggleButton : MonoBehaviour
{
[SerializeField]
public ToggleSpriteData[] toggleSpriteData;
public bool isOn = false;
Button btn;
public UnityEvent<bool> onToggleValueChanged = new UnityEvent<bool>();
void Start()
{
btn = GetComponent<Button>();
btn.onClick.AddListener(OnClick);
}
void OnClick()
{
SetIsOn(!isOn);
}
public void SetIsOn(bool isOn)
{
this.isOn = isOn;
foreach (var spriteData in toggleSpriteData)
{
spriteData.target.sprite = isOn ? spriteData.onSprite : spriteData.offSprite;
}
onToggleValueChanged.Invoke(isOn);
}
}
[Serializable]
public class ToggleSpriteData{
public Image target;
public Sprite onSprite;
public Sprite offSprite;
}
+1 -1
View File
@@ -1,2 +1,2 @@
fileFormatVersion: 2
fileFormatVersion: 2
guid: 32dd41e420d0032409b03beba9058456