|
|
|
@@ -0,0 +1,956 @@
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using TMPro;
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEditor.SceneManagement;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.EventSystems;
|
|
|
|
|
using UnityEngine.UI;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Tools → VRBeatSaber → 전체 자동 설정
|
|
|
|
|
/// 씬 생성, 프리팹 생성, Build Settings 등록까지 한 번에 처리
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static class VRBeatSaberSceneBuilder
|
|
|
|
|
{
|
|
|
|
|
private static GameObject s_cardPrefab;
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// 뒤로가기 버튼 패치 (기존 씬에 추가)
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
[MenuItem("Tools/VRBeatSaber/뒤로가기 버튼 추가 (기존 씬 패치)")]
|
|
|
|
|
public static void PatchBackButtons()
|
|
|
|
|
{
|
|
|
|
|
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return;
|
|
|
|
|
|
|
|
|
|
// SongSelect: 뒤로(Intro) + DetailPanel 닫기(X)
|
|
|
|
|
PatchSceneWithBackButton(
|
|
|
|
|
"Assets/Scenes/SongSelect.unity",
|
|
|
|
|
() =>
|
|
|
|
|
{
|
|
|
|
|
FindAndBindBackButton<SongSelectManager>("backButton", "< 뒤로", new Vector2(-500, 310), new Vector2(130, 50));
|
|
|
|
|
AddDetailPanelCloseButton();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// SongCreator: 뒤로(Intro)
|
|
|
|
|
PatchSceneWithBackButton(
|
|
|
|
|
"Assets/Scenes/SongCreator.unity",
|
|
|
|
|
() => FindAndBindBackButton<SongCreatorManager>("backButton", "< 뒤로", new Vector2(-405, 345), new Vector2(130, 50)));
|
|
|
|
|
|
|
|
|
|
// MapEditorScene: 뒤로(SongCreator)
|
|
|
|
|
PatchSceneWithBackButton(
|
|
|
|
|
"Assets/Scenes/MapEditorScene.unity",
|
|
|
|
|
FindAndBindBackButtonOnMapEditor);
|
|
|
|
|
|
|
|
|
|
// Game: 뒤로(SongSelect) — 우측 상단 소형 버튼
|
|
|
|
|
PatchSceneWithBackButton(
|
|
|
|
|
"Assets/Scenes/Game.unity",
|
|
|
|
|
AddGameSceneBackButton);
|
|
|
|
|
|
|
|
|
|
AssetDatabase.Refresh();
|
|
|
|
|
EditorUtility.DisplayDialog("완료", "뒤로가기 버튼 추가 완료!\n\n추가된 버튼:\n- SongSelect: < 뒤로 + 상세패널 X 닫기\n- SongCreator: < 뒤로\n- MapEditorScene: < 뒤로\n- Game: < 뒤로", "확인");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void PatchSceneWithBackButton(string scenePath, System.Action patchAction)
|
|
|
|
|
{
|
|
|
|
|
if (!File.Exists(scenePath)) { Debug.LogWarning($"[SceneBuilder] 씬 없음: {scenePath}"); return; }
|
|
|
|
|
var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
|
|
|
|
|
patchAction();
|
|
|
|
|
EditorSceneManager.SaveScene(scene);
|
|
|
|
|
Debug.Log($"[SceneBuilder] 패치 완료: {scenePath}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void FindAndBindBackButton<T>(string fieldName, string label, Vector2 pos, Vector2 size) where T : MonoBehaviour
|
|
|
|
|
{
|
|
|
|
|
var mgr = Object.FindObjectOfType<T>();
|
|
|
|
|
if (mgr == null) { Debug.LogWarning($"[SceneBuilder] {typeof(T).Name} 없음"); return; }
|
|
|
|
|
|
|
|
|
|
// 이미 있으면 건너뜀
|
|
|
|
|
var so = new SerializedObject(mgr);
|
|
|
|
|
if (so.FindProperty(fieldName)?.objectReferenceValue != null) return;
|
|
|
|
|
|
|
|
|
|
// Canvas 찾기
|
|
|
|
|
var canvas = Object.FindObjectOfType<Canvas>();
|
|
|
|
|
if (canvas == null) return;
|
|
|
|
|
|
|
|
|
|
var backGO = MakeButton("BackButton", canvas.transform, label, pos, size);
|
|
|
|
|
backGO.GetComponent<UnityEngine.UI.Image>().color = new Color(0.18f, 0.18f, 0.18f);
|
|
|
|
|
|
|
|
|
|
Bind(so, fieldName, backGO.GetComponent<Button>());
|
|
|
|
|
so.ApplyModifiedProperties();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SongDetailPanel 닫기(X) 버튼
|
|
|
|
|
private static void AddDetailPanelCloseButton()
|
|
|
|
|
{
|
|
|
|
|
var detail = Object.FindObjectOfType<SongDetailPanel>();
|
|
|
|
|
if (detail == null) { Debug.LogWarning("[SceneBuilder] SongDetailPanel 없음"); return; }
|
|
|
|
|
|
|
|
|
|
var so = new SerializedObject(detail);
|
|
|
|
|
if (so.FindProperty("closeButton")?.objectReferenceValue != null) return;
|
|
|
|
|
|
|
|
|
|
// DetailPanel의 RectTransform 우측 상단에 X 버튼 배치
|
|
|
|
|
var detailRT = detail.GetComponent<RectTransform>();
|
|
|
|
|
if (detailRT == null) return;
|
|
|
|
|
|
|
|
|
|
var closeGO = MakeButton("CloseButton", detail.transform, "✕", new Vector2(detailRT.sizeDelta.x / 2f - 30, detailRT.sizeDelta.y / 2f - 30), new Vector2(50, 50));
|
|
|
|
|
closeGO.GetComponent<Image>().color = new Color(0.6f, 0.15f, 0.15f);
|
|
|
|
|
|
|
|
|
|
Bind(so, "closeButton", closeGO.GetComponent<Button>());
|
|
|
|
|
so.ApplyModifiedProperties();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Game씬 뒤로가기 버튼 (Canvas 없으면 생성)
|
|
|
|
|
private static void AddGameSceneBackButton()
|
|
|
|
|
{
|
|
|
|
|
var spawner = Object.FindObjectOfType<Spawner>();
|
|
|
|
|
if (spawner == null) { Debug.LogWarning("[SceneBuilder] Spawner 없음"); return; }
|
|
|
|
|
|
|
|
|
|
// Game씬에 Canvas가 있는지 확인, 없으면 생성
|
|
|
|
|
var canvas = Object.FindObjectOfType<Canvas>();
|
|
|
|
|
if (canvas == null)
|
|
|
|
|
{
|
|
|
|
|
var canvasGO = new GameObject("HUDCanvas");
|
|
|
|
|
var c = canvasGO.AddComponent<Canvas>();
|
|
|
|
|
c.renderMode = RenderMode.WorldSpace;
|
|
|
|
|
canvasGO.AddComponent<CanvasScaler>();
|
|
|
|
|
|
|
|
|
|
var trackedType = System.Type.GetType(
|
|
|
|
|
"UnityEngine.XR.Interaction.Toolkit.UI.TrackedDeviceGraphicRaycaster, Unity.XR.Interaction.Toolkit");
|
|
|
|
|
if (trackedType != null)
|
|
|
|
|
canvasGO.AddComponent(trackedType);
|
|
|
|
|
else
|
|
|
|
|
canvasGO.AddComponent<GraphicRaycaster>();
|
|
|
|
|
|
|
|
|
|
var rt = canvasGO.GetComponent<RectTransform>();
|
|
|
|
|
rt.sizeDelta = new Vector2(400, 80);
|
|
|
|
|
// 플레이어 앞 위쪽, 항상 보이는 위치
|
|
|
|
|
canvasGO.transform.position = new Vector3(0f, 2.2f, 1.5f);
|
|
|
|
|
canvasGO.transform.localScale = Vector3.one * 0.002f;
|
|
|
|
|
canvas = c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 이미 BackButton이 있으면 건너뜀
|
|
|
|
|
if (canvas.transform.Find("BackButton") != null) return;
|
|
|
|
|
|
|
|
|
|
var backGO = MakeButton("BackButton", canvas.transform, "< 뒤로", new Vector2(-130, 0), new Vector2(150, 60));
|
|
|
|
|
backGO.GetComponent<Image>().color = new Color(0.2f, 0.2f, 0.2f, 0.85f);
|
|
|
|
|
|
|
|
|
|
// Spawner에 backButton 필드가 없으므로 런타임 클릭 이벤트만 추가
|
|
|
|
|
// (Spawner는 Quest B버튼용 InputActionReference 사용, 버튼은 클릭 처리 필요)
|
|
|
|
|
// → GameBackButton 컴포넌트로 처리
|
|
|
|
|
var handler = backGO.AddComponent<GameBackButton>();
|
|
|
|
|
var sso = new SerializedObject(handler);
|
|
|
|
|
var targetProp = sso.FindProperty("targetScene");
|
|
|
|
|
if (targetProp != null) targetProp.stringValue = "SongSelect";
|
|
|
|
|
sso.ApplyModifiedProperties();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void FindAndBindBackButtonOnMapEditor()
|
|
|
|
|
{
|
|
|
|
|
var mgr = Object.FindObjectOfType<MapEditor>();
|
|
|
|
|
if (mgr == null) { Debug.LogWarning("[SceneBuilder] MapEditor 없음"); return; }
|
|
|
|
|
|
|
|
|
|
var so = new SerializedObject(mgr);
|
|
|
|
|
if (so.FindProperty("backButton")?.objectReferenceValue != null) return;
|
|
|
|
|
|
|
|
|
|
var canvas = Object.FindObjectOfType<Canvas>();
|
|
|
|
|
if (canvas == null)
|
|
|
|
|
{
|
|
|
|
|
// MapEditorScene에 Canvas가 없을 수 있으므로 직접 생성
|
|
|
|
|
var canvasGO = new GameObject("Canvas");
|
|
|
|
|
var c = canvasGO.AddComponent<Canvas>();
|
|
|
|
|
c.renderMode = RenderMode.WorldSpace;
|
|
|
|
|
canvasGO.AddComponent<CanvasScaler>();
|
|
|
|
|
canvasGO.AddComponent<GraphicRaycaster>();
|
|
|
|
|
var rt = canvasGO.GetComponent<RectTransform>();
|
|
|
|
|
rt.sizeDelta = new Vector2(200, 60);
|
|
|
|
|
canvasGO.transform.position = new Vector3(-0.8f, 1.8f, 2f);
|
|
|
|
|
canvasGO.transform.localScale = Vector3.one * 0.002f;
|
|
|
|
|
canvas = canvasGO.GetComponent<Canvas>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var backGO = MakeButton("BackButton", canvas.transform, "< 뒤로", new Vector2(0, 0), new Vector2(180, 55));
|
|
|
|
|
Bind(so, "backButton", backGO.GetComponent<Button>());
|
|
|
|
|
so.ApplyModifiedProperties();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[MenuItem("Tools/VRBeatSaber/씬 재빌드 (SongCreator + SongSelect)")]
|
|
|
|
|
public static void RebuildCreatorAndSelect()
|
|
|
|
|
{
|
|
|
|
|
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return;
|
|
|
|
|
|
|
|
|
|
EnsureFolder("Assets/Scenes");
|
|
|
|
|
EnsureFolder("Assets/Prefab");
|
|
|
|
|
|
|
|
|
|
s_cardPrefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefab/SongCard.prefab");
|
|
|
|
|
if (s_cardPrefab == null) s_cardPrefab = CreateSongCardPrefab();
|
|
|
|
|
|
|
|
|
|
BuildSongSelectScene();
|
|
|
|
|
BuildSongCreatorScene();
|
|
|
|
|
|
|
|
|
|
AssetDatabase.Refresh();
|
|
|
|
|
EditorUtility.DisplayDialog("완료",
|
|
|
|
|
"SongSelect + SongCreator 씬 재빌드 완료!\n\n" +
|
|
|
|
|
"Inspector에서 NasPublisher 비밀번호를 확인하세요.",
|
|
|
|
|
"확인");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[MenuItem("Tools/VRBeatSaber/전체 자동 설정 (한 번만 실행)")]
|
|
|
|
|
public static void SetupAll()
|
|
|
|
|
{
|
|
|
|
|
if (!EditorUtility.DisplayDialog(
|
|
|
|
|
"VRBeatSaber 자동 설정",
|
|
|
|
|
"Intro / SongSelect / SongCreator 씬과\nSongCard 프리팹, Build Settings를 자동으로 설정합니다.\n\n현재 씬이 저장되지 않으면 경고가 뜹니다.",
|
|
|
|
|
"실행", "취소")) return;
|
|
|
|
|
|
|
|
|
|
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return;
|
|
|
|
|
|
|
|
|
|
EnsureFolder("Assets/Scenes");
|
|
|
|
|
EnsureFolder("Assets/Prefab");
|
|
|
|
|
|
|
|
|
|
// 1. SongCard 프리팹
|
|
|
|
|
s_cardPrefab = CreateSongCardPrefab();
|
|
|
|
|
|
|
|
|
|
// 2. SongSelect 씬 생성
|
|
|
|
|
BuildSongSelectScene();
|
|
|
|
|
|
|
|
|
|
// 3. SongCreator 씬 생성
|
|
|
|
|
BuildSongCreatorScene();
|
|
|
|
|
|
|
|
|
|
// 4. Intro 씬에 UI 추가
|
|
|
|
|
ModifyIntroScene();
|
|
|
|
|
|
|
|
|
|
// 5. Game 씬 AudioClip 제거
|
|
|
|
|
FixGameScene();
|
|
|
|
|
|
|
|
|
|
// 6. Build Settings 등록
|
|
|
|
|
RegisterBuildSettings();
|
|
|
|
|
|
|
|
|
|
AssetDatabase.Refresh();
|
|
|
|
|
|
|
|
|
|
EditorUtility.DisplayDialog(
|
|
|
|
|
"완료",
|
|
|
|
|
"자동 설정 완료!\n\n" +
|
|
|
|
|
"[ 직접 해야 할 것 (2가지) ]\n\n" +
|
|
|
|
|
"1. SongCreator 씬 → [CreatorManager]\n" +
|
|
|
|
|
" → NasPublisher → Nas Password\n" +
|
|
|
|
|
" (DSM 비밀번호 입력)\n\n" +
|
|
|
|
|
"2. Quest에 MP3 파일 넣기\n" +
|
|
|
|
|
" adb push 파일.mp3\n" +
|
|
|
|
|
" /sdcard/Android/data/{패키지명}/files/input/",
|
|
|
|
|
"확인");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// 1. SongCard 프리팹
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
private static GameObject CreateSongCardPrefab()
|
|
|
|
|
{
|
|
|
|
|
var root = new GameObject("SongCard");
|
|
|
|
|
root.AddComponent<Image>().color = new Color(0.12f, 0.12f, 0.12f);
|
|
|
|
|
var btn = root.AddComponent<Button>();
|
|
|
|
|
var rt = root.GetComponent<RectTransform>();
|
|
|
|
|
rt.sizeDelta = new Vector2(430, 90);
|
|
|
|
|
|
|
|
|
|
// 다운로드 뱃지 (우측 녹색 원)
|
|
|
|
|
var badge = new GameObject("DownloadedBadge");
|
|
|
|
|
badge.transform.SetParent(root.transform, false);
|
|
|
|
|
badge.AddComponent<Image>().color = new Color(0.2f, 0.9f, 0.4f);
|
|
|
|
|
var bRt = badge.GetComponent<RectTransform>();
|
|
|
|
|
bRt.anchorMin = new Vector2(1, 0.5f);
|
|
|
|
|
bRt.anchorMax = new Vector2(1, 0.5f);
|
|
|
|
|
bRt.pivot = new Vector2(1, 0.5f);
|
|
|
|
|
bRt.anchoredPosition = new Vector2(-12, 0);
|
|
|
|
|
bRt.sizeDelta = new Vector2(20, 20);
|
|
|
|
|
|
|
|
|
|
// 제목
|
|
|
|
|
var titleTMP = MakeTMP("TitleText", root.transform, "곡 제목",
|
|
|
|
|
new Vector2(-10, 16), new Vector2(370, 30), 20);
|
|
|
|
|
|
|
|
|
|
// 아티스트
|
|
|
|
|
var artistTMP = MakeTMP("ArtistText", root.transform, "아티스트",
|
|
|
|
|
new Vector2(-30, -16), new Vector2(260, 24), 15);
|
|
|
|
|
artistTMP.color = new Color(0.7f, 0.7f, 0.7f);
|
|
|
|
|
|
|
|
|
|
// 길이
|
|
|
|
|
var durTMP = MakeTMP("DurationText", root.transform, "0:00",
|
|
|
|
|
new Vector2(-45, -16), new Vector2(120, 24), 15);
|
|
|
|
|
durTMP.color = new Color(0.6f, 0.6f, 0.6f);
|
|
|
|
|
durTMP.alignment = TextAlignmentOptions.MidlineRight;
|
|
|
|
|
|
|
|
|
|
// SongCard 컴포넌트 바인딩
|
|
|
|
|
var card = root.AddComponent<SongCard>();
|
|
|
|
|
var so = new SerializedObject(card);
|
|
|
|
|
Bind(so, "titleText", titleTMP);
|
|
|
|
|
Bind(so, "artistText", artistTMP);
|
|
|
|
|
Bind(so, "durationText", durTMP);
|
|
|
|
|
Bind(so, "downloadedBadge", badge);
|
|
|
|
|
Bind(so, "button", btn);
|
|
|
|
|
so.ApplyModifiedProperties();
|
|
|
|
|
|
|
|
|
|
var prefab = PrefabUtility.SaveAsPrefabAsset(root, "Assets/Prefab/SongCard.prefab");
|
|
|
|
|
Object.DestroyImmediate(root);
|
|
|
|
|
Debug.Log("[SceneBuilder] SongCard 프리팹 생성 완료");
|
|
|
|
|
return prefab;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// 2. SongSelect 씬
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
private static void BuildSongSelectScene()
|
|
|
|
|
{
|
|
|
|
|
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
|
|
|
|
|
MakeCamera(new Vector3(0, 1.6f, 0));
|
|
|
|
|
MakeEventSystem();
|
|
|
|
|
|
|
|
|
|
// SongLibrary는 DontDestroyOnLoad를 사용하므로 단독 오브젝트에 배치
|
|
|
|
|
// (GameManager와 같이 두면 DownloadManager까지 DontDestroyOnLoad로 이동해 버림)
|
|
|
|
|
var slGO = new GameObject("[SongLibrary]");
|
|
|
|
|
slGO.AddComponent<SongLibrary>();
|
|
|
|
|
|
|
|
|
|
// GameManager: 씬마다 새로 생성되는 매니저들
|
|
|
|
|
var gm = new GameObject("[GameManager]");
|
|
|
|
|
gm.AddComponent<CacheManager>();
|
|
|
|
|
var dlMgr = gm.AddComponent<DownloadManager>();
|
|
|
|
|
|
|
|
|
|
var canvas = MakeCanvas(new Vector2(1200, 700), new Vector3(0, 1.5f, 2f));
|
|
|
|
|
|
|
|
|
|
// 탭
|
|
|
|
|
var tabPanel = MakePanel("TabPanel", canvas.transform, new Vector2(0, 310), new Vector2(1180, 60));
|
|
|
|
|
var backBtnSS = MakeButton("BackButton", tabPanel.transform, "< 뒤로", new Vector2(-500, 0), new Vector2(130, 50));
|
|
|
|
|
var tabAllBtn = MakeButton("TabAllBtn", tabPanel.transform, "전체", new Vector2(-290, 0), new Vector2(200, 50));
|
|
|
|
|
var tabOwnedBtn = MakeButton("TabOwnedBtn", tabPanel.transform, "보유중", new Vector2(-80, 0), new Vector2(200, 50));
|
|
|
|
|
|
|
|
|
|
// 목록 (좌측)
|
|
|
|
|
var listPanel = MakePanel("ListPanel", canvas.transform, new Vector2(-355, -30), new Vector2(460, 600));
|
|
|
|
|
var (_, cardContainer) = MakeScrollRect("SongScroll", listPanel.transform, Vector2.zero, new Vector2(450, 590));
|
|
|
|
|
|
|
|
|
|
// 상세 패널 (우측)
|
|
|
|
|
var detailGO = MakePanel("DetailPanel", canvas.transform, new Vector2(250, -30), new Vector2(680, 600));
|
|
|
|
|
var detail = detailGO.AddComponent<SongDetailPanel>();
|
|
|
|
|
|
|
|
|
|
var titleTxt = MakeTMP("TitleText", detailGO.transform, "곡 제목", new Vector2(0, 250), new Vector2(640, 50), 28);
|
|
|
|
|
var artistTxt = MakeTMP("ArtistText", detailGO.transform, "아티스트", new Vector2(0, 200), new Vector2(640, 36), 20);
|
|
|
|
|
var infoTxt = MakeTMP("InfoText", detailGO.transform, "BPM | 길이", new Vector2(0, 160), new Vector2(640, 30), 18);
|
|
|
|
|
|
|
|
|
|
var diffPanel = MakePanel("DiffPanel", detailGO.transform, new Vector2(0, 90), new Vector2(640, 60));
|
|
|
|
|
var btnNorm = MakeButton("NormalBtn", diffPanel.transform, "Normal", new Vector2(-240, 0), new Vector2(140, 50));
|
|
|
|
|
var btnHard = MakeButton("HardBtn", diffPanel.transform, "Hard", new Vector2(-80, 0), new Vector2(140, 50));
|
|
|
|
|
var btnExp = MakeButton("ExpertBtn", diffPanel.transform, "Expert", new Vector2(80, 0), new Vector2(140, 50));
|
|
|
|
|
var btnExpP = MakeButton("ExpertPlusBtn", diffPanel.transform, "Expert+", new Vector2(240, 0), new Vector2(140, 50));
|
|
|
|
|
|
|
|
|
|
var actPanel = MakePanel("ActionPanel", detailGO.transform, new Vector2(0, 15), new Vector2(640, 60));
|
|
|
|
|
var dlBtn = MakeButton("DownloadBtn", actPanel.transform, "다운로드", new Vector2(-165, 0), new Vector2(290, 50));
|
|
|
|
|
var delBtn = MakeButton("DeleteBtn", actPanel.transform, "삭제", new Vector2(165, 0), new Vector2(290, 50));
|
|
|
|
|
|
|
|
|
|
var progGroup = MakePanel("ProgressGroup", detailGO.transform, new Vector2(0, -50), new Vector2(640, 40));
|
|
|
|
|
var progSlider = MakeSlider("ProgressSlider", progGroup.transform, new Vector2(-70, 0), new Vector2(480, 28));
|
|
|
|
|
var progText = MakeTMP("ProgressText", progGroup.transform, "0%", new Vector2(275, 0), new Vector2(100, 30), 16);
|
|
|
|
|
|
|
|
|
|
var playBtn = MakeButton("PlayButton", detailGO.transform, "플레이", new Vector2(0, -140), new Vector2(300, 65));
|
|
|
|
|
var closeBtn = MakeButton("CloseButton", detailGO.transform, "X", new Vector2(290, 270), new Vector2(50, 50));
|
|
|
|
|
closeBtn.GetComponent<Image>().color = new Color(0.6f, 0.15f, 0.15f);
|
|
|
|
|
|
|
|
|
|
var loadingOvr = MakePanel("LoadingOverlay", canvas.transform, Vector2.zero, new Vector2(1200, 700));
|
|
|
|
|
loadingOvr.GetComponent<Image>().color = new Color(0, 0, 0, 0.75f);
|
|
|
|
|
MakeTMP("LoadingText", loadingOvr.transform, "불러오는 중...", Vector2.zero, new Vector2(400, 60), 26);
|
|
|
|
|
|
|
|
|
|
var errorOvr = MakePanel("ErrorOverlay", canvas.transform, Vector2.zero, new Vector2(1200, 700));
|
|
|
|
|
errorOvr.GetComponent<Image>().color = new Color(0, 0, 0, 0.75f);
|
|
|
|
|
var errorTxt = MakeTMP("ErrorText", errorOvr.transform, "", Vector2.zero, new Vector2(700, 120), 22);
|
|
|
|
|
|
|
|
|
|
loadingOvr.SetActive(false);
|
|
|
|
|
errorOvr.SetActive(false);
|
|
|
|
|
detailGO.SetActive(false);
|
|
|
|
|
|
|
|
|
|
// SongDetailPanel 바인딩
|
|
|
|
|
var dso = new SerializedObject(detail);
|
|
|
|
|
Bind(dso, "titleText", titleTxt);
|
|
|
|
|
Bind(dso, "artistText", artistTxt);
|
|
|
|
|
Bind(dso, "infoText", infoTxt);
|
|
|
|
|
Bind(dso, "btnNormal", btnNorm.GetComponent<Button>());
|
|
|
|
|
Bind(dso, "btnHard", btnHard.GetComponent<Button>());
|
|
|
|
|
Bind(dso, "btnExpert", btnExp.GetComponent<Button>());
|
|
|
|
|
Bind(dso, "btnExpertPlus", btnExpP.GetComponent<Button>());
|
|
|
|
|
Bind(dso, "downloadButton", dlBtn.GetComponent<Button>());
|
|
|
|
|
Bind(dso, "deleteButton", delBtn.GetComponent<Button>());
|
|
|
|
|
Bind(dso, "playButton", playBtn.GetComponent<Button>());
|
|
|
|
|
Bind(dso, "closeButton", closeBtn.GetComponent<Button>());
|
|
|
|
|
Bind(dso, "progressGroup", progGroup);
|
|
|
|
|
Bind(dso, "progressSlider", progSlider);
|
|
|
|
|
Bind(dso, "progressText", progText);
|
|
|
|
|
dso.ApplyModifiedProperties();
|
|
|
|
|
|
|
|
|
|
// SongSelectManager 바인딩
|
|
|
|
|
var mgr = canvas.AddComponent<SongSelectManager>();
|
|
|
|
|
var mso = new SerializedObject(mgr);
|
|
|
|
|
Bind(mso, "backButton", backBtnSS.GetComponent<Button>());
|
|
|
|
|
Bind(mso, "tabAllBtn", tabAllBtn.GetComponent<Button>());
|
|
|
|
|
Bind(mso, "tabOwnedBtn", tabOwnedBtn.GetComponent<Button>());
|
|
|
|
|
Bind(mso, "cardContainer", cardContainer);
|
|
|
|
|
Bind(mso, "songCardPrefab", s_cardPrefab);
|
|
|
|
|
Bind(mso, "detailPanel", detail);
|
|
|
|
|
Bind(mso, "downloadManager", dlMgr);
|
|
|
|
|
Bind(mso, "loadingOverlay", loadingOvr);
|
|
|
|
|
Bind(mso, "errorOverlay", errorOvr);
|
|
|
|
|
Bind(mso, "errorText", errorTxt);
|
|
|
|
|
mso.ApplyModifiedProperties();
|
|
|
|
|
|
|
|
|
|
EditorSceneManager.SaveScene(scene, "Assets/Scenes/SongSelect.unity");
|
|
|
|
|
Debug.Log("[SceneBuilder] SongSelect 씬 완료");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// 3. SongCreator 씬
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
private static void BuildSongCreatorScene()
|
|
|
|
|
{
|
|
|
|
|
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
|
|
|
|
|
MakeCamera(new Vector3(0, 1.6f, 0));
|
|
|
|
|
MakeEventSystem();
|
|
|
|
|
|
|
|
|
|
// SongLibrary 싱글턴 — SongCreator에서 직접 쓰진 않지만 DontDestroyOnLoad 체계 유지
|
|
|
|
|
new GameObject("[SongLibrary]").AddComponent<SongLibrary>();
|
|
|
|
|
|
|
|
|
|
var gm = new GameObject("[CreatorManager]");
|
|
|
|
|
var uploader = gm.AddComponent<BeatSageUploader>();
|
|
|
|
|
var publisher = gm.AddComponent<NasPublisher>();
|
|
|
|
|
var scMgr = gm.AddComponent<SongCreatorManager>();
|
|
|
|
|
|
|
|
|
|
var canvas = MakeCanvas(new Vector2(900, 800), new Vector3(0, 1.5f, 2f));
|
|
|
|
|
|
|
|
|
|
MakeTMP("TitleLabel", canvas.transform, "노래 만들기", new Vector2(0, 365), new Vector2(860, 55), 32);
|
|
|
|
|
|
|
|
|
|
// 음원 선택 (드롭다운 + 새로고침)
|
|
|
|
|
var audioPanel = MakePanel("AudioPanel", canvas.transform, new Vector2(0, 280), new Vector2(860, 80));
|
|
|
|
|
var audioDd = MakeDropdown("AudioDropdown", audioPanel.transform, new Vector2(-98, 0), new Vector2(540, 55));
|
|
|
|
|
var refreshBtn = MakeButton("RefreshBtn", audioPanel.transform, "새로고침", new Vector2(295, 0), new Vector2(160, 52));
|
|
|
|
|
var pathHint = MakeTMP("PathHint", canvas.transform, "", new Vector2(0, 230), new Vector2(860, 26), 12);
|
|
|
|
|
pathHint.color = new Color(0.6f, 0.6f, 0.6f);
|
|
|
|
|
|
|
|
|
|
// 음원 추가 (파일 선택 / URL 다운로드)
|
|
|
|
|
var addPanel = MakePanel("AddPanel", canvas.transform, new Vector2(0, 155), new Vector2(860, 100));
|
|
|
|
|
var addStatusTxt = MakeTMP("AddStatusText", addPanel.transform, "", new Vector2(90, 25), new Vector2(530, 36), 13);
|
|
|
|
|
addStatusTxt.color = new Color(0.7f, 0.9f, 0.7f);
|
|
|
|
|
var urlField = MakeInputField("UrlInput", addPanel.transform, "MP3 직접 URL 입력", new Vector2(-100, 18), new Vector2(600, 40));
|
|
|
|
|
var urlDlBtn = MakeButton("UrlDownloadBtn", addPanel.transform, "URL 다운로드", new Vector2(305, 20), new Vector2(195, 40));
|
|
|
|
|
var filePickBtn = MakeButton("FilePickerBtn", addPanel.transform, "파일 선택", new Vector2(304, -29), new Vector2(195, 40));
|
|
|
|
|
|
|
|
|
|
// 메타데이터
|
|
|
|
|
var metaPanel = MakePanel("MetaPanel", canvas.transform, new Vector2(0, 55), new Vector2(860, 80));
|
|
|
|
|
var titleInput = MakeInputField("TitleInput", metaPanel.transform, "곡 제목", new Vector2(-280, 0), new Vector2(255, 50));
|
|
|
|
|
var artistInput = MakeInputField("ArtistInput", metaPanel.transform, "아티스트", new Vector2(0, 0), new Vector2(255, 50));
|
|
|
|
|
var bpmInput = MakeInputField("BpmInput", metaPanel.transform, "BPM", new Vector2(280, 0), new Vector2(255, 50));
|
|
|
|
|
|
|
|
|
|
// 난이도 토글 (Easy 제거, ExpertPlus 추가)
|
|
|
|
|
var diffPanel = MakePanel("DiffPanel", canvas.transform, new Vector2(0, -45), new Vector2(860, 60));
|
|
|
|
|
var togNormal = MakeToggle("NormalToggle", diffPanel.transform, "Normal", new Vector2(-206, 0));
|
|
|
|
|
var togHard = MakeToggle("HardToggle", diffPanel.transform, "Hard", new Vector2(-51, 0));
|
|
|
|
|
var togExpert = MakeToggle("ExpertToggle", diffPanel.transform, "Expert", new Vector2(104, 0));
|
|
|
|
|
var togExpertPlus = MakeToggle("ExpertPlusToggle", diffPanel.transform, "Expert+", new Vector2(259, 0));
|
|
|
|
|
togNormal.isOn = true;
|
|
|
|
|
togHard.isOn = true;
|
|
|
|
|
togExpert.isOn = true;
|
|
|
|
|
togExpertPlus.isOn = true;
|
|
|
|
|
|
|
|
|
|
var backBtnSC = MakeButton("BackButton", canvas.transform, "< 뒤로", new Vector2(346, 365), new Vector2(130, 50));
|
|
|
|
|
|
|
|
|
|
var actPanel = MakePanel("ActionPanel", canvas.transform, new Vector2(0, -145), new Vector2(860, 75));
|
|
|
|
|
var genBtn = MakeButton("GenerateButton", actPanel.transform, "AI 생성 시작", new Vector2(-175, 0), new Vector2(440, 60));
|
|
|
|
|
var manualBtn = MakeButton("ManualButton", actPanel.transform, "직접 만들기 >", new Vector2(245, 0), new Vector2(185, 40));
|
|
|
|
|
|
|
|
|
|
var progGroup = MakePanel("ProgressGroup", canvas.transform, new Vector2(0, -265), new Vector2(860, 90));
|
|
|
|
|
var statusTxt = MakeTMP("StatusText", progGroup.transform, "", new Vector2(0, 25), new Vector2(840, 34), 17);
|
|
|
|
|
var progSlider = MakeSlider("ProgressSlider", progGroup.transform, new Vector2(0, -15), new Vector2(820, 26));
|
|
|
|
|
progGroup.SetActive(false);
|
|
|
|
|
|
|
|
|
|
var sso = new SerializedObject(scMgr);
|
|
|
|
|
Bind(sso, "audioDropdown", audioDd);
|
|
|
|
|
Bind(sso, "refreshBtn", refreshBtn.GetComponent<Button>());
|
|
|
|
|
Bind(sso, "inputPathHint", pathHint);
|
|
|
|
|
Bind(sso, "filePickerBtn", filePickBtn.GetComponent<Button>());
|
|
|
|
|
Bind(sso, "addStatusText", addStatusTxt);
|
|
|
|
|
Bind(sso, "urlInput", urlField);
|
|
|
|
|
Bind(sso, "urlDownloadBtn", urlDlBtn.GetComponent<Button>());
|
|
|
|
|
Bind(sso, "titleInput", titleInput);
|
|
|
|
|
Bind(sso, "artistInput", artistInput);
|
|
|
|
|
Bind(sso, "bpmInput", bpmInput);
|
|
|
|
|
Bind(sso, "toggleNormal", togNormal);
|
|
|
|
|
Bind(sso, "toggleHard", togHard);
|
|
|
|
|
Bind(sso, "toggleExpert", togExpert);
|
|
|
|
|
Bind(sso, "toggleExpertPlus", togExpertPlus);
|
|
|
|
|
Bind(sso, "backButton", backBtnSC.GetComponent<Button>());
|
|
|
|
|
Bind(sso, "generateButton", genBtn.GetComponent<Button>());
|
|
|
|
|
Bind(sso, "manualEditorButton", manualBtn.GetComponent<Button>());
|
|
|
|
|
Bind(sso, "progressGroup", progGroup);
|
|
|
|
|
Bind(sso, "statusText", statusTxt);
|
|
|
|
|
Bind(sso, "progressSlider", progSlider);
|
|
|
|
|
Bind(sso, "beatSageUploader", uploader);
|
|
|
|
|
Bind(sso, "nasPublisher", publisher);
|
|
|
|
|
sso.ApplyModifiedProperties();
|
|
|
|
|
|
|
|
|
|
EditorSceneManager.SaveScene(scene, "Assets/Scenes/SongCreator.unity");
|
|
|
|
|
Debug.Log("[SceneBuilder] SongCreator 씬 완료");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// 4. Intro 씬 수정
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
private static void ModifyIntroScene()
|
|
|
|
|
{
|
|
|
|
|
const string path = "Assets/Scenes/Intro.unity";
|
|
|
|
|
if (!File.Exists(path)) { Debug.LogWarning("[SceneBuilder] Intro.unity 없음 — 건너뜀"); return; }
|
|
|
|
|
|
|
|
|
|
var scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Single);
|
|
|
|
|
|
|
|
|
|
// 이미 IntroManager가 있으면 건너뜀
|
|
|
|
|
if (Object.FindObjectOfType<IntroManager>() != null)
|
|
|
|
|
{
|
|
|
|
|
Debug.Log("[SceneBuilder] Intro 씬 IntroManager 이미 존재 — 건너뜀");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var canvas = MakeCanvas(new Vector2(600, 420), new Vector3(0, 1.5f, 2f));
|
|
|
|
|
MakeTMP("TitleText", canvas.transform, "VR BEAT SABER", new Vector2(0, 145), new Vector2(560, 70), 38);
|
|
|
|
|
var playBtn = MakeButton("PlayButton", canvas.transform, "게임하기", new Vector2(0, 30), new Vector2(420, 85));
|
|
|
|
|
var createBtn = MakeButton("CreateButton", canvas.transform, "노래만들기", new Vector2(0, -80), new Vector2(420, 85));
|
|
|
|
|
|
|
|
|
|
var introGO = new GameObject("[IntroManager]");
|
|
|
|
|
var mgr = introGO.AddComponent<IntroManager>();
|
|
|
|
|
var iso = new SerializedObject(mgr);
|
|
|
|
|
Bind(iso, "playButton", playBtn.GetComponent<Button>());
|
|
|
|
|
Bind(iso, "createButton", createBtn.GetComponent<Button>());
|
|
|
|
|
iso.ApplyModifiedProperties();
|
|
|
|
|
|
|
|
|
|
EditorSceneManager.SaveScene(scene);
|
|
|
|
|
Debug.Log("[SceneBuilder] Intro 씬 수정 완료");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// 5. Game 씬 — AudioClip 참조 제거
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
private static void FixGameScene()
|
|
|
|
|
{
|
|
|
|
|
const string path = "Assets/Scenes/Game.unity";
|
|
|
|
|
if (!File.Exists(path)) { Debug.LogWarning("[SceneBuilder] Game.unity 없음 — 건너뜀"); return; }
|
|
|
|
|
|
|
|
|
|
var scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Single);
|
|
|
|
|
var spawner = Object.FindObjectOfType<Spawner>();
|
|
|
|
|
|
|
|
|
|
if (spawner == null) { Debug.LogWarning("[SceneBuilder] Spawner 없음"); return; }
|
|
|
|
|
|
|
|
|
|
// Spawner가 참조하는 AudioSource의 AudioClip 제거 (런타임에 로컬 파일에서 로드)
|
|
|
|
|
var sso = new SerializedObject(spawner);
|
|
|
|
|
var asProp = sso.FindProperty("audioSource");
|
|
|
|
|
if (asProp?.objectReferenceValue is AudioSource audioSrc)
|
|
|
|
|
{
|
|
|
|
|
var asObj = new SerializedObject(audioSrc);
|
|
|
|
|
var clipProp = asObj.FindProperty("m_audioClip");
|
|
|
|
|
if (clipProp != null)
|
|
|
|
|
{
|
|
|
|
|
clipProp.objectReferenceValue = null;
|
|
|
|
|
asObj.ApplyModifiedProperties();
|
|
|
|
|
Debug.Log("[SceneBuilder] AudioClip 참조 제거 완료");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorSceneManager.SaveScene(scene);
|
|
|
|
|
Debug.Log("[SceneBuilder] Game 씬 정리 완료");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// 6. Build Settings
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
private static void RegisterBuildSettings()
|
|
|
|
|
{
|
|
|
|
|
var paths = new[]
|
|
|
|
|
{
|
|
|
|
|
"Assets/Scenes/Intro.unity",
|
|
|
|
|
"Assets/Scenes/SongSelect.unity",
|
|
|
|
|
"Assets/Scenes/SongCreator.unity",
|
|
|
|
|
"Assets/Scenes/Game.unity",
|
|
|
|
|
"Assets/Scenes/MapEditorScene.unity",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var list = new List<EditorBuildSettingsScene>();
|
|
|
|
|
foreach (var p in paths)
|
|
|
|
|
{
|
|
|
|
|
if (File.Exists(p))
|
|
|
|
|
list.Add(new EditorBuildSettingsScene(p, true));
|
|
|
|
|
else
|
|
|
|
|
Debug.LogWarning($"[SceneBuilder] 씬 파일 없음: {p}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorBuildSettings.scenes = list.ToArray();
|
|
|
|
|
Debug.Log($"[SceneBuilder] Build Settings 등록 완료: {list.Count}개");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// UI 헬퍼
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
private static void MakeCamera(Vector3 pos)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject("Main Camera");
|
|
|
|
|
go.AddComponent<Camera>();
|
|
|
|
|
go.AddComponent<AudioListener>();
|
|
|
|
|
go.tag = "MainCamera";
|
|
|
|
|
go.transform.position = pos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void MakeEventSystem()
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject("EventSystem");
|
|
|
|
|
go.AddComponent<EventSystem>();
|
|
|
|
|
// New Input System 사용 시 StandaloneInputModule 대신 InputSystemUIInputModule 필요
|
|
|
|
|
// 없으면 StandaloneInputModule로 폴백 (Player Settings에서 Both로 설정 시 동작)
|
|
|
|
|
var inputModuleType = System.Type.GetType(
|
|
|
|
|
"UnityEngine.InputSystem.UI.InputSystemUIInputModule, Unity.InputSystem");
|
|
|
|
|
if (inputModuleType != null)
|
|
|
|
|
go.AddComponent(inputModuleType);
|
|
|
|
|
else
|
|
|
|
|
go.AddComponent<StandaloneInputModule>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static GameObject MakeCanvas(Vector2 size, Vector3 worldPos)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject("Canvas");
|
|
|
|
|
var c = go.AddComponent<Canvas>();
|
|
|
|
|
c.renderMode = RenderMode.WorldSpace;
|
|
|
|
|
go.AddComponent<CanvasScaler>();
|
|
|
|
|
|
|
|
|
|
// VR(XR Interaction Toolkit)에서는 TrackedDeviceGraphicRaycaster 필요
|
|
|
|
|
var trackedRaycasterType = System.Type.GetType(
|
|
|
|
|
"UnityEngine.XR.Interaction.Toolkit.UI.TrackedDeviceGraphicRaycaster, Unity.XR.Interaction.Toolkit");
|
|
|
|
|
if (trackedRaycasterType != null)
|
|
|
|
|
go.AddComponent(trackedRaycasterType);
|
|
|
|
|
else
|
|
|
|
|
go.AddComponent<GraphicRaycaster>();
|
|
|
|
|
var rt = go.GetComponent<RectTransform>();
|
|
|
|
|
rt.sizeDelta = size;
|
|
|
|
|
go.transform.position = worldPos;
|
|
|
|
|
go.transform.localScale = Vector3.one * 0.002f;
|
|
|
|
|
return go;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static GameObject MakePanel(string name, Transform parent, Vector2 pos, Vector2 size)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject(name);
|
|
|
|
|
go.transform.SetParent(parent, false);
|
|
|
|
|
var img = go.AddComponent<Image>();
|
|
|
|
|
img.color = new Color(0.08f, 0.08f, 0.08f, 0.88f);
|
|
|
|
|
img.sprite = null;
|
|
|
|
|
var rt = go.GetComponent<RectTransform>();
|
|
|
|
|
rt.anchoredPosition = pos;
|
|
|
|
|
rt.sizeDelta = size;
|
|
|
|
|
return go;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static GameObject MakeButton(string name, Transform parent, string label, Vector2 pos, Vector2 size)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject(name);
|
|
|
|
|
go.transform.SetParent(parent, false);
|
|
|
|
|
var img = go.AddComponent<Image>();
|
|
|
|
|
img.color = new Color(0.22f, 0.22f, 0.22f);
|
|
|
|
|
img.sprite = null;
|
|
|
|
|
go.AddComponent<Button>();
|
|
|
|
|
var rt = go.GetComponent<RectTransform>();
|
|
|
|
|
rt.anchoredPosition = pos;
|
|
|
|
|
rt.sizeDelta = size;
|
|
|
|
|
|
|
|
|
|
var tgo = new GameObject("Text");
|
|
|
|
|
tgo.transform.SetParent(go.transform, false);
|
|
|
|
|
var tmp = tgo.AddComponent<TextMeshProUGUI>();
|
|
|
|
|
tmp.text = label;
|
|
|
|
|
tmp.fontSize = 18;
|
|
|
|
|
tmp.alignment = TextAlignmentOptions.Center;
|
|
|
|
|
FullStretch(tgo.GetComponent<RectTransform>());
|
|
|
|
|
return go;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TMP_Text MakeTMP(string name, Transform parent, string text, Vector2 pos, Vector2 size, int fs)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject(name);
|
|
|
|
|
go.transform.SetParent(parent, false);
|
|
|
|
|
var tmp = go.AddComponent<TextMeshProUGUI>();
|
|
|
|
|
tmp.text = text;
|
|
|
|
|
tmp.fontSize = fs;
|
|
|
|
|
tmp.alignment = TextAlignmentOptions.Center;
|
|
|
|
|
var rt = go.GetComponent<RectTransform>();
|
|
|
|
|
rt.anchoredPosition = pos;
|
|
|
|
|
rt.sizeDelta = size;
|
|
|
|
|
return tmp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TMP_InputField MakeInputField(string name, Transform parent, string ph, Vector2 pos, Vector2 size)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject(name);
|
|
|
|
|
go.transform.SetParent(parent, false);
|
|
|
|
|
var fImg = go.AddComponent<Image>();
|
|
|
|
|
fImg.color = new Color(0.15f, 0.15f, 0.15f);
|
|
|
|
|
fImg.sprite = null;
|
|
|
|
|
var field = go.AddComponent<TMP_InputField>();
|
|
|
|
|
var rt = go.GetComponent<RectTransform>();
|
|
|
|
|
rt.anchoredPosition = pos;
|
|
|
|
|
rt.sizeDelta = size;
|
|
|
|
|
|
|
|
|
|
var area = new GameObject("Text Area");
|
|
|
|
|
area.transform.SetParent(go.transform, false);
|
|
|
|
|
var aRt = area.AddComponent<RectTransform>();
|
|
|
|
|
aRt.anchorMin = Vector2.zero; aRt.anchorMax = Vector2.one;
|
|
|
|
|
aRt.sizeDelta = new Vector2(-16, -8);
|
|
|
|
|
|
|
|
|
|
var phGO = new GameObject("Placeholder");
|
|
|
|
|
phGO.transform.SetParent(area.transform, false);
|
|
|
|
|
var phTMP = phGO.AddComponent<TextMeshProUGUI>();
|
|
|
|
|
phTMP.text = ph; phTMP.color = new Color(0.5f, 0.5f, 0.5f); phTMP.fontSize = 16;
|
|
|
|
|
FullStretch(phGO.GetComponent<RectTransform>());
|
|
|
|
|
|
|
|
|
|
var inGO = new GameObject("Text");
|
|
|
|
|
inGO.transform.SetParent(area.transform, false);
|
|
|
|
|
var inTMP = inGO.AddComponent<TextMeshProUGUI>();
|
|
|
|
|
inTMP.fontSize = 16;
|
|
|
|
|
FullStretch(inGO.GetComponent<RectTransform>());
|
|
|
|
|
|
|
|
|
|
field.textViewport = aRt;
|
|
|
|
|
field.placeholder = phTMP;
|
|
|
|
|
field.textComponent = inTMP;
|
|
|
|
|
return field;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static TMP_Dropdown MakeDropdown(string name, Transform parent, Vector2 pos, Vector2 size)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject(name);
|
|
|
|
|
go.transform.SetParent(parent, false);
|
|
|
|
|
var ddImg = go.AddComponent<Image>();
|
|
|
|
|
ddImg.color = new Color(0.15f, 0.15f, 0.15f);
|
|
|
|
|
ddImg.sprite = null;
|
|
|
|
|
var dd = go.AddComponent<TMP_Dropdown>();
|
|
|
|
|
var rt = go.GetComponent<RectTransform>();
|
|
|
|
|
rt.anchoredPosition = pos;
|
|
|
|
|
rt.sizeDelta = size;
|
|
|
|
|
|
|
|
|
|
// Caption label (선택된 항목 표시)
|
|
|
|
|
var labelGO = new GameObject("Label");
|
|
|
|
|
labelGO.transform.SetParent(go.transform, false);
|
|
|
|
|
var labelTMP = labelGO.AddComponent<TextMeshProUGUI>();
|
|
|
|
|
labelTMP.text = "";
|
|
|
|
|
labelTMP.fontSize = 16;
|
|
|
|
|
var labelRt = labelGO.GetComponent<RectTransform>();
|
|
|
|
|
labelRt.anchorMin = Vector2.zero; labelRt.anchorMax = Vector2.one;
|
|
|
|
|
labelRt.offsetMin = new Vector2(10, 2); labelRt.offsetMax = new Vector2(-28, -2);
|
|
|
|
|
|
|
|
|
|
// Arrow indicator
|
|
|
|
|
var arrowGO = new GameObject("Arrow");
|
|
|
|
|
arrowGO.transform.SetParent(go.transform, false);
|
|
|
|
|
arrowGO.AddComponent<Image>().color = Color.white;
|
|
|
|
|
var arrowRt = arrowGO.GetComponent<RectTransform>();
|
|
|
|
|
arrowRt.anchorMin = new Vector2(1, 0.5f);
|
|
|
|
|
arrowRt.anchorMax = new Vector2(1, 0.5f);
|
|
|
|
|
arrowRt.pivot = new Vector2(1, 0.5f);
|
|
|
|
|
arrowRt.anchoredPosition = new Vector2(-6, 0);
|
|
|
|
|
arrowRt.sizeDelta = new Vector2(20, 20);
|
|
|
|
|
|
|
|
|
|
// Template (드롭다운 목록, 비활성 상태로 시작)
|
|
|
|
|
var tplGO = new GameObject("Template");
|
|
|
|
|
tplGO.transform.SetParent(go.transform, false);
|
|
|
|
|
tplGO.AddComponent<Image>().color = new Color(0.12f, 0.12f, 0.12f);
|
|
|
|
|
var tplSr = tplGO.AddComponent<ScrollRect>();
|
|
|
|
|
var tplRt = tplGO.GetComponent<RectTransform>();
|
|
|
|
|
tplRt.anchorMin = new Vector2(0, 0); tplRt.anchorMax = new Vector2(1, 0);
|
|
|
|
|
tplRt.pivot = new Vector2(0.5f, 1);
|
|
|
|
|
tplRt.anchoredPosition = new Vector2(0, 2);
|
|
|
|
|
tplRt.sizeDelta = new Vector2(0, 150);
|
|
|
|
|
|
|
|
|
|
var vpGO = new GameObject("Viewport");
|
|
|
|
|
vpGO.transform.SetParent(tplGO.transform, false);
|
|
|
|
|
vpGO.AddComponent<Image>();
|
|
|
|
|
vpGO.AddComponent<Mask>().showMaskGraphic = false;
|
|
|
|
|
var vpRt = vpGO.GetComponent<RectTransform>();
|
|
|
|
|
FullStretch(vpRt);
|
|
|
|
|
|
|
|
|
|
var contentGO = new GameObject("Content");
|
|
|
|
|
contentGO.transform.SetParent(vpGO.transform, false);
|
|
|
|
|
var contentRt = contentGO.AddComponent<RectTransform>();
|
|
|
|
|
contentRt.anchorMin = new Vector2(0, 1); contentRt.anchorMax = Vector2.one;
|
|
|
|
|
contentRt.pivot = new Vector2(0.5f, 1);
|
|
|
|
|
contentRt.anchoredPosition = Vector2.zero; contentRt.sizeDelta = Vector2.zero;
|
|
|
|
|
|
|
|
|
|
tplSr.viewport = vpRt;
|
|
|
|
|
tplSr.content = contentRt;
|
|
|
|
|
tplSr.horizontal = false;
|
|
|
|
|
|
|
|
|
|
// Item 템플릿 (Content 안, 드롭다운이 복제해서 사용)
|
|
|
|
|
var itemGO = new GameObject("Item");
|
|
|
|
|
itemGO.transform.SetParent(contentGO.transform, false);
|
|
|
|
|
var itemTgl = itemGO.AddComponent<Toggle>();
|
|
|
|
|
var itemRt = itemGO.GetComponent<RectTransform>();
|
|
|
|
|
itemRt.anchorMin = new Vector2(0, 0.5f); itemRt.anchorMax = new Vector2(1, 0.5f);
|
|
|
|
|
itemRt.sizeDelta = new Vector2(0, 30);
|
|
|
|
|
|
|
|
|
|
var iBgGO = new GameObject("Item Background");
|
|
|
|
|
iBgGO.transform.SetParent(itemGO.transform, false);
|
|
|
|
|
var iBgImg = iBgGO.AddComponent<Image>();
|
|
|
|
|
iBgImg.color = new Color(0.15f, 0.15f, 0.15f);
|
|
|
|
|
FullStretch(iBgGO.GetComponent<RectTransform>());
|
|
|
|
|
|
|
|
|
|
var iCkGO = new GameObject("Item Checkmark");
|
|
|
|
|
iCkGO.transform.SetParent(itemGO.transform, false);
|
|
|
|
|
var iCkImg = iCkGO.AddComponent<Image>();
|
|
|
|
|
iCkImg.color = new Color(0.2f, 0.9f, 0.4f);
|
|
|
|
|
var iCkRt = iCkGO.GetComponent<RectTransform>();
|
|
|
|
|
iCkRt.anchorMin = new Vector2(0, 0.5f); iCkRt.anchorMax = new Vector2(0, 0.5f);
|
|
|
|
|
iCkRt.sizeDelta = new Vector2(18, 18); iCkRt.anchoredPosition = new Vector2(12, 0);
|
|
|
|
|
|
|
|
|
|
var iLblGO = new GameObject("Item Label");
|
|
|
|
|
iLblGO.transform.SetParent(itemGO.transform, false);
|
|
|
|
|
var iLblTMP = iLblGO.AddComponent<TextMeshProUGUI>();
|
|
|
|
|
iLblTMP.text = "Option"; iLblTMP.fontSize = 14;
|
|
|
|
|
var iLblRt = iLblGO.GetComponent<RectTransform>();
|
|
|
|
|
iLblRt.anchorMin = Vector2.zero; iLblRt.anchorMax = Vector2.one;
|
|
|
|
|
iLblRt.offsetMin = new Vector2(28, 1); iLblRt.offsetMax = new Vector2(-8, -2);
|
|
|
|
|
|
|
|
|
|
itemTgl.targetGraphic = iBgImg;
|
|
|
|
|
itemTgl.graphic = iCkImg;
|
|
|
|
|
|
|
|
|
|
dd.template = tplRt;
|
|
|
|
|
dd.captionText = labelTMP;
|
|
|
|
|
dd.itemText = iLblTMP;
|
|
|
|
|
|
|
|
|
|
tplGO.SetActive(false);
|
|
|
|
|
return dd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Toggle MakeToggle(string name, Transform parent, string label, Vector2 pos)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject(name);
|
|
|
|
|
go.transform.SetParent(parent, false);
|
|
|
|
|
var toggle = go.AddComponent<Toggle>();
|
|
|
|
|
var rt = go.GetComponent<RectTransform>();
|
|
|
|
|
rt.anchoredPosition = pos;
|
|
|
|
|
rt.sizeDelta = new Vector2(140, 40);
|
|
|
|
|
|
|
|
|
|
var bg = new GameObject("Background");
|
|
|
|
|
bg.transform.SetParent(go.transform, false);
|
|
|
|
|
bg.AddComponent<Image>().color = new Color(0.25f, 0.25f, 0.25f);
|
|
|
|
|
var bRt = bg.GetComponent<RectTransform>();
|
|
|
|
|
bRt.anchoredPosition = new Vector2(-45, 0);
|
|
|
|
|
bRt.sizeDelta = new Vector2(28, 28);
|
|
|
|
|
|
|
|
|
|
var ck = new GameObject("Checkmark");
|
|
|
|
|
ck.transform.SetParent(bg.transform, false);
|
|
|
|
|
var ckImg = ck.AddComponent<Image>();
|
|
|
|
|
ckImg.color = new Color(0.2f, 0.9f, 0.4f);
|
|
|
|
|
FullStretch(ck.GetComponent<RectTransform>());
|
|
|
|
|
|
|
|
|
|
var lGO = new GameObject("Label");
|
|
|
|
|
lGO.transform.SetParent(go.transform, false);
|
|
|
|
|
var lTMP = lGO.AddComponent<TextMeshProUGUI>();
|
|
|
|
|
lTMP.text = label; lTMP.fontSize = 16;
|
|
|
|
|
var lRt = lGO.GetComponent<RectTransform>();
|
|
|
|
|
lRt.anchoredPosition = new Vector2(20, 0);
|
|
|
|
|
lRt.sizeDelta = new Vector2(95, 30);
|
|
|
|
|
|
|
|
|
|
toggle.targetGraphic = bg.GetComponent<Image>();
|
|
|
|
|
toggle.graphic = ckImg;
|
|
|
|
|
toggle.isOn = false;
|
|
|
|
|
return toggle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Slider MakeSlider(string name, Transform parent, Vector2 pos, Vector2 size)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject(name);
|
|
|
|
|
go.transform.SetParent(parent, false);
|
|
|
|
|
var slider = go.AddComponent<Slider>();
|
|
|
|
|
var rt = go.GetComponent<RectTransform>();
|
|
|
|
|
rt.anchoredPosition = pos;
|
|
|
|
|
rt.sizeDelta = size;
|
|
|
|
|
|
|
|
|
|
var bg = new GameObject("Background");
|
|
|
|
|
bg.transform.SetParent(go.transform, false);
|
|
|
|
|
bg.AddComponent<Image>().color = new Color(0.25f, 0.25f, 0.25f);
|
|
|
|
|
var bRt = bg.GetComponent<RectTransform>();
|
|
|
|
|
bRt.anchorMin = new Vector2(0, 0.25f); bRt.anchorMax = new Vector2(1, 0.75f);
|
|
|
|
|
bRt.sizeDelta = Vector2.zero;
|
|
|
|
|
|
|
|
|
|
var fa = new GameObject("Fill Area");
|
|
|
|
|
fa.transform.SetParent(go.transform, false);
|
|
|
|
|
var faRt = fa.AddComponent<RectTransform>();
|
|
|
|
|
faRt.anchorMin = new Vector2(0, 0.25f); faRt.anchorMax = new Vector2(1, 0.75f);
|
|
|
|
|
faRt.sizeDelta = new Vector2(-20, 0);
|
|
|
|
|
|
|
|
|
|
var fill = new GameObject("Fill");
|
|
|
|
|
fill.transform.SetParent(fa.transform, false);
|
|
|
|
|
fill.AddComponent<Image>().color = new Color(0.2f, 0.7f, 1f);
|
|
|
|
|
var fRt = fill.GetComponent<RectTransform>();
|
|
|
|
|
fRt.anchorMin = Vector2.zero; fRt.anchorMax = new Vector2(0, 1);
|
|
|
|
|
fRt.sizeDelta = new Vector2(10, 0);
|
|
|
|
|
|
|
|
|
|
slider.fillRect = fRt;
|
|
|
|
|
slider.value = 0f;
|
|
|
|
|
return slider;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static (ScrollRect, Transform) MakeScrollRect(string name, Transform parent, Vector2 pos, Vector2 size)
|
|
|
|
|
{
|
|
|
|
|
var go = new GameObject(name);
|
|
|
|
|
go.transform.SetParent(parent, false);
|
|
|
|
|
go.AddComponent<Image>().color = new Color(0, 0, 0, 0.2f);
|
|
|
|
|
var sr = go.AddComponent<ScrollRect>();
|
|
|
|
|
var rt = go.GetComponent<RectTransform>();
|
|
|
|
|
rt.anchoredPosition = pos; rt.sizeDelta = size;
|
|
|
|
|
|
|
|
|
|
var vp = new GameObject("Viewport");
|
|
|
|
|
vp.transform.SetParent(go.transform, false);
|
|
|
|
|
vp.AddComponent<Image>();
|
|
|
|
|
vp.AddComponent<Mask>().showMaskGraphic = false;
|
|
|
|
|
FullStretch(vp.GetComponent<RectTransform>());
|
|
|
|
|
|
|
|
|
|
var content = new GameObject("CardContainer");
|
|
|
|
|
content.transform.SetParent(vp.transform, false);
|
|
|
|
|
var vlg = content.AddComponent<VerticalLayoutGroup>();
|
|
|
|
|
vlg.spacing = 8; vlg.childForceExpandWidth = true; vlg.childForceExpandHeight = false;
|
|
|
|
|
vlg.padding = new RectOffset(8, 8, 8, 8);
|
|
|
|
|
content.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
|
|
|
|
var cRt = content.GetComponent<RectTransform>();
|
|
|
|
|
cRt.anchorMin = new Vector2(0, 1); cRt.anchorMax = Vector2.one;
|
|
|
|
|
cRt.pivot = new Vector2(0.5f, 1); cRt.sizeDelta = Vector2.zero;
|
|
|
|
|
|
|
|
|
|
sr.viewport = vp.GetComponent<RectTransform>();
|
|
|
|
|
sr.content = cRt;
|
|
|
|
|
sr.horizontal = false;
|
|
|
|
|
return (sr, content.transform);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void FullStretch(RectTransform rt)
|
|
|
|
|
{
|
|
|
|
|
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
|
|
|
|
|
rt.anchoredPosition = Vector2.zero; rt.sizeDelta = Vector2.zero;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void Bind(SerializedObject so, string field, Object value)
|
|
|
|
|
{
|
|
|
|
|
var prop = so.FindProperty(field);
|
|
|
|
|
if (prop != null) prop.objectReferenceValue = value;
|
|
|
|
|
else Debug.LogWarning($"[SceneBuilder] 필드 없음: {field}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void EnsureFolder(string path)
|
|
|
|
|
{
|
|
|
|
|
if (!Directory.Exists(path))
|
|
|
|
|
{
|
|
|
|
|
var parts = path.Split('/');
|
|
|
|
|
AssetDatabase.CreateFolder(string.Join("/", parts[..^1]), parts[^1]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|