using UnityEngine; using UnityEngine.UI; using TMPro; using UnityEngine.SceneManagement; using System.Collections; using System.Collections.Generic; using DG.Tweening; public class SongSelectUI : MonoBehaviour { public SongList songList; // 存储歌曲列表的 ScriptableObject public GameObject songButtonPrefab; // 歌曲按钮的 Prefab (Button) public Transform contentPanel; // 滚动视图的 Content 面板 [SerializeField] Button button_Main; [SerializeField] string ui_Main_Scene_Name = "UI_UI"; [Header("Enter Animation")] [SerializeField] bool play_Enter_Anim = true; [SerializeField] float enter_Anim_Delay = 0.05f; [Header("Async Build")] [SerializeField] bool useAsyncBuild = true; [SerializeField] int buildBatch = 8; [Header("Switch Animations")] [SerializeField] bool play_Switch_Anim = true; [SerializeField] RectTransform list_Anim_Root; [SerializeField] float list_FadeOut_Time = 0.12f; [SerializeField] float list_FadeIn_Time = 0.18f; [SerializeField] float list_Move_Offset = 24f; [SerializeField] Ease list_Move_Ease = Ease.OutCubic; [SerializeField] bool list_Use_Unscaled = true; // Current DLC key used for per-DLC saved song selection public static string CurrentDlcKey = "dlc_default_0"; // Track pending restore to avoid multiple overlapping restores private Coroutine restoreCoroutine; private Coroutine buildCoroutine; private Coroutine switchCoroutine; private CanvasGroup listCanvasGroup; private Vector2 listBasePos; private bool listBaseCached; void Start() { if (songList == null || songButtonPrefab == null || contentPanel == null) { Debug.LogError("请在 Inspector 中分配所有字段!"); return; } button_Main.onClick.AddListener( () => StartCoroutine(LoadSceneAsync(ui_Main_Scene_NAME()))); // Do not populate the song list at start. DLC buttons will request songs when clicked. if (play_Enter_Anim) { UI_SelectSong_EnterAnim anim = GetComponent(); if (anim == null) { anim = gameObject.AddComponent(); } anim.SetDelay(enter_Anim_Delay); anim.Play(); } } private IEnumerator LoadSceneAsync(string sceneName) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single); while (!asyncLoad.isDone) { yield return null; } } // 显示所有歌曲 (kept for manual use) public void DisplaySongs() { DisplaySongsFromList(songList != null ? songList.songs : null); } // Instantiate from a provided list (used by dlcButton to show DLC songs) public void DisplaySongsFromList(List list) { if (buildCoroutine != null) { StopCoroutine(buildCoroutine); } if (switchCoroutine != null) { StopCoroutine(switchCoroutine); switchCoroutine = null; } if (play_Switch_Anim) { switchCoroutine = StartCoroutine(SwitchSongsRoutine(list)); } else { buildCoroutine = StartCoroutine(BuildSongsAsync(list)); } } private IEnumerator SwitchSongsRoutine(List list) { yield return PlayListSwitchOut(); yield return BuildSongsAsync(list); yield return PlayListSwitchIn(); switchCoroutine = null; } private IEnumerator BuildSongsAsync(List list) { ClearSongList(); if (list == null) { buildCoroutine = null; yield break; } // allow UI to render a frame before heavy instantiation yield return null; int count = 0; foreach (var song in list) { InstantiateSongButton(song); count++; if (useAsyncBuild && buildBatch > 0 && (count % buildBatch) == 0) { yield return null; } } // After populating, attempt to restore per-DLC selected song (next frame to avoid stale objects) if (restoreCoroutine != null) StopCoroutine(restoreCoroutine); restoreCoroutine = StartCoroutine(RestoreSelectedSongNextFrame()); buildCoroutine = null; } private RectTransform GetListAnimRoot() { if (list_Anim_Root != null) { return list_Anim_Root; } return contentPanel as RectTransform; } private CanvasGroup EnsureListCanvasGroup(RectTransform root) { if (root == null) return null; if (listCanvasGroup == null || listCanvasGroup.gameObject != root.gameObject) { listCanvasGroup = root.GetComponent(); if (listCanvasGroup == null) { listCanvasGroup = root.gameObject.AddComponent(); } } return listCanvasGroup; } private void CacheListBase(RectTransform root) { if (root == null) return; if (!listBaseCached) { listBasePos = root.anchoredPosition; listBaseCached = true; } } private IEnumerator PlayListSwitchOut() { if (!play_Switch_Anim) { yield break; } RectTransform root = GetListAnimRoot(); if (root == null) { yield break; } CacheListBase(root); CanvasGroup cg = EnsureListCanvasGroup(root); if (cg == null) { yield break; } root.DOKill(); cg.DOKill(); float moveOffset = list_Move_Offset; if (root.GetComponent() != null) { moveOffset = 0f; } Sequence seq = DOTween.Sequence().SetUpdate(list_Use_Unscaled); seq.Join(cg.DOFade(0f, list_FadeOut_Time)); if (Mathf.Abs(moveOffset) > 0.01f) { seq.Join(root.DOAnchorPos(listBasePos + new Vector2(-moveOffset, 0f), list_FadeOut_Time).SetEase(list_Move_Ease)); } yield return seq.WaitForCompletion(); } private IEnumerator PlayListSwitchIn() { if (!play_Switch_Anim) { yield break; } RectTransform root = GetListAnimRoot(); if (root == null) { yield break; } CacheListBase(root); CanvasGroup cg = EnsureListCanvasGroup(root); if (cg == null) { yield break; } root.DOKill(); cg.DOKill(); float moveOffset = list_Move_Offset; if (root.GetComponent() != null) { moveOffset = 0f; } if (Mathf.Abs(moveOffset) > 0.01f) { root.anchoredPosition = listBasePos + new Vector2(moveOffset, 0f); } else { root.anchoredPosition = listBasePos; } cg.alpha = 0f; Sequence seq = DOTween.Sequence().SetUpdate(list_Use_Unscaled); seq.Join(cg.DOFade(1f, list_FadeIn_Time)); if (Mathf.Abs(moveOffset) > 0.01f) { seq.Join(root.DOAnchorPos(listBasePos, list_FadeIn_Time).SetEase(list_Move_Ease)); } yield return seq.WaitForCompletion(); } private IEnumerator RestoreSelectedSongNextFrame() { // Wait one frame so Destroy() in ClearSongList fully completes and stale SongButtons are gone. yield return null; RestoreSelectedSongForCurrentDlc(); restoreCoroutine = null; } private void RestoreSelectedSongForCurrentDlc() { // read saved index using CurrentDlcKey string dlcKey = (string.IsNullOrEmpty(CurrentDlcKey) ? "default_0" : CurrentDlcKey); string key = "song_selected_" + dlcKey; // collect song buttons under contentPanel var allSongButtons = contentPanel.GetComponentsInChildren(true); if (allSongButtons == null || allSongButtons.Length == 0) { Debug.LogWarning("SongSelectUI.RestoreSelectedSongForCurrentDlc: no song buttons found to restore selection"); return; } // Ensure every DLC has a default persisted value (0) int savedIndex; if (!PlayerPrefs.HasKey(key)) { PlayerPrefs.SetInt(key, 0); PlayerPrefs.Save(); savedIndex = 0; } else { savedIndex = PlayerPrefs.GetInt(key, 0); } if (savedIndex < 0) savedIndex = 0; if (savedIndex >= allSongButtons.Length) savedIndex = 0; var btn = allSongButtons[savedIndex]; if (btn == null) { // hard fallback to first btn = allSongButtons[0]; } if (btn != null) { // simulate click btn.OnSongButtonClick(); Debug.Log($"SongSelectUI: restored and clicked song index={savedIndex} for key={key}, totalButtons={allSongButtons.Length}"); } } private void ClearSongList() { if (contentPanel == null) return; for (int i = contentPanel.childCount - 1; i >= 0; i--) { #if UNITY_EDITOR if (!Application.isPlaying) DestroyImmediate(contentPanel.GetChild(i).gameObject); else Destroy(contentPanel.GetChild(i).gameObject); #else Destroy(contentPanel.GetChild(i).gameObject); #endif } } private void InstantiateSongButton(SongData song) { if (songButtonPrefab == null || contentPanel == null) return; GameObject songButtonObj = Instantiate(songButtonPrefab, contentPanel); songButtonObj.transform.SetParent(contentPanel, false); SongData sd = song; if (SongDataLibrary.Instance != null && SongDataLibrary.Instance.IsLoaded) { sd = SongDataLibrary.Instance.GetSongDataByID(song.songID) ?? song; } int bestTotal = sd != null ? sd.personalRecord : song.personalRecord; int bestDiff = -1; string bestDiffName = ""; if (sd != null) { sd.GetAbsoluteHighestScore(out bestTotal, out bestDiff, out bestDiffName); } SongButton songButton = songButtonObj.GetComponent(); if (songButton != null) { songButton.thisSong_so = sd != null ? sd : song; int diffForDisplay = bestDiff >= 0 ? bestDiff : song.difficultyID; songButton.SetButtonData(song.songName, bestTotal, diffForDisplay, song.illustration, song.songID); } Image songImage = songButtonObj.GetComponentInChildren(); TMP_Text[] texts = songButtonObj.GetComponentsInChildren(); if (songImage != null && song.illustration != null) { songImage.sprite = song.illustration; songImage.enabled = true; } if (texts.Length >= 4) { texts[0].text = song.songName; texts[1].text = "Record : " + bestTotal; if (bestTotal <= 0) { texts[3].text = ""; } else { texts[3].text = string.IsNullOrEmpty(bestDiffName) ? bestDiff.ToString() : bestDiffName; } texts[2].text = "SongID : " + song.songID.ToString(); } Button button = songButtonObj.GetComponent