using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.UI; using TMPro; public static class VRBeatSaberSceneBuilder { private const string MenuScene = "Assets/VRBeatsKit/Scenes/Menu.unity"; // ───────────────────────────────────────────── // ③ Menu — Rebuild SongSelect Panel // // Canvas(SongSelect) size: 105.885 × 68.223 // BG child covers full canvas (stretch anchors) // BG local coord origin = center // X: -52.94 ~ +52.94 // Y: -34.11 ~ +34.11 // ───────────────────────────────────────────── [MenuItem("Tools/VRBeatSaber/③ Menu — Rebuild SongSelect Panel")] public static void RebuildSongSelectPanel() { var scene = EditorSceneManager.OpenScene(MenuScene, OpenSceneMode.Single); var songSelectGO = GameObject.Find("SongSelect"); if (songSelectGO == null) { Debug.LogError("[SceneBuilder] 'SongSelect' not found."); return; } var bgTransform = songSelectGO.transform.Find("BG"); if (bgTransform == null) { Debug.LogError("[SceneBuilder] 'SongSelect/BG' not found."); return; } // Clear BG children for (int i = bgTransform.childCount - 1; i >= 0; i--) Object.DestroyImmediate(bgTransform.GetChild(i).gameObject); // Create/reuse SongSystem root GO for SongLibrary (must be root for DontDestroyOnLoad) var sysGO = GameObject.Find("SongSystem"); if (sysGO == null) sysGO = new GameObject("SongSystem"); var oldLib = sysGO.GetComponent(); if (oldLib != null) Object.DestroyImmediate(oldLib); var songLibrary = sysGO.AddComponent(); // Add/replace SongSelectManager + DownloadManager on SongSelect GO var oldSSM = songSelectGO.GetComponent(); if (oldSSM != null) Object.DestroyImmediate(oldSSM); var oldDM = songSelectGO.GetComponent(); if (oldDM != null) Object.DestroyImmediate(oldDM); var downloadManager = songSelectGO.AddComponent(); var songSelectManager = songSelectGO.AddComponent(); var bg = bgTransform; // ── Header ────────────────────────────────────────── CreateLabel(bg, "Title", "SONG SELECT", new Vector2(0f, 28.5f), new Vector2(100f, 9f), 8.5f, Color.white, TextAlignmentOptions.Center); CreateDivider(bg, "DivHeader", new Vector2(0f, 23.5f), new Vector2(104f, 0.5f)); var tabAllBtn = CreateStyledButton(bg, "TabAll", "ALL", new Vector2(-18f, 19.5f), new Vector2(30f, 7f), 5f); var tabOwnedBtn = CreateStyledButton(bg, "TabOwned", "OWNED", new Vector2( 14f, 19.5f), new Vector2(30f, 7f), 5f); CreateDivider(bg, "DivTabs", new Vector2(0f, 15.5f), new Vector2(104f, 0.5f)); // ── Content area: Y from 15 to -34.11, height ~49 ─── // ListPanel (left half) var listPanelGO = new GameObject("ListPanel"); listPanelGO.transform.SetParent(bg, false); SetRect(listPanelGO, new Vector2(-26.6f, -9.4f), new Vector2(52.7f, 49f)); // Vertical divider CreateDivider(bg, "DivVertical", new Vector2(0.1f, -9.4f), new Vector2(0.5f, 49f)); // DetailPanel (right half, hidden until card clicked) var detailPanelGO = new GameObject("DetailPanel"); detailPanelGO.transform.SetParent(bg, false); SetRect(detailPanelGO, new Vector2(26.6f, -9.4f), new Vector2(52.7f, 49f)); // ── ListPanel contents ─────────────────────────────── RectTransform scrollContent; GameObject loadingOverlay; GameObject errorOverlay; TMP_Text errorText; BuildScrollList(listPanelGO.transform, out scrollContent, out loadingOverlay, out errorOverlay, out errorText); // ── DetailPanel contents ───────────────────────────── var detailPanelComp = detailPanelGO.AddComponent(); Button btnNormal, btnHard, btnExpert, btnExpertPlus; Button downloadBtn, deleteBtn, playBtn, closeBtn; GameObject progressGroup; Slider progressSlider; TMP_Text progressText; TMP_Text titleTmp, artistTmp, infoTmp; BuildDetailPanelUI(detailPanelGO.transform, out titleTmp, out artistTmp, out infoTmp, out btnNormal, out btnHard, out btnExpert, out btnExpertPlus, out downloadBtn, out deleteBtn, out playBtn, out closeBtn, out progressGroup, out progressSlider, out progressText); detailPanelGO.SetActive(false); // ── Wire SongDetailPanel refs ──────────────────────── var dpSO = new SerializedObject(detailPanelComp); dpSO.FindProperty("titleText") .objectReferenceValue = titleTmp; dpSO.FindProperty("artistText") .objectReferenceValue = artistTmp; dpSO.FindProperty("infoText") .objectReferenceValue = infoTmp; dpSO.FindProperty("btnNormal") .objectReferenceValue = btnNormal; dpSO.FindProperty("btnHard") .objectReferenceValue = btnHard; dpSO.FindProperty("btnExpert") .objectReferenceValue = btnExpert; dpSO.FindProperty("btnExpertPlus") .objectReferenceValue = btnExpertPlus; dpSO.FindProperty("downloadButton") .objectReferenceValue = downloadBtn; dpSO.FindProperty("deleteButton") .objectReferenceValue = deleteBtn; dpSO.FindProperty("playButton") .objectReferenceValue = playBtn; dpSO.FindProperty("closeButton") .objectReferenceValue = closeBtn; dpSO.FindProperty("progressGroup") .objectReferenceValue = progressGroup; dpSO.FindProperty("progressSlider") .objectReferenceValue = progressSlider; dpSO.FindProperty("progressText") .objectReferenceValue = progressText; dpSO.FindProperty("gameSceneName") .stringValue = "Game"; dpSO.ApplyModifiedPropertiesWithoutUndo(); // ── Wire SongSelectManager refs ────────────────────── var smSO = new SerializedObject(songSelectManager); smSO.FindProperty("tabAllBtn") .objectReferenceValue = tabAllBtn.GetComponent