Files
bansonic_beta_main/Assets/songsDatabase/SongDataController/SongSelectUI.cs
T

383 lines
12 KiB
C#

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; // Documentation text normalized.
public GameObject songButtonPrefab; // Documentation text normalized.
public Transform contentPanel; // Documentation text normalized.
[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()
{
// Defensive reset: prevent select-scene enter animations from freezing invisible.
if (Time.timeScale <= 0f)
{
Time.timeScale = 1f;
}
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<UI_SelectSong_EnterAnim>();
if (anim == null)
{
anim = gameObject.AddComponent<UI_SelectSong_EnterAnim>();
}
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;
}
}
// Documentation text normalized.
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<SongData> 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<SongData> list)
{
yield return PlayListSwitchOut();
yield return BuildSongsAsync(list);
yield return PlayListSwitchIn();
switchCoroutine = null;
}
private IEnumerator BuildSongsAsync(List<SongData> 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<CanvasGroup>();
if (listCanvasGroup == null)
{
listCanvasGroup = root.gameObject.AddComponent<CanvasGroup>();
}
}
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<LayoutGroup>() != 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<LayoutGroup>() != 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<SongButton>(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<SongButton>();
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<Image>();
TMP_Text[] texts = songButtonObj.GetComponentsInChildren<TMP_Text>();
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<Button>();
if (button != null && songButton != null)
button.onClick.AddListener(() => songButton.OnSongButtonClick());
}
// helper to avoid hardcoding scene name string in delegate - keeps serializer-friendly field
private string ui_Main_Scene_NAME() => ui_Main_Scene_Name;
}