using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; using UnityEngine.SceneManagement; using Object = UnityEngine.Object; [ExecuteAlways] public class BgmUiBinder : MonoBehaviour { public static BgmUiBinder Instance { get; private set; } [Header("Lookup Names")] public string musicRootName = "MusicPic"; public string timeRootName = "TIME"; public string progressName = "BGM_Progress"; public string pointerName = "Zhizhen"; public string spectrumRootName = "SpectrumRoot"; public string musicPicCanvasName = "MusicPicCanvas"; [Header("Buttons")] public string prevButtonName = "Previous_Song"; public string nextButtonName = "Next_Song"; public string pauseButtonName = "pause_Song"; public string musicToggleButtonName = "Button_Music"; public Button prevButton; public Button nextButton; public Button pauseButton; public Button musicToggleButton; public bool useManualMusicToggle = true; public bool bindMusicToggleButton = false; [Header("Bindings (optional)")] public Image progressImage; public Slider progressSlider; public TMP_Text timeTmp; public Text timeText; public RectTransform pointerRect; public Vector2 pointerOffset = Vector2.zero; public bool enableMusicPicGyro = false; [Header("MusicPic Toggle")] public bool keepMusicPicAcrossScenes = false; public bool showMusicPicInLobby = false; public bool forceHideOnSceneLoad = true; public string lobbySceneName = "UI_UI"; public string gameplaySceneName = "gamePlay_gamePlay"; [Tooltip("Main menu scene name where spectrum should be hidden.")] public string mainSceneName = "Main_main"; public bool hideSpectrumInMainScene = true; public float musicPicFadeDuration = 0.3f; public bool useUnscaledTime = true; [Header("Auto Create")] public bool autoCreateProgress = false; public Vector2 defaultProgressSize = new Vector2(320f, 8f); public Vector2 defaultProgressPosition = Vector2.zero; public Color progressColor = Color.white; public Vector2 pointerStartPos = new Vector2(-190.98f, -87.05f); public Vector2 pointerEndPos = new Vector2(195.3f, -87.05f); public Vector2 spectrumPosition = new Vector2(-238.9f, -111.5f); private readonly List timeTmps = new List(); private readonly List timeTexts = new List(); private System.Text.StringBuilder _sb = new System.Text.StringBuilder(32); private int _lastSeconds = -1; private int _lastTotalSeconds = -1; private RectTransform musicRoot; private bool hasExplicitMusicRoot = false; private RectTransform progressRect; private float pointerBaseY = 0f; private bool musicPicVisible = true; private Coroutine musicPicTween; private Coroutine lobbyShowRoutine; private CanvasGroup musicPicGroup; private RectTransform spectrumRoot; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; } private string _cachedGameplaySceneName; private bool _isGameplayScene; private bool _spectrumHiddenInGameplay = false; private void OnEnable() { SceneManager.sceneLoaded += OnSceneLoaded; _cachedGameplaySceneName = gameplaySceneName; _isGameplayScene = SceneManager.GetActiveScene().name == _cachedGameplaySceneName; _spectrumHiddenInGameplay = false; _buttonsBound = false; Bind(); ApplyDefaultMusicPicVisibility(SceneManager.GetActiveScene().name); if (Application.isPlaying) { var mgr = BgmPlaybackManager.EnsureInstance(); mgr.EnsurePlaying(); } } private void OnDisable() { SceneManager.sceneLoaded -= OnSceneLoaded; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { _isGameplayScene = scene.name == _cachedGameplaySceneName; _spectrumHiddenInGameplay = false; _buttonsBound = false; Bind(); ApplyDefaultMusicPicVisibility(scene.name); } public void Bind() { musicRoot = null; hasExplicitMusicRoot = false; Canvas sceneCanvas = FindSceneCanvas(); var controller = Object.FindAnyObjectByType(); if (controller != null && controller.musicPicRoot != null) { SetMusicRoot(controller.musicPicRoot, sceneCanvas); return; } RectTransform persistentMusic = null; var persistentCanvasGo = GameObject.Find(musicPicCanvasName); if (!keepMusicPicAcrossScenes && persistentCanvasGo != null) { if (Application.isPlaying) Destroy(persistentCanvasGo); else DestroyImmediate(persistentCanvasGo); persistentCanvasGo = null; } if (persistentCanvasGo != null) { var candidate = persistentCanvasGo.transform.Find(musicRootName); if (candidate != null) persistentMusic = candidate as RectTransform; } var candidates = FindAllMusicPic(); RectTransform sceneMusic = null; var activeScene = SceneManager.GetActiveScene(); for (int i = 0; i < candidates.Count; i++) { var c = candidates[i]; if (c == null || c.gameObject.scene != activeScene) continue; if (sceneMusic == null) sceneMusic = c; if (c.GetComponentInParent(true) != null) { sceneMusic = c; break; } } if (persistentMusic != null) musicRoot = persistentMusic; else musicRoot = sceneMusic; hasExplicitMusicRoot = musicRoot != null; if (candidates.Count > 1) { for (int i = 0; i < candidates.Count; i++) { var c = candidates[i]; if (c == null) continue; if (c == musicRoot) continue; if (Application.isPlaying) Destroy(c.gameObject); else DestroyImmediate(c.gameObject); } } if (musicRoot == null) { var canvas = Object.FindAnyObjectByType(); if (canvas != null) musicRoot = canvas.GetComponent(); } if (hasExplicitMusicRoot) { EnsureMusicPicCanvas(sceneCanvas); EnsureMusicPicCanvasGroup(); if (Application.isPlaying && forceHideOnSceneLoad) SetMusicPicVisible(false, true); } if (progressSlider == null && progressImage == null) { if (musicRoot != null) { var target = musicRoot.Find(progressName); if (target != null) { progressSlider = target.GetComponent(); progressImage = target.GetComponent(); } } } if (autoCreateProgress && progressSlider == null && progressImage == null && musicRoot != null) { var go = new GameObject(progressName, typeof(RectTransform), typeof(Image)); var rt = go.GetComponent(); rt.SetParent(musicRoot, false); rt.sizeDelta = defaultProgressSize; rt.anchoredPosition = defaultProgressPosition; var img = go.GetComponent(); img.type = Image.Type.Filled; img.fillMethod = Image.FillMethod.Horizontal; img.fillOrigin = 0; img.fillAmount = 0f; img.color = progressColor; progressImage = img; } // If a progress bar exists outside MusicPic, ignore it and let cleanup remove it. if (musicRoot != null) { if (progressSlider != null && !progressSlider.transform.IsChildOf(musicRoot)) progressSlider = null; if (progressImage != null && !progressImage.transform.IsChildOf(musicRoot)) progressImage = null; } // Cleanup any BGM_Progress that isn't under MusicPic CleanupProgressOutsideMusicRoot(); progressRect = null; if (progressSlider != null) progressRect = progressSlider.GetComponent(); if (progressRect == null && progressImage != null) progressRect = progressImage.rectTransform; if (pointerRect == null) { Transform pointer = null; if (musicRoot != null) pointer = musicRoot.Find(pointerName); if (pointer == null) { var go = FindByNameContains(pointerName); if (go != null) pointer = go.transform; } if (pointer != null) pointerRect = pointer.GetComponent(); } if (pointerRect != null) { pointerBaseY = pointerRect.anchoredPosition.y; pointerRect.anchoredPosition = pointerStartPos; pointerBaseY = pointerStartPos.y; } if (enableMusicPicGyro && musicRoot != null) { var gyro = musicRoot.GetComponent(); if (gyro == null) gyro = musicRoot.gameObject.AddComponent(); gyro.target = musicRoot; } BindButtons(); EnsureSpectrum(); CacheTimeTargets(); HideSpectrumInGameplay(SceneManager.GetActiveScene().name); } public void SetMusicRoot(RectTransform root, Canvas sceneCanvas = null) { musicRoot = root; hasExplicitMusicRoot = musicRoot != null; if (!hasExplicitMusicRoot) return; EnsureMusicPicCanvas(sceneCanvas); EnsureMusicPicCanvasGroup(); EnsureSpectrum(); CacheTimeTargets(); } private void CacheTimeTargets() { timeTmps.Clear(); timeTexts.Clear(); if (timeTmp != null) timeTmps.Add(timeTmp); if (timeText != null) timeTexts.Add(timeText); if (timeTmps.Count == 0 && timeTexts.Count == 0) { Transform timeRoot = null; if (musicRoot != null) { var all = musicRoot.GetComponentsInChildren(true); for (int i = 0; i < all.Length; i++) { var t = all[i]; if (t == null) continue; if (string.Equals(t.name, timeRootName, StringComparison.OrdinalIgnoreCase)) { timeRoot = t; break; } } } if (timeRoot == null) { var timeGo = GameObject.Find(timeRootName); if (timeGo != null) timeRoot = timeGo.transform; } if (timeRoot != null) { timeTmps.AddRange(timeRoot.GetComponentsInChildren(true)); timeTexts.AddRange(timeRoot.GetComponentsInChildren(true)); if (!timeRoot.gameObject.activeSelf) timeRoot.gameObject.SetActive(true); for (int i = 0; i < timeTmps.Count; i++) { if (timeTmps[i] != null && !timeTmps[i].gameObject.activeSelf) timeTmps[i].gameObject.SetActive(true); } for (int i = 0; i < timeTexts.Count; i++) { if (timeTexts[i] != null && !timeTexts[i].gameObject.activeSelf) timeTexts[i].gameObject.SetActive(true); } } } if (timeTmps.Count == 0 && timeTexts.Count == 0) { var allTmps = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); foreach (var t in allTmps) { if (t == null) continue; if (t.name.ToUpper().Contains("TIME") || (t.text != null && t.text.ToUpper().Contains("TIME"))) { timeTmps.Add(t); break; } } } if (timeTmps.Count == 0 && timeTexts.Count == 0) { var allTexts = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); foreach (var t in allTexts) { if (t == null) continue; if (t.name.ToUpper().Contains("TIME") || (t.text != null && t.text.ToUpper().Contains("TIME"))) { timeTexts.Add(t); break; } } } } private bool _buttonsBound = false; private void Update() { if (!Application.isPlaying) return; if (_isGameplayScene && !_spectrumHiddenInGameplay) { HideSpectrumInGameplay(_cachedGameplaySceneName); _spectrumHiddenInGameplay = true; } if (!_buttonsBound) { BindButtons(); _buttonsBound = true; } if (useManualMusicToggle && musicToggleButton != null && Input.GetMouseButtonDown(0)) { if (IsPointerOverButton(musicToggleButton)) ToggleMusicPic(); } // keep spectrum alive if MusicPic gets rebuilt or moved if (spectrumRoot == null && musicRoot != null) { EnsureSpectrum(); } var mgr = BgmPlaybackManager.Instance; if (mgr == null || !mgr.IsReady) return; float total = mgr.TotalTime; float current = mgr.CurrentTime; if (total <= 0.01f) return; float t = Mathf.Clamp01(current / total); if (progressSlider != null) progressSlider.value = t; if (progressImage != null) progressImage.fillAmount = t; UpdatePointer(t); int curS = Mathf.FloorToInt(current); int totS = Mathf.FloorToInt(total); if (curS != _lastSeconds || totS != _lastTotalSeconds) { _lastSeconds = curS; _lastTotalSeconds = totS; _sb.Clear(); AppendTime(_sb, curS); _sb.Append(" / "); AppendTime(_sb, totS); string timeStr = _sb.ToString(); for (int i = 0; i < timeTmps.Count; i++) { if (timeTmps[i] != null) timeTmps[i].text = timeStr; } for (int i = 0; i < timeTexts.Count; i++) { if (timeTexts[i] != null) timeTexts[i].text = timeStr; } } } private void AppendTime(System.Text.StringBuilder sb, int totalSeconds) { int m = totalSeconds / 60; int s = totalSeconds % 60; if (m < 10) sb.Append('0'); sb.Append(m); sb.Append(':'); if (s < 10) sb.Append('0'); sb.Append(s); } private void LateUpdate() { if (musicRoot != null && spectrumRoot != null && enableMusicPicGyro) spectrumRoot.localRotation = musicRoot.localRotation; } private void UpdatePointer(float t) { if (pointerRect == null || progressRect == null) return; var parent = pointerRect.parent as RectTransform; if (parent == null) return; float x = Mathf.Lerp(pointerStartPos.x, pointerEndPos.x, t); pointerRect.anchoredPosition = new Vector2(x + pointerOffset.x, pointerStartPos.y + pointerOffset.y); } private void CleanupProgressOutsideMusicRoot() { var all = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < all.Length; i++) { var rt = all[i]; if (rt == null) continue; if (!string.Equals(rt.name, progressName, StringComparison.OrdinalIgnoreCase)) continue; if (musicRoot != null && rt.transform.IsChildOf(musicRoot)) continue; if (Application.isPlaying) Destroy(rt.gameObject); else DestroyImmediate(rt.gameObject); } } private void BindButtons() { if (prevButton == null) prevButton = FindButton(prevButtonName); if (nextButton == null) nextButton = FindButton(nextButtonName); if (pauseButton == null) pauseButton = FindButton(pauseButtonName); if (musicToggleButton == null) musicToggleButton = FindButton(musicToggleButtonName); useManualMusicToggle = musicToggleButton == null; var mgr = BgmPlaybackManager.Instance ?? BgmPlaybackManager.EnsureInstance(); if (prevButton != null) { prevButton.onClick.RemoveAllListeners(); prevButton.onClick.AddListener(mgr.Previous); } if (nextButton != null) { nextButton.onClick.RemoveAllListeners(); nextButton.onClick.AddListener(mgr.Next); } if (pauseButton != null) { pauseButton.onClick.RemoveAllListeners(); pauseButton.onClick.AddListener(mgr.TogglePause); } if (musicToggleButton != null) { if (bindMusicToggleButton) { musicToggleButton.onClick.RemoveAllListeners(); musicToggleButton.onClick.AddListener(ToggleMusicPic); } } } private Button FindButton(string name) { if (string.IsNullOrEmpty(name)) return null; var go = FindByNameContains(name); if (go == null) return null; return go.GetComponent