技能主要更新,修复卡顿并加入动画,以及各种其他更新。

This commit is contained in:
FloatGaming
2026-02-07 21:05:17 +08:00
parent abeca51be5
commit 1ac3cd0104
1349 changed files with 1526749 additions and 24850 deletions
@@ -77,7 +77,15 @@ public class SongButton : MonoBehaviour
{
Debug.Log("SetButtonData called, songID: " + songID);
var sd = SongDataLibrary.Instance != null ? SongDataLibrary.Instance.GetSongDataByID(songID) : null;
SongData sd = null;
if (SongDataLibrary.Instance != null)
{
sd = SongDataLibrary.Instance.GetSongDataByID(songID);
}
if (sd == null)
{
sd = thisSong_so as SongData;
}
if (sd != null)
{
if (songNameText != null) songNameText.text = sd.songName;
@@ -206,9 +214,25 @@ public class SongButton : MonoBehaviour
public void OnSongButtonClick()
{
Debug.Log("Song button clicked, song_songSerialID: " + song_songSerialID);
SongData selectedSong = SongDataLibrary.Instance.GetSongDataByID(song_songSerialID);
SongData selectedSong = null;
if (SongDataLibrary.Instance != null)
{
selectedSong = SongDataLibrary.Instance.GetSongDataByID(song_songSerialID);
}
if (selectedSong == null)
{
selectedSong = thisSong_so as SongData;
}
if (selectedSong != null)
{
int bestTotal = 0;
int bestDiff = -1;
string bestDiffName = "";
selectedSong.GetAbsoluteHighestScore(out bestTotal, out bestDiff, out bestDiffName);
if (bestDiff >= 0)
{
selectedSong.thisLevel_selectedDifficultyID = bestDiff;
}
if (currentSelected != null && currentSelected != this)
{
currentSelected.FadeOutSelectedBorder();
@@ -217,7 +241,7 @@ public class SongButton : MonoBehaviour
FadeInSelectedBorder();
SongDataHolder.SelectedSongData = selectedSong;
var selectedInfo = FindObjectOfType<selected_songInfo>();
var selectedInfo = Object.FindAnyObjectByType<selected_songInfo>();
if (selectedInfo != null)
{
selectedInfo.UpdateSelectedSongDisplay();
@@ -1,9 +1,10 @@
using UnityEngine;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.SceneManagement;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
public class SongSelectUI : MonoBehaviour
{
@@ -13,12 +14,31 @@ public class SongSelectUI : MonoBehaviour
[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()
{
@@ -29,36 +49,201 @@ public class SongSelectUI : MonoBehaviour
}
button_Main.onClick.AddListener(
() => SceneManager.LoadScene(ui_Main_Scene_NAME(), LoadSceneMode.Single));
() => 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;
}
}
// 显示所有歌曲 (kept for manual use)
public void DisplaySongs()
{
ClearSongList();
foreach (SongData song in songList.songs)
{
InstantiateSongButton(song);
}
if (restoreCoroutine != null) StopCoroutine(restoreCoroutine);
restoreCoroutine = StartCoroutine(RestoreSelectedSongNextFrame());
DisplaySongsFromList(songList != null ? songList.songs : null);
}
// Instantiate from a provided list (used by dlcButton to show DLC songs)
public void DisplaySongsFromList(List<SongData> list)
{
ClearSongList();
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));
}
}
if (list == null) return;
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()
@@ -135,10 +320,25 @@ public class SongSelectUI : MonoBehaviour
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.SetButtonData(song.songName, song.personalRecord, song.difficultyID, song.illustration, song.songID);
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>();
@@ -153,8 +353,15 @@ public class SongSelectUI : MonoBehaviour
if (texts.Length >= 4)
{
texts[0].text = song.songName;
texts[1].text = "Record : " + song.personalRecord;
texts[3].text = "" + song.difficultyID;
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();
}
+127 -23
View File
@@ -1,22 +1,34 @@
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
public class SongDataLibrary : MonoBehaviour
{
public static SongDataLibrary Instance;
private Dictionary<int, SongData> songDataDictionary = new Dictionary<int, SongData>();
// 指定存放在 "song_songIndex" 目录下的各个子文件夹名称
// 例如:如果你有 Assets/Resources/song_songIndex/FolderA FolderB,则在 Inspector 中填写 { "FolderA", "FolderB" }
// ָ "song_songIndex" Ŀ¼µĸļ
// Assets/Resources/song_songIndex/FolderA FolderB Inspector д { "FolderA", "FolderB" }
public string[] subfolderNames;
[Header("Song List")]
[SerializeField] SongList songList;
[SerializeField] bool useSongListIfAvailable = true;
[Header("Async Load Settings")]
[SerializeField] int yieldEvery = 16;
[SerializeField] bool logLoadProgress = false;
public bool IsLoaded { get; private set; }
public bool IsLoading { get; private set; }
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
LoadSongData();
StartCoroutine(LoadSongDataCoroutine());
}
else
{
@@ -24,49 +36,141 @@ public class SongDataLibrary : MonoBehaviour
}
}
private void LoadSongData()
private IEnumerator LoadSongDataCoroutine()
{
// 先加载直接放在 "song_songIndex" 目录下的 SongData
SongData[] assets = Resources.LoadAll<SongData>("song_songIndex");
foreach (SongData data in assets)
if (IsLoading || IsLoaded)
{
if (!songDataDictionary.ContainsKey(data.songID))
yield break;
}
IsLoading = true;
songDataDictionary.Clear();
// Allow one frame to render before heavy load begins
yield return null;
// prefer SongList if available to avoid heavy Resources.LoadAll
if (useSongListIfAvailable)
{
if (songList == null)
{
songDataDictionary.Add(data.songID, data);
Debug.Log("加载到 SongData, songID: " + data.songID + ", 名称: " + data.songName);
SongSelectUI ui = Object.FindAnyObjectByType<SongSelectUI>();
if (ui != null)
{
songList = ui.songList;
}
}
else
if (songList != null && songList.songs != null && songList.songs.Count > 0)
{
Debug.LogWarning("重复的 songID: " + data.songID);
yield return LoadFromList(songList.songs, "songList");
IsLoaded = true;
IsLoading = false;
yield break;
}
}
// 再遍历每个子文件夹,加载其中的 SongData
foreach (string subfolder in subfolderNames)
yield return LoadFromPath("song_songIndex", "root");
if (subfolderNames != null)
{
SongData[] subAssets = Resources.LoadAll<SongData>("song_songIndex/" + subfolder);
foreach (SongData data in subAssets)
for (int i = 0; i < subfolderNames.Length; i++)
{
if (!songDataDictionary.ContainsKey(data.songID))
string subfolder = subfolderNames[i];
if (string.IsNullOrEmpty(subfolder))
{
songDataDictionary.Add(data.songID, data);
Debug.Log("从 " + subfolder + " 加载到 SongData, songID: " + data.songID + ", 名称: " + data.songName);
continue;
}
else
yield return LoadFromPath("song_songIndex/" + subfolder, subfolder);
// yield a frame between folders to reduce hitch
yield return null;
}
}
IsLoaded = true;
IsLoading = false;
}
private IEnumerator LoadFromPath(string path, string label)
{
SongData[] assets = Resources.LoadAll<SongData>(path);
if (assets == null || assets.Length == 0)
{
if (logLoadProgress)
{
Debug.Log("SongDataLibrary: no assets found at '" + path + "'");
}
yield break;
}
int yielded = 0;
foreach (SongData data in assets)
{
if (data == null)
{
continue;
}
if (!songDataDictionary.ContainsKey(data.songID))
{
songDataDictionary.Add(data.songID, data);
if (logLoadProgress)
{
Debug.LogWarning("重复的 songID: " + data.songID);
Debug.Log("Loaded SongData [" + label + "] songID: " + data.songID + ", name: " + data.songName);
}
}
else if (logLoadProgress)
{
Debug.LogWarning("Duplicate songID: " + data.songID);
}
yielded++;
if (yieldEvery > 0 && (yielded % yieldEvery) == 0)
{
// yield to spread work across frames
yield return null;
}
}
}
public SongData GetSongDataByID(int songID)
private IEnumerator LoadFromList(List<SongData> list, string label)
{
if (list == null || list.Count == 0)
{
yield break;
}
int yielded = 0;
for (int i = 0; i < list.Count; i++)
{
SongData data = list[i];
if (data == null) continue;
if (!songDataDictionary.ContainsKey(data.songID))
{
songDataDictionary.Add(data.songID, data);
if (logLoadProgress)
{
Debug.Log("Loaded SongData [" + label + "] songID: " + data.songID + ", name: " + data.songName);
}
}
else if (logLoadProgress)
{
Debug.LogWarning("Duplicate songID: " + data.songID);
}
yielded++;
if (yieldEvery > 0 && (yielded % yieldEvery) == 0)
{
yield return null;
}
}
}
public SongData GetSongDataByID(int songID)
{
if (songDataDictionary.ContainsKey(songID))
{
return songDataDictionary[songID];
}
Debug.LogError("未找到 songID: " + songID);
Debug.LogError("δҵ songID: " + songID);
return null;
}
}
+35 -24
View File
@@ -3,56 +3,67 @@ using UnityEngine;
public class SongDataManager : MonoBehaviour
{
// 保存 JSON 文件的目录路径
// JSON ļĿ¼·
private string jsonDirectory;
[Header("Settings")]
public bool autoSaveOnStart = false; // 默认关闭,仅在需要同步数据时手动开启
void Start()
{
// 获取持久化数据路径,并在下创建 "SongDataJson" 文件夹
// 获取持久化存储路径,并在该目录下创建 "SongDataJson" 文件夹
jsonDirectory = Path.Combine(Application.persistentDataPath, "SongDataJson");
// 如果目录不存在,则创建目录
// 如果目录不存在,则创建目录
if (!Directory.Exists(jsonDirectory))
{
Directory.CreateDirectory(jsonDirectory);
Debug.Log("创建 JSON 存储目录: " + jsonDirectory);
}
else
{
Debug.Log("JSON 存储目录已存在: " + jsonDirectory);
Debug.Log("创建 JSON 存储目录: " + jsonDirectory);
}
// 调用方法,将所有 SongData 资源转换为 JSON 并保存
SaveAllSongDataToJson();
// 只有开启了 autoSaveOnStart 且在编辑器模式下才自动执行
if (autoSaveOnStart)
{
StartCoroutine(SaveAllSongDataToJsonRoutine());
}
}
void SaveAllSongDataToJson()
public void TriggerSaveAllSongs()
{
// 从 Resources 文件夹下的 "SongData" 子目录加载所有 SongData 资源
StartCoroutine(SaveAllSongDataToJsonRoutine());
}
System.Collections.IEnumerator SaveAllSongDataToJsonRoutine()
{
// 从 Resources 文件夹下的 "song_songIndex" 子目录加载所有 SongData 资源
SongData[] allSongData = Resources.LoadAll<SongData>("song_songIndex");
// 如果没有加载到任何 SongData,输出警告信息
if (allSongData.Length == 0)
{
Debug.LogWarning("未在 Resources/SongData 中找到任何 SongData 资源");
Debug.LogWarning("未在 Resources/song_songIndex 找到任何 SongData 资源");
yield break;
}
// 遍历每个 SongData 资源
foreach (SongData song in allSongData)
Debug.Log($"开始异步保存 {allSongData.Length} 个歌曲数据...");
// 遍历每个 SongData 资源
for (int i = 0; i < allSongData.Length; i++)
{
// 使用 SongDataSerializable 将 SongData 转换为可序列化对象
SongData song = allSongData[i];
// 使用 SongDataSerializable 将 SongData 转换为可序列化对象
SongDataSerializable jsonData = new SongDataSerializable(song);
// 将对象转换为 JSON 格式字符串,true 表示格式化输出(便于阅读)
string json = JsonUtility.ToJson(jsonData, true);
// 构造 JSON 文件的完整路径,文件名使用 songID 命名,例如 "123.json"
string filePath = Path.Combine(jsonDirectory, song.songID + ".json");
// 将 JSON 内容写入文件
File.WriteAllText(filePath, json);
// 输出调试信息,显示已保存的 JSON 文件路径
Debug.Log("已保存 JSON 文件: " + filePath);
// 每处理 5 个文件等待一帧,防止主线程卡死
if (i % 5 == 0)
{
yield return null;
}
}
Debug.Log("所有 JSON 文件保存完毕。");
}
}
+154 -21
View File
@@ -1,31 +1,41 @@
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Collections;
using System.IO;
public class dlcButton : MonoBehaviour
{
[Header("基本套件")]
[Header("׼")]
public Image dlc_profileImgae;
public Text dlcName;
public Text dlcID;
public Text dlcProducer;
public Text dlcPubliushDate;
public Image dlc_nowSelectedImageBoarder;
[Header("介绍")]
[Header("")]
public Text dlcDescription;
[Header("DLC SO (可在 Inspector 指定)")]
[Header("DLC SO ( Inspector ָ)")]
public dlcData dlcDataSO;
[Header("DLC 内容 (运行时会由脚本填充)")]
[Header("DLC (ʱɽű)")]
public List<SongData> dlcContent = new List<SongData>();
[Header("搜索路径 (编辑器/运行时)")]
[Header("· (/ʱ)")]
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;
private static string cachedFolder = string.Empty;
// Selection management (shared across all dlcButton instances)
private static List<dlcButton> allButtons = new List<dlcButton>();
@@ -206,7 +216,7 @@ public class dlcButton : MonoBehaviour
// mark selection first
Select();
var s = GameObject.FindObjectOfType<SongSelectUI>();
var s = Object.FindAnyObjectByType<SongSelectUI>();
if (s != null)
{
s.DisplaySongsFromList(dlcContent);
@@ -388,41 +398,164 @@ public class dlcButton : MonoBehaviour
// Resolve dlcData after all buttons have been instantiated
public void ResolveDlcData()
{
if (Resolved)
{
return;
}
StartCoroutine(ResolveDlcDataAsync());
}
public IEnumerator ResolveDlcDataAsync()
{
if (Resolved)
{
yield break;
}
Debug.Log($"dlcButton.ResolveDlcData called on '{gameObject.name}'");
if (dlcDataSO != null)
{
Debug.Log($"dlcButton: dlcDataSO already assigned on '{gameObject.name}' -> {dlcDataSO.name}");
ApplyDlcData(dlcDataSO);
return;
Resolved = true;
yield break;
}
int id = 0;
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))
{
Debug.Log($"dlcButton: Resolving by dlcID text {id} for '{gameObject.name}'");
LoadDlcDataById(id);
return;
return true;
}
string goName = gameObject.name;
if (!string.IsNullOrEmpty(goName))
{
var parts = goName.Split('_');
int parsed = 0;
if (parts.Length > 0 && int.TryParse(parts[0], out parsed))
if (parts.Length > 0 && int.TryParse(parts[0], out id))
{
Debug.Log($"dlcButton: Resolving by GameObject name prefix {parsed} for '{gameObject.name}'");
LoadDlcDataById(parsed);
}
else
{
Debug.LogWarning($"dlcButton.ResolveDlcData: could not parse id from GameObject name '{goName}'");
return true;
}
}
else
return false;
}
private bool TryResolveFromCache(int id)
{
if (!cacheBuilt)
{
Debug.LogWarning($"dlcButton.ResolveDlcData: no dlcID text and no name to parse for '{gameObject.name}'");
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 string NormalizeResourcesPath(string path)
{
if (string.IsNullOrEmpty(path))
{
return string.Empty;
}
string p = path.Replace("\\", "/");
int idx = p.IndexOf("Resources/", System.StringComparison.OrdinalIgnoreCase);
if (idx >= 0)
{
p = p.Substring(idx + "Resources/".Length);
}
return p.Trim('/');
}
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();
cachedFolder = NormalizeResourcesPath(runtimeFolder);
// allow one frame before heavy load
yield return null;
dlcData[] arr = null;
if (!string.IsNullOrEmpty(cachedFolder))
{
arr = Resources.LoadAll<dlcData>(cachedFolder);
}
if (arr == null || arr.Length == 0)
{
arr = Resources.LoadAll<dlcData>("");
}
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;
}
}
+8 -8
View File
@@ -10,26 +10,26 @@ public class dlcData : ScriptableObject
[Header("DLC֮ID")]
public int dlcID;
[Header("DLC名称")]
[Header("DLC")]
public string dlcName;
[Header("DLC出品人")]
[Header("DLCƷ")]
public string dlcProducer;
[Header("DLC是否已解锁")]
[Header("DLCǷѽ")]
public bool dlcIsUnlocked;
[Header("DLC简介")]
[Header("DLC")]
[TextArea(3,10)]
public string dlcDescription;
[Header("DLC发行日期 yyyy-mm-dd")]
[Header("DLC yyyy-mm-dd")]
public string dlcPublishDate;
[Header("DLC所含曲目(ScriptableObjects")]
[Header("DLCĿScriptableObjects")]
public List<SongData> songList = new List<SongData>();
[Header("DLC结算音乐")]
[Tooltip("在结算界面播放的背景音乐AudioClip")]
[Header("DLC")]
[Tooltip("ڽŵıAudioClip")]
public AudioClip settlementMusic;
}
+19 -4
View File
@@ -1,5 +1,6 @@
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
public class loadDlcListPrefab : MonoBehaviour
@@ -106,14 +107,27 @@ public class loadDlcListPrefab : MonoBehaviour
var rt = dlc_scContent.GetComponent<RectTransform>();
if (rt != null) UnityEngine.UI.LayoutRebuilder.ForceRebuildLayoutImmediate(rt);
// After all buttons are instantiated, have each dlcButton resolve its dlcData (deferred lookup)
var allButtons = dlc_scContent.GetComponentsInChildren<dlcButton>(true);
foreach (var b in allButtons)
StartCoroutine(ResolveAndRestore(allButtons));
}
private IEnumerator ResolveAndRestore(dlcButton[] allButtons)
{
if (allButtons != null)
{
if (b != null)
b.ResolveDlcData();
for (int i = 0; i < allButtons.Length; i++)
{
var b = allButtons[i];
if (b != null)
{
yield return b.ResolveDlcDataAsync();
}
}
}
// Let layout and any fades settle for a frame
yield return null;
// Restore last selected DLC button from PlayerPrefs and simulate a click on it
int savedIndex = PlayerPrefs.GetInt("dlc_selected_index", 0);
if (allButtons != null && allButtons.Length > 0)
@@ -138,4 +152,5 @@ public class loadDlcListPrefab : MonoBehaviour
Debug.LogWarning("loadDlcListPrefab: no dlc buttons found after RefreshUI");
}
}
}
+190 -70
View File
@@ -3,12 +3,13 @@ using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections;
using DG.Tweening;
public class selected_songInfo : MonoBehaviour
{
public Image t_songCoverImage;
public Image btm_songCoverImage;
[Header("极简信息")]
[Header("Ϣ")]
public Text songNameText;
public Text currentDifficulty;
public Text sumScore_thisDifficulty;
@@ -17,22 +18,22 @@ public class selected_songInfo : MonoBehaviour
public Image c_DifficultyImage;
public Image c_imageMask;
[Header("分级颜色")]
[Header("ּɫ")]
public Color _0to5d5;
public Color _5d5to11;
public Color _11to16d5;
public Color _16d5to22;
public Color _equal22;
[Header("对应的难度按钮")]
[Header("ӦѶȰť")]
public List<Button> difficultyButtons = new List<Button>();
[Header("对应的难度文本")]
[Header("ӦѶı")]
public List<Text> difficultyTexts = new List<Text>();
[Header("enter detail page")]
public Button enterDetailPageButton;
[Header("quick enter game play")]
public Button quickEnter_gamePlay;
[Header("指定image背景色")]
[Header("ָimageɫ")]
public Sprite buttons_bgImage;
[Header("black mask")]
public Image blackMaskImage;
@@ -40,41 +41,63 @@ public class selected_songInfo : MonoBehaviour
// store the original sprites so we don't overwrite the designer's setup
private List<Sprite> originalButtonSprites = new List<Sprite>();
[Header("Switch Animations")]
[SerializeField] bool play_SongSwitch_Anim = true;
[SerializeField] RectTransform songInfo_Root;
[SerializeField] float songSwitch_Time = 0.22f;
[SerializeField] float songSwitch_Move = 30f;
[SerializeField] Ease songSwitch_Ease = Ease.OutCubic;
[SerializeField] bool play_DifficultySwitch_Anim = true;
[SerializeField] float diffSwitch_Punch = 0.06f;
[SerializeField] float diffSwitch_Punch_Time = 0.18f;
[SerializeField] float diffSwitch_Text_Fade = 0.08f;
[SerializeField] bool switch_Use_Unscaled = true;
private CanvasGroup songInfo_Canvas;
private Vector2 songInfo_BasePos;
private bool songInfo_BaseCached;
private int lastSongId = -1;
private static void LogVerbose(string message)
{
if (GameConfig.verboseLogs) Debug.LogWarning(message);
}
void Awake()
{
Debug.LogWarning("selected_songInfo.Awake called");
LogVerbose("selected_songInfo.Awake called");
}
void OnEnable()
{
Debug.LogWarning("selected_songInfo.OnEnable called");
LogVerbose("selected_songInfo.OnEnable called");
// register listeners here to ensure binding even if Start wasn't called yet
if (enterDetailPageButton != null)
{
enterDetailPageButton.onClick.RemoveListener(OnEnterDetailPageClicked);
enterDetailPageButton.onClick.AddListener(OnEnterDetailPageClicked);
Debug.LogWarning("enterDetailPageButton listener added");
LogVerbose("enterDetailPageButton listener added");
}
else
{
Debug.LogWarning("enterDetailPageButton is null in OnEnable");
LogVerbose("enterDetailPageButton is null in OnEnable");
}
if (quickEnter_gamePlay != null)
{
quickEnter_gamePlay.onClick.RemoveListener(OnQuickEnterClicked);
quickEnter_gamePlay.onClick.AddListener(OnQuickEnterClicked);
Debug.LogWarning("quickEnter_gamePlay listener added");
LogVerbose("quickEnter_gamePlay listener added");
}
else
{
Debug.LogWarning("quickEnter_gamePlay is null in OnEnable");
LogVerbose("quickEnter_gamePlay is null in OnEnable");
}
}
void OnDisable()
{
Debug.LogWarning("selected_songInfo.OnDisable called");
LogVerbose("selected_songInfo.OnDisable called");
if (enterDetailPageButton != null)
{
enterDetailPageButton.onClick.RemoveListener(OnEnterDetailPageClicked);
@@ -87,14 +110,14 @@ public class selected_songInfo : MonoBehaviour
void OnDestroy()
{
Debug.LogWarning("selected_songInfo.OnDestroy called");
LogVerbose("selected_songInfo.OnDestroy called");
// ensure we remove sceneLoaded subscription if still present
SceneManager.sceneLoaded -= OnSceneLoadedAfterQuickEnter;
}
void Start()
{
Debug.LogWarning("selected_songInfo.Start called");
LogVerbose("selected_songInfo.Start called");
// ensure black mask is transparent and inactive at start
if (blackMaskImage != null)
@@ -137,8 +160,8 @@ public class selected_songInfo : MonoBehaviour
UpdateSelectedSongDisplay();
// Diagnostic: confirm Start ran and key refs
Debug.LogWarning($"selected_songInfo.Start: quickEnter_gamePlay is {(quickEnter_gamePlay == null ? "NULL" : "ASSIGNED")}, enterDetail assigned={(enterDetailPageButton == null ? "NULL" : "ASSIGNED")}, blackMask assigned={(blackMaskImage == null ? "NULL" : "ASSIGNED")}");
Debug.LogWarning($"selected_songInfo.Start: SongDataHolder.SelectedSongData is {(SongDataHolder.SelectedSongData == null ? "NULL" : SongDataHolder.SelectedSongData.songName)}");
LogVerbose($"selected_songInfo.Start: quickEnter_gamePlay is {(quickEnter_gamePlay == null ? "NULL" : "ASSIGNED")}, enterDetail assigned={(enterDetailPageButton == null ? "NULL" : "ASSIGNED")}, blackMask assigned={(blackMaskImage == null ? "NULL" : "ASSIGNED")}");
LogVerbose($"selected_songInfo.Start: SongDataHolder.SelectedSongData is {(SongDataHolder.SelectedSongData == null ? "NULL" : SongDataHolder.SelectedSongData.songName)}");
}
public void UpdateSelectedSongDisplay()
@@ -270,6 +293,8 @@ public class selected_songInfo : MonoBehaviour
int enterTimes = song.game_enterTimes;
total_gameTime.text = enterTimes.ToString();
}
TryPlaySongSwitchAnim(song.songID);
}
else
{
@@ -286,7 +311,7 @@ public class selected_songInfo : MonoBehaviour
}
if (songNameText != null)
{
songNameText.text = "未选择歌曲";
songNameText.text = "δѡ";
}
// restore original difficulty button backgrounds and text colors
@@ -294,6 +319,7 @@ public class selected_songInfo : MonoBehaviour
if (sumScore_thisDifficulty != null) sumScore_thisDifficulty.text = "0";
if (total_gameTime != null) total_gameTime.text = "0";
lastSongId = -1;
}
}
@@ -366,6 +392,7 @@ public class selected_songInfo : MonoBehaviour
// refresh displayed song info so currentDifficulty / difficulty bar update to new difficulty
UpdateSelectedSongDisplay();
PlayDifficultySwitchAnim(index);
}
else
{
@@ -373,11 +400,95 @@ public class selected_songInfo : MonoBehaviour
}
}
private void EnsureSongInfoRoot()
{
if (songInfo_Root == null)
{
songInfo_Root = transform as RectTransform;
}
if (songInfo_Root == null) return;
if (songInfo_Canvas == null || songInfo_Canvas.gameObject != songInfo_Root.gameObject)
{
songInfo_Canvas = songInfo_Root.GetComponent<CanvasGroup>();
if (songInfo_Canvas == null)
{
songInfo_Canvas = songInfo_Root.gameObject.AddComponent<CanvasGroup>();
}
}
}
private void TryPlaySongSwitchAnim(int songId)
{
if (!play_SongSwitch_Anim)
{
lastSongId = songId;
return;
}
if (lastSongId == songId)
{
return;
}
lastSongId = songId;
EnsureSongInfoRoot();
if (songInfo_Root == null || songInfo_Canvas == null)
{
return;
}
songInfo_BasePos = songInfo_Root.anchoredPosition;
songInfo_BaseCached = true;
songInfo_Root.DOKill();
songInfo_Canvas.DOKill();
songInfo_Root.anchoredPosition = songInfo_BasePos + new Vector2(-songSwitch_Move, 0f);
songInfo_Canvas.alpha = 0f;
Sequence seq = DOTween.Sequence().SetUpdate(switch_Use_Unscaled);
seq.Join(songInfo_Root.DOAnchorPos(songInfo_BasePos, songSwitch_Time).SetEase(songSwitch_Ease));
seq.Join(songInfo_Canvas.DOFade(1f, songSwitch_Time));
}
private void PlayDifficultySwitchAnim(int index)
{
if (!play_DifficultySwitch_Anim)
{
return;
}
if (difficultyButtons != null && index >= 0 && index < difficultyButtons.Count)
{
var btn = difficultyButtons[index];
if (btn != null)
{
Transform t = btn.transform;
t.DOKill();
t.localScale = Vector3.one;
t.DOPunchScale(Vector3.one * diffSwitch_Punch, diffSwitch_Punch_Time, 6, 0.7f)
.SetUpdate(switch_Use_Unscaled);
}
}
FlashGraphic(currentDifficulty);
FlashGraphic(sumScore_thisDifficulty);
if (c_DifficultyImage != null)
{
FlashGraphic(c_DifficultyImage);
}
}
private void FlashGraphic(Graphic g)
{
if (g == null) return;
g.DOKill();
Color baseColor = g.color;
float baseAlpha = baseColor.a <= 0f ? 1f : baseColor.a;
baseColor.a = baseAlpha;
g.color = baseColor;
Sequence seq = DOTween.Sequence().SetUpdate(switch_Use_Unscaled);
seq.Append(g.DOFade(Mathf.Clamp01(baseAlpha * 0.35f), diffSwitch_Text_Fade));
seq.Append(g.DOFade(baseAlpha, diffSwitch_Text_Fade));
}
public void OnEnterDetailPageClicked()
{
if (SongDataHolder.SelectedSongData != null)
{
SceneManager.LoadScene("Songs_Select");
StartCoroutine(LoadSceneAsync("Songs_Select"));
}
else
{
@@ -385,6 +496,15 @@ public class selected_songInfo : MonoBehaviour
}
}
private IEnumerator LoadSceneAsync(string sceneName)
{
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone)
{
yield return null;
}
}
private void OnQuickEnterClicked()
{
// use SongData from holder or SongButton; ensure selection exists
@@ -392,7 +512,7 @@ public class selected_songInfo : MonoBehaviour
if (sd == null) sd = SongDataHolder.SelectedSongData;
if (sd == null)
{
Debug.LogWarning("No song selected - cannot quick enter gameplay.");
LogVerbose("No song selected - cannot quick enter gameplay.");
return;
}
@@ -404,7 +524,7 @@ public class selected_songInfo : MonoBehaviour
return;
}
Debug.LogWarning($"QuickEnter clicked, selected song: {sd.songName} id={sd.songID} difficulty={sd.thisLevel_selectedDifficultyID}");
LogVerbose($"QuickEnter clicked, selected song: {sd.songName} id={sd.songID} difficulty={sd.thisLevel_selectedDifficultyID}");
// pass selection to BeatmapManager pending fields so newly loaded scene picks it up
BeatmapManager.SetPendingSong(sd, sd.thisLevel_selectedDifficultyID);
@@ -420,7 +540,7 @@ public class selected_songInfo : MonoBehaviour
private IEnumerator QuickEnterSequence()
{
Debug.LogWarning("QuickEnterSequence started: beginning fade");
LogVerbose("QuickEnterSequence started: beginning fade");
// Fade black mask in over 0.25 seconds
float duration = 0.25f;
if (blackMaskImage != null)
@@ -455,7 +575,7 @@ public class selected_songInfo : MonoBehaviour
blackMaskImage.color = c;
}
Debug.LogWarning("Fade complete, loading gameplay scene...");
LogVerbose("Fade complete, loading gameplay scene...");
// after black screen, load gameplay scene asynchronously and wait for completion
SceneManager.sceneLoaded += OnSceneLoadedAfterQuickEnter;
var asyncOp = SceneManager.LoadSceneAsync("gamePlay_gamePlay");
@@ -478,8 +598,8 @@ public class selected_songInfo : MonoBehaviour
private SongData FindSongDataFromSelectedButton()
{
var allButtons = FindObjectsOfType<SongButton>();
Debug.LogWarning($"FindSongDataFromSelectedButton: found {allButtons.Length} SongButton instances");
var allButtons = Object.FindObjectsByType<SongButton>(FindObjectsInactive.Exclude, FindObjectsSortMode.InstanceID);
LogVerbose($"FindSongDataFromSelectedButton: found {allButtons.Length} SongButton instances");
foreach (var sb in allButtons)
{
if (sb == null) continue;
@@ -490,7 +610,7 @@ public class selected_songInfo : MonoBehaviour
Image img = null;
if (field != null) img = field.GetValue(sb) as Image;
float alpha = img != null ? img.color.a : -1f;
Debug.LogWarning($"SongButton id field value for '{sb.name}': selected_boarder alpha={alpha}");
LogVerbose($"SongButton id field value for '{sb.name}': selected_boarder alpha={alpha}");
if (img != null && img.color.a > 0.5f)
{
@@ -501,7 +621,7 @@ public class selected_songInfo : MonoBehaviour
var soObj = soField.GetValue(sb);
if (soObj != null && soObj is SongData)
{
Debug.LogWarning($"Selected SongButton has thisSong_so assigned: {((SongData)soObj).songName}");
LogVerbose($"Selected SongButton has thisSong_so assigned: {((SongData)soObj).songName}");
return soObj as SongData;
}
}
@@ -511,11 +631,11 @@ public class selected_songInfo : MonoBehaviour
if (idField != null)
{
int id = (int)idField.GetValue(sb);
Debug.LogWarning($"Selected SongButton id = {id}");
LogVerbose($"Selected SongButton id = {id}");
var sd = SongDataLibrary.Instance != null ? SongDataLibrary.Instance.GetSongDataByID(id) : null;
if (sd != null)
{
Debug.LogWarning($"Found SongData by id: {sd.songName}");
LogVerbose($"Found SongData by id: {sd.songName}");
return sd;
}
}
@@ -523,10 +643,10 @@ public class selected_songInfo : MonoBehaviour
}
catch (System.Exception ex)
{
Debug.LogWarning("Exception while inspecting SongButton: " + ex.Message);
LogVerbose("Exception while inspecting SongButton: " + ex.Message);
}
}
Debug.LogWarning("FindSongDataFromSelectedButton: no selected SongButton found");
LogVerbose("FindSongDataFromSelectedButton: no selected SongButton found");
return null;
}
@@ -566,7 +686,7 @@ public class selected_songInfo : MonoBehaviour
if (selected == null)
{
Debug.LogWarning("OnSceneLoadedAfterQuickEnter: SelectedSongData is null");
LogVerbose("OnSceneLoadedAfterQuickEnter: SelectedSongData is null");
SceneManager.sceneLoaded -= OnSceneLoadedAfterQuickEnter;
// allow this object to be destroyed now
Destroy(this.gameObject);
@@ -574,26 +694,26 @@ public class selected_songInfo : MonoBehaviour
}
// find managers in the new scene
var beatmapManager = FindObjectOfType<BeatmapManager>();
var gameManager = FindObjectOfType<GameManager>();
var beatmapManager = Object.FindAnyObjectByType<BeatmapManager>();
var gameManager = Object.FindAnyObjectByType<GameManager>();
Debug.LogWarning($"selected song: {selected.songName} (id {selected.songID}), difficulty {selected.thisLevel_selectedDifficultyID}");
LogVerbose($"selected song: {selected.songName} (id {selected.songID}), difficulty {selected.thisLevel_selectedDifficultyID}");
// get chart TextAsset from SongData for current selected difficulty
TextAsset chartAsset = selected.GetChartFile(selected.thisLevel_selectedDifficultyID);
if (chartAsset == null)
{
Debug.LogWarning($"Chart file for difficulty {selected.thisLevel_selectedDifficultyID} not found on SongData {selected.songName}");
LogVerbose($"Chart file for difficulty {selected.thisLevel_selectedDifficultyID} not found on SongData {selected.songName}");
}
else
{
Debug.LogWarning($"Chart asset found, size={chartAsset.text?.Length ?? 0} chars");
LogVerbose($"Chart asset found, size={chartAsset.text?.Length ?? 0} chars");
}
// parse beatmap only and assign music, but DO NOT start spawning until player starts
if (beatmapManager != null && chartAsset != null)
{
Debug.LogWarning("Parsing chart JSON via BeatmapManager.ParseJsonOnly");
LogVerbose("Parsing chart JSON via BeatmapManager.ParseJsonOnly");
bool parsed = false;
try
{
@@ -601,21 +721,21 @@ public class selected_songInfo : MonoBehaviour
}
catch (System.Exception ex)
{
Debug.LogWarning("Exception during ParseJsonOnly: " + ex.Message + "\n" + ex.StackTrace);
LogVerbose("Exception during ParseJsonOnly: " + ex.Message + "\n" + ex.StackTrace);
}
Debug.LogWarning($"ParseJsonOnly returned {parsed}");
LogVerbose($"ParseJsonOnly returned {parsed}");
if (!parsed)
{
Debug.LogWarning("Failed to parse chart JSON from SongData chart file.");
LogVerbose("Failed to parse chart JSON from SongData chart file.");
}
else
{
Debug.LogWarning($"Beatmap parsed: beatmap is {(beatmapManager.beatmap != null ? "NOT null" : "null")}, parsedMusicFile={beatmapManager.parsedMusicFile}, globalDelaySeconds={beatmapManager.globalDelaySeconds}");
LogVerbose($"Beatmap parsed: beatmap is {(beatmapManager.beatmap != null ? "NOT null" : "null")}, parsedMusicFile={beatmapManager.parsedMusicFile}, globalDelaySeconds={beatmapManager.globalDelaySeconds}");
}
}
else
{
if (beatmapManager == null) Debug.LogWarning("BeatmapManager not found in gameplay scene");
if (beatmapManager == null) LogVerbose("BeatmapManager not found in gameplay scene");
}
// assign audio clip to GameManager.musicSource if available
@@ -639,7 +759,7 @@ public class selected_songInfo : MonoBehaviour
Debug.LogWarning("Failed to Play() for warmup: " + ex.Message);
}
Debug.LogWarning("Assigned SongData.audioFile to GameManager.musicSource.clip (playOnAwake disabled)");
LogVerbose("Assigned SongData.audioFile to GameManager.musicSource.clip (playOnAwake disabled)");
// Request PauseManager-driven pause (centralized)
RequestPauseManagerPause();
@@ -649,7 +769,7 @@ public class selected_songInfo : MonoBehaviour
// try to load by parsedMusicFile similar to pressStart normal mode
if (gameManager.musicSource != null && beatmapManager != null && !string.IsNullOrEmpty(beatmapManager.parsedMusicFile))
{
Debug.LogWarning($"Attempting Resources.Load for audio: {beatmapManager.parsedMusicFile}");
LogVerbose($"Attempting Resources.Load for audio: {beatmapManager.parsedMusicFile}");
var ac = Resources.Load<AudioClip>(beatmapManager.parsedMusicFile);
if (ac != null)
{
@@ -660,25 +780,25 @@ public class selected_songInfo : MonoBehaviour
{
gameManager.musicSource.mute = true;
gameManager.musicSource.Play();
Debug.LogWarning("Warmed audio playback (muted) after Resources.Load");
LogVerbose("Warmed audio playback (muted) after Resources.Load");
}
catch (System.Exception ex)
{
Debug.LogWarning("Failed to Play() for warmup after Resources.Load: " + ex.Message);
LogVerbose("Failed to Play() for warmup after Resources.Load: " + ex.Message);
}
Debug.LogWarning("Loaded audio via Resources.Load(parsedMusicFile) and disabled playOnAwake");
LogVerbose("Loaded audio via Resources.Load(parsedMusicFile) and disabled playOnAwake");
RequestPauseManagerPause();
}
else
{
Debug.LogWarning($"Resources.Load failed for '{beatmapManager.parsedMusicFile}'");
LogVerbose($"Resources.Load failed for '{beatmapManager.parsedMusicFile}'");
}
}
else
{
Debug.LogWarning("No audio assigned: selected.audioFile null and parsedMusicFile empty or musicSource missing");
LogVerbose("No audio assigned: selected.audioFile null and parsedMusicFile empty or musicSource missing");
}
}
}
@@ -689,16 +809,16 @@ public class selected_songInfo : MonoBehaviour
// Start coroutine to wait for player input (Space) to begin playback and spawning
// Use a safe host for coroutine in case this MonoBehaviour is destroyed unexpectedly
var pauseMgr = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
var pauseMgr = PauseManager.Instance != null ? PauseManager.Instance : Object.FindAnyObjectByType<PauseManager>();
MonoBehaviour coroutineHost = (pauseMgr as MonoBehaviour) ?? (gameManager as MonoBehaviour) ?? (beatmapManager as MonoBehaviour) ?? this;
if (coroutineHost != null)
{
Debug.LogWarning("Starting WaitForPlayerStartAndBegin coroutine on host: " + coroutineHost.name);
LogVerbose("Starting WaitForPlayerStartAndBegin coroutine on host: " + coroutineHost.name);
coroutineHost.StartCoroutine(WaitForPlayerStartAndBegin(beatmapManager, gameManager));
}
else
{
Debug.LogWarning("No valid coroutine host found to start WaitForPlayerStartAndBegin.");
LogVerbose("No valid coroutine host found to start WaitForPlayerStartAndBegin.");
}
// unsubscribe from sceneLoaded
@@ -707,24 +827,24 @@ public class selected_songInfo : MonoBehaviour
private IEnumerator WaitForPlayerStartAndBegin(BeatmapManager beatmapManager, GameManager gameManager)
{
Debug.LogWarning("Waiting for player to press Space to start playback...");
LogVerbose("Waiting for player to press Space to start playback...");
// Wait until player presses Space
while (!Input.GetKeyDown(KeyCode.Space))
{
yield return null;
}
Debug.LogWarning("Player pressed Space. Preparing to start playback.");
LogVerbose("Player pressed Space. Preparing to start playback.");
// wait for NotePool prewarm to complete (block until finished to avoid hitch)
if (NotePool.Instance != null)
{
Debug.LogWarning("Waiting for NotePool prewarm to finish (blocking)...");
LogVerbose("Waiting for NotePool prewarm to finish (blocking)...");
while (!NotePool.Instance.IsPrewarmed)
{
yield return null;
}
Debug.LogWarning("NotePool prewarm completed.");
LogVerbose("NotePool prewarm completed.");
}
// ensure audio has been warmed (attempt play muted if not already playing)
@@ -739,18 +859,18 @@ public class selected_songInfo : MonoBehaviour
}
// Prewarm particle systems via AnimationController to avoid first-play hitch
var anim = AnimationController.Global ?? FindObjectOfType<AnimationController>();
var anim = AnimationController.Global != null ? AnimationController.Global : Object.FindAnyObjectByType<AnimationController>();
if (anim != null)
{
Debug.LogWarning("Starting AnimationController particle prewarm (will wait until complete)...");
LogVerbose("Starting AnimationController particle prewarm (will wait until complete)...");
// yield until the prewarm completes
yield return StartCoroutine(anim.PrewarmParticlesRoutine(2));
Debug.LogWarning("AnimationController particle prewarm complete.");
LogVerbose("AnimationController particle prewarm complete.");
}
// Unpause via PauseManager
var pauseMgr = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
pauseMgr?.Pause(false);
var pauseMgr = PauseManager.Instance != null ? PauseManager.Instance : Object.FindAnyObjectByType<PauseManager>();
if (pauseMgr != null) pauseMgr.Pause(false);
// small buffer to avoid hitching immediately after unpause: wait configured delay from GameManager
// Use scaled WaitForSeconds so PauseManager (Escape) will pause the countdown
@@ -761,12 +881,12 @@ public class selected_songInfo : MonoBehaviour
// Start spawning notes
if (beatmapManager != null && beatmapManager.beatmap != null)
{
Debug.LogWarning("Calling beatmapManager.LoadBeatmap");
LogVerbose("Calling beatmapManager.LoadBeatmap");
beatmapManager.LoadBeatmap(beatmapManager.beatmap);
}
else
{
Debug.LogWarning("Cannot start beatmap: beatmapManager or beatmap is null.");
LogVerbose("Cannot start beatmap: beatmapManager or beatmap is null.");
}
// fade out black mask in gameManager if available
@@ -781,14 +901,14 @@ public class selected_songInfo : MonoBehaviour
float delay = beatmapManager != null ? beatmapManager.globalDelaySeconds : 0f;
if (gameManager.musicSource.clip != null)
{
Debug.LogWarning($"Starting audio playback with delay {delay}");
LogVerbose($"Starting audio playback with delay {delay}");
// Use GameManager helper to ensure unmute and start playback reliably
try { gameManager.PlayMusicWithDelay(delay); }
catch (System.Exception ex) { Debug.LogWarning("Failed to start music via GameManager.PlayMusicWithDelay: " + ex); }
catch (System.Exception ex) { LogVerbose("Failed to start music via GameManager.PlayMusicWithDelay: " + ex); }
}
else
{
Debug.LogWarning("GameManager.musicSource.clip is null; skipping audio playback.");
LogVerbose("GameManager.musicSource.clip is null; skipping audio playback.");
}
}
@@ -809,7 +929,7 @@ public class selected_songInfo : MonoBehaviour
Debug.LogWarning($"enterDetailPageButton assigned={(enterDetailPageButton==null?"NULL":"ASSIGNED")}");
Debug.LogWarning($"blackMaskImage assigned={(blackMaskImage==null?"NULL":"ASSIGNED")}");
Debug.LogWarning($"SongDataHolder.SelectedSongData={(SongDataHolder.SelectedSongData==null?"NULL":SongDataHolder.SelectedSongData.songName)}");
var allButtons = FindObjectsOfType<SongButton>();
var allButtons = Object.FindObjectsByType<SongButton>(FindObjectsInactive.Exclude, FindObjectsSortMode.InstanceID);
Debug.LogWarning($"Found {allButtons.Length} SongButton instances in scene");
for (int i = 0; i < allButtons.Length; i++)
{
@@ -825,16 +945,16 @@ public class selected_songInfo : MonoBehaviour
private void RequestPauseManagerPause()
{
if (pauseRequested) return;
var pm = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
var pm = PauseManager.Instance != null ? PauseManager.Instance : Object.FindAnyObjectByType<PauseManager>();
if (pm != null)
{
pm.Pause(true);
Debug.LogWarning("selected_songInfo: PauseManager.Pause(true) requested (centralized)");
LogVerbose("selected_songInfo: PauseManager.Pause(true) requested (centralized)");
pauseRequested = true;
}
else
{
Debug.LogWarning("selected_songInfo: PauseManager not found when requesting pause");
LogVerbose("selected_songInfo: PauseManager not found when requesting pause");
}
}