using System.IO; using AibisDream.UI.Terminal; using TMPro; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.UI; namespace AibisDream.EditorTools.TerminalUI { public static class TerminalMainUIPrefabBuilder { private const string PrefabRoot = "Assets/GameContent/Feature_MainUI/Prefabs"; private const string CommonRoot = PrefabRoot + "/Common"; private const string FontPath = "Assets/Font/Assets/WenQuanYi Bitmap Song 14px SDF.asset"; private static readonly Vector2 PanelSize = new Vector2(1920f, 1080f); private static readonly Vector2 ReferenceSize = new Vector2(768f, 432f); private const float UiScale = 2.5f; private static TMP_FontAsset _font; [MenuItem("AIBIS/Terminal UI/Rebuild MainUI Prefabs")] public static void BuildAll() { EnsureFolders(); _font = AssetDatabase.LoadAssetAtPath(FontPath); if (_font == null) { Debug.LogError($"[TerminalMainUIPrefabBuilder] Font not found: {FontPath}"); return; } BuildCommonPrefabs(); BuildStartPanel(); BuildSettingPanel(); BuildChapterPanel(); BuildRecordPanel(); BuildLoadingPanel(); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log("[TerminalMainUIPrefabBuilder] Terminal MainUI prefabs rebuilt."); } public static void BuildAllAndInstallToPersistence() { BuildAll(); InstallToPersistenceScene(); } [MenuItem("AIBIS/Terminal UI/Install MainUI Prefabs To Open Persistence")] public static void InstallToPersistenceScene() { var uiCanvas = GameObject.Find("UI Canvas"); if (uiCanvas == null) { Debug.LogError("[TerminalMainUIPrefabBuilder] Open Persistence scene first. UI Canvas was not found."); return; } InstantiateUnderCanvas("TerminalStartPanel.prefab", uiCanvas.transform, false); InstantiateUnderCanvas("TerminalSettingPanel.prefab", uiCanvas.transform, false); InstantiateUnderCanvas("TerminalChapterPanel.prefab", uiCanvas.transform, false); InstantiateUnderCanvas("TerminalRecordPanel.prefab", uiCanvas.transform, false); InstantiateUnderCanvas("TerminalLoadingPanel.prefab", uiCanvas.transform, false); EditorSceneManager.MarkSceneDirty(uiCanvas.scene); } private static void BuildCommonPrefabs() { SavePrefab(CreateHeaderTemplate(), CommonRoot + "/TerminalHeader.prefab"); SavePrefab(CreateMenuItemTemplate(), CommonRoot + "/TerminalMenuItem.prefab"); SavePrefab(CreateSliderTemplate(), CommonRoot + "/TerminalSliderItem.prefab"); SavePrefab(CreateOptionTemplate(), CommonRoot + "/TerminalOptionItem.prefab"); SavePrefab(CreateChapterCardTemplate(), CommonRoot + "/TerminalChapterCard.prefab"); SavePrefab(CreateRecordItemTemplate(), CommonRoot + "/TerminalRecordItem.prefab"); AssetDatabase.Refresh(); } private static void BuildStartPanel() { var root = CreatePanelRoot("TerminalStartPanel", typeof(TerminalStartPanel)); var panel = root.GetComponent(); var animator = root.GetComponent(); var header = AddHeader(root.transform, Point(384f, 21f), "", "CHAPER xx/xxx - LOADING - DT"); AddText(root.transform, "SELECT OPERATION", 34f, TextAlignmentOptions.Left, Point(177f, 124f), Size(220f, 28f)); var newGame = AddMenuItem(root.transform, Point(384f, 173f), "NEW GAME.....", "pending", "Current date: 04/2020", false); var chapter = AddMenuItem(root.transform, Point(384f, 197f), "CHAPTER......", "check ok", "Current date: 00/2020", false); chapter.SetSelected(true); var setting = AddMenuItem(root.transform, Point(384f, 221f), "SETTING......", "pending", "Current date: xx/xxxx", false); var quit = AddMenuItem(root.transform, Point(384f, 245f), "QUIT.........", "-----", "-----: 00/0000", true); AddSideMarks(root.transform, Point(384f, 209f).y); AddFooter(root.transform); SetObject(panel, "panelAnimator", animator); SetObject(panel, "header", header); SetObject(panel, "newGameItem", newGame); SetObject(panel, "chapterItem", chapter); SetObject(panel, "settingItem", setting); SetObject(panel, "quitItem", quit); SavePrefab(root, PrefabRoot + "/TerminalStartPanel.prefab"); } private static void BuildSettingPanel() { var root = CreatePanelRoot("TerminalSettingPanel", typeof(TerminalSettingPanel)); var panel = root.GetComponent(); var animator = root.GetComponent(); var header = AddHeader(root.transform, Point(384f, 21f), "", "MUSIC 50/100 - AUDIO INPUT 001"); AddText(root.transform, "SELECT OPERATION", 34f, TextAlignmentOptions.Left, Point(177f, 76f), Size(220f, 28f)); var volume = AddSlider(root.transform, Point(384f, 109f)); var windowMode = AddOption(root.transform, Point(360f, 133f)); var textSpeed = AddSlider(root.transform, Point(384f, 157f)); var language = AddOption(root.transform, Point(360f, 181f)); var continueButton = AddTextButton(root.transform, "CONTINUE", Point(228f, 365f), false); var backButton = AddTextButton(root.transform, "MAIN MENU", Point(384f, 365f), false); var quitButton = AddTextButton(root.transform, "QUIT", Point(540f, 365f), true); AddSideMarks(root.transform, Point(384f, 209f).y); AddFooter(root.transform); SetObject(panel, "panelAnimator", animator); SetObject(panel, "header", header); SetObject(panel, "volumeSlider", volume); SetObject(panel, "textSpeedSlider", textSpeed); SetObject(panel, "windowModeOption", windowMode); SetObject(panel, "languageOption", language); SetObject(panel, "continueButton", continueButton); SetObject(panel, "backToMainButton", backButton); SetObject(panel, "quitButton", quitButton); SavePrefab(root, PrefabRoot + "/TerminalSettingPanel.prefab"); } private static void BuildChapterPanel() { var root = CreatePanelRoot("TerminalChapterPanel", typeof(TerminalChapterPanel)); var panel = root.GetComponent(); var animator = root.GetComponent(); var header = AddHeader(root.transform, Point(384f, 21f), "", string.Empty); var left = AddChapterCard(root.transform, Point(173f, 171f), Size(106f, 106f)); var current = AddChapterCard(root.transform, Point(384f, 176f), Size(235f, 235f)); var right = AddChapterCard(root.transform, Point(595f, 171f), Size(106f, 106f)); var empty = AddText(root.transform, "NO CHAPTER DATA", 34f, TextAlignmentOptions.Center, Vector2.zero, Size(240f, 32f)); var selectedTitle = AddText(root.transform, "CHAPTER TITLE", 32f, TextAlignmentOptions.Center, Point(384f, 320f), Size(210f, 28f)); var previousButton = AddTextButton(root.transform, "<", Point(312f, 320f), false); var nextButton = AddTextButton(root.transform, ">", Point(456f, 320f), false); var restartButton = AddTextButton(root.transform, "RESTART", Point(384f, 356f), false); var backButton = AddTextButton(root.transform, "BACK", Point(384f, 368f), true); AddSideMarks(root.transform, Point(384f, 209f).y); AddFooter(root.transform); SetObject(panel, "panelAnimator", animator); SetObject(panel, "header", header); SetObject(panel, "leftPreview", left); SetObject(panel, "currentView", current); SetObject(panel, "rightPreview", right); SetObject(panel, "emptyText", empty); SetObject(panel, "selectedTitleText", selectedTitle); SetObject(panel, "previousButton", previousButton); SetObject(panel, "nextButton", nextButton); SetObject(panel, "restartButton", restartButton); SetObject(panel, "backButton", backButton); SavePrefab(root, PrefabRoot + "/TerminalChapterPanel.prefab"); } private static void BuildRecordPanel() { var root = CreatePanelRoot("TerminalRecordPanel", typeof(TerminalRecordPanel)); var panel = root.GetComponent(); var animator = root.GetComponent(); var header = AddHeader(root.transform, Point(384f, 21f), "", "EMR 511/42 - Clinical Record"); var viewport = CreateUIObject("Record Scroll", root.transform, Point(384f, 188f), Size(410f, 245f)); var scrollRect = viewport.AddComponent(); var mask = viewport.AddComponent(); mask.showMaskGraphic = false; var viewportImage = viewport.AddComponent(); viewportImage.color = new Color(1f, 1f, 1f, 0.001f); var content = CreateUIObject("Content", viewport.transform, Vector2.zero, Size(410f, 245f)); var layout = content.AddComponent(); layout.spacing = 18f; layout.childControlHeight = true; layout.childControlWidth = true; layout.childForceExpandHeight = false; layout.childForceExpandWidth = true; content.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; scrollRect.viewport = viewport.GetComponent(); scrollRect.content = content.GetComponent(); scrollRect.horizontal = false; var emptyText = AddText(viewport.transform, "NO CLINICAL RECORD", 30f, TextAlignmentOptions.Center, Vector2.zero, Size(260f, 30f)); var backButton = AddTextButton(root.transform, "BACK", Point(384f, 368f), true); var itemPrefabObject = AssetDatabase.LoadAssetAtPath(CommonRoot + "/TerminalRecordItem.prefab"); var itemPrefab = itemPrefabObject != null ? itemPrefabObject.GetComponent() : null; AddSideMarks(root.transform, Point(384f, 209f).y); SetObject(panel, "panelAnimator", animator); SetObject(panel, "header", header); SetObject(panel, "backButton", backButton); SetObject(panel, "scrollRect", scrollRect); SetObject(panel, "recordRoot", content.transform); SetObject(panel, "recordItemPrefab", itemPrefab); SetObject(panel, "emptyText", emptyText); SavePrefab(root, PrefabRoot + "/TerminalRecordPanel.prefab"); } private static void BuildLoadingPanel() { var root = CreatePanelRoot("TerminalLoadingPanel", typeof(TerminalLoadingPanel)); var panel = root.GetComponent(); var animator = root.GetComponent(); var avatar = CreateUIObject("Avatar", root.transform, Point(248f, 160f), Size(100f, 100f)).AddComponent(); avatar.color = new Color(1f, 1f, 1f, 0.08f); var company = AddText(root.transform, "SATISFYING INC", 32f, TextAlignmentOptions.Left, Point(444f, 116f), Size(220f, 28f)); var device = AddText(root.transform, "Device Model: Dell P5999 DX130\nDevice Serial Number: SN46456546DAS", 24f, TextAlignmentOptions.Left, Point(472f, 145f), Size(330f, 42f)); var user = AddText(root.transform, "User Name: Luth\nUser Title: DOC.\nEmployee ID: D42", 24f, TextAlignmentOptions.Left, Point(444f, 192f), Size(260f, 58f)); var bar = CreateUIObject("Progress Bar", root.transform, Point(384f, 244f), Size(374f, 12f)).AddComponent(); bar.color = Color.white; var fill = CreateUIObject("Fill", bar.transform, Vector2.zero, Size(374f, 12f)).AddComponent(); fill.color = Color.white; var fillRect = fill.GetComponent(); fillRect.anchorMin = new Vector2(0f, 0f); fillRect.anchorMax = new Vector2(0f, 1f); fillRect.offsetMin = Vector2.zero; fillRect.offsetMax = Vector2.zero; var loading = AddText(root.transform, "LOADING...", 28f, TextAlignmentOptions.Center, Point(384f, 266f), Size(120f, 24f)); AddText(root.transform, "AM **:**\nWE.92 2650", 24f, TextAlignmentOptions.Left, Point(390f, 352f), Size(120f, 45f)); AddSideMarks(root.transform, Point(384f, 209f).y); SetObject(panel, "panelAnimator", animator); SetObject(panel, "companyText", company); SetObject(panel, "deviceText", device); SetObject(panel, "userText", user); SetObject(panel, "loadingText", loading); SetObject(panel, "avatarImage", avatar); SetObject(panel, "progressFill", fillRect); SavePrefab(root, PrefabRoot + "/TerminalLoadingPanel.prefab"); } private static GameObject CreatePanelRoot(string name, System.Type panelType) { var root = CreateUIObject(name, null, Vector2.zero, PanelSize); var rect = root.GetComponent(); rect.anchorMin = new Vector2(0.5f, 0.5f); rect.anchorMax = new Vector2(0.5f, 0.5f); rect.sizeDelta = PanelSize; var canvasGroup = root.AddComponent(); var animator = root.AddComponent(); root.AddComponent(panelType); SetObject(animator, "canvasGroup", canvasGroup); root.SetActive(false); return root; } private static TerminalHeaderView AddHeader(Transform parent, Vector2 pos, string title, string status) { var go = InstantiateCommon("TerminalHeader.prefab", parent); SetRect(go, pos, Size(415f, 17f)); var header = go.GetComponent(); header.SetText(title, status); return header; } private static TerminalMenuItemView AddMenuItem(Transform parent, Vector2 pos, string label, string status, string detail, bool danger) { var go = InstantiateCommon("TerminalMenuItem.prefab", parent); SetRect(go, pos, Size(415f, 18f)); var view = go.GetComponent(); view.SetContent(label, status, detail, danger); return view; } private static TerminalSliderView AddSlider(Transform parent, Vector2 pos) { var go = InstantiateCommon("TerminalSliderItem.prefab", parent); SetRect(go, pos, Size(410f, 18f)); return go.GetComponent(); } private static TerminalOptionItemView AddOption(Transform parent, Vector2 pos) { var go = InstantiateCommon("TerminalOptionItem.prefab", parent); SetRect(go, pos, Size(250f, 18f)); return go.GetComponent(); } private static TerminalChapterItemView AddChapterCard(Transform parent, Vector2 pos, Vector2 size) { var go = InstantiateCommon("TerminalChapterCard.prefab", parent); SetRect(go, pos, size); return go.GetComponent(); } private static Button AddTextButton(Transform parent, string text, Vector2 pos, bool danger) { var go = CreateUIObject(text + " Button", parent, pos, Size(100f, 22f)); var button = AddTransparentBaseButton(go); CreateLine(go.transform, "Top Line", new Vector2(0f, 20f), Size(100f, 1f)); CreateLine(go.transform, "Bottom Line", new Vector2(0f, -20f), Size(100f, 1f)); AddText(go.transform, text, 28f, TextAlignmentOptions.Center, Vector2.zero, Size(100f, 18f), danger ? new Color(1f, 0.18f, 0.2f) : Color.white); return button; } private static void AddFooter(Transform parent) { AddText(parent, "Please keep this device secure. Do not lose, transfer, or remove it from the premises without authorization.\nUnauthorized use of this device is strictly prohibited.", 13f, TextAlignmentOptions.Left, Point(384f, 385f), Size(325f, 24f)); } private static void AddSideMarks(Transform parent, float y) { CreateLine(parent, "Left Mark", new Vector2(Point(68f, 216f).x, y), Size(96f, 1f)); CreateLine(parent, "Right Mark", new Vector2(Point(700f, 216f).x, y), Size(96f, 1f)); } private static GameObject CreateHeaderTemplate() { var root = CreateUIObject("TerminalHeader", null, Vector2.zero, Size(415f, 17f)); var bg = root.AddComponent(); bg.color = Color.white; var title = AddText(root.transform, "", 28f, TextAlignmentOptions.Left, new Vector2(-275f, 0f), Size(100f, 17f), Color.black); var status = AddText(root.transform, "STATUS", 28f, TextAlignmentOptions.Left, new Vector2(75f, 0f), Size(270f, 17f), Color.black); var header = root.AddComponent<TerminalHeaderView>(); SetObject(header, "titleText", title); SetObject(header, "statusText", status); return root; } private static GameObject CreateMenuItemTemplate() { var root = CreateUIObject("TerminalMenuItem", null, Vector2.zero, Size(415f, 18f)); AddTransparentBaseButton(root); var bar = CreateUIObject("Selection Bar", root.transform, new Vector2(0f, 0f), Size(415f, 16f)).AddComponent<Image>(); bar.color = Color.white; bar.raycastTarget = false; var cursor = AddText(root.transform, ">", 28f, TextAlignmentOptions.Center, new Vector2(-497f, 0f), Size(16f, 16f)); var label = AddText(root.transform, "Label", 28f, TextAlignmentOptions.Left, new Vector2(-342f, 0f), Size(128f, 16f)); var status = AddText(root.transform, "Status", 28f, TextAlignmentOptions.Left, new Vector2(-95f, 0f), Size(95f, 16f)); var detail = AddText(root.transform, "Detail", 18f, TextAlignmentOptions.Left, new Vector2(245f, 0f), Size(200f, 16f)); var view = root.AddComponent<TerminalMenuItemView>(); SetObject(view, "labelText", label); SetObject(view, "statusText", status); SetObject(view, "detailText", detail); SetObject(view, "cursor", cursor); SetObject(view, "selectionBar", bar); return root; } private static GameObject CreateSliderTemplate() { var root = CreateUIObject("TerminalSliderItem", null, Vector2.zero, Size(410f, 18f)); var label = AddText(root.transform, "Label", 28f, TextAlignmentOptions.Left, new Vector2(-330f, 0f), Size(100f, 16f)); var value = AddText(root.transform, "50/100", 28f, TextAlignmentOptions.Left, new Vector2(-130f, 0f), Size(70f, 16f)); var sliderObject = CreateUIObject("Slider", root.transform, new Vector2(165f, 0f), Size(200f, 12f)); var slider = sliderObject.AddComponent<Slider>(); slider.transition = Selectable.Transition.None; var bg = CreateUIObject("Background", sliderObject.transform, Vector2.zero, Size(200f, 11f)).AddComponent<Image>(); bg.color = new Color(1f, 1f, 1f, 0.08f); var fill = CreateUIObject("Fill", sliderObject.transform, Vector2.zero, Size(200f, 11f)).AddComponent<Image>(); fill.color = Color.white; var handle = CreateUIObject("Handle", sliderObject.transform, Vector2.zero, Size(5f, 12f)).AddComponent<Image>(); handle.color = Color.white; slider.fillRect = fill.GetComponent<RectTransform>(); slider.handleRect = handle.GetComponent<RectTransform>(); var view = root.AddComponent<TerminalSliderView>(); SetObject(view, "labelText", label); SetObject(view, "valueText", value); SetObject(view, "slider", slider); SetObject(view, "fillRect", fill.GetComponent<RectTransform>()); return root; } private static GameObject CreateOptionTemplate() { var root = CreateUIObject("TerminalOptionItem", null, Vector2.zero, Size(250f, 18f)); var label = AddText(root.transform, "Label", 28f, TextAlignmentOptions.Left, new Vector2(-205f, 0f), Size(100f, 16f)); var value = AddText(root.transform, "<Value>", 28f, TextAlignmentOptions.Left, new Vector2(45f, 0f), Size(120f, 16f)); var prev = CreateTinyButton(root.transform, "<", new Vector2(-35f, 0f)); var next = CreateTinyButton(root.transform, ">", new Vector2(270f, 0f)); var view = root.AddComponent<TerminalOptionItemView>(); SetObject(view, "labelText", label); SetObject(view, "valueText", value); SetObject(view, "previousButton", prev); SetObject(view, "nextButton", next); return root; } private static GameObject CreateChapterCardTemplate() { var root = CreateUIObject("TerminalChapterCard", null, Vector2.zero, Size(140f, 120f)); AddTransparentBaseButton(root); var cover = CreateUIObject("Cover", root.transform, new Vector2(0f, 25f), Size(120f, 80f)).AddComponent<Image>(); cover.color = new Color(1f, 1f, 1f, 0.2f); cover.raycastTarget = false; var frame = CreateUIObject("Selection Frame", root.transform, new Vector2(0f, 25f), Size(125f, 85f)).AddComponent<Image>(); frame.color = new Color(1f, 1f, 1f, 0.16f); frame.raycastTarget = false; var title = AddText(root.transform, "Title", 26f, TextAlignmentOptions.Center, new Vector2(0f, -95f), Size(120f, 18f)); var chapter = AddText(root.transform, "Chapter", 20f, TextAlignmentOptions.Center, new Vector2(0f, -125f), Size(120f, 16f)); var status = AddText(root.transform, "PENDING", 16f, TextAlignmentOptions.Center, new Vector2(0f, -150f), Size(120f, 14f)); var view = root.AddComponent<TerminalChapterItemView>(); SetObject(view, "coverImage", cover); SetObject(view, "titleText", title); SetObject(view, "chapterText", chapter); SetObject(view, "statusText", status); SetObject(view, "selectionFrame", frame); return root; } private static GameObject CreateRecordItemTemplate() { var root = CreateUIObject("TerminalRecordItem", null, Vector2.zero, Size(410f, 32f)); var layout = root.AddComponent<HorizontalLayoutGroup>(); layout.spacing = 24f; layout.childControlHeight = true; layout.childControlWidth = false; layout.childForceExpandHeight = false; layout.childForceExpandWidth = false; var actor = AddText(root.transform, "Actor", 24f, TextAlignmentOptions.TopLeft, Vector2.zero, Size(55f, 30f)); actor.gameObject.AddComponent<LayoutElement>().preferredWidth = Size(55f, 1f).x; var line = AddText(root.transform, "Record line", 24f, TextAlignmentOptions.TopLeft, Vector2.zero, Size(330f, 30f)); line.gameObject.AddComponent<LayoutElement>().preferredWidth = Size(330f, 1f).x; var view = root.AddComponent<TerminalRecordItemView>(); SetObject(view, "actorNameText", actor); SetObject(view, "lineText", line); return root; } private static Button CreateTinyButton(Transform parent, string text, Vector2 pos) { var go = CreateUIObject(text + " Button", parent, pos, Size(18f, 18f)); var button = AddTransparentBaseButton(go); AddText(go.transform, text, 24f, TextAlignmentOptions.Center, Vector2.zero, Size(18f, 18f)); return button; } private static TMP_Text AddText(Transform parent, string text, float size, TextAlignmentOptions alignment, Vector2 pos, Vector2 rectSize) { return AddText(parent, text, size, alignment, pos, rectSize, Color.white); } private static TMP_Text AddText(Transform parent, string text, float size, TextAlignmentOptions alignment, Vector2 pos, Vector2 rectSize, Color color) { var go = CreateUIObject("Text", parent, pos, rectSize); var tmp = go.AddComponent<TextMeshProUGUI>(); tmp.font = _font; tmp.fontSize = size; tmp.alignment = alignment; tmp.color = color; tmp.text = text; tmp.raycastTarget = false; return tmp; } private static Image CreateLine(Transform parent, string name, Vector2 pos, Vector2 size) { var line = CreateUIObject(name, parent, pos, size).AddComponent<Image>(); line.color = Color.white; line.raycastTarget = false; return line; } private static BaseButton AddTransparentBaseButton(GameObject go) { var hitArea = go.AddComponent<Image>(); hitArea.color = new Color(1f, 1f, 1f, 0.001f); hitArea.raycastTarget = true; var button = go.AddComponent<BaseButton>(); button.targetGraphic = hitArea; button.transition = Selectable.Transition.None; return button; } private static GameObject InstantiateCommon(string prefabName, Transform parent) { var prefab = AssetDatabase.LoadAssetAtPath<GameObject>($"{CommonRoot}/{prefabName}"); if (prefab == null) { Debug.LogError($"[TerminalMainUIPrefabBuilder] Missing common prefab: {prefabName}"); return CreateUIObject(prefabName, parent, Vector2.zero, Vector2.one); } var instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab); instance.transform.SetParent(parent, false); return instance; } private static GameObject CreateUIObject(string name, Transform parent, Vector2 anchoredPosition, Vector2 size) { var go = new GameObject(name, typeof(RectTransform)); if (parent != null) { go.transform.SetParent(parent, false); } SetRect(go, anchoredPosition, size); return go; } private static void SetRect(GameObject go, Vector2 anchoredPosition, Vector2 size) { var rect = go.GetComponent<RectTransform>(); rect.anchorMin = new Vector2(0.5f, 0.5f); rect.anchorMax = new Vector2(0.5f, 0.5f); rect.pivot = new Vector2(0.5f, 0.5f); rect.anchoredPosition = anchoredPosition; rect.sizeDelta = size; } private static Vector2 Point(float x, float y) { return new Vector2((x - ReferenceSize.x * 0.5f) * UiScale, (ReferenceSize.y * 0.5f - y) * UiScale); } private static Vector2 Size(float width, float height) { return new Vector2(width * UiScale, height * UiScale); } private static void SetObject(Object target, string propertyName, Object value) { var serializedObject = new SerializedObject(target); var property = serializedObject.FindProperty(propertyName); if (property == null) { Debug.LogError($"[TerminalMainUIPrefabBuilder] Missing property {propertyName} on {target.name}"); return; } property.objectReferenceValue = value; serializedObject.ApplyModifiedPropertiesWithoutUndo(); } private static void SavePrefab(GameObject go, string path) { PrefabUtility.SaveAsPrefabAsset(go, path); Object.DestroyImmediate(go); } private static void InstantiateUnderCanvas(string prefabName, Transform canvas, bool active) { var prefab = AssetDatabase.LoadAssetAtPath<GameObject>($"{PrefabRoot}/{prefabName}"); if (prefab == null) { Debug.LogError($"[TerminalMainUIPrefabBuilder] Missing prefab {prefabName}"); return; } if (canvas.Find(prefab.name) != null) { return; } var instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab, canvas); var rect = instance.GetComponent<RectTransform>(); rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; instance.SetActive(active); } private static void EnsureFolders() { Directory.CreateDirectory(PrefabRoot); Directory.CreateDirectory(CommonRoot); } } }