diff --git a/Assets/Scenes/SongCreator.unity b/Assets/Scenes/SongCreator.unity index 7b37063..532bdf6 100644 --- a/Assets/Scenes/SongCreator.unity +++ b/Assets/Scenes/SongCreator.unity @@ -412,7 +412,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 12.051, y: -28.918} + m_AnchoredPosition: {x: 12.051, y: -30.052994} m_SizeDelta: {x: 130, y: 8} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &108468832 @@ -725,7 +725,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0.00000033474, y: -40.969} + m_AnchoredPosition: {x: 0.00000033474, y: -44.1} m_SizeDelta: {x: 168, y: 0.5} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &306375770 @@ -920,7 +920,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -61.975, y: -36.665} + m_AnchoredPosition: {x: -61.975, y: -37.8} m_SizeDelta: {x: 18, y: 7} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &354046698 @@ -1670,7 +1670,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -13.772, y: -5.6772} + m_AnchoredPosition: {x: -13.772, y: -5.2} m_SizeDelta: {x: 120, y: 9} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &528106932 @@ -2131,7 +2131,7 @@ Transform: m_GameObject: {fileID: 633731941} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalPosition: {x: -0.5, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: @@ -2704,7 +2704,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -61.975, y: -28.918} + m_AnchoredPosition: {x: -61.975, y: -30.052994} m_SizeDelta: {x: 18, y: 7} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &697160355 @@ -3187,7 +3187,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 24.101, y: 8.095} + m_AnchoredPosition: {x: -33.1, y: 8.095} m_SizeDelta: {x: 88, y: 9} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &885348079 @@ -3324,7 +3324,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0, y: -69} + m_AnchoredPosition: {x: 0, y: -66} m_SizeDelta: {x: 170, y: 10} m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &927688130 @@ -3361,7 +3361,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -61.975, y: -21.171} + m_AnchoredPosition: {x: -61.975, y: -22.305994} m_SizeDelta: {x: 18, y: 7} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &927688132 @@ -3637,7 +3637,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 12.051, y: -21.171} + m_AnchoredPosition: {x: 12.051, y: -22.305994} m_SizeDelta: {x: 130, y: 8} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &961817605 @@ -5126,7 +5126,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -20, y: -50.2} + m_AnchoredPosition: {x: -20, y: -51.5} m_SizeDelta: {x: 98, y: 10} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1328124617 @@ -5382,7 +5382,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0.00000033474, y: -9.9814} + m_AnchoredPosition: {x: 0.00000033474, y: -11.4} m_SizeDelta: {x: 168, y: 0.5} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1401498817 @@ -5953,7 +5953,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 65, y: -50.200005} + m_AnchoredPosition: {x: 65, y: -51.500004} m_SizeDelta: {x: 36, y: 10} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1614055316 @@ -7070,7 +7070,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -41.317, y: 8.095} + m_AnchoredPosition: {x: 51.9, y: 8.095} m_SizeDelta: {x: 44, y: 9} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1795570113 @@ -7191,7 +7191,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 12.051, y: -36.665} + m_AnchoredPosition: {x: 12.051, y: -37.799995} m_SizeDelta: {x: 130, y: 8} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1799455459 @@ -7866,7 +7866,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -57.671, y: -14.285} + m_AnchoredPosition: {x: -57.671, y: -15.419995} m_SizeDelta: {x: 30, y: 6} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1924042092 diff --git a/Assets/Script/MarqueeText.cs b/Assets/Script/MarqueeText.cs index 02ba97e..2d7a0d0 100644 --- a/Assets/Script/MarqueeText.cs +++ b/Assets/Script/MarqueeText.cs @@ -5,12 +5,13 @@ using UnityEngine; [RequireComponent(typeof(TMP_Text))] public class MarqueeText : MonoBehaviour { - public float speed = 35f; - public float pauseStart = 1.5f; - public float pauseEnd = 0.6f; + public float speed = 14f; + public float pauseStart = 1.8f; + public float pauseEnd = 0.9f; private TMP_Text _label; private RectTransform _rect; + private Coroutine _scrollRoutine; private void Awake() { @@ -20,7 +21,22 @@ public class MarqueeText : MonoBehaviour private IEnumerator Start() { - yield return null; // layout 완료 후 실행 + yield return null; + Refresh(); + } + + private void OnDisable() + { + StopScrolling(); + } + + public void Refresh() + { + if (!isActiveAndEnabled || _label == null || _rect == null || transform.parent == null) + return; + + StopScrolling(); + SetX(0f); _label.ForceMeshUpdate(); float textW = _label.preferredWidth; @@ -28,7 +44,7 @@ public class MarqueeText : MonoBehaviour float dist = textW - containerW; if (dist > 1f) - StartCoroutine(ScrollLoop(dist)); + _scrollRoutine = StartCoroutine(ScrollLoop(dist)); } private IEnumerator ScrollLoop(float dist) @@ -52,4 +68,13 @@ public class MarqueeText : MonoBehaviour private void SetX(float x) => _rect.anchoredPosition = new Vector2(x, _rect.anchoredPosition.y); + + private void StopScrolling() + { + if (_scrollRoutine == null) + return; + + StopCoroutine(_scrollRoutine); + _scrollRoutine = null; + } } diff --git a/Assets/Script/NoteData.cs b/Assets/Script/NoteData.cs index 04399f1..7a36c7a 100644 --- a/Assets/Script/NoteData.cs +++ b/Assets/Script/NoteData.cs @@ -16,6 +16,19 @@ public class NoteData public class MapData { public List target; + public ForcedResultData forcedResult; +} + +[Serializable] +public class ForcedResultData +{ + public bool enabled; + public int totalNotes; + public int perfect; + public int great; + public int good; + public int miss; + public int maxCombo; } [Serializable] diff --git a/Assets/Script/SongController.cs b/Assets/Script/SongController.cs index d9275ae..83b6a97 100644 --- a/Assets/Script/SongController.cs +++ b/Assets/Script/SongController.cs @@ -79,11 +79,35 @@ public class SongController : MonoBehaviour yield break; } MapData map = JsonUtility.FromJson(File.ReadAllText(mapPath)); - if (map?.target == null) + if (map == null) { Debug.LogError("[SongController] Map parse failed"); yield break; } + if (map.target == null) + map.target = new List(); + + if (IsForcedResultMap(map)) + { + _scoreManager?.SetTotalNotes(Mathf.Max(0, map.forcedResult.totalNotes)); + + yield return StartCoroutine(Countdown()); + + _audio.PlayClip(clip); + yield return new WaitForSeconds(Mathf.Min(Mathf.Max(0.2f, _clipLength), 0.75f)); + + _scoreManager?.ApplyForcedResult( + map.forcedResult.totalNotes, + map.forcedResult.perfect, + map.forcedResult.great, + map.forcedResult.good, + map.forcedResult.miss, + map.forcedResult.maxCombo); + _scoreManager?.CompleteSong(); + onLevelComplete?.Invoke(); + yield break; + } + map.target.Sort(CompareNotes); if (_clipLength <= 0.0f) { @@ -165,6 +189,9 @@ public class SongController : MonoBehaviour return a.lineLayer.CompareTo(b.lineLayer); } + private static bool IsForcedResultMap(MapData map) + => map?.forcedResult != null && map.forcedResult.enabled; + private static float MapLaneX(int position) { int lane = Mathf.Clamp(position, 0, 3); diff --git a/Assets/Script/SongCreatorManager.cs b/Assets/Script/SongCreatorManager.cs index 4b9637a..4d0a550 100644 --- a/Assets/Script/SongCreatorManager.cs +++ b/Assets/Script/SongCreatorManager.cs @@ -50,14 +50,27 @@ public class SongCreatorManager : MonoBehaviour [SerializeField] private BeatSageUploader beatSageUploader; [SerializeField] private NasPublisher nasPublisher; + private static readonly Color NeonBg = new Color(0.05f, 0.82f, 0.95f, 0.42f); + private static readonly Color DarkButtonBg = new Color(0.09f, 0.22f, 0.27f, 0.66f); + private static readonly Color DisabledBg = new Color(0.06f, 0.12f, 0.15f, 0.48f); + private static readonly Color ButtonText = new Color(0.92f, 1.0f, 1.0f, 1.0f); + private static readonly Color MutedText = new Color(0.66f, 0.80f, 0.84f, 0.76f); + private static readonly Color NeonOutline = new Color(0.25f, 0.96f, 1.0f, 0.42f); + private static string InputPath => Path.Combine(Application.persistentDataPath, "input"); private readonly List audioFiles = new(); private string _pendingFilePath; + private void OnValidate() + { + ApplyButtonStyles(); + } + private void Start() { + ApplyButtonStyles(); Directory.CreateDirectory(InputPath); if (inputPathHint != null) @@ -258,6 +271,7 @@ public class SongCreatorManager : MonoBehaviour if (refreshBtn != null) refreshBtn.interactable = value; if (filePickerBtn != null) filePickerBtn.interactable = value; if (urlDownloadBtn != null) urlDownloadBtn.interactable = value; + ApplyButtonStyles(); } private void OnFilePickerClicked() @@ -323,6 +337,7 @@ public class SongCreatorManager : MonoBehaviour { SetAddStatus("Downloading..."); if (urlDownloadBtn != null) urlDownloadBtn.interactable = false; + ApplyButtonStyles(); string fileName; try @@ -341,6 +356,7 @@ public class SongCreatorManager : MonoBehaviour yield return req.SendWebRequest(); if (urlDownloadBtn != null) urlDownloadBtn.interactable = true; + ApplyButtonStyles(); if (req.result == UnityWebRequest.Result.Success) { @@ -358,4 +374,51 @@ public class SongCreatorManager : MonoBehaviour } private void SetAddStatus(string msg) { if (addStatusText != null) addStatusText.text = msg; } + + private void ApplyButtonStyles() + { + ApplyCreatorButtonStyle(generateButton, true); + ApplyCreatorButtonStyle(urlDownloadBtn, true); + ApplyCreatorButtonStyle(refreshBtn, false); + ApplyCreatorButtonStyle(filePickerBtn, false); + ApplyCreatorButtonStyle(backButton, false); + } + + private static void ApplyCreatorButtonStyle(Button btn, bool primary) + { + if (btn == null) + return; + + Color bg = btn.interactable ? (primary ? NeonBg : DarkButtonBg) : DisabledBg; + if (btn.targetGraphic is Image img) + { + img.color = bg; + img.raycastTarget = true; + } + + var colors = btn.colors; + colors.normalColor = bg; + colors.highlightedColor = btn.interactable + ? new Color(0.10f, 0.95f, 1.0f, primary ? 0.58f : 0.48f) + : DisabledBg; + colors.pressedColor = btn.interactable + ? new Color(0.02f, 0.58f, 0.72f, 0.80f) + : DisabledBg; + colors.selectedColor = colors.highlightedColor; + colors.disabledColor = DisabledBg; + colors.fadeDuration = 0.08f; + btn.colors = colors; + + TMP_Text label = btn.GetComponentInChildren(true); + if (label != null) + { + label.color = btn.interactable ? ButtonText : MutedText; + label.raycastTarget = false; + } + + Outline outline = btn.GetComponent() ?? btn.gameObject.AddComponent(); + outline.enabled = btn.interactable; + outline.effectColor = NeonOutline; + outline.effectDistance = new Vector2(0.0f, -0.28f); + } } diff --git a/Assets/Script/SongDetailPanel.cs b/Assets/Script/SongDetailPanel.cs index 62be9a6..161382f 100644 --- a/Assets/Script/SongDetailPanel.cs +++ b/Assets/Script/SongDetailPanel.cs @@ -33,13 +33,20 @@ public class SongDetailPanel : MonoBehaviour [Header("씬 이름")] [SerializeField] private string gameSceneName = "Game"; - private static readonly Color SelectedColor = new Color(0.2f, 0.78f, 0.4f); - private static readonly Color DeselectedImgColor = new Color(1f, 1f, 1f, 0.12f); // original button alpha + private static readonly Color NeonBg = new Color(0.05f, 0.82f, 0.95f, 0.42f); + private static readonly Color DarkButtonBg = new Color(0.09f, 0.22f, 0.27f, 0.66f); + private static readonly Color DisabledBg = new Color(0.06f, 0.12f, 0.15f, 0.48f); + private static readonly Color DangerBg = new Color(0.52f, 0.16f, 0.22f, 0.72f); + private static readonly Color ButtonText = new Color(0.92f, 1.0f, 1.0f, 1.0f); + private static readonly Color MutedText = new Color(0.66f, 0.80f, 0.84f, 0.76f); + private static readonly Color NeonOutline = new Color(0.25f, 0.96f, 1.0f, 0.42f); private SongInfo currentSong; private string selectedDifficulty; private DownloadManager downloadManager; private SongSelectManager selectManager; + private MarqueeText titleMarquee; + private MarqueeText artistMarquee; private readonly (string key, Func btn)[] diffSlots = { @@ -49,6 +56,22 @@ public class SongDetailPanel : MonoBehaviour ("expertplus", p => p.btnExpertPlus), }; + private void Awake() + { + HideDifficultyLabel(); + titleMarquee = ConfigureMarqueeText(titleText, 5.0f, 7.2f); + artistMarquee = ConfigureMarqueeText(artistText, 3.4f, 4.4f); + ConfigureOneLineText(infoText, 3.2f, 4.2f, TextAlignmentOptions.MidlineLeft); + ConfigureButtonText(btnNormal, 3.2f, 4.0f); + ConfigureButtonText(btnHard, 3.2f, 4.0f); + ConfigureButtonText(btnExpert, 3.2f, 4.0f); + ConfigureButtonText(btnExpertPlus, 3.0f, 3.8f); + ConfigureButtonText(downloadButton, 3.5f, 4.4f); + ConfigureButtonText(deleteButton, 3.5f, 4.4f); + ConfigureButtonText(playButton, 3.5f, 4.4f); + ConfigureButtonText(closeButton, 5.2f, 6.4f); + } + // ── Public API ─────────────────────────────────────────── public void Show(SongInfo song, DownloadManager dm, SongSelectManager sm) @@ -61,9 +84,11 @@ public class SongDetailPanel : MonoBehaviour titleText.text = song.title; artistText.text = song.artist; infoText.text = song.duration > 0 - ? $"BPM {Mathf.RoundToInt(song.bpm)} | {FormatDuration(song.duration)}" + ? $"BPM {Mathf.RoundToInt(song.bpm)} {FormatDuration(song.duration)}" : $"BPM {Mathf.RoundToInt(song.bpm)}"; + titleMarquee?.Refresh(); + artistMarquee?.Refresh(); RefreshUI(); } @@ -92,8 +117,11 @@ public class SongDetailPanel : MonoBehaviour downloadButton.gameObject.SetActive(!downloaded); deleteButton.gameObject.SetActive(downloaded); + downloadButton.interactable = !downloaded; + deleteButton.interactable = downloaded; playButton.interactable = downloaded && selectedDifficulty != null; progressGroup.SetActive(false); + UpdateActionButtonStyles(downloaded); downloadButton.onClick.RemoveAllListeners(); downloadButton.onClick.AddListener(OnDownloadClicked); @@ -116,6 +144,7 @@ public class SongDetailPanel : MonoBehaviour selectedDifficulty = difficulty; playButton.interactable = true; UpdateDiffColors(); + UpdateActionButtonStyles(true); } private void UpdateDiffColors() @@ -125,14 +154,7 @@ public class SongDetailPanel : MonoBehaviour Button btn = getBtn(this); bool selected = key == selectedDifficulty; - if (btn.targetGraphic is Image img) - img.color = selected ? SelectedColor : DeselectedImgColor; - - var cb = btn.colors; - cb.normalColor = Color.white; - cb.highlightedColor = selected ? new Color(0.3f, 0.95f, 0.55f) : new Color(1f, 1f, 1f, 0.25f); - cb.pressedColor = selected ? new Color(0.15f, 0.6f, 0.3f) : new Color(1f, 1f, 1f, 0.35f); - btn.colors = cb; + ApplyButtonStyle(btn, selected ? NeonBg : DarkButtonBg, selected, btn.interactable, false); } } @@ -234,4 +256,129 @@ public class SongDetailPanel : MonoBehaviour private static string FormatDuration(int seconds) => $"{seconds / 60}:{seconds % 60:D2}"; + + private void HideDifficultyLabel() + { + Transform label = transform.Find("LblDifficulty"); + if (label != null) + label.gameObject.SetActive(false); + } + + private void UpdateActionButtonStyles(bool downloaded) + { + ApplyButtonStyle(downloadButton, NeonBg, true, !downloaded, false); + ApplyButtonStyle(deleteButton, DangerBg, true, downloaded, true); + ApplyButtonStyle(playButton, NeonBg, true, playButton.interactable, false); + ApplyButtonStyle(closeButton, DarkButtonBg, false, true, false); + } + + private static void ApplyButtonStyle(Button btn, Color activeBg, bool outlined, bool enabled, bool danger) + { + if (btn == null) + return; + + Color bg = enabled ? activeBg : DisabledBg; + if (btn.targetGraphic is Image img) + img.color = bg; + + var colors = btn.colors; + colors.normalColor = bg; + colors.highlightedColor = enabled + ? (danger ? new Color(0.72f, 0.23f, 0.30f, 0.86f) : new Color(0.10f, 0.95f, 1.0f, 0.58f)) + : DisabledBg; + colors.pressedColor = enabled + ? (danger ? new Color(0.42f, 0.10f, 0.15f, 0.92f) : new Color(0.02f, 0.58f, 0.72f, 0.80f)) + : DisabledBg; + colors.selectedColor = colors.highlightedColor; + colors.disabledColor = DisabledBg; + colors.fadeDuration = 0.08f; + btn.colors = colors; + + TMP_Text label = btn.GetComponentInChildren(); + if (label != null) + label.color = enabled ? ButtonText : MutedText; + + Outline outline = btn.GetComponent() ?? btn.gameObject.AddComponent(); + outline.enabled = outlined && enabled; + outline.effectColor = danger ? new Color(1.0f, 0.35f, 0.42f, 0.34f) : NeonOutline; + outline.effectDistance = new Vector2(0.0f, -0.28f); + } + + private static MarqueeText ConfigureMarqueeText(TMP_Text text, float minSize, float maxSize) + { + if (text == null) + return null; + + RectTransform textRect = text.rectTransform; + Transform originalParent = textRect.parent; + int siblingIndex = textRect.GetSiblingIndex(); + string maskName = $"{text.name}Mask"; + Transform existingMask = originalParent != null ? originalParent.Find(maskName) : null; + RectTransform maskRect; + + if (existingMask != null) + { + maskRect = existingMask as RectTransform; + if (textRect.parent != existingMask) + textRect.SetParent(existingMask, false); + } + else + { + var mask = new GameObject(maskName); + mask.transform.SetParent(originalParent, false); + mask.transform.SetSiblingIndex(siblingIndex); + maskRect = mask.AddComponent(); + maskRect.anchorMin = textRect.anchorMin; + maskRect.anchorMax = textRect.anchorMax; + maskRect.pivot = textRect.pivot; + maskRect.anchoredPosition = textRect.anchoredPosition; + maskRect.sizeDelta = textRect.sizeDelta; + maskRect.localRotation = textRect.localRotation; + maskRect.localScale = textRect.localScale; + mask.AddComponent(); + textRect.SetParent(mask.transform, false); + } + + textRect.anchorMin = new Vector2(0f, 0f); + textRect.anchorMax = new Vector2(0f, 1f); + textRect.pivot = new Vector2(0f, 0.5f); + textRect.anchoredPosition = Vector2.zero; + textRect.localRotation = Quaternion.identity; + textRect.localScale = Vector3.one; + textRect.sizeDelta = new Vector2(260.0f, 0f); + + ConfigureOneLineText(text, minSize, maxSize, TextAlignmentOptions.MidlineLeft); + text.overflowMode = TextOverflowModes.Overflow; + text.raycastTarget = false; + + MarqueeText marquee = text.GetComponent() ?? text.gameObject.AddComponent(); + marquee.speed = 9f; + marquee.pauseStart = 1.25f; + marquee.pauseEnd = 0.8f; + return marquee; + } + + private static void ConfigureButtonText(Button btn, float minSize, float maxSize) + { + if (btn == null) + return; + + TMP_Text label = btn.GetComponentInChildren(); + ConfigureOneLineText(label, minSize, maxSize, TextAlignmentOptions.Center); + if (label != null) + label.raycastTarget = false; + } + + private static void ConfigureOneLineText(TMP_Text text, float minSize, float maxSize, TextAlignmentOptions alignment) + { + if (text == null) + return; + + text.enableAutoSizing = true; + text.fontSizeMin = minSize; + text.fontSizeMax = maxSize; + text.alignment = alignment; + text.overflowMode = TextOverflowModes.Ellipsis; + text.textWrappingMode = TextWrappingModes.NoWrap; + } } diff --git a/Assets/Script/SongSelectManager.cs b/Assets/Script/SongSelectManager.cs index 14efa34..d2aca16 100644 --- a/Assets/Script/SongSelectManager.cs +++ b/Assets/Script/SongSelectManager.cs @@ -16,8 +16,11 @@ public class SongSelectManager : MonoBehaviour [SerializeField] private TMP_Text errorText; - private static readonly Color TabActive = new Color(1f, 1f, 1f, 0.45f); - private static readonly Color TabInactive = new Color(1f, 1f, 1f, 0.12f); + private static readonly Color TabActiveBg = new Color(0.05f, 0.82f, 0.95f, 0.42f); + private static readonly Color TabInactiveBg = new Color(0.09f, 0.22f, 0.27f, 0.66f); + private static readonly Color TabActiveText = new Color(0.92f, 1.0f, 1.0f, 1.0f); + private static readonly Color TabInactiveText = new Color(0.72f, 0.86f, 0.90f, 0.82f); + private static readonly Color TabActiveOutline = new Color(0.25f, 0.96f, 1.0f, 0.55f); private static string CachePath => Path.Combine(Application.persistentDataPath, "songs_cache.json"); @@ -49,17 +52,39 @@ public class SongSelectManager : MonoBehaviour private void SetTabVisual(bool owned) { - ApplyTabColor(tabAllBtn, owned ? TabInactive : TabActive); - ApplyTabColor(tabOwnedBtn, owned ? TabActive : TabInactive); + ApplyTabStyle(tabAllBtn, !owned); + ApplyTabStyle(tabOwnedBtn, owned); } - private static void ApplyTabColor(Button btn, Color c) + private static void ApplyTabStyle(Button btn, bool active) { + if (btn == null) + return; + + Color bg = active ? TabActiveBg : TabInactiveBg; if (btn.targetGraphic is Image img) - img.color = c; + img.color = bg; + var colors = btn.colors; - colors.normalColor = Color.white; + colors.normalColor = bg; + colors.highlightedColor = active + ? new Color(0.10f, 0.95f, 1.0f, 0.58f) + : new Color(0.14f, 0.34f, 0.40f, 0.72f); + colors.pressedColor = active + ? new Color(0.02f, 0.58f, 0.72f, 0.72f) + : new Color(0.08f, 0.20f, 0.24f, 0.82f); + colors.selectedColor = colors.highlightedColor; + colors.disabledColor = new Color(0.05f, 0.10f, 0.12f, 0.45f); btn.colors = colors; + + TMP_Text label = btn.GetComponentInChildren(); + if (label != null) + label.color = active ? TabActiveText : TabInactiveText; + + Outline outline = btn.GetComponent() ?? btn.gameObject.AddComponent(); + outline.enabled = active; + outline.effectColor = TabActiveOutline; + outline.effectDistance = new Vector2(0.0f, -0.35f); } private void FetchSongs() @@ -70,9 +95,10 @@ public class SongSelectManager : MonoBehaviour downloadManager.FetchSongsList( onSuccess: list => { - allSongs = list.songs; - SaveCache(list); - SongLibrary.Instance.ValidateWithFileSystem(downloadManager, list.songs); + allSongs = list.songs ?? new List(); + AddLocalForcedRankDummies(allSongs); + SaveCache(new SongsList { version = list.version, songs = allSongs }); + SongLibrary.Instance.ValidateWithFileSystem(downloadManager, allSongs); loadingOverlay.SetActive(false); RefreshCards(); }, @@ -134,14 +160,16 @@ public class SongSelectManager : MonoBehaviour bc.fadeDuration = 0.1f; btn.colors = bc; + float textLeftInset = downloaded ? 12f : 5f; + // Title — RectMask2D 컨테이너 안에서 마퀴 스크롤 var titleMask = new GameObject("TitleMask"); titleMask.transform.SetParent(card.transform, false); var tmr = titleMask.AddComponent(); tmr.anchorMin = new Vector2(0f, 0.5f); tmr.anchorMax = new Vector2(1f, 1f); - tmr.offsetMin = new Vector2(5f, 0f); - tmr.offsetMax = new Vector2(downloaded ? -20f : -3f, 0f); + tmr.offsetMin = new Vector2(textLeftInset, 0f); + tmr.offsetMax = new Vector2(-3f, 0f); titleMask.AddComponent(); var titleGO = new GameObject("Title"); @@ -166,41 +194,37 @@ public class SongSelectManager : MonoBehaviour var artistGO = new GameObject("Artist"); artistGO.transform.SetParent(card.transform, false); var ar = artistGO.AddComponent(); - ar.anchorMin = new Vector2(0f, 0f); - ar.anchorMax = new Vector2(1f, 0.5f); - ar.offsetMin = new Vector2(5f, 1f); + ar.anchorMin = new Vector2(0f, 0.04f); + ar.anchorMax = new Vector2(1f, 0.48f); + ar.offsetMin = new Vector2(textLeftInset, 0f); ar.offsetMax = new Vector2(-3f, 0f); var aTmp = artistGO.AddComponent(); if (_cardFont != null) aTmp.font = _cardFont; - aTmp.text = song.artist; - aTmp.fontSize = 4f; - aTmp.color = new Color(1f, 1f, 1f, 0.6f); - aTmp.alignment = TextAlignmentOptions.MidlineLeft; + aTmp.text = song.artist; + aTmp.fontSize = 4f; + aTmp.enableAutoSizing = true; + aTmp.fontSizeMin = 2.8f; + aTmp.fontSizeMax = 4f; + aTmp.color = new Color(1f, 1f, 1f, 0.6f); + aTmp.alignment = TextAlignmentOptions.MidlineLeft; + aTmp.overflowMode = TextOverflowModes.Ellipsis; + aTmp.textWrappingMode = TextWrappingModes.NoWrap; - // Downloaded badge + // Downloaded check mark if (downloaded) { - var badge = new GameObject("Badge"); - badge.transform.SetParent(card.transform, false); - var br = badge.AddComponent(); - br.anchorMin = new Vector2(1f, 0.5f); - br.anchorMax = new Vector2(1f, 0.5f); - br.pivot = new Vector2(1f, 0.5f); - br.anchoredPosition = new Vector2(-3f, 0f); - br.sizeDelta = new Vector2(14f, 5.5f); - badge.AddComponent().color = new Color(0.2f, 0.78f, 0.4f, 0.85f); + var checkGO = new GameObject("OwnedCheck"); + checkGO.transform.SetParent(card.transform, false); + var cr = checkGO.AddComponent(); + cr.anchorMin = new Vector2(0f, 0f); + cr.anchorMax = new Vector2(0f, 1f); + cr.pivot = new Vector2(0f, 0.5f); + cr.anchoredPosition = new Vector2(3.0f, 0f); + cr.sizeDelta = new Vector2(6f, 0f); - var bl = new GameObject("Text"); - bl.transform.SetParent(badge.transform, false); - var blr = bl.AddComponent(); - blr.anchorMin = Vector2.zero; - blr.anchorMax = Vector2.one; - blr.offsetMin = blr.offsetMax = Vector2.zero; - var blTmp = bl.AddComponent(); - blTmp.text = "OWNED"; - blTmp.fontSize = 3.5f; - blTmp.color = Color.white; - blTmp.alignment = TextAlignmentOptions.Center; + Color checkColor = new Color(0.36f, 1.0f, 0.58f, 0.95f); + CreateCheckStroke(checkGO.transform, "ShortStroke", new Vector2(1.8f, 7.1f), new Vector2(1.5f, 0.35f), 42.0f, checkColor); + CreateCheckStroke(checkGO.transform, "LongStroke", new Vector2(3.25f, 7.85f), new Vector2(3.7f, 0.35f), -45.0f, checkColor); } SongInfo captured = song; @@ -213,6 +237,25 @@ public class SongSelectManager : MonoBehaviour detailPanel.Show(song, downloadManager, this); } + private static void CreateCheckStroke(Transform parent, string name, Vector2 anchoredPosition, + Vector2 size, float rotationZ, Color color) + { + var stroke = new GameObject(name); + stroke.transform.SetParent(parent, false); + + var rect = stroke.AddComponent(); + rect.anchorMin = new Vector2(0f, 0f); + rect.anchorMax = new Vector2(0f, 0f); + rect.pivot = new Vector2(0.5f, 0.5f); + rect.anchoredPosition = anchoredPosition; + rect.sizeDelta = size; + rect.localRotation = Quaternion.Euler(0f, 0f, rotationZ); + + var img = stroke.AddComponent(); + img.color = color; + img.raycastTarget = false; + } + private static void SaveCache(SongsList list) { try { File.WriteAllText(CachePath, JsonUtility.ToJson(list, true)); } @@ -225,4 +268,61 @@ public class SongSelectManager : MonoBehaviour try { return JsonUtility.FromJson(File.ReadAllText(CachePath)); } catch { return null; } } + + private static void AddLocalForcedRankDummies(List songs) + { + string root = Path.Combine(Application.persistentDataPath, "beatsaber"); + AddLocalForcedRankDummy(songs, root, "dummy_rank_m", "M", 10); + AddLocalForcedRankDummy(songs, root, "dummy_rank_splus", "S+", 100); + AddLocalForcedRankDummy(songs, root, "dummy_rank_s", "S", 100); + AddLocalForcedRankDummy(songs, root, "dummy_rank_a", "A", 100); + AddLocalForcedRankDummy(songs, root, "dummy_rank_b", "B", 100); + AddLocalForcedRankDummy(songs, root, "dummy_rank_c", "C", 100); + AddLocalForcedRankDummy(songs, root, "dummy_rank_d", "D", 100); + AddLocalForcedRankDummy(songs, root, "dummy_rank_f", "F", 100); + AddLocalForcedRankDummy(songs, root, "dummy_rank_splus_border", "S+ 98%", 100); + AddLocalForcedRankDummy(songs, root, "dummy_rank_f_zero", "F 0%", 20); + } + + private static void AddLocalForcedRankDummy(List songs, string root, string id, string title, int noteCount) + { + if (songs.Exists(song => song.id == id)) + return; + + string songDir = Path.Combine(root, id); + string audioPath = Path.Combine(songDir, $"{id}.mp3"); + string mapFile = $"Map_{id}_forced.json"; + string mapPath = Path.Combine(songDir, mapFile); + if (!File.Exists(audioPath) || !File.Exists(mapPath)) + return; + + long audioSize = new FileInfo(audioPath).Length; + long mapSize = new FileInfo(mapPath).Length; + DifficultyInfo info = new DifficultyInfo + { + mapFile = mapFile, + mapSize = mapSize, + noteCount = noteCount + }; + + songs.Insert(0, new SongInfo + { + id = id, + title = title, + artist = "Forced Rank Dummy", + bpm = 120.0f, + duration = 1, + audioFile = $"dummy/{id}.mp3", + audioSize = audioSize, + coverImage = "", + difficulties = new DifficultyMap + { + normal = info, + hard = info, + expert = info, + expertplus = info + }, + addedAt = "2026-05-29" + }); + } } diff --git a/Assets/VRBeatsKit/Scenes/Menu.unity b/Assets/VRBeatsKit/Scenes/Menu.unity index 7c7911b..92f1b88 100644 --- a/Assets/VRBeatsKit/Scenes/Menu.unity +++ b/Assets/VRBeatsKit/Scenes/Menu.unity @@ -156,8 +156,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -18, y: 19.5} - m_SizeDelta: {x: 30, y: 7} + m_AnchoredPosition: {x: -19, y: 19.5} + m_SizeDelta: {x: 34, y: 7} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &11933137 MonoBehaviour: @@ -456,8 +456,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0, y: 4} - m_SizeDelta: {x: 50, y: 0.4} + m_AnchoredPosition: {x: 0, y: 4.2} + m_SizeDelta: {x: 68, y: 0.4} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &97527770 MonoBehaviour: @@ -652,7 +652,7 @@ MonoBehaviour: m_spriteAsset: {fileID: 0} m_tintAllSprites: 0 m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: 0 + m_TextStyleHashCode: -1183493901 m_overrideHtmlColors: 0 m_faceColor: serializedVersion: 2 @@ -1267,8 +1267,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0, y: 12} - m_SizeDelta: {x: 50, y: 6} + m_AnchoredPosition: {x: -16.05, y: 10.8} + m_SizeDelta: {x: 42, y: 4.8} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &285384529 MonoBehaviour: @@ -1290,7 +1290,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: + m_text: Anesthesia m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} @@ -1299,7 +1299,7 @@ MonoBehaviour: m_fontMaterials: [] m_fontColor32: serializedVersion: 2 - rgba: 4294967295 + rgba: 3439329279 m_fontColor: {r: 1, g: 1, b: 1, a: 0.8} m_enableVertexGradient: 0 m_colorMode: 3 @@ -1312,7 +1312,7 @@ MonoBehaviour: m_spriteAsset: {fileID: 0} m_tintAllSprites: 0 m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: 0 + m_TextStyleHashCode: -1183493901 m_overrideHtmlColors: 0 m_faceColor: serializedVersion: 2 @@ -1324,7 +1324,7 @@ MonoBehaviour: m_fontSizeMin: 18 m_fontSizeMax: 72 m_fontStyle: 0 - m_HorizontalAlignment: 2 + m_HorizontalAlignment: 1 m_VerticalAlignment: 512 m_textAlignment: 65535 m_characterSpacing: 0 @@ -1405,8 +1405,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -26.6, y: -9.4} - m_SizeDelta: {x: 52.7, y: 49} + m_AnchoredPosition: {x: -44, y: -9.4} + m_SizeDelta: {x: 56, y: 49} m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &313102848 GameObject: @@ -1682,7 +1682,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 1 + m_IsActive: 0 --- !u!224 &365318615 RectTransform: m_ObjectHideFlags: 0 @@ -1830,16 +1830,16 @@ RectTransform: m_GameObject: {fileID: 365636951} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 0.9, y: 0.9, z: 0.9} - m_ConstrainProportionsScale: 1 + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1536039027} m_Father: {fileID: 1223157292} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 12, y: -14} - m_SizeDelta: {x: 22, y: 7} + m_AnchoredPosition: {x: 17, y: -9.7} + m_SizeDelta: {x: 27, y: 6} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &365636953 MonoBehaviour: @@ -1957,7 +1957,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0.1, y: -9.4} + m_AnchoredPosition: {x: -15, y: -9.4} m_SizeDelta: {x: 0.5, y: 49} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &393692317 @@ -2300,16 +2300,16 @@ RectTransform: m_GameObject: {fileID: 549476134} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 0.9, y: 0.9, z: 0.9} - m_ConstrainProportionsScale: 1 + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 138927897} m_Father: {fileID: 1223157292} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -12, y: -5} - m_SizeDelta: {x: 22, y: 7} + m_AnchoredPosition: {x: -17, y: -2} + m_SizeDelta: {x: 27, y: 6} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &549476136 MonoBehaviour: @@ -2428,7 +2428,7 @@ RectTransform: m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} m_AnchoredPosition: {x: 0, y: 15.5} - m_SizeDelta: {x: 104, y: 0.5} + m_SizeDelta: {x: 144, y: 0.5} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &584186484 MonoBehaviour: @@ -2534,7 +2534,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: -0.54999924, y: 0.040000916} + m_AnchoredPosition: {x: -0.55000305, y: 0.040000916} m_SizeDelta: {x: 1.1, y: 3.12} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &624828134 @@ -2639,8 +2639,8 @@ RectTransform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 661667650} m_LocalRotation: {x: 0, y: 0.38268343, z: 0, w: 0.92387956} - m_LocalPosition: {x: 0, y: 0, z: 20.29} - m_LocalScale: {x: 0.25, y: 0.25, z: 1} + m_LocalPosition: {x: 0, y: 0, z: 21.44} + m_LocalScale: {x: 0.21, y: 0.21, z: 1} m_ConstrainProportionsScale: 0 m_Children: - {fileID: 140294464} @@ -2652,7 +2652,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 45, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 24.01, y: 4.71} + m_AnchoredPosition: {x: 28.85, y: 4.71} m_SizeDelta: {x: 105.885, y: 71.226} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &661667652 @@ -2786,8 +2786,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 14, y: 19.5} - m_SizeDelta: {x: 30, y: 7} + m_AnchoredPosition: {x: 17.5, y: 19.5} + m_SizeDelta: {x: 34, y: 7} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &711973508 MonoBehaviour: @@ -3087,7 +3087,7 @@ MonoBehaviour: m_spriteAsset: {fileID: 0} m_tintAllSprites: 0 m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: 0 + m_TextStyleHashCode: -1183493901 m_overrideHtmlColors: 0 m_faceColor: serializedVersion: 2 @@ -3224,7 +3224,7 @@ MonoBehaviour: m_spriteAsset: {fileID: 0} m_tintAllSprites: 0 m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: 0 + m_TextStyleHashCode: -1183493901 m_overrideHtmlColors: 0 m_faceColor: serializedVersion: 2 @@ -3310,16 +3310,16 @@ RectTransform: m_GameObject: {fileID: 848577108} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 0.9, y: 0.9, z: 0.9} - m_ConstrainProportionsScale: 1 + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 2121890138} m_Father: {fileID: 1223157292} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 12, y: -5} - m_SizeDelta: {x: 22, y: 7} + m_AnchoredPosition: {x: 17, y: -2} + m_SizeDelta: {x: 27, y: 6} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &848577110 MonoBehaviour: @@ -3437,8 +3437,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0, y: 7} - m_SizeDelta: {x: 50, y: 5} + m_AnchoredPosition: {x: 20.5, y: 10.8} + m_SizeDelta: {x: 28, y: 4.8} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &861505383 MonoBehaviour: @@ -3460,7 +3460,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: + m_text: BPM 120 m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} @@ -3469,7 +3469,7 @@ MonoBehaviour: m_fontMaterials: [] m_fontColor32: serializedVersion: 2 - rgba: 4294967295 + rgba: 2583691263 m_fontColor: {r: 1, g: 1, b: 1, a: 0.6} m_enableVertexGradient: 0 m_colorMode: 3 @@ -3482,7 +3482,7 @@ MonoBehaviour: m_spriteAsset: {fileID: 0} m_tintAllSprites: 0 m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: 0 + m_TextStyleHashCode: -1183493901 m_overrideHtmlColors: 0 m_faceColor: serializedVersion: 2 @@ -3494,7 +3494,7 @@ MonoBehaviour: m_fontSizeMin: 18 m_fontSizeMax: 72 m_fontStyle: 0 - m_HorizontalAlignment: 2 + m_HorizontalAlignment: 1 m_VerticalAlignment: 512 m_textAlignment: 65535 m_characterSpacing: 0 @@ -3611,8 +3611,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -3, y: 18.5} - m_SizeDelta: {x: 38, y: 8} + m_AnchoredPosition: {x: -5.5, y: 17} + m_SizeDelta: {x: 63, y: 7.4} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &921515098 MonoBehaviour: @@ -3634,7 +3634,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: --- + m_text: Oxlo m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} @@ -3656,13 +3656,13 @@ MonoBehaviour: m_spriteAsset: {fileID: 0} m_tintAllSprites: 0 m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: 0 + m_TextStyleHashCode: -1183493901 m_overrideHtmlColors: 0 m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 6.5 - m_fontSizeBase: 6.5 + m_fontSize: 7.2 + m_fontSizeBase: 7.2 m_fontWeight: 400 m_enableAutoSizing: 0 m_fontSizeMin: 18 @@ -3817,16 +3817,16 @@ RectTransform: m_GameObject: {fileID: 967100893} m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 0.9, y: 0.9, z: 0.9} - m_ConstrainProportionsScale: 1 + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 829525444} m_Father: {fileID: 1223157292} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 16.400002, y: -22.900002} - m_SizeDelta: {x: 16, y: 7} + m_AnchoredPosition: {x: 22.5, y: -20.2} + m_SizeDelta: {x: 18, y: 6.4} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &967100895 MonoBehaviour: @@ -4019,8 +4019,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0, y: -18.5} - m_SizeDelta: {x: 50, y: 0.4} + m_AnchoredPosition: {x: 0, y: -16} + m_SizeDelta: {x: 68, y: 0.4} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1010856770 MonoBehaviour: @@ -4095,7 +4095,7 @@ RectTransform: m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} m_AnchoredPosition: {x: 0, y: 23.5} - m_SizeDelta: {x: 104, y: 0.5} + m_SizeDelta: {x: 144, y: 0.5} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1014388332 MonoBehaviour: @@ -4255,7 +4255,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 0 + m_IsActive: 1 --- !u!224 &1223157292 RectTransform: m_ObjectHideFlags: 0 @@ -4265,7 +4265,7 @@ RectTransform: m_GameObject: {fileID: 1223157291} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 0.89393, y: 0.89393, z: 0.89393} + m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1293606945} @@ -4287,8 +4287,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 26.6, y: -9.4} - m_SizeDelta: {x: 52.7, y: 49} + m_AnchoredPosition: {x: 29, y: -9.4} + m_SizeDelta: {x: 86, y: 49} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1223157293 MonoBehaviour: @@ -4486,7 +4486,7 @@ MonoBehaviour: m_spriteAsset: {fileID: 0} m_tintAllSprites: 0 m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: 0 + m_TextStyleHashCode: -1183493901 m_overrideHtmlColors: 0 m_faceColor: serializedVersion: 2 @@ -4656,8 +4656,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 21, y: 20.5} - m_SizeDelta: {x: 8, y: 7} + m_AnchoredPosition: {x: 37, y: 20.5} + m_SizeDelta: {x: 7, y: 6.5} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1293606946 MonoBehaviour: @@ -4769,16 +4769,16 @@ RectTransform: m_GameObject: {fileID: 1388756479} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 0.9, y: 0.9, z: 0.9} - m_ConstrainProportionsScale: 1 + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1245764537} m_Father: {fileID: 1223157292} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -9.5, y: -22.7} - m_SizeDelta: {x: 34, y: 7} + m_AnchoredPosition: {x: -12.5, y: -20.2} + m_SizeDelta: {x: 40, y: 6.4} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1388756481 MonoBehaviour: @@ -5026,16 +5026,16 @@ RectTransform: m_GameObject: {fileID: 1436526096} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 0.9, y: 0.9, z: 0.9} - m_ConstrainProportionsScale: 1 + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1786385005} m_Father: {fileID: 1223157292} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -12, y: -14} - m_SizeDelta: {x: 22, y: 7} + m_AnchoredPosition: {x: -17, y: -9.7} + m_SizeDelta: {x: 27, y: 6} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1436526098 MonoBehaviour: @@ -5241,8 +5241,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0.67, y: 4.46} - m_SizeDelta: {x: 105.885, y: 68.223} + m_AnchoredPosition: {x: 0.4, y: 4.5} + m_SizeDelta: {x: 148, y: 68.2} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1445586371 MonoBehaviour: @@ -5493,7 +5493,7 @@ MonoBehaviour: m_spriteAsset: {fileID: 0} m_tintAllSprites: 0 m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: 0 + m_TextStyleHashCode: -1183493901 m_overrideHtmlColors: 0 m_faceColor: serializedVersion: 2 @@ -5933,7 +5933,7 @@ RectTransform: m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} m_AnchoredPosition: {x: 0, y: 28.5} - m_SizeDelta: {x: 100, y: 9} + m_SizeDelta: {x: 138, y: 9} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1754869934 MonoBehaviour: @@ -6233,7 +6233,7 @@ MonoBehaviour: m_spriteAsset: {fileID: 0} m_tintAllSprites: 0 m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: 0 + m_TextStyleHashCode: -1183493901 m_overrideHtmlColors: 0 m_faceColor: serializedVersion: 2 @@ -6626,7 +6626,7 @@ MonoBehaviour: m_spriteAsset: {fileID: 0} m_tintAllSprites: 0 m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: 0 + m_TextStyleHashCode: -1183493901 m_overrideHtmlColors: 0 m_faceColor: serializedVersion: 2 @@ -6720,8 +6720,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -6, y: -21.5} - m_SizeDelta: {x: 34, y: 7} + m_AnchoredPosition: {x: -12.5, y: -20.2} + m_SizeDelta: {x: 40, y: 6.4} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1888615411 MonoBehaviour: @@ -7157,8 +7157,8 @@ RectTransform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1946485404} m_LocalRotation: {x: 0, y: -0.38268343, z: 0, w: 0.92387956} - m_LocalPosition: {x: 0, y: 0, z: 18.33} - m_LocalScale: {x: 0.25, y: 0.25, z: 1} + m_LocalPosition: {x: 0, y: 0, z: 20.96} + m_LocalScale: {x: 0.21, y: 0.21, z: 1} m_ConstrainProportionsScale: 0 m_Children: - {fileID: 2121492652} @@ -7167,7 +7167,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: -45, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -21.77, y: 4.39} + m_AnchoredPosition: {x: -27.37, y: 4.39} m_SizeDelta: {x: 105.89, y: 66.53} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1946485406 @@ -7727,7 +7727,7 @@ MonoBehaviour: m_spriteAsset: {fileID: 0} m_tintAllSprites: 0 m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: 0 + m_TextStyleHashCode: -1183493901 m_overrideHtmlColors: 0 m_faceColor: serializedVersion: 2 diff --git a/Assets/VRBeatsKit/Scripts/UI/FinalScoreLabel.cs b/Assets/VRBeatsKit/Scripts/UI/FinalScoreLabel.cs index 93b78c5..e4eea7c 100644 --- a/Assets/VRBeatsKit/Scripts/UI/FinalScoreLabel.cs +++ b/Assets/VRBeatsKit/Scripts/UI/FinalScoreLabel.cs @@ -11,12 +11,16 @@ namespace VRBeats private string initialValue = ""; private ScoreManager scoreManager = null; private GameObject resultRoot = null; + private TextMeshProUGUI rankBackText = null; private TextMeshProUGUI rankShadowText = null; private TextMeshProUGUI rankDepthText = null; + private TextMeshProUGUI rankRimText = null; private TextMeshProUGUI rankMainText = null; + private TextMeshProUGUI rankHighlightText = null; private TextMeshProUGUI resultScoreText = null; private TextMeshProUGUI resultAccuracyText = null; private TextMeshProUGUI resultComboText = null; + private CanvasGroup scoreHudCanvasGroup = null; private void Awake() { @@ -24,6 +28,9 @@ namespace VRBeats initialValue += "0"; scoreManager = FindFirstObjectByType(); + if (scoreManager != null) + scoreHudCanvasGroup = scoreManager.GetComponent() ?? + scoreManager.gameObject.AddComponent(); ApplyPopupTextStyle(); BuildResultLayout(); } @@ -34,6 +41,7 @@ namespace VRBeats return; SetTitleActive(false); + SetScoreHudVisible(false); gameObject.CancelAllTweens(); if (resultRoot != null) @@ -58,6 +66,7 @@ namespace VRBeats scoreText.gameObject.SetActive(true); SetTitleActive(true); + SetScoreHudVisible(true); ApplyPopupTextStyle(); if (scoreText != null) @@ -67,9 +76,12 @@ namespace VRBeats private void PopulateResultLayout() { if (scoreManager == null || + rankBackText == null || rankShadowText == null || rankDepthText == null || + rankRimText == null || rankMainText == null || + rankHighlightText == null || resultScoreText == null || resultAccuracyText == null || resultComboText == null) @@ -78,16 +90,20 @@ namespace VRBeats string rank = scoreManager.Rank; Color mainColor = HexToColor(scoreManager.RankColorHex); Color depthColor = HexToColor(GetRankDepthColorHex(rank)); + Color rimColor = HexToColor(GetRankRimColorHex(rank)); + rankBackText.text = rank; rankShadowText.text = rank; rankDepthText.text = rank; rankDepthText.color = depthColor; + rankRimText.text = rank; rankMainText.text = rank; - rankMainText.color = mainColor; + rankHighlightText.text = rank; + ApplyMetalRankColors(mainColor, depthColor, rimColor); resultScoreText.text = - $"SCORE\n{scoreManager.CurrentScore:N0}"; + $"SCORE\n{scoreManager.CurrentScore:N0}"; resultAccuracyText.text = - $"ACCURACY {scoreManager.AccuracyPercent:0.0}%"; + $"ACCURACY {scoreManager.AccuracyPercent:0.0}%"; resultComboText.text = $"MAX COMBO {scoreManager.MaxCombo}"; } @@ -123,6 +139,16 @@ namespace VRBeats scoreText.richText = true; } + private void SetScoreHudVisible(bool visible) + { + if (scoreHudCanvasGroup == null) + return; + + scoreHudCanvasGroup.alpha = visible ? 1.0f : 0.0f; + scoreHudCanvasGroup.interactable = false; + scoreHudCanvasGroup.blocksRaycasts = false; + } + private void BuildResultLayout() { if (scoreText == null) @@ -137,32 +163,47 @@ namespace VRBeats RectTransform rootRect = root.AddComponent(); rootRect.anchorMin = new Vector2(0.5f, 0.5f); rootRect.anchorMax = new Vector2(0.5f, 0.5f); - rootRect.anchoredPosition = Vector2.zero; - rootRect.sizeDelta = new Vector2(620.0f, 250.0f); + rootRect.anchoredPosition = new Vector2(5.0f, 3.1f); + rootRect.sizeDelta = new Vector2(82.0f, 34.0f); root.SetActive(false); resultRoot = root; - // Rank badge left side — hierarchy order = draw order (shadow first, main on top) + // Panel-local coordinates are small world-canvas units, not screen pixels. + rankBackText = MakeTmpLabel(root.transform, "RankBackText", + new Vector2(-20.9f, -1.1f), new Vector2(37.0f, 27.0f), 16.0f, + new Color(0.0f, 0.0f, 0.0f, 0.48f), TextAlignmentOptions.Midline); rankShadowText = MakeTmpLabel(root.transform, "RankShadowText", - new Vector2(-166.0f, 6.0f), new Vector2(200.0f, 200.0f), 14.0f, - new Color(0.0f, 0.0f, 0.0f, 0.55f), TextAlignmentOptions.Midline); + new Vector2(-21.35f, -0.55f), new Vector2(37.0f, 27.0f), 16.0f, + new Color(0.0f, 0.06f, 0.14f, 0.82f), TextAlignmentOptions.Midline); rankDepthText = MakeTmpLabel(root.transform, "RankDepthText", - new Vector2(-168.0f, 8.0f), new Vector2(200.0f, 200.0f), 14.0f, + new Vector2(-21.8f, -0.1f), new Vector2(37.0f, 27.0f), 16.0f, + Color.white, TextAlignmentOptions.Midline); + rankRimText = MakeTmpLabel(root.transform, "RankRimText", + new Vector2(-22.15f, 0.22f), new Vector2(37.0f, 27.0f), 16.0f, Color.white, TextAlignmentOptions.Midline); rankMainText = MakeTmpLabel(root.transform, "RankMainText", - new Vector2(-170.0f, 10.0f), new Vector2(200.0f, 200.0f), 14.0f, + new Vector2(-22.45f, 0.5f), new Vector2(37.0f, 27.0f), 16.0f, + Color.white, TextAlignmentOptions.Midline); + rankHighlightText = MakeTmpLabel(root.transform, "RankHighlightText", + new Vector2(-22.85f, 1.0f), new Vector2(37.0f, 27.0f), 16.0f, Color.white, TextAlignmentOptions.Midline); - // Score, accuracy, combo right side resultScoreText = MakeTmpLabel(root.transform, "ResultScoreText", - new Vector2(70.0f, 58.0f), new Vector2(260.0f, 90.0f), 5.5f, + new Vector2(16.8f, 7.4f), new Vector2(43.0f, 10.8f), 5.35f, Color.white, TextAlignmentOptions.MidlineLeft); resultAccuracyText = MakeTmpLabel(root.transform, "ResultAccuracyText", - new Vector2(70.0f, 0.0f), new Vector2(260.0f, 46.0f), 3.4f, + new Vector2(16.8f, -1.2f), new Vector2(43.0f, 5.4f), 3.05f, new Color(0.84f, 0.97f, 1.0f, 0.9f), TextAlignmentOptions.MidlineLeft); resultComboText = MakeTmpLabel(root.transform, "ResultComboText", - new Vector2(70.0f, -52.0f), new Vector2(260.0f, 44.0f), 3.0f, + new Vector2(16.8f, -6.8f), new Vector2(43.0f, 5.4f), 2.95f, new Color(0.84f, 0.97f, 1.0f, 1.0f), TextAlignmentOptions.MidlineLeft); + + ConfigureRankLayer(rankBackText, new Color(0.0f, 0.0f, 0.0f, 0.72f), 0.34f); + ConfigureRankLayer(rankShadowText, new Color(0.0f, 0.0f, 0.0f, 0.74f), 0.2f); + ConfigureRankLayer(rankDepthText, new Color(0.0f, 0.0f, 0.0f, 0.55f), 0.13f); + ConfigureRankLayer(rankRimText, new Color(1.0f, 1.0f, 1.0f, 0.82f), 0.08f); + ConfigureRankLayer(rankMainText, new Color(0.0f, 0.16f, 0.28f, 0.7f), 0.06f); + ConfigureRankLayer(rankHighlightText, new Color(1.0f, 1.0f, 1.0f, 0.35f), 0.02f); } private TextMeshProUGUI MakeTmpLabel(Transform parent, string name, @@ -178,11 +219,14 @@ namespace VRBeats TextMeshProUGUI tmp = go.AddComponent(); tmp.fontSize = fontSize; + tmp.enableAutoSizing = true; + tmp.fontSizeMin = fontSize * 0.6f; + tmp.fontSizeMax = fontSize; tmp.color = color; tmp.alignment = align; - tmp.overflowMode = TextOverflowModes.Overflow; + tmp.overflowMode = TextOverflowModes.Truncate; tmp.textWrappingMode = TextWrappingModes.NoWrap; - tmp.lineSpacing = -8.0f; + tmp.lineSpacing = -4.0f; tmp.raycastTarget = false; if (scoreText != null && scoreText.font != null) @@ -194,6 +238,68 @@ namespace VRBeats return tmp; } + private static void ConfigureRankLayer(TextMeshProUGUI tmp, Color outlineColor, float outlineWidth) + { + if (tmp == null) + return; + + tmp.fontSizeMin = tmp.fontSize * 0.75f; + tmp.outlineColor = outlineColor; + tmp.outlineWidth = outlineWidth; + tmp.characterSpacing = -1.0f; + } + + private void ApplyMetalRankColors(Color mainColor, Color depthColor, Color rimColor) + { + Color darkMetal = new Color(0.02f, 0.08f, 0.14f, 0.82f); + Color steel = new Color(0.70f, 0.95f, 1.0f, 1.0f); + Color whiteHot = new Color(1.0f, 1.0f, 1.0f, 1.0f); + Color lowerMain = Color.Lerp(mainColor, depthColor, 0.55f); + + SetSolidRankLayer(rankBackText, new Color(0.0f, 0.0f, 0.0f, 0.50f)); + SetSolidRankLayer(rankShadowText, darkMetal); + SetRankGradient(rankDepthText, + Color.Lerp(depthColor, steel, 0.22f), + depthColor, + new Color(0.0f, 0.12f, 0.22f, 0.95f), + new Color(0.0f, 0.05f, 0.10f, 0.95f)); + SetRankGradient(rankRimText, + whiteHot, + rimColor, + Color.Lerp(rimColor, mainColor, 0.35f), + Color.Lerp(mainColor, depthColor, 0.45f)); + SetRankGradient(rankMainText, + whiteHot, + Color.Lerp(whiteHot, rimColor, 0.45f), + Color.Lerp(mainColor, steel, 0.18f), + lowerMain); + SetRankGradient(rankHighlightText, + new Color(1.0f, 1.0f, 1.0f, 0.42f), + new Color(0.92f, 1.0f, 1.0f, 0.28f), + new Color(1.0f, 1.0f, 1.0f, 0.08f), + new Color(1.0f, 1.0f, 1.0f, 0.02f)); + } + + private static void SetSolidRankLayer(TextMeshProUGUI tmp, Color color) + { + if (tmp == null) + return; + + tmp.enableVertexGradient = false; + tmp.color = color; + } + + private static void SetRankGradient(TextMeshProUGUI tmp, + Color topLeft, Color topRight, Color bottomLeft, Color bottomRight) + { + if (tmp == null) + return; + + tmp.enableVertexGradient = true; + tmp.color = Color.white; + tmp.colorGradient = new VertexGradient(topLeft, topRight, bottomLeft, bottomRight); + } + private static string GetRankDepthColorHex(string rank) { switch (rank) @@ -209,6 +315,21 @@ namespace VRBeats } } + private static string GetRankRimColorHex(string rank) + { + switch (rank) + { + case "M": return "#FFFFFF"; + case "S+": return "#E8FFFF"; + case "S": return "#FFF4B8"; + case "A": return "#F1FFD8"; + case "B": return "#FFF5B8"; + case "C": return "#FFE0B8"; + case "D": return "#FFD5D5"; + default: return "#E8F0F8"; + } + } + private static Color HexToColor(string hex) { if (ColorUtility.TryParseHtmlString(hex, out Color color)) diff --git a/Assets/VRBeatsKit/Scripts/UI/ScoreManager.cs b/Assets/VRBeatsKit/Scripts/UI/ScoreManager.cs index e7ae754..5703f0e 100644 --- a/Assets/VRBeatsKit/Scripts/UI/ScoreManager.cs +++ b/Assets/VRBeatsKit/Scripts/UI/ScoreManager.cs @@ -142,6 +142,25 @@ namespace VRBeats UpdateScoreTween(); } + public void ApplyForcedResult(int noteCount, int perfect, int great, int good, int miss, int forcedMaxCombo) + { + totalNoteCount = Mathf.Max(0, noteCount); + perfectCount = Mathf.Max(0, perfect); + greatCount = Mathf.Max(0, great); + goodCount = Mathf.Max(0, good); + missCount = Mathf.Max(0, miss); + judgedNoteCount = perfectCount + greatCount + goodCount + missCount; + earnedAccuracyPoints = perfectCount * 1000 + greatCount * 900 + goodCount * 700; + maxCombo = Mathf.Clamp(forcedMaxCombo, 0, Mathf.Max(totalNoteCount, judgedNoteCount)); + currentCombo = maxCombo; + currentMultiplier = missCount > 0 ? 1.0f : GetComboMultiplier(currentCombo); + lastJudgement = missCount > 0 ? BeatJudgement.Miss : BeatJudgement.Perfect; + judgementTimer = 0.45f; + resultFinalized = false; + UpdateScoreTween(); + UpdateMultiplierLoaderValue(); + } + public void CompleteSong() { if (resultFinalized)