631 lines
23 KiB
C#
631 lines
23 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using Platinio.TweenEngine;
|
|
using VRBeats.ScriptableEvents;
|
|
|
|
namespace VRBeats
|
|
{
|
|
public class ScoreManager : MonoBehaviour
|
|
{
|
|
private enum BeatJudgement
|
|
{
|
|
Perfect,
|
|
Great,
|
|
Good,
|
|
Miss
|
|
}
|
|
|
|
[SerializeField] private Text multiplierLabel = null;
|
|
[SerializeField] private Text scoreLabel = null;
|
|
[SerializeField] private Image multiplierLoader = null;
|
|
[SerializeField] private float scoreFollowTime = 1.0f;
|
|
[SerializeField] private CanvasGroup canvasGroup = null;
|
|
[SerializeField] private GameEvent onGameOver = null;
|
|
|
|
[Header("DJMAX Style Score")]
|
|
[SerializeField] private Text comboLabel = null;
|
|
[SerializeField] private Text accuracyLabel = null;
|
|
[SerializeField] private Text judgementLabel = null;
|
|
[SerializeField] private bool createMissingHudLabels = true;
|
|
[SerializeField] private bool applyHudPlacement = true;
|
|
[SerializeField] private Vector2 hudAnchoredPosition = new Vector2(0.0f, 1.65f);
|
|
[SerializeField] private float perfectWindow = 0.11f;
|
|
[SerializeField] private float greatWindow = 0.20f;
|
|
[SerializeField] private float goodWindow = 0.32f;
|
|
|
|
private int maxMultiplier = 0;
|
|
private const int MaxCourseScore = 1000000;
|
|
private const float ProgressBarWidth = 150.0f;
|
|
private float currentMultiplier = 1.0f;
|
|
private int acumulateErrors = 0;
|
|
private int errorLimit = 0;
|
|
private int totalNoteCount = 0;
|
|
private int judgedNoteCount = 0;
|
|
private int currentCombo = 0;
|
|
private int maxCombo = 0;
|
|
private int perfectCount = 0;
|
|
private int greatCount = 0;
|
|
private int goodCount = 0;
|
|
private int missCount = 0;
|
|
private int earnedAccuracyPoints = 0;
|
|
private float visualScore = 0.0f;
|
|
private int scoreTweenID = -1;
|
|
private int loaderTweenID = -1;
|
|
private BeatJudgement lastJudgement = BeatJudgement.Perfect;
|
|
private float judgementTimer = 0.0f;
|
|
private Text progressLabel = null;
|
|
private Text rankLabel = null;
|
|
private Image progressBarBackground = null;
|
|
private Image progressBarFill = null;
|
|
private Vector3 comboBaseScale = Vector3.one;
|
|
private float songCurrentTime = 0.0f;
|
|
private float songDuration = 0.0f;
|
|
private bool resultFinalized = false;
|
|
private bool destroyed = false;
|
|
|
|
private static bool hasPendingSliceTiming = false;
|
|
private static float pendingSliceTiming = 0.0f;
|
|
private static Font hudFont = null;
|
|
private Image ringBackground = null;
|
|
|
|
public int CurrentScore
|
|
{
|
|
get
|
|
{
|
|
float accuracyRatio = AccuracyPercent / 100.0f;
|
|
float comboRatio = totalNoteCount > 0
|
|
? maxCombo / (float)totalNoteCount
|
|
: 0.0f;
|
|
return Mathf.RoundToInt(800000.0f * accuracyRatio + 200000.0f * comboRatio);
|
|
}
|
|
}
|
|
public float AccuracyPercent
|
|
{
|
|
get
|
|
{
|
|
int denominatorNotes = totalNoteCount > 0 ? totalNoteCount : judgedNoteCount;
|
|
if (denominatorNotes <= 0)
|
|
return 100.0f;
|
|
|
|
return (float)earnedAccuracyPoints / (denominatorNotes * 1000) * 100.0f;
|
|
}
|
|
}
|
|
|
|
public string Rank
|
|
{
|
|
get
|
|
{
|
|
if (CurrentScore >= MaxCourseScore) return "M";
|
|
|
|
float accuracy = AccuracyPercent;
|
|
if (accuracy >= 98.0f) return "S+";
|
|
if (accuracy >= 95.0f) return "S";
|
|
if (accuracy >= 90.0f) return "A";
|
|
if (accuracy >= 80.0f) return "B";
|
|
if (accuracy >= 70.0f) return "C";
|
|
if (accuracy >= 60.0f) return "D";
|
|
return "F";
|
|
}
|
|
}
|
|
|
|
public int MaxCombo => maxCombo;
|
|
public string RankColorHex => GetRankColorHex();
|
|
|
|
private void Awake()
|
|
{
|
|
maxMultiplier = VR_BeatManager.instance.GameSettings.MaxMultiplier;
|
|
errorLimit = VR_BeatManager.instance.GameSettings.ErrorLimit;
|
|
|
|
if (multiplierLoader != null)
|
|
multiplierLoader.fillAmount = 0.0f;
|
|
|
|
canvasGroup ??= GetComponent<CanvasGroup>() ?? gameObject.AddComponent<CanvasGroup>();
|
|
PrepareHud();
|
|
}
|
|
|
|
public static void ReportSliceTiming(float timingErrorSeconds)
|
|
{
|
|
pendingSliceTiming = timingErrorSeconds;
|
|
hasPendingSliceTiming = true;
|
|
}
|
|
|
|
public static void ReportMiss()
|
|
{
|
|
hasPendingSliceTiming = false;
|
|
pendingSliceTiming = 0.0f;
|
|
}
|
|
|
|
public void SetTotalNotes(int noteCount)
|
|
{
|
|
totalNoteCount = Mathf.Max(0, noteCount);
|
|
resultFinalized = false;
|
|
UpdateScoreTween();
|
|
}
|
|
|
|
public void CompleteSong()
|
|
{
|
|
if (resultFinalized)
|
|
return;
|
|
|
|
resultFinalized = true;
|
|
|
|
int missedUnjudgedNotes = Mathf.Max(0, totalNoteCount - judgedNoteCount);
|
|
if (missedUnjudgedNotes > 0)
|
|
{
|
|
missCount += missedUnjudgedNotes;
|
|
judgedNoteCount += missedUnjudgedNotes;
|
|
currentCombo = 0;
|
|
currentMultiplier = 1.0f;
|
|
}
|
|
|
|
UpdateScoreTween();
|
|
}
|
|
|
|
public void SetSongProgress(float currentTime, float duration)
|
|
{
|
|
songDuration = Mathf.Max(0.0f, duration);
|
|
songCurrentTime = songDuration > 0.0f
|
|
? Mathf.Clamp(currentTime, 0.0f, songDuration)
|
|
: Mathf.Max(0.0f, currentTime);
|
|
}
|
|
|
|
public void OnGameOver()
|
|
{
|
|
gameObject.CancelAllTweens();
|
|
if (canvasGroup != null)
|
|
{
|
|
canvasGroup.alpha = 0.0f;
|
|
canvasGroup.interactable = false;
|
|
canvasGroup.blocksRaycasts = false;
|
|
}
|
|
|
|
SetSaberVisibility(false);
|
|
}
|
|
|
|
public void OnGameRestart()
|
|
{
|
|
ResetThisComponent();
|
|
gameObject.CancelAllTweens();
|
|
if (canvasGroup != null)
|
|
{
|
|
canvasGroup.alpha = 1.0f;
|
|
canvasGroup.interactable = true;
|
|
canvasGroup.blocksRaycasts = false;
|
|
}
|
|
|
|
SetSaberVisibility(true);
|
|
}
|
|
|
|
public void ResetThisComponent()
|
|
{
|
|
currentMultiplier = 1.0f;
|
|
visualScore = 0;
|
|
acumulateErrors = 0;
|
|
judgedNoteCount = 0;
|
|
currentCombo = 0;
|
|
maxCombo = 0;
|
|
perfectCount = 0;
|
|
greatCount = 0;
|
|
goodCount = 0;
|
|
missCount = 0;
|
|
earnedAccuracyPoints = 0;
|
|
judgementTimer = 0.0f;
|
|
resultFinalized = false;
|
|
hasPendingSliceTiming = false;
|
|
pendingSliceTiming = 0.0f;
|
|
|
|
if (multiplierLoader != null)
|
|
multiplierLoader.fillAmount = 0.0f;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
UpdateUI();
|
|
}
|
|
|
|
public void OnCorrectSlice()
|
|
{
|
|
if (destroyed)
|
|
return;
|
|
|
|
BeatJudgement judgement = ConsumeJudgement();
|
|
RegisterJudgement(judgement);
|
|
|
|
acumulateErrors = judgement == BeatJudgement.Miss ? acumulateErrors + 1 : 0;
|
|
|
|
UpdateScoreTween();
|
|
UpdateMultiplierLoaderValue();
|
|
}
|
|
|
|
public void OnIncorrectSlice()
|
|
{
|
|
if (destroyed)
|
|
return;
|
|
|
|
RegisterJudgement(BeatJudgement.Miss);
|
|
acumulateErrors++;
|
|
currentMultiplier = 1.0f;
|
|
|
|
UpdateScoreTween();
|
|
UpdateMultiplierLoaderValue();
|
|
|
|
if (acumulateErrors > errorLimit)
|
|
onGameOver.Invoke();
|
|
}
|
|
|
|
public string BuildResultSummary(int minScoreLength)
|
|
{
|
|
string score = CurrentScore.ToString("N0");
|
|
|
|
return $"<line-height=76%><size=300%><color={GetRankColorHex()}>{Rank}</color></size>" +
|
|
$"<pos=255><voffset=0.48em><size=92%>{score}</size></voffset>\n" +
|
|
$"<pos=255><size=72%><color=#D7F7FF>MAX COMBO {maxCombo}</color></size>";
|
|
}
|
|
|
|
private void CancelTweenById(int id)
|
|
{
|
|
if (id != -1)
|
|
PlatinioTween.instance.CancelTween(id);
|
|
}
|
|
|
|
private void UpdateMultiplierLoaderValue()
|
|
{
|
|
if (destroyed || multiplierLoader == null)
|
|
return;
|
|
|
|
float multiplierLoaderValue = GetComboTierProgress();
|
|
CancelTweenById(loaderTweenID);
|
|
loaderTweenID = PlatinioTween.instance.ValueTween(multiplierLoader.fillAmount, multiplierLoaderValue, 1.0f)
|
|
.SetEase(Ease.EaseOutExpo)
|
|
.SetOnUpdateFloat(delegate (float value)
|
|
{
|
|
if (multiplierLoader != null)
|
|
multiplierLoader.fillAmount = value;
|
|
})
|
|
.SetOwner(multiplierLoader.gameObject)
|
|
.ID;
|
|
}
|
|
|
|
private void UpdateScoreTween()
|
|
{
|
|
CancelTweenById(scoreTweenID);
|
|
scoreTweenID = PlatinioTween.instance.ValueTween(visualScore, CurrentScore, scoreFollowTime)
|
|
.SetEase(Ease.EaseOutExpo)
|
|
.SetOnUpdateFloat(delegate (float value) { visualScore = value; })
|
|
.ID;
|
|
}
|
|
|
|
private void UpdateUI()
|
|
{
|
|
if (destroyed)
|
|
return;
|
|
|
|
if (multiplierLabel != null)
|
|
multiplierLabel.text = $"x{Mathf.RoundToInt(currentMultiplier)}";
|
|
if (scoreLabel != null)
|
|
scoreLabel.text = $"{Mathf.CeilToInt(visualScore):N0}";
|
|
if (comboLabel != null)
|
|
comboLabel.text = currentCombo > 0
|
|
? $"<size=18><color=#E6F8FF>COMBO</color></size>\n<size=42>{currentCombo}</size>"
|
|
: "<size=18><color=#E6F8FF>COMBO</color></size>\n<size=42>0</size>";
|
|
if (accuracyLabel != null)
|
|
accuracyLabel.text = $"{AccuracyPercent:0.0}%";
|
|
if (rankLabel != null)
|
|
rankLabel.text = $"<color={GetRankColorHex()}>{Rank}</color>";
|
|
if (progressLabel != null)
|
|
progressLabel.text = songDuration > 0.0f
|
|
? $"{FormatTime(songCurrentTime)} / {FormatTime(songDuration)}"
|
|
: "";
|
|
if (progressBarFill != null)
|
|
SetProgressBarFill(songDuration > 0.0f
|
|
? Mathf.Clamp01(songCurrentTime / songDuration)
|
|
: 0.0f);
|
|
|
|
if (judgementLabel == null)
|
|
return;
|
|
|
|
judgementTimer -= Time.deltaTime;
|
|
judgementLabel.text = judgementTimer > 0.0f ? GetJudgementText(lastJudgement) : "";
|
|
judgementLabel.color = GetJudgementColor(lastJudgement);
|
|
}
|
|
|
|
private BeatJudgement ConsumeJudgement()
|
|
{
|
|
if (!hasPendingSliceTiming)
|
|
return BeatJudgement.Perfect;
|
|
|
|
float timing = pendingSliceTiming;
|
|
hasPendingSliceTiming = false;
|
|
pendingSliceTiming = 0.0f;
|
|
|
|
if (timing <= perfectWindow) return BeatJudgement.Perfect;
|
|
if (timing <= greatWindow) return BeatJudgement.Great;
|
|
if (timing <= goodWindow) return BeatJudgement.Good;
|
|
return BeatJudgement.Good;
|
|
}
|
|
|
|
private void RegisterJudgement(BeatJudgement judgement)
|
|
{
|
|
lastJudgement = judgement;
|
|
judgementTimer = 0.45f;
|
|
judgedNoteCount++;
|
|
|
|
if (judgement == BeatJudgement.Perfect)
|
|
{
|
|
perfectCount++;
|
|
earnedAccuracyPoints += 1000;
|
|
currentCombo++;
|
|
}
|
|
else if (judgement == BeatJudgement.Great)
|
|
{
|
|
greatCount++;
|
|
earnedAccuracyPoints += 900;
|
|
currentCombo++;
|
|
}
|
|
else if (judgement == BeatJudgement.Good)
|
|
{
|
|
goodCount++;
|
|
earnedAccuracyPoints += 700;
|
|
currentCombo++;
|
|
}
|
|
else
|
|
{
|
|
missCount++;
|
|
currentCombo = 0;
|
|
}
|
|
|
|
maxCombo = Mathf.Max(maxCombo, currentCombo);
|
|
currentMultiplier = judgement == BeatJudgement.Miss
|
|
? 1.0f
|
|
: Mathf.Min(GetComboMultiplier(currentCombo), Mathf.Max(1.0f, maxMultiplier));
|
|
|
|
PulseComboLabel(judgement);
|
|
}
|
|
|
|
private static float GetComboMultiplier(int combo)
|
|
{
|
|
if (combo >= 50) return 1.5f;
|
|
if (combo >= 30) return 1.35f;
|
|
if (combo >= 15) return 1.2f;
|
|
if (combo >= 5) return 1.1f;
|
|
return 1.0f;
|
|
}
|
|
|
|
private static string GetJudgementText(BeatJudgement judgement)
|
|
{
|
|
switch (judgement)
|
|
{
|
|
case BeatJudgement.Perfect: return "PERFECT";
|
|
case BeatJudgement.Great: return "GREAT";
|
|
case BeatJudgement.Good: return "GOOD";
|
|
default: return "BREAK";
|
|
}
|
|
}
|
|
|
|
private static Color GetJudgementColor(BeatJudgement judgement)
|
|
{
|
|
switch (judgement)
|
|
{
|
|
case BeatJudgement.Perfect: return new Color(0.25f, 0.95f, 1.0f, 1.0f);
|
|
case BeatJudgement.Great: return new Color(0.58f, 1.0f, 0.45f, 1.0f);
|
|
case BeatJudgement.Good: return new Color(1.0f, 0.8f, 0.35f, 1.0f);
|
|
default: return new Color(1.0f, 0.25f, 0.45f, 1.0f);
|
|
}
|
|
}
|
|
|
|
private float GetComboTierProgress()
|
|
{
|
|
int lower = 0;
|
|
int upper = 5;
|
|
if (currentCombo >= 50) return 1.0f;
|
|
if (currentCombo >= 30) { lower = 30; upper = 50; }
|
|
else if (currentCombo >= 15) { lower = 15; upper = 30; }
|
|
else if (currentCombo >= 5) { lower = 5; upper = 15; }
|
|
|
|
return Mathf.InverseLerp(lower, upper, currentCombo);
|
|
}
|
|
|
|
private void PrepareHud()
|
|
{
|
|
RectTransform rect = transform as RectTransform;
|
|
if (applyHudPlacement && rect != null)
|
|
rect.anchoredPosition = hudAnchoredPosition;
|
|
|
|
comboLabel = comboLabel != null ? comboLabel : FindHudText("Combo");
|
|
accuracyLabel = accuracyLabel != null ? accuracyLabel : FindHudText("Accuracy");
|
|
rankLabel = rankLabel != null ? rankLabel : FindHudText("Rank");
|
|
judgementLabel = judgementLabel != null ? judgementLabel : FindHudText("Judgement");
|
|
progressLabel = progressLabel != null ? progressLabel : FindHudText("SongProgress");
|
|
progressBarBackground = progressBarBackground != null ? progressBarBackground : FindHudImage("SongProgressBarBackground");
|
|
progressBarFill = progressBarFill != null ? progressBarFill : FindHudImage("SongProgressBarFill");
|
|
|
|
if (!createMissingHudLabels)
|
|
return;
|
|
|
|
comboLabel ??= CreateHudText("Combo");
|
|
accuracyLabel ??= CreateHudText("Accuracy");
|
|
rankLabel ??= CreateHudText("Rank");
|
|
judgementLabel ??= CreateHudText("Judgement");
|
|
progressLabel ??= CreateHudText("SongProgress");
|
|
progressBarBackground ??= CreateHudImage("SongProgressBarBackground");
|
|
progressBarFill ??= CreateHudImage("SongProgressBarFill");
|
|
ringBackground = ringBackground != null ? ringBackground : FindHudImage("MultiplierRingBg");
|
|
ringBackground ??= CreateHudImage("MultiplierRingBg");
|
|
|
|
ConfigureText(comboLabel, new Vector2(-335.0f, 92.0f), new Vector2(190.0f, 118.0f), 34, Color.white, TextAnchor.MiddleCenter);
|
|
ConfigureText(scoreLabel, new Vector2(-335.0f, 10.0f), new Vector2(190.0f, 42.0f), 22, Color.white, TextAnchor.MiddleCenter);
|
|
ConfigureText(accuracyLabel, new Vector2(-335.0f, -34.0f), new Vector2(190.0f, 32.0f), 18, new Color(0.84f, 0.94f, 1.0f, 1.0f), TextAnchor.MiddleCenter);
|
|
ConfigureText(rankLabel, new Vector2(-335.0f, -92.0f), new Vector2(190.0f, 72.0f), 48, Color.white, TextAnchor.MiddleCenter);
|
|
ConfigureText(judgementLabel, new Vector2(0.0f, 118.0f), new Vector2(280.0f, 56.0f), 28, new Color(0.25f, 0.95f, 1.0f, 1.0f), TextAnchor.MiddleCenter);
|
|
ConfigureText(multiplierLabel, new Vector2(335.0f, 38.0f), new Vector2(118.0f, 76.0f), 34, Color.white, TextAnchor.MiddleCenter);
|
|
ConfigureText(progressLabel, new Vector2(335.0f, -75.0f), new Vector2(180.0f, 30.0f), 17, Color.white, TextAnchor.MiddleCenter);
|
|
ConfigureImage(multiplierLoader, new Vector2(335.0f, 38.0f), new Vector2(112.0f, 112.0f), new Color(1.0f, 1.0f, 1.0f, 0.78f));
|
|
ConfigureImage(ringBackground, new Vector2(335.0f, 38.0f), new Vector2(112.0f, 112.0f), new Color(1.0f, 1.0f, 1.0f, 0.15f));
|
|
if (ringBackground != null && multiplierLoader != null)
|
|
{
|
|
ringBackground.sprite = multiplierLoader.sprite;
|
|
ringBackground.type = Image.Type.Simple;
|
|
ringBackground.transform.SetSiblingIndex(multiplierLoader.transform.GetSiblingIndex());
|
|
}
|
|
ConfigureImage(progressBarBackground, new Vector2(335.0f, -48.0f), new Vector2(ProgressBarWidth, 5.0f), new Color(1.0f, 1.0f, 1.0f, 0.22f));
|
|
ConfigureImage(progressBarFill, new Vector2(335.0f - ProgressBarWidth * 0.5f, -48.0f), new Vector2(ProgressBarWidth, 5.0f), Color.white);
|
|
if (progressBarFill != null)
|
|
{
|
|
RectTransform fillRect = progressBarFill.rectTransform;
|
|
fillRect.pivot = new Vector2(0.0f, 0.5f);
|
|
SetProgressBarFill(0.0f);
|
|
}
|
|
comboBaseScale = comboLabel != null ? comboLabel.transform.localScale : Vector3.one;
|
|
}
|
|
|
|
private Text CreateHudText(string name)
|
|
{
|
|
GameObject textObject = new GameObject(name);
|
|
textObject.layer = gameObject.layer;
|
|
textObject.transform.SetParent(transform, false);
|
|
|
|
RectTransform rect = textObject.AddComponent<RectTransform>();
|
|
textObject.AddComponent<CanvasRenderer>();
|
|
Text text = textObject.AddComponent<Text>();
|
|
return text;
|
|
}
|
|
|
|
private Image CreateHudImage(string name)
|
|
{
|
|
GameObject imageObject = new GameObject(name);
|
|
imageObject.layer = gameObject.layer;
|
|
imageObject.transform.SetParent(transform, false);
|
|
|
|
imageObject.AddComponent<CanvasRenderer>();
|
|
return imageObject.AddComponent<Image>();
|
|
}
|
|
|
|
private static void ConfigureText(Text text, Vector2 anchoredPosition, Vector2 size, int fontSize, Color color, TextAnchor alignment)
|
|
{
|
|
if (text == null)
|
|
return;
|
|
|
|
if (text.font == null)
|
|
text.font = HudFont;
|
|
|
|
RectTransform rect = text.rectTransform;
|
|
rect.anchorMin = new Vector2(0.5f, 0.5f);
|
|
rect.anchorMax = new Vector2(0.5f, 0.5f);
|
|
rect.anchoredPosition = anchoredPosition;
|
|
rect.sizeDelta = size;
|
|
|
|
text.fontSize = fontSize;
|
|
text.color = color;
|
|
text.alignment = alignment;
|
|
text.horizontalOverflow = HorizontalWrapMode.Overflow;
|
|
text.verticalOverflow = VerticalWrapMode.Overflow;
|
|
text.raycastTarget = false;
|
|
text.supportRichText = true;
|
|
text.lineSpacing = 0.86f;
|
|
|
|
Shadow shadow = text.GetComponent<Shadow>() ?? text.gameObject.AddComponent<Shadow>();
|
|
shadow.effectColor = new Color(0.0f, 0.0f, 0.0f, 0.72f);
|
|
shadow.effectDistance = new Vector2(3.0f, -3.0f);
|
|
}
|
|
|
|
private static void ConfigureImage(Image image, Vector2 anchoredPosition, Vector2 size)
|
|
{
|
|
ConfigureImage(image, anchoredPosition, size, new Color(1.0f, 1.0f, 1.0f, 0.85f));
|
|
}
|
|
|
|
private static void ConfigureImage(Image image, Vector2 anchoredPosition, Vector2 size, Color color)
|
|
{
|
|
if (image == null)
|
|
return;
|
|
|
|
RectTransform rect = image.rectTransform;
|
|
rect.anchorMin = new Vector2(0.5f, 0.5f);
|
|
rect.anchorMax = new Vector2(0.5f, 0.5f);
|
|
rect.anchoredPosition = anchoredPosition;
|
|
rect.sizeDelta = size;
|
|
image.color = color;
|
|
image.raycastTarget = false;
|
|
}
|
|
|
|
private void SetProgressBarFill(float progress)
|
|
{
|
|
if (progressBarFill == null)
|
|
return;
|
|
|
|
RectTransform rect = progressBarFill.rectTransform;
|
|
rect.sizeDelta = new Vector2(ProgressBarWidth * Mathf.Clamp01(progress), rect.sizeDelta.y);
|
|
}
|
|
|
|
private static Font HudFont
|
|
{
|
|
get
|
|
{
|
|
if (hudFont == null)
|
|
hudFont = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
|
|
|
return hudFont;
|
|
}
|
|
}
|
|
|
|
private Text FindHudText(string objectName)
|
|
{
|
|
Transform child = transform.Find(objectName);
|
|
return child != null ? child.GetComponent<Text>() : null;
|
|
}
|
|
|
|
private Image FindHudImage(string objectName)
|
|
{
|
|
Transform child = transform.Find(objectName);
|
|
return child != null ? child.GetComponent<Image>() : null;
|
|
}
|
|
|
|
private string GetRankColorHex()
|
|
{
|
|
switch (Rank)
|
|
{
|
|
case "M": return "#E8B7FF";
|
|
case "S+": return "#41F2FF";
|
|
case "S": return "#FFD95C";
|
|
case "A": return "#B9FF72";
|
|
case "B": return "#FFE06A";
|
|
case "C": return "#FFB15C";
|
|
case "D": return "#FF7C7C";
|
|
default: return "#A9B7C0";
|
|
}
|
|
}
|
|
|
|
private void PulseComboLabel(BeatJudgement judgement)
|
|
{
|
|
if (comboLabel == null || judgement == BeatJudgement.Miss)
|
|
return;
|
|
|
|
comboLabel.gameObject.CancelAllTweens();
|
|
comboLabel.transform.localScale = comboBaseScale * 1.08f;
|
|
comboLabel.transform.ScaleTween(comboBaseScale, 0.16f).SetEase(Ease.EaseOutExpo).SetOwner(comboLabel.gameObject);
|
|
}
|
|
|
|
private static string FormatTime(float seconds)
|
|
{
|
|
int wholeSeconds = Mathf.Max(0, Mathf.FloorToInt(seconds));
|
|
int minutes = wholeSeconds / 60;
|
|
int remainingSeconds = wholeSeconds % 60;
|
|
return $"{minutes}:{remainingSeconds:00}";
|
|
}
|
|
|
|
private static void SetSaberVisibility(bool visible)
|
|
{
|
|
VR_Saber[] sabers = FindObjectsByType<VR_Saber>(FindObjectsSortMode.None);
|
|
for (int i = 0; i < sabers.Length; i++)
|
|
{
|
|
if (sabers[i] == null)
|
|
continue;
|
|
|
|
if (visible)
|
|
sabers[i].MakeVisible();
|
|
else
|
|
sabers[i].MakeInvisible();
|
|
}
|
|
}
|
|
}
|
|
}
|