Files
bansonic_beta_main/Assets/songsDatabase/dlcData/dlcButton.cs
T
2026-06-30 21:21:30 +08:00

661 lines
20 KiB
C#

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using System.Collections;
using System.IO;
public class dlcButton : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
private static readonly bool VerboseLogs = false;
[Header("Inspector")]
public Image dlc_profileImgae;
public Text dlcName;
public Text dlcID;
public Text dlcProducer;
public Text dlcPubliushDate;
public Text dlcSongsAmount;
public Image dlc_nowSelectedImageBoarder;
[Header("Inspector")]
public GameObject dlcDetailParent;
public Text dlcDescription;
[Header("Inspector")]
public dlcData dlcDataSO;
[Header("Inspector")]
public List<SongData> dlcContent = new List<SongData>();
[Header("Inspector")]
public string editorSearchFolder = "Assets/Resources/dlc_dlcIndex";
public string runtimeResourcesFolder = "Resources/dlc_dlcIndex";
private Button _btn;
public bool Resolved { get; private set; }
// Shared cache to avoid repeated Resources.LoadAll scans
private static Dictionary<int, dlcData> cachedById = new Dictionary<int, dlcData>();
private static List<dlcData> cachedAll = new List<dlcData>();
private static bool cacheBuilt = false;
private static bool cacheBuilding = false;
// Selection management (shared across all dlcButton instances)
private static List<dlcButton> allButtons = new List<dlcButton>();
private static int selectedIndex = -1;
private const string PlayerPrefsKey = "dlc_selected_index";
private int instanceIndex = -1;
private Coroutine hoverCoroutine;
private void UpdateSongsAmountText()
{
if (dlcSongsAmount != null)
{
dlcSongsAmount.text = dlcContent != null ? dlcContent.Count.ToString() : "0";
}
}
public static void ResetSelectionState()
{
allButtons.Clear();
selectedIndex = -1;
}
public void OnPointerEnter(PointerEventData eventData)
{
if (hoverCoroutine != null) StopCoroutine(hoverCoroutine);
hoverCoroutine = StartCoroutine(HandleHover());
}
public void OnPointerExit(PointerEventData eventData)
{
if (hoverCoroutine != null) StopCoroutine(hoverCoroutine);
if (dlcDetailParent != null) dlcDetailParent.SetActive(false);
}
public void SetDlcData(dlcData so)
{
dlcDataSO = so;
ApplyDlcData(so);
}
private IEnumerator HandleHover()
{
yield return new WaitForSecondsRealtime(0.5f);
// 灏濊瘯浠庡満鏅腑鏌ユ壘 dlcDetailParent (濡傛灉鑷韩寮曠敤鐨勪负绌?
if (dlcDetailParent == null)
{
var details = GameObject.FindObjectsByType<dlcButton>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (var b in details)
{
if (b != this && b.dlcDetailParent != null)
{
dlcDetailParent = b.dlcDetailParent;
dlcDescription = b.dlcDescription;
break;
}
}
}
if (dlcDetailParent != null)
{
dlcDetailParent.SetActive(true);
// 鏇存柊绠€浠嬩俊鎭?
if (dlcDescription != null)
{
if (dlcDataSO != null)
{
dlcDescription.text = string.IsNullOrEmpty(dlcDataSO.dlcDescription)
? LocalizationService.Get("dlc.no_description", "暂无DLC简介信息。")
: dlcDataSO.dlcDescription;
}
else
{
dlcDescription.text = LocalizationService.Get("dlc.no_description", "暂无DLC简介信息。");
}
}
}
}
void Awake()
{
_btn = GetComponent<Button>();
if (_btn != null)
{
_btn.onClick.RemoveAllListeners();
_btn.onClick.AddListener(OnButtonClicked_ShowSongs);
}
// ensure border initial state
if (dlc_nowSelectedImageBoarder != null)
{
if (!dlc_nowSelectedImageBoarder.gameObject.activeSelf)
{
dlc_nowSelectedImageBoarder.gameObject.SetActive(true);
}
var c = dlc_nowSelectedImageBoarder.color;
c.a = 0f;
dlc_nowSelectedImageBoarder.color = c;
}
// register this button for centralized selection handling
RegisterInstance();
if (VerboseLogs) Debug.Log($"dlcButton.Awake: registered '{gameObject.name}' as index {instanceIndex}");
}
private void RegisterInstance()
{
if (!allButtons.Contains(this))
{
instanceIndex = allButtons.Count;
allButtons.Add(this);
// If no selectedIndex loaded yet, load from PlayerPrefs default 0
if (selectedIndex == -1)
{
selectedIndex = PlayerPrefs.GetInt(PlayerPrefsKey, 0);
}
// If this instance matches the persisted selection, apply selection
if (instanceIndex == selectedIndex)
{
// ensure other buttons are deselected
ApplySelectionToAll(instanceIndex);
}
else
{
// ensure unselected visual
SetSelectedVisual(false, immediate: true);
}
}
else
{
instanceIndex = allButtons.IndexOf(this);
}
}
private void UnregisterInstance()
{
int idx = allButtons.IndexOf(this);
if (idx >= 0)
{
allButtons.Remove(this);
if (selectedIndex == idx)
{
selectedIndex = -1;
}
else if (selectedIndex > idx)
{
selectedIndex--;
}
}
}
void OnDestroy()
{
UnregisterInstance();
}
// Ensure only one button is selected and persist the index
public void Select()
{
ApplySelectionToAll(instanceIndex);
selectedIndex = instanceIndex;
PlayerPrefs.SetInt(PlayerPrefsKey, selectedIndex);
PlayerPrefs.Save();
if (VerboseLogs) Debug.Log($"dlcButton.Select: '{gameObject.name}' selected (index {instanceIndex}) and saved to PlayerPrefs");
}
private static void ApplySelectionToAll(int selectIdx)
{
for (int i = 0; i < allButtons.Count; i++)
{
var btn = allButtons[i];
if (btn == null) continue;
bool shouldSelect = (i == selectIdx);
btn.SetSelectedVisual(shouldSelect);
}
}
// fades the border in or out; if immediate, set without coroutine
private Coroutine borderFadeCoroutine = null;
private Coroutine showSongsRetryCoroutine = null;
private void SetSelectedVisual(bool selected, bool immediate = false)
{
if (dlc_nowSelectedImageBoarder == null) return;
if (borderFadeCoroutine != null)
{
StopCoroutine(borderFadeCoroutine);
borderFadeCoroutine = null;
}
if (immediate)
{
if (!dlc_nowSelectedImageBoarder.gameObject.activeSelf)
{
dlc_nowSelectedImageBoarder.gameObject.SetActive(true);
}
var c = dlc_nowSelectedImageBoarder.color;
c.a = selected ? 1f : 0f;
dlc_nowSelectedImageBoarder.color = c;
}
else
{
dlc_nowSelectedImageBoarder.gameObject.SetActive(true);
borderFadeCoroutine = StartCoroutine(FadeBorder(dlc_nowSelectedImageBoarder, selected ? 1f : 0f, 0.25f));
}
}
private IEnumerator<YieldInstruction> FadeBorder(Image img, float targetAlpha, float duration)
{
if (img == null) yield break;
float startAlpha = img.color.a;
float t = 0f;
// if fading out, ensure we start from current alpha
while (t < duration)
{
t += Time.unscaledDeltaTime;
float a = Mathf.Lerp(startAlpha, targetAlpha, Mathf.Clamp01(t / duration));
var c = img.color;
c.a = a;
img.color = c;
yield return null;
}
var final = img.color;
final.a = targetAlpha;
img.color = final;
borderFadeCoroutine = null;
}
void Start()
{
// Do not auto-resolve here anymore. Resolution will be triggered externally after instantiation of all buttons.
}
public void OnButtonClicked_ShowSongs()
{
// set current DLC key for SongSelectUI to allow per-DLC song selection restore
string dlcKey = null;
if (dlcDataSO != null)
{
dlcKey = "dlc_" + dlcDataSO.dlcID;
}
else if (dlcID != null && int.TryParse(dlcID.text, out int parsedId))
{
dlcKey = "dlc_" + parsedId;
}
else
{
dlcKey = "dlc_instance_" + instanceIndex;
}
// assign to SongSelectUI static key
try { SongSelectUI.CurrentDlcKey = dlcKey; } catch { }
// mark selection first
Select();
if (TryDisplaySongsNow())
{
return;
}
if (showSongsRetryCoroutine != null)
{
StopCoroutine(showSongsRetryCoroutine);
}
showSongsRetryCoroutine = StartCoroutine(RetryDisplaySongsWhenUIReady());
}
private SongSelectUI FindSongSelectUI()
{
var ui = SceneObjectLookupCache.FindAny<SongSelectUI>();
if (ui != null)
{
return ui;
}
var all = Object.FindObjectsByType<SongSelectUI>(FindObjectsInactive.Include, FindObjectsSortMode.None);
if (all != null && all.Length > 0)
{
return all[0];
}
return null;
}
private bool TryDisplaySongsNow()
{
var ui = FindSongSelectUI();
if (ui == null)
{
return false;
}
if (SongSelectUI.ForceFirstSongOnNextRestore)
{
SongDataHolder.SelectedSongData = null;
}
ui.DisplaySongsFromList(GetAccessibleDlcContent());
return true;
}
private List<SongData> GetAccessibleDlcContent()
{
List<SongData> accessible = new List<SongData>();
if (dlcContent == null)
{
return accessible;
}
for (int i = 0; i < dlcContent.Count; i++)
{
SongData song = dlcContent[i];
if (song != null && DlcContentAccess.IsSongAccessible(song))
{
accessible.Add(song);
}
}
return accessible;
}
private IEnumerator RetryDisplaySongsWhenUIReady()
{
const int maxRetryFrames = 8;
for (int i = 0; i < maxRetryFrames; i++)
{
yield return null;
if (TryDisplaySongsNow())
{
showSongsRetryCoroutine = null;
yield break;
}
}
showSongsRetryCoroutine = null;
Debug.LogWarning("dlcButton: SongSelectUI not found in scene to display songs");
}
// Try to find a dlcData SO by id and apply its songList to dlcContent
public void LoadDlcDataById(int id)
{
if (VerboseLogs) Debug.Log($"dlcButton.LoadDlcDataById: searching for id={id} (editorFolder='{editorSearchFolder}', runtimeFolder='{runtimeResourcesFolder}') on '{gameObject.name}'");
#if UNITY_EDITOR
// Editor search: search recursively for asset files under editorSearchFolder
if (!string.IsNullOrEmpty(editorSearchFolder) && Directory.Exists(editorSearchFolder))
{
try
{
var files = Directory.GetFiles(editorSearchFolder, "*.asset", SearchOption.AllDirectories);
if (VerboseLogs) Debug.Log($"dlcButton.LoadDlcDataById: found {files.Length} asset files under {editorSearchFolder}");
foreach (var path in files)
{
string fileName = Path.GetFileNameWithoutExtension(path);
if (fileName.StartsWith(id.ToString()))
{
string assetPath = path.Replace("\\", "/");
if (VerboseLogs) Debug.Log($"dlcButton.LoadDlcDataById: candidate assetPath={assetPath}");
var so = UnityEditor.AssetDatabase.LoadAssetAtPath<dlcData>(assetPath);
if (so != null)
{
if (VerboseLogs) Debug.Log($"dlcButton.LoadDlcDataById: loaded dlcData SO '{so.name}' for id={id}");
dlcDataSO = so;
ApplyDlcData(so);
return;
}
}
}
}
catch (System.Exception e)
{
Debug.LogWarning("dlcButton LoadDlcDataById (Editor) failed: " + e.Message);
}
}
else
{
if (VerboseLogs) Debug.Log($"dlcButton.LoadDlcDataById: editorSearchFolder '{editorSearchFolder}' does not exist or not set");
}
#endif
// Runtime: search Resources (load all dlcData then filter by name prefix)
try
{
var all = RuntimeResourcesCache.LoadAllDlcs();
if (VerboseLogs) Debug.Log($"dlcButton.LoadDlcDataById: runtime cache returned {(all != null ? all.Length : 0)} items");
if (all != null)
{
foreach (var so in all)
{
if (so == null) continue;
if (so.dlcID == id || so.name.StartsWith(id.ToString()))
{
if (VerboseLogs) Debug.Log($"dlcButton.LoadDlcDataById: found runtime dlcData '{so.name}' for id={id}");
dlcDataSO = so;
ApplyDlcData(so);
return;
}
}
}
}
catch (System.Exception e)
{
Debug.LogWarning($"dlcButton.LoadDlcDataById: runtime cache search failed: {e.Message}");
}
// if no dlcData found, keep dlcContent empty but attempt old behavior: search SongData by id prefix
if (VerboseLogs) Debug.Log($"dlcButton.LoadDlcDataById: no dlcData found for id={id}, falling back to song prefix search");
LoadContentBySongIdPrefix(id);
}
// Populate dlcContent from dlcData SO and update UI texts/images
private void ApplyDlcData(dlcData so)
{
if (so == null) return;
dlcContent = new List<SongData>();
if (so.songList != null)
{
foreach (var s in so.songList)
if (s != null) dlcContent.Add(s);
}
UpdateSongsAmountText();
if (dlcName != null) dlcName.text = so.dlcName ?? dlcName.text;
if (dlcProducer != null) dlcProducer.text = so.dlcProducer ?? dlcProducer.text;
if (dlcDescription != null) dlcDescription.text = so.dlcDescription ?? dlcDescription.text;
if (dlcPubliushDate != null) dlcPubliushDate.text = so.dlcPublishDate ?? dlcPubliushDate.text;
if (dlc_profileImgae != null)
{
dlc_profileImgae.sprite = so.dlc_image;
dlc_profileImgae.enabled = so.dlc_image != null;
}
if (VerboseLogs) Debug.Log($"dlcButton.ApplyDlcData: applied dlcData '{so.name}' with {dlcContent.Count} songs to button '{gameObject.name}'");
}
// Legacy: if no dlcData SO, search SongData assets by id prefix and fill dlcContent
public void LoadContentBySongIdPrefix(int id)
{
dlcContent.Clear();
#if UNITY_EDITOR
if (!string.IsNullOrEmpty(editorSearchFolder) && Directory.Exists(editorSearchFolder))
{
try
{
var files = Directory.GetFiles(editorSearchFolder, "*.asset", SearchOption.AllDirectories);
foreach (var path in files)
{
var fileName = Path.GetFileNameWithoutExtension(path);
if (fileName.StartsWith(id.ToString()))
{
var assetPath = path.Replace("\\", "/");
var so = UnityEditor.AssetDatabase.LoadAssetAtPath<SongData>(assetPath);
if (so != null) dlcContent.Add(so);
}
}
}
catch (System.Exception e)
{
Debug.LogWarning("dlcButton LoadContentBySongIdPrefix (Editor) failed: " + e.Message);
}
}
#endif
SongData[] allSongs = RuntimeResourcesCache.LoadAllSongs();
if (allSongs != null)
{
foreach (var so in allSongs)
{
if (so == null) continue;
if (so.name.StartsWith(id.ToString())) dlcContent.Add(so);
}
}
UpdateSongsAmountText();
if (VerboseLogs) Debug.Log($"dlcButton: Loaded {dlcContent.Count} SongData entries for DLC id={id} (fallback search)");
}
// Resolve dlcData after all buttons have been instantiated
public void ResolveDlcData()
{
if (Resolved)
{
return;
}
StartCoroutine(ResolveDlcDataAsync());
}
public IEnumerator ResolveDlcDataAsync()
{
if (Resolved)
{
yield break;
}
if (VerboseLogs) Debug.Log($"dlcButton.ResolveDlcData called on '{gameObject.name}'");
if (dlcDataSO != null)
{
if (VerboseLogs) Debug.Log($"dlcButton: dlcDataSO already assigned on '{gameObject.name}' -> {dlcDataSO.name}");
ApplyDlcData(dlcDataSO);
Resolved = true;
yield break;
}
int id;
if (!TryGetResolveId(out id))
{
Debug.LogWarning($"dlcButton.ResolveDlcData: no dlcID text and no name to parse for '{gameObject.name}'");
Resolved = true;
yield break;
}
yield return BuildCacheIfNeeded(runtimeResourcesFolder);
if (TryResolveFromCache(id))
{
Resolved = true;
yield break;
}
LoadContentBySongIdPrefix(id);
Resolved = true;
}
private bool TryGetResolveId(out int id)
{
id = 0;
if (dlcID != null && int.TryParse(dlcID.text, out id))
{
return true;
}
string goName = gameObject.name;
if (!string.IsNullOrEmpty(goName))
{
var parts = goName.Split('_');
if (parts.Length > 0 && int.TryParse(parts[0], out id))
{
return true;
}
}
return false;
}
private bool TryResolveFromCache(int id)
{
if (!cacheBuilt)
{
return false;
}
dlcData so;
if (cachedById.TryGetValue(id, out so) && so != null)
{
dlcDataSO = so;
ApplyDlcData(so);
return true;
}
string prefix = id.ToString();
for (int i = 0; i < cachedAll.Count; i++)
{
var item = cachedAll[i];
if (item == null) continue;
if (item.name.StartsWith(prefix))
{
dlcDataSO = item;
ApplyDlcData(item);
return true;
}
}
return false;
}
private static IEnumerator BuildCacheIfNeeded(string runtimeFolder)
{
if (cacheBuilt)
{
yield break;
}
if (cacheBuilding)
{
while (!cacheBuilt)
{
yield return null;
}
yield break;
}
cacheBuilding = true;
cachedById.Clear();
cachedAll.Clear();
// allow one frame before heavy load
yield return null;
dlcData[] arr = RuntimeResourcesCache.LoadAllDlcs();
if (arr != null)
{
for (int i = 0; i < arr.Length; i++)
{
var so = arr[i];
if (so == null) continue;
cachedAll.Add(so);
if (!cachedById.ContainsKey(so.dlcID))
{
cachedById.Add(so.dlcID, so);
}
}
}
cacheBuilt = true;
cacheBuilding = false;
}
}