长音符再修复 加入了关卡连接 优化了一些默认加载

This commit is contained in:
FloatGaming
2026-01-13 11:22:39 +08:00
parent 2738089af1
commit 2f9d4aae0c
426 changed files with 299413 additions and 1050 deletions
@@ -161,6 +161,15 @@ public class SongButton : MonoBehaviour
{
selectedInfo.UpdateSelectedSongDisplay();
}
// Save per-DLC selected song index to PlayerPrefs
string dlcKey = SongSelectUI.CurrentDlcKey;
if (string.IsNullOrEmpty(dlcKey)) dlcKey = "default_0";
string prefsKey = "song_selected_" + dlcKey;
int index = GetIndexAmongSongButtons();
PlayerPrefs.SetInt(prefsKey, index);
PlayerPrefs.Save();
Debug.Log($"SongButton: saved selected song index {index} for key {prefsKey}");
}
else
{
@@ -168,6 +177,23 @@ public class SongButton : MonoBehaviour
}
}
private int GetIndexAmongSongButtons()
{
// Use parent traversal to find only SongButton siblings, to avoid mismatch when other UI children exist.
var parent = transform.parent;
if (parent == null) return 0;
int idx = 0;
for (int i = 0; i < parent.childCount; i++)
{
var child = parent.GetChild(i);
var sb = child != null ? child.GetComponent<SongButton>() : null;
if (sb == null) continue;
if (sb == this) return idx;
idx++;
}
return 0;
}
private void FadeInSelectedBorder()
{
if (selected_boarder != null)
@@ -12,9 +12,4 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 7c972c378bd4832459dead70062eeb5c, type: 3}
m_Name: SongList
m_EditorClassIdentifier:
songs:
- {fileID: 11400000, guid: 955a44007aeca3245a438a8af3e97e04, type: 2}
- {fileID: 11400000, guid: 23248216085203145b3c154e9da5a0dd, type: 2}
- {fileID: 11400000, guid: 0737633aca7fca844b2b4ae0572dcc87, type: 2}
- {fileID: 11400000, guid: e8e4a1801ca7b1f47b60f5d67f792d7b, type: 2}
- {fileID: 11400000, guid: 958379745549ed54ba4da95ba82b0049, type: 2}
songs: []
@@ -2,6 +2,7 @@
using UnityEngine.UI;
using TMPro;
using UnityEngine.SceneManagement;
using System.Collections;
using System.Collections.Generic;
public class SongSelectUI : MonoBehaviour
@@ -13,6 +14,12 @@ public class SongSelectUI : MonoBehaviour
[SerializeField] Button button_Main;
[SerializeField] string ui_Main_Scene_Name = "UI_UI";
// 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;
void Start()
{
if (songList == null || songButtonPrefab == null || contentPanel == null)
@@ -35,6 +42,9 @@ public class SongSelectUI : MonoBehaviour
{
InstantiateSongButton(song);
}
if (restoreCoroutine != null) StopCoroutine(restoreCoroutine);
restoreCoroutine = StartCoroutine(RestoreSelectedSongNextFrame());
}
// Instantiate from a provided list (used by dlcButton to show DLC songs)
@@ -45,6 +55,63 @@ public class SongSelectUI : MonoBehaviour
if (list == null) return;
foreach (var song in list)
InstantiateSongButton(song);
// After populating, attempt to restore per-DLC selected song (next frame to avoid stale objects)
if (restoreCoroutine != null) StopCoroutine(restoreCoroutine);
restoreCoroutine = StartCoroutine(RestoreSelectedSongNextFrame());
}
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()
@@ -193,7 +193,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -131.06, y: 2}
m_SizeDelta: {x: 80, y: 80}
m_SizeDelta: {x: 70, y: 70}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4297944811495197019
CanvasRenderer:
@@ -270,6 +270,7 @@ RectTransform:
- {fileID: 570804039580569020}
- {fileID: 333634122881333044}
- {fileID: 3306384613169609027}
- {fileID: 4819405071236471537}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
@@ -376,7 +377,9 @@ MonoBehaviour:
dlcID: {fileID: 115205934777297662}
dlcProducer: {fileID: 2931296379136689410}
dlcPubliushDate: {fileID: 3713972282091736206}
dlc_nowSelectedImageBoarder: {fileID: 2824183749825073581}
dlcDescription: {fileID: 0}
dlcDataSO: {fileID: 0}
dlcContent:
- {fileID: 0}
editorSearchFolder: Assets/Resources/dlc_dlcIndex
@@ -460,6 +463,81 @@ MonoBehaviour:
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: dlcNameHere
--- !u!1 &6799314756353582751
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4819405071236471537}
- component: {fileID: 9008192983113540843}
- component: {fileID: 2824183749825073581}
m_Layer: 5
m_Name: boarder
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4819405071236471537
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6799314756353582751}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 5010078828579475845}
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.0897, y: 1}
m_SizeDelta: {x: 364.8207, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &9008192983113540843
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6799314756353582751}
m_CullTransparentMesh: 1
--- !u!114 &2824183749825073581
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6799314756353582751}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.2264151, g: 0.2264151, b: 0.2264151, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 7f3b1220317607e44bbb8f0e1998044a, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 0
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 3
--- !u!1 &8646457970808721701
GameObject:
m_ObjectHideFlags: 0
@@ -494,8 +572,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: 98.09924, y: -37.3367}
m_SizeDelta: {x: 160, y: 16}
m_AnchoredPosition: {x: 94.8, y: -36.5}
m_SizeDelta: {x: 160, y: 12}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2498827089920741302
CanvasRenderer:
@@ -527,7 +605,7 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: 775c674e81662c644b64550d2e8f74e0, type: 3}
m_FontSize: 14
m_FontSize: 12
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
+190 -27
View File
@@ -11,6 +11,7 @@ public class dlcButton : MonoBehaviour
public Text dlcID;
public Text dlcProducer;
public Text dlcPubliushDate;
public Image dlc_nowSelectedImageBoarder;
[Header("介绍")]
public Text dlcDescription;
@@ -26,6 +27,14 @@ public class dlcButton : MonoBehaviour
private Button _btn;
// 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";
// instance index in registration order
private int instanceIndex = -1;
void Awake()
{
_btn = GetComponent<Button>();
@@ -34,49 +43,141 @@ public class dlcButton : MonoBehaviour
_btn.onClick.RemoveAllListeners();
_btn.onClick.AddListener(OnButtonClicked_ShowSongs);
}
// register this button for centralized selection handling
RegisterInstance();
// ensure border initial state
if (dlc_nowSelectedImageBoarder != null)
{
var c = dlc_nowSelectedImageBoarder.color;
c.a = 0f;
dlc_nowSelectedImageBoarder.color = c;
dlc_nowSelectedImageBoarder.gameObject.SetActive(false);
}
Debug.Log($"dlcButton.Awake: registered '{gameObject.name}' as index {instanceIndex}");
}
// Resolve dlcData after all buttons have been instantiated
public void ResolveDlcData()
private void RegisterInstance()
{
Debug.Log($"dlcButton.ResolveDlcData called on '{gameObject.name}'");
if (dlcDataSO != null)
if (!allButtons.Contains(this))
{
Debug.Log($"dlcButton: dlcDataSO already assigned on '{gameObject.name}' -> {dlcDataSO.name}");
ApplyDlcData(dlcDataSO);
return;
}
instanceIndex = allButtons.Count;
allButtons.Add(this);
int 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;
}
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 no selectedIndex loaded yet, load from PlayerPrefs default 0
if (selectedIndex == -1)
{
Debug.Log($"dlcButton: Resolving by GameObject name prefix {parsed} for '{gameObject.name}'");
LoadDlcDataById(parsed);
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
{
Debug.LogWarning($"dlcButton.ResolveDlcData: could not parse id from GameObject name '{goName}'");
// ensure unselected visual
SetSelectedVisual(false, immediate: true);
}
}
}
private void UnregisterInstance()
{
if (allButtons.Contains(this))
{
int idx = allButtons.IndexOf(this);
allButtons.Remove(this);
// if removed index was before selectedIndex, adjust saved index
if (idx >= 0 && idx < instanceIndex)
{
// nothing: instanceIndex only used at registration time
}
}
}
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();
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 void SetSelectedVisual(bool selected, bool immediate = false)
{
if (dlc_nowSelectedImageBoarder == null) return;
if (borderFadeCoroutine != null)
{
StopCoroutine(borderFadeCoroutine);
borderFadeCoroutine = null;
}
if (immediate)
{
dlc_nowSelectedImageBoarder.gameObject.SetActive(selected);
var c = dlc_nowSelectedImageBoarder.color;
c.a = selected ? 1f : 0f;
dlc_nowSelectedImageBoarder.color = c;
}
else
{
Debug.LogWarning($"dlcButton.ResolveDlcData: no dlcID text and no name to parse for '{gameObject.name}'");
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;
if (Mathf.Approximately(targetAlpha, 0f))
{
img.gameObject.SetActive(false);
}
borderFadeCoroutine = null;
}
void Start()
{
// Do not auto-resolve here anymore. Resolution will be triggered externally after instantiation of all buttons.
@@ -84,6 +185,27 @@ public class dlcButton : MonoBehaviour
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();
var s = GameObject.FindObjectOfType<SongSelectUI>();
if (s != null)
{
@@ -262,4 +384,45 @@ public class dlcButton : MonoBehaviour
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()
{
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;
}
int 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;
}
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))
{
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}'");
}
}
else
{
Debug.LogWarning($"dlcButton.ResolveDlcData: no dlcID text and no name to parse for '{gameObject.name}'");
}
}
}
+24
View File
@@ -113,5 +113,29 @@ public class loadDlcListPrefab : MonoBehaviour
if (b != null)
b.ResolveDlcData();
}
// 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)
{
if (savedIndex < 0) savedIndex = 0;
if (savedIndex >= allButtons.Length) savedIndex = 0;
var savedBtn = allButtons[savedIndex];
if (savedBtn != null)
{
// simulate a user clicking the button: this will Select() and display its songs
savedBtn.OnButtonClicked_ShowSongs();
Debug.Log($"loadDlcListPrefab: restored and clicked saved dlc button index={savedIndex} name={savedBtn.gameObject.name}");
}
else
{
Debug.LogWarning($"loadDlcListPrefab: saved dlc button at index {savedIndex} was null");
}
}
else
{
Debug.LogWarning("loadDlcListPrefab: no dlc buttons found after RefreshUI");
}
}
}
+638 -3
View File
@@ -2,24 +2,128 @@ using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections;
public class selected_songInfo : MonoBehaviour
{
public Image t_songCoverImage;
public Image btm_songCoverImage;
public Text songNameText;
[Header("")]
[Header("对应的难度按钮")]
public List<Button> difficultyButtons = new List<Button>();
[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背景色")]
public Sprite buttons_bgImage;
[Header("black mask")]
public Image blackMaskImage;
// store the original sprites so we don't overwrite the designer's setup
private List<Sprite> originalButtonSprites = new List<Sprite>();
void Awake()
{
Debug.LogWarning("selected_songInfo.Awake called");
}
void OnEnable()
{
Debug.LogWarning("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");
}
else
{
Debug.LogWarning("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");
}
else
{
Debug.LogWarning("quickEnter_gamePlay is null in OnEnable");
}
}
void OnDisable()
{
Debug.LogWarning("selected_songInfo.OnDisable called");
if (enterDetailPageButton != null)
{
enterDetailPageButton.onClick.RemoveListener(OnEnterDetailPageClicked);
}
if (quickEnter_gamePlay != null)
{
quickEnter_gamePlay.onClick.RemoveListener(OnQuickEnterClicked);
}
}
void OnDestroy()
{
Debug.LogWarning("selected_songInfo.OnDestroy called");
// ensure we remove sceneLoaded subscription if still present
SceneManager.sceneLoaded -= OnSceneLoadedAfterQuickEnter;
}
void Start()
{
if (enterDetailPageButton != null)
Debug.LogWarning("selected_songInfo.Start called");
// ensure black mask is transparent and inactive at start
if (blackMaskImage != null)
{
enterDetailPageButton.onClick.AddListener(OnEnterDetailPageClicked);
var c = blackMaskImage.color;
c.a = 0f;
blackMaskImage.color = c;
blackMaskImage.gameObject.SetActive(false);
}
// capture original sprites for difficulty buttons before we make any changes
originalButtonSprites.Clear();
if (difficultyButtons != null)
{
foreach (var btn in difficultyButtons)
{
if (btn == null)
{
originalButtonSprites.Add(null);
continue;
}
var img = btn.GetComponent<Image>();
originalButtonSprites.Add(img != null ? img.sprite : null);
}
}
// attach click listeners to difficulty buttons (map list index 0..n-1 -> difficulty 0..n-1)
if (difficultyButtons != null)
{
for (int i = 0; i < difficultyButtons.Count; i++)
{
var btn = difficultyButtons[i];
if (btn == null) continue;
int idx = i; // capture
btn.onClick.RemoveAllListeners();
btn.onClick.AddListener(() => OnDifficultyButtonClicked(idx));
}
}
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)}");
}
public void UpdateSelectedSongDisplay()
@@ -49,6 +153,9 @@ public class selected_songInfo : MonoBehaviour
{
songNameText.text = song.songName;
}
// set difficulty button backgrounds according to currently selected difficulty in the song SO
SetDifficultyButtonBackground(song.thisLevel_selectedDifficultyID);
}
else
{
@@ -67,6 +174,82 @@ public class selected_songInfo : MonoBehaviour
{
songNameText.text = "未选择歌曲";
}
// restore original difficulty button backgrounds and text colors
SetDifficultyButtonBackground(-1);
}
}
private void EnsureOriginalSpritesCount()
{
if (difficultyButtons == null) return;
while (originalButtonSprites.Count < difficultyButtons.Count)
{
var btn = difficultyButtons[originalButtonSprites.Count];
if (btn == null) originalButtonSprites.Add(null);
else
{
var img = btn.GetComponent<Image>();
originalButtonSprites.Add(img != null ? img.sprite : null);
}
}
}
private void SetDifficultyButtonBackground(int selectedDifficulty)
{
if (difficultyButtons == null || difficultyButtons.Count == 0) return;
EnsureOriginalSpritesCount();
for (int i = 0; i < difficultyButtons.Count; i++)
{
var btn = difficultyButtons[i];
if (btn == null) continue;
var img = btn.GetComponent<Image>();
if (img == null) continue;
if (i == selectedDifficulty)
{
// apply highlight sprite to selected button
if (buttons_bgImage != null)
{
img.sprite = buttons_bgImage;
img.color = Color.white;
}
// set corresponding text (if exists) to white
if (difficultyTexts != null && i < difficultyTexts.Count && difficultyTexts[i] != null)
{
difficultyTexts[i].color = Color.white;
}
}
else
{
// restore original sprite if we recorded one; otherwise leave as-is
if (i < originalButtonSprites.Count)
{
img.sprite = originalButtonSprites[i];
}
// set corresponding text (if exists) to black
if (difficultyTexts != null && i < difficultyTexts.Count && difficultyTexts[i] != null)
{
difficultyTexts[i].color = Color.black;
}
}
}
}
private void OnDifficultyButtonClicked(int index)
{
// map button index 0..3 to song SO difficulty 0..3
if (SongDataHolder.SelectedSongData != null)
{
SongDataHolder.SelectedSongData.thisLevel_selectedDifficultyID = Mathf.Clamp(index, 0, 3);
// immediately update UI to reflect new selection
SetDifficultyButtonBackground(SongDataHolder.SelectedSongData.thisLevel_selectedDifficultyID);
}
else
{
Debug.LogWarning("No song selected - difficulty button click ignored.");
}
}
@@ -81,4 +264,456 @@ public class selected_songInfo : MonoBehaviour
Debug.LogWarning("No song selected, cannot enter detail page.");
}
}
private void OnQuickEnterClicked()
{
// use SongData from holder or SongButton; ensure selection exists
SongData sd = FindSongDataFromSelectedButton();
if (sd == null) sd = SongDataHolder.SelectedSongData;
if (sd == null)
{
Debug.LogWarning("No song selected - cannot quick enter gameplay.");
return;
}
// Validate that the selected difficulty has a chart file before proceeding
var chart = sd.GetChartFile(sd.thisLevel_selectedDifficultyID);
if (chart == null)
{
Debug.LogError($"QuickEnter aborted: Song '{sd.songName}' has no chart file for difficulty {sd.thisLevel_selectedDifficultyID}.");
return;
}
Debug.LogWarning($"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);
// keep selected in holder to make it accessible in new scene
SongDataHolder.SelectedSongData = sd;
// mark this object to persist across scene load so callbacks/coroutines are valid
DontDestroyOnLoad(this.gameObject);
// start fade and load sequence
StartCoroutine(QuickEnterSequence());
}
private IEnumerator QuickEnterSequence()
{
Debug.LogWarning("QuickEnterSequence started: beginning fade");
// Fade black mask in over 0.25 seconds
float duration = 0.25f;
if (blackMaskImage != null)
{
// if mask is inactive, ensure its alpha is zero before activating
if (!blackMaskImage.gameObject.activeSelf)
{
Color cc = blackMaskImage.color;
cc.a = 0f;
blackMaskImage.color = cc;
blackMaskImage.gameObject.SetActive(true);
}
else
{
// active: explicitly set alpha to 0 to start fade from transparent
Color cc = blackMaskImage.color;
cc.a = 0f;
blackMaskImage.color = cc;
}
float t = 0f;
Color c = blackMaskImage.color;
while (t < duration)
{
t += Time.deltaTime;
float alpha = Mathf.Clamp01(t / duration);
c.a = alpha;
blackMaskImage.color = c;
yield return null;
}
c.a = 1f;
blackMaskImage.color = c;
}
Debug.LogWarning("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");
if (asyncOp != null)
{
// wait until load completes
while (!asyncOp.isDone)
{
yield return null;
}
}
else
{
// fallback to synchronous load
SceneManager.LoadScene("gamePlay_gamePlay");
}
yield break;
}
private SongData FindSongDataFromSelectedButton()
{
var allButtons = FindObjectsOfType<SongButton>();
Debug.LogWarning($"FindSongDataFromSelectedButton: found {allButtons.Length} SongButton instances");
foreach (var sb in allButtons)
{
if (sb == null) continue;
try
{
// log selection marker info for each button to help debugging
var field = sb.GetType().GetField("selected_boarder", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
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}");
if (img != null && img.color.a > 0.5f)
{
// prefer thisSong_so if present
var soField = sb.GetType().GetField("thisSong_so", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
if (soField != null)
{
var soObj = soField.GetValue(sb);
if (soObj != null && soObj is SongData)
{
Debug.LogWarning($"Selected SongButton has thisSong_so assigned: {((SongData)soObj).songName}");
return soObj as SongData;
}
}
// fallback: use song id on SongButton
var idField = sb.GetType().GetField("song_songSerialID", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
if (idField != null)
{
int id = (int)idField.GetValue(sb);
Debug.LogWarning($"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}");
return sd;
}
}
}
}
catch (System.Exception ex)
{
Debug.LogWarning("Exception while inspecting SongButton: " + ex.Message);
}
}
Debug.LogWarning("FindSongDataFromSelectedButton: no selected SongButton found");
return null;
}
private void OnSceneLoadedAfterQuickEnter(Scene scene, LoadSceneMode mode)
{
// If this object has been destroyed, unsubscribe and bail out safely
if (!this)
{
SceneManager.sceneLoaded -= OnSceneLoadedAfterQuickEnter;
return;
}
// only handle target gameplay scene
if (scene.name != "gamePlay_gamePlay")
{
// unsubscribe anyway
SceneManager.sceneLoaded -= OnSceneLoadedAfterQuickEnter;
return;
}
// If the black mask reference exists but the GameObject is disabled, ensure its alpha is 0
if (blackMaskImage != null && !blackMaskImage.gameObject.activeSelf)
{
var cc = blackMaskImage.color;
cc.a = 0f;
blackMaskImage.color = cc;
}
// perform setup: load chart JSON from selected SongData's selected difficulty and set audio
SongData selected = null;
// Prefer SongButton's assigned SO if available
selected = FindSongDataFromSelectedButton();
// Fallback to SongDataHolder
if (selected == null) selected = SongDataHolder.SelectedSongData;
if (selected == null)
{
Debug.LogWarning("OnSceneLoadedAfterQuickEnter: SelectedSongData is null");
SceneManager.sceneLoaded -= OnSceneLoadedAfterQuickEnter;
// allow this object to be destroyed now
Destroy(this.gameObject);
return;
}
// find managers in the new scene
var beatmapManager = FindObjectOfType<BeatmapManager>();
var gameManager = FindObjectOfType<GameManager>();
Debug.LogWarning($"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}");
}
else
{
Debug.LogWarning($"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");
bool parsed = false;
try
{
parsed = beatmapManager.ParseJsonOnly(chartAsset.text);
}
catch (System.Exception ex)
{
Debug.LogWarning("Exception during ParseJsonOnly: " + ex.Message + "\n" + ex.StackTrace);
}
Debug.LogWarning($"ParseJsonOnly returned {parsed}");
if (!parsed)
{
Debug.LogWarning("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}");
}
}
else
{
if (beatmapManager == null) Debug.LogWarning("BeatmapManager not found in gameplay scene");
}
// assign audio clip to GameManager.musicSource if available
if (gameManager != null)
{
if (selected.audioFile != null && gameManager.musicSource != null)
{
gameManager.musicSource.clip = selected.audioFile;
gameManager.musicSource.loop = false;
// make sure AudioSource won't auto-play due to inspector settings
gameManager.musicSource.playOnAwake = false;
// warm up audio by playing muted so decoding happens before unpause
try
{
gameManager.musicSource.mute = true;
gameManager.musicSource.Play();
Debug.LogWarning("Warmed audio playback (muted) after assigning SongData.audioFile");
}
catch (System.Exception ex)
{
Debug.LogWarning("Failed to Play() for warmup: " + ex.Message);
}
Debug.LogWarning("Assigned SongData.audioFile to GameManager.musicSource.clip (playOnAwake disabled)");
// Request PauseManager-driven pause (centralized)
RequestPauseManagerPause();
}
else
{
// 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}");
var ac = Resources.Load<AudioClip>(beatmapManager.parsedMusicFile);
if (ac != null)
{
gameManager.musicSource.clip = ac;
gameManager.musicSource.playOnAwake = false;
// warm up audio by playing muted
try
{
gameManager.musicSource.mute = true;
gameManager.musicSource.Play();
Debug.LogWarning("Warmed audio playback (muted) after Resources.Load");
}
catch (System.Exception ex)
{
Debug.LogWarning("Failed to Play() for warmup after Resources.Load: " + ex.Message);
}
Debug.LogWarning("Loaded audio via Resources.Load(parsedMusicFile) and disabled playOnAwake");
RequestPauseManagerPause();
}
else
{
Debug.LogWarning($"Resources.Load failed for '{beatmapManager.parsedMusicFile}'");
}
}
else
{
Debug.LogWarning("No audio assigned: selected.audioFile null and parsedMusicFile empty or musicSource missing");
}
}
}
// Ensure PauseManager pause is requested; the RequestPauseManagerPause call above will have already
// attempted to pause the game if audio was set. If not yet requested, call it now to reuse PauseManager.
RequestPauseManagerPause();
// 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>();
MonoBehaviour coroutineHost = (pauseMgr as MonoBehaviour) ?? (gameManager as MonoBehaviour) ?? (beatmapManager as MonoBehaviour) ?? this;
if (coroutineHost != null)
{
Debug.LogWarning("Starting WaitForPlayerStartAndBegin coroutine on host: " + coroutineHost.name);
coroutineHost.StartCoroutine(WaitForPlayerStartAndBegin(beatmapManager, gameManager));
}
else
{
Debug.LogWarning("No valid coroutine host found to start WaitForPlayerStartAndBegin.");
}
// unsubscribe from sceneLoaded
SceneManager.sceneLoaded -= OnSceneLoadedAfterQuickEnter;
}
private IEnumerator WaitForPlayerStartAndBegin(BeatmapManager beatmapManager, GameManager gameManager)
{
Debug.LogWarning("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.");
// 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)...");
while (!NotePool.Instance.IsPrewarmed)
{
yield return null;
}
Debug.LogWarning("NotePool prewarm completed.");
}
// ensure audio has been warmed (attempt play muted if not already playing)
if (gameManager != null && gameManager.musicSource != null && gameManager.musicSource.clip != null && !gameManager.musicSource.isPlaying)
{
try
{
gameManager.musicSource.mute = true;
gameManager.musicSource.Play();
}
catch { }
}
// Prewarm particle systems via AnimationController to avoid first-play hitch
var anim = AnimationController.Global ?? FindObjectOfType<AnimationController>();
if (anim != null)
{
Debug.LogWarning("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.");
}
// Unpause via PauseManager
var pauseMgr = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
pauseMgr?.Pause(false);
// small buffer to avoid hitching immediately after unpause: wait configured realtime delay from GameManager
float unpauseBuffer = 3f;
if (gameManager != null) unpauseBuffer = Mathf.Max(0f, gameManager.playbackStartDelay);
yield return new WaitForSecondsRealtime(unpauseBuffer);
// Start spawning notes
if (beatmapManager != null && beatmapManager.beatmap != null)
{
Debug.LogWarning("Calling beatmapManager.LoadBeatmap");
beatmapManager.LoadBeatmap(beatmapManager.beatmap);
}
else
{
Debug.LogWarning("Cannot start beatmap: beatmapManager or beatmap is null.");
}
// fade out black mask in gameManager if available
if (gameManager != null)
{
gameManager.StartCoroutine(gameManager.FadeOutBlackMask(0.25f));
}
// Play audio using GameManager.musicSource with delay
if (gameManager != null && gameManager.musicSource != null)
{
float delay = beatmapManager != null ? beatmapManager.globalDelaySeconds : 0f;
if (gameManager.musicSource.clip != null)
{
Debug.LogWarning($"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); }
}
else
{
Debug.LogWarning("GameManager.musicSource.clip is null; skipping audio playback.");
}
}
// done with this helper object - allow it to be destroyed
Destroy(this.gameObject);
yield break;
}
// Debug helper: press F1 at runtime to print diagnostic state to Console (warnings)
void Update()
{
if (Input.GetKeyDown(KeyCode.F1))
{
Debug.LogWarning("selected_songInfo.DebugTrigger: F1 pressed - dumping diagnostic info");
Debug.LogWarning($"GameObject activeInHierarchy={gameObject.activeInHierarchy}, enabled={enabled}");
Debug.LogWarning($"quickEnter_gamePlay assigned={(quickEnter_gamePlay==null?"NULL":"ASSIGNED")}");
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>();
Debug.LogWarning($"Found {allButtons.Length} SongButton instances in scene");
for (int i = 0; i < allButtons.Length; i++)
{
var sb = allButtons[i];
if (sb == null) continue;
Debug.LogWarning($" [{i}] name={sb.name} active={sb.gameObject.activeInHierarchy}");
}
}
}
// Helper to centralize PauseManager pause request so selected_songInfo reuses PauseManager implementation
private bool pauseRequested = false;
private void RequestPauseManagerPause()
{
if (pauseRequested) return;
var pm = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
if (pm != null)
{
pm.Pause(true);
Debug.LogWarning("selected_songInfo: PauseManager.Pause(true) requested (centralized)");
pauseRequested = true;
}
else
{
Debug.LogWarning("selected_songInfo: PauseManager not found when requesting pause");
}
}
}