비트 찍기 완료 및 클로드를 통한 api작업
This commit is contained in:
@@ -0,0 +1,740 @@
|
||||
#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 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 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 btnEasy = MakeButton("EasyBtn", diffPanel.transform, "Easy", new Vector2(-240, 0), new Vector2(140, 50));
|
||||
var btnNorm = MakeButton("NormalBtn", diffPanel.transform, "Normal", new Vector2(-80, 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(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 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, "btnEasy", btnEasy.GetComponent<Button>());
|
||||
Bind(dso, "btnNormal", btnNorm.GetComponent<Button>());
|
||||
Bind(dso, "btnHard", btnHard.GetComponent<Button>());
|
||||
Bind(dso, "btnExpert", btnExp.GetComponent<Button>());
|
||||
Bind(dso, "downloadButton", dlBtn.GetComponent<Button>());
|
||||
Bind(dso, "deleteButton", delBtn.GetComponent<Button>());
|
||||
Bind(dso, "playButton", playBtn.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, "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, 760), new Vector3(0, 1.5f, 2f));
|
||||
|
||||
MakeTMP("TitleLabel", canvas.transform, "노래 만들기", new Vector2(0, 345), new Vector2(860, 55), 32);
|
||||
|
||||
var audioPanel = MakePanel("AudioPanel", canvas.transform, new Vector2(0, 250), new Vector2(860, 90));
|
||||
var audioDd = MakeDropdown("AudioDropdown", audioPanel.transform, new Vector2(-120, 0), new Vector2(540, 60));
|
||||
var refreshBtn = MakeButton("RefreshBtn", audioPanel.transform, "새로고침", new Vector2(255, 0), new Vector2(160, 58));
|
||||
var pathHint = MakeTMP("PathHint", canvas.transform, "", new Vector2(0, 195), new Vector2(860, 28), 13);
|
||||
|
||||
var metaPanel = MakePanel("MetaPanel", canvas.transform, new Vector2(0, 105), new Vector2(860, 110));
|
||||
var titleInput = MakeInputField("TitleInput", metaPanel.transform, "곡 제목", new Vector2(-280, 0), new Vector2(255, 55));
|
||||
var artistInput = MakeInputField("ArtistInput", metaPanel.transform, "아티스트", new Vector2(0, 0), new Vector2(255, 55));
|
||||
var bpmInput = MakeInputField("BpmInput", metaPanel.transform, "BPM", new Vector2(280, 0), new Vector2(255, 55));
|
||||
|
||||
var diffPanel = MakePanel("DiffPanel", canvas.transform, new Vector2(0, -15), new Vector2(860, 65));
|
||||
var togEasy = MakeToggle("EasyToggle", diffPanel.transform, "Easy", new Vector2(-310, 0));
|
||||
var togNormal = MakeToggle("NormalToggle", diffPanel.transform, "Normal", new Vector2(-155, 0));
|
||||
var togHard = MakeToggle("HardToggle", diffPanel.transform, "Hard", new Vector2(0, 0));
|
||||
var togExpert = MakeToggle("ExpertToggle", diffPanel.transform, "Expert", new Vector2(155, 0));
|
||||
togHard.isOn = true;
|
||||
togExpert.isOn = true;
|
||||
|
||||
var actPanel = MakePanel("ActionPanel", canvas.transform, new Vector2(0, -125), new Vector2(860, 80));
|
||||
var genBtn = MakeButton("GenerateButton", actPanel.transform, "AI 생성 시작", new Vector2(-175, 0), new Vector2(440, 65));
|
||||
var manualBtn = MakeButton("ManualButton", actPanel.transform, "직접 만들기 →", new Vector2(245, 0), new Vector2(185, 42));
|
||||
|
||||
var progGroup = MakePanel("ProgressGroup", canvas.transform, new Vector2(0, -250), new Vector2(860, 95));
|
||||
var statusTxt = MakeTMP("StatusText", progGroup.transform, "", new Vector2(0, 28), new Vector2(840, 36), 18);
|
||||
var progSlider = MakeSlider("ProgressSlider", progGroup.transform, new Vector2(0, -15), new Vector2(820, 28));
|
||||
progGroup.SetActive(false);
|
||||
|
||||
var sso = new SerializedObject(scMgr);
|
||||
Bind(sso, "audioDropdown", audioDd);
|
||||
Bind(sso, "refreshBtn", refreshBtn.GetComponent<Button>());
|
||||
Bind(sso, "inputPathHint", pathHint);
|
||||
Bind(sso, "titleInput", titleInput);
|
||||
Bind(sso, "artistInput", artistInput);
|
||||
Bind(sso, "bpmInput", bpmInput);
|
||||
Bind(sso, "toggleEasy", togEasy);
|
||||
Bind(sso, "toggleNormal", togNormal);
|
||||
Bind(sso, "toggleHard", togHard);
|
||||
Bind(sso, "toggleExpert", togExpert);
|
||||
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);
|
||||
go.AddComponent<Image>().color = new Color(0.08f, 0.08f, 0.08f, 0.88f);
|
||||
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);
|
||||
go.AddComponent<Image>().color = new Color(0.22f, 0.22f, 0.22f);
|
||||
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.MidlineLeft;
|
||||
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);
|
||||
go.AddComponent<Image>().color = new Color(0.15f, 0.15f, 0.15f);
|
||||
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);
|
||||
go.AddComponent<Image>().color = new Color(0.15f, 0.15f, 0.15f);
|
||||
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
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8041d843681d86e41a11512f66dd9933
|
||||
Reference in New Issue
Block a user