Files
Colosseum/Assets/_Game/Scripts/Editor/PassiveTreeUiPrefabBuilder.cs
dal4segno d78e0edabd feat: 패시브 트리 레이아웃 자동화 및 UI 정리
- 패시브 트리 노드 배치를 삼각형 기반 자동 배치 구조로 전환하고 축 및 브릿지 반경을 재정리\n- 패시브 UI 프리팹과 런타임 렌더링을 수정해 노드 겹침, 링크 관통, 상태별 간격 변화, 하단 여백 문제를 정리\n- 프로토타입 패시브 노드, 트리, 프리셋 자산을 재생성해 최신 레이아웃과 확장 노드 구성을 반영
2026-03-27 04:43:14 +09:00

645 lines
33 KiB
C#

using System.IO;
using TMPro;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UI;
using Colosseum.Passives;
using Colosseum.UI;
namespace Colosseum.Editor
{
/// <summary>
/// 패시브 트리 uGUI 프리팹을 재생성하는 에디터 유틸리티입니다.
/// </summary>
public static class PassiveTreeUiPrefabBuilder
{
private const string PrefabFolder = "Assets/_Game/Prefabs/UI/PassiveTree";
private const string ViewPrefabPath = PrefabFolder + "/UI_PassiveTreeView.prefab";
private const string NodePrefabPath = PrefabFolder + "/UI_PassiveTreeNode.prefab";
private const string PlayerHudPrefabPath = "Assets/_Game/Prefabs/UI/UI_PlayerResources.prefab";
private const string PreferredFontAssetPath = "Assets/_Game/Fonts/TMP/TMP_MaruBuri.asset";
private const string NormalIconSpritePath = "Assets/_Game/Icons/Icon_Passive_Normal.png";
private const string SpecialIconSpritePath = "Assets/_Game/Icons/Icon_Passive_Special.png";
private const string PassiveCatalogAssetPath = "Assets/_Game/Data/Passives/Data_PassivePrototypeCatalog.asset";
private static readonly Color PanelBackgroundColor = new(0.07f, 0.07f, 0.10f, 0.98f);
private static readonly Color SectionBackgroundColor = new(0.12f, 0.12f, 0.16f, 0.96f);
private static readonly Color SectionOverlayColor = new(0f, 0f, 0f, 0.12f);
private static readonly Color ToggleButtonColor = new(0.18f, 0.21f, 0.16f, 0.96f);
private static readonly Color CloseButtonColor = new(0.34f, 0.20f, 0.20f, 0.96f);
private static readonly Color ButtonColor = new(0.20f, 0.20f, 0.24f, 0.96f);
private static readonly Color TextColor = new(0.90f, 0.88f, 0.82f, 1f);
private static readonly Color NodeColor = new(0.16f, 0.16f, 0.18f, 0.98f);
private static readonly Color NodeFillColor = new(0.08f, 0.08f, 0.10f, 0.98f);
private static readonly Color NodeOutlineColor = new(0f, 0f, 0f, 0.45f);
[MenuItem("Tools/Colosseum/Passives/Rebuild Passive UI Prefabs")]
public static void RebuildPassiveUiPrefabs()
{
EnsureFolders();
PassiveTreeNodeView nodePrefab = BuildNodePrefab();
PassiveTreeViewReferences viewPrefab = BuildViewPrefab();
BindPlayerHudPrefab(viewPrefab, nodePrefab);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("[PassiveTreeUiPrefabBuilder] 패시브 UI 프리팹 재생성을 완료했습니다.");
}
private static void EnsureFolders()
{
if (!AssetDatabase.IsValidFolder("Assets/_Game/Prefabs/UI"))
{
AssetDatabase.CreateFolder("Assets/_Game/Prefabs", "UI");
}
if (!AssetDatabase.IsValidFolder(PrefabFolder))
{
AssetDatabase.CreateFolder("Assets/_Game/Prefabs/UI", "PassiveTree");
}
}
private static PassiveTreeNodeView BuildNodePrefab()
{
GameObject root = new("UI_PassiveTreeNode", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button), typeof(Outline), typeof(PassiveTreeNodeView));
try
{
RectTransform rect = root.GetComponent<RectTransform>();
rect.sizeDelta = new Vector2(62f, 62f);
Image background = root.GetComponent<Image>();
background.sprite = LoadPassiveNodeSprite(false);
background.type = Image.Type.Simple;
background.preserveAspect = true;
background.color = NodeColor;
Outline outline = root.GetComponent<Outline>();
outline.effectColor = NodeOutlineColor;
outline.effectDistance = new Vector2(2f, 2f);
Button button = root.GetComponent<Button>();
button.targetGraphic = background;
GameObject fillObject = CreateUiObject("Fill", root.transform, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
RectTransform fillRect = fillObject.GetComponent<RectTransform>();
StretchRect(fillRect);
fillRect.offsetMin = new Vector2(17f, 17f);
fillRect.offsetMax = new Vector2(-17f, -17f);
Image fillImage = fillObject.GetComponent<Image>();
fillImage.sprite = GetBuiltinSprite();
fillImage.type = Image.Type.Sliced;
fillImage.color = NodeFillColor;
fillImage.raycastTarget = false;
GameObject innerObject = CreateUiObject("InnerIcon", root.transform, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
RectTransform innerRect = innerObject.GetComponent<RectTransform>();
StretchRect(innerRect);
innerRect.offsetMin = new Vector2(9f, 9f);
innerRect.offsetMax = new Vector2(-9f, -9f);
Image innerImage = innerObject.GetComponent<Image>();
innerImage.sprite = LoadPassiveNodeSprite(false);
innerImage.type = Image.Type.Simple;
innerImage.preserveAspect = true;
innerImage.color = new Color(0.32f, 0.32f, 0.34f, 0.36f);
innerImage.raycastTarget = false;
PassiveTreeNodeView references = root.GetComponent<PassiveTreeNodeView>();
SetSerializedReference(references, "rootRect", rect);
SetSerializedReference(references, "backgroundImage", background);
SetSerializedReference(references, "fillImage", fillImage);
SetSerializedReference(references, "innerImage", innerImage);
SetSerializedReference(references, "button", button);
SetSerializedReference(references, "outline", outline);
GameObject saved = SavePrefab(root, NodePrefabPath);
return saved.GetComponent<PassiveTreeNodeView>();
}
finally
{
Object.DestroyImmediate(root);
}
}
private static PassiveTreeViewReferences BuildViewPrefab()
{
GameObject root = new("UI_PassiveTreeView", typeof(RectTransform), typeof(PassiveTreeViewReferences));
try
{
RectTransform rootRect = root.GetComponent<RectTransform>();
StretchRect(rootRect);
Button toggleButton = CreateButton(root.transform, "Button_PassiveTree", "패시브", new Vector2(110f, 40f), 20f, FontStyles.Bold, ToggleButtonColor, TextAnchor.MiddleCenter);
RectTransform toggleRect = toggleButton.GetComponent<RectTransform>();
toggleRect.anchorMin = new Vector2(1f, 0f);
toggleRect.anchorMax = new Vector2(1f, 0f);
toggleRect.pivot = new Vector2(1f, 0f);
toggleRect.anchoredPosition = new Vector2(-132f, 48f);
GameObject overlayRootObject = CreateUiObject("OverlayRoot", root.transform, typeof(RectTransform));
RectTransform overlayRootRect = overlayRootObject.GetComponent<RectTransform>();
StretchRect(overlayRootRect);
overlayRootObject.SetActive(false);
Image backdrop = AddImage(overlayRootObject, "Backdrop", new Color(0f, 0f, 0f, 0.52f));
StretchRect(backdrop.rectTransform);
GameObject panelObject = CreateUiObject("Panel", overlayRootObject.transform, typeof(RectTransform), typeof(Image), typeof(VerticalLayoutGroup));
RectTransform panelRect = panelObject.GetComponent<RectTransform>();
panelRect.anchorMin = new Vector2(0.5f, 0.5f);
panelRect.anchorMax = new Vector2(0.5f, 0.5f);
panelRect.pivot = new Vector2(0.5f, 0.5f);
panelRect.sizeDelta = new Vector2(1440f, 900f);
Image panelImage = panelObject.GetComponent<Image>();
panelImage.sprite = GetBuiltinSprite();
panelImage.type = Image.Type.Sliced;
panelImage.color = PanelBackgroundColor;
VerticalLayoutGroup panelLayout = panelObject.GetComponent<VerticalLayoutGroup>();
panelLayout.padding = new RectOffset(22, 22, 22, 22);
panelLayout.spacing = 16f;
panelLayout.childAlignment = TextAnchor.UpperLeft;
panelLayout.childControlWidth = true;
panelLayout.childControlHeight = true;
panelLayout.childForceExpandWidth = true;
panelLayout.childForceExpandHeight = false;
BuildHeader(panelObject.transform, out TextMeshProUGUI pointsSummaryText, out Button closeButton);
BuildBody(panelObject.transform,
out TextMeshProUGUI selectionSummaryText,
out Button nonePresetButton,
out Button defensePresetButton,
out Button supportPresetButton,
out Button attackPresetButton,
out Button clearButton,
out RectTransform graphRect,
out RectTransform connectionLayer,
out RectTransform nodeLayer,
out RectTransform detailContent,
out TextMeshProUGUI detailText,
out Button selectNodeButton,
out TextMeshProUGUI selectNodeButtonLabel);
BuildFooter(panelObject.transform, out TextMeshProUGUI statusText);
PassiveTreeViewReferences references = root.GetComponent<PassiveTreeViewReferences>();
SetSerializedReference(references, "rootRect", rootRect);
SetSerializedReference(references, "overlayRoot", overlayRootObject);
SetSerializedReference(references, "panelRect", panelRect);
SetSerializedReference(references, "toggleButton", toggleButton);
SetSerializedReference(references, "toggleButtonLabel", toggleButton.GetComponentInChildren<TextMeshProUGUI>(true));
SetSerializedReference(references, "pointsSummaryText", pointsSummaryText);
SetSerializedReference(references, "closeButton", closeButton);
SetSerializedReference(references, "selectionSummaryText", selectionSummaryText);
SetSerializedReference(references, "nonePresetButton", nonePresetButton);
SetSerializedReference(references, "defensePresetButton", defensePresetButton);
SetSerializedReference(references, "supportPresetButton", supportPresetButton);
SetSerializedReference(references, "attackPresetButton", attackPresetButton);
SetSerializedReference(references, "clearButton", clearButton);
SetSerializedReference(references, "graphRect", graphRect);
SetSerializedReference(references, "connectionLayer", connectionLayer);
SetSerializedReference(references, "nodeLayer", nodeLayer);
SetSerializedReference(references, "detailContent", detailContent);
SetSerializedReference(references, "detailText", detailText);
SetSerializedReference(references, "selectNodeButton", selectNodeButton);
SetSerializedReference(references, "selectNodeButtonLabel", selectNodeButtonLabel);
SetSerializedReference(references, "statusText", statusText);
GameObject saved = SavePrefab(root, ViewPrefabPath);
return saved.GetComponent<PassiveTreeViewReferences>();
}
finally
{
Object.DestroyImmediate(root);
}
}
private static void BindPlayerHudPrefab(PassiveTreeViewReferences viewPrefab, PassiveTreeNodeView nodePrefab)
{
GameObject root = PrefabUtility.LoadPrefabContents(PlayerHudPrefabPath);
try
{
PassiveTreeUI passiveTreeUi = root.GetComponent<PassiveTreeUI>();
if (passiveTreeUi == null)
{
passiveTreeUi = root.AddComponent<PassiveTreeUI>();
}
SerializedObject passiveTreeUiObject = new(passiveTreeUi);
passiveTreeUiObject.FindProperty("viewPrefab").objectReferenceValue = viewPrefab;
passiveTreeUiObject.FindProperty("nodePrefab").objectReferenceValue = nodePrefab;
passiveTreeUiObject.FindProperty("passivePrototypeCatalog").objectReferenceValue = LoadPassivePrototypeCatalog();
passiveTreeUiObject.FindProperty("normalNodeSprite").objectReferenceValue = LoadPassiveNodeSprite(false);
passiveTreeUiObject.FindProperty("specialNodeSprite").objectReferenceValue = LoadPassiveNodeSprite(true);
passiveTreeUiObject.FindProperty("innerNodeSprite").objectReferenceValue = LoadPassiveNodeSprite(false);
passiveTreeUiObject.ApplyModifiedPropertiesWithoutUndo();
PrefabUtility.SaveAsPrefabAsset(root, PlayerHudPrefabPath);
}
finally
{
PrefabUtility.UnloadPrefabContents(root);
}
}
private static void BuildHeader(Transform parent, out TextMeshProUGUI pointsSummaryText, out Button closeButton)
{
GameObject headerObject = CreateUiObject("Header", parent, typeof(RectTransform), typeof(Image), typeof(LayoutElement), typeof(HorizontalLayoutGroup));
RectTransform headerRect = headerObject.GetComponent<RectTransform>();
headerObject.GetComponent<LayoutElement>().minHeight = 54f;
Image headerImage = headerObject.GetComponent<Image>();
headerImage.sprite = GetBuiltinSprite();
headerImage.type = Image.Type.Sliced;
headerImage.color = SectionBackgroundColor;
HorizontalLayoutGroup headerLayout = headerObject.GetComponent<HorizontalLayoutGroup>();
headerLayout.padding = new RectOffset(14, 14, 8, 8);
headerLayout.spacing = 10f;
headerLayout.childAlignment = TextAnchor.MiddleLeft;
headerLayout.childControlWidth = true;
headerLayout.childControlHeight = true;
headerLayout.childForceExpandWidth = false;
headerLayout.childForceExpandHeight = false;
GameObject titleGroup = CreateUiObject("TitleGroup", headerObject.transform, typeof(RectTransform), typeof(LayoutElement), typeof(VerticalLayoutGroup));
titleGroup.GetComponent<LayoutElement>().flexibleWidth = 1f;
VerticalLayoutGroup titleLayout = titleGroup.GetComponent<VerticalLayoutGroup>();
titleLayout.spacing = 0f;
titleLayout.childAlignment = TextAnchor.MiddleLeft;
titleLayout.childControlWidth = true;
titleLayout.childControlHeight = true;
titleLayout.childForceExpandWidth = true;
titleLayout.childForceExpandHeight = false;
TextMeshProUGUI title = CreateText(titleGroup.transform, "Label_Title", 24f, TextAlignmentOptions.Left, FontStyles.Bold);
title.text = "패시브 트리";
title.textWrappingMode = TextWrappingModes.NoWrap;
pointsSummaryText = CreateText(headerObject.transform, "Label_Points", 17f, TextAlignmentOptions.Right, FontStyles.Normal);
pointsSummaryText.text = string.Empty;
pointsSummaryText.textWrappingMode = TextWrappingModes.NoWrap;
pointsSummaryText.gameObject.AddComponent<LayoutElement>().preferredWidth = 420f;
closeButton = CreateButton(headerObject.transform, "Button_Close", "닫기", new Vector2(84f, 34f), 18f, FontStyles.Bold, CloseButtonColor, TextAnchor.MiddleCenter);
}
private static void BuildBody(
Transform parent,
out TextMeshProUGUI selectionSummaryText,
out Button nonePresetButton,
out Button defensePresetButton,
out Button supportPresetButton,
out Button attackPresetButton,
out Button clearButton,
out RectTransform graphRect,
out RectTransform connectionLayer,
out RectTransform nodeLayer,
out RectTransform detailContent,
out TextMeshProUGUI detailText,
out Button selectNodeButton,
out TextMeshProUGUI selectNodeButtonLabel)
{
GameObject bodyObject = CreateUiObject("Body", parent, typeof(RectTransform), typeof(LayoutElement), typeof(HorizontalLayoutGroup));
LayoutElement bodyLayoutElement = bodyObject.GetComponent<LayoutElement>();
bodyLayoutElement.flexibleHeight = 1f;
HorizontalLayoutGroup bodyLayout = bodyObject.GetComponent<HorizontalLayoutGroup>();
bodyLayout.spacing = 16f;
bodyLayout.childAlignment = TextAnchor.UpperLeft;
bodyLayout.childControlWidth = true;
bodyLayout.childControlHeight = true;
bodyLayout.childForceExpandWidth = true;
bodyLayout.childForceExpandHeight = true;
RectTransform graphSection = CreateSectionRoot(bodyObject.transform, "Section_Graph", 0f);
graphSection.GetComponent<LayoutElement>().flexibleWidth = 1f;
graphSection.GetComponent<LayoutElement>().minWidth = 820f;
CreateSectionTitle(graphSection, "트리 그래프");
GameObject graphSurface = CreateUiObject("GraphSurface", graphSection, typeof(RectTransform), typeof(LayoutElement), typeof(Image));
graphRect = graphSurface.GetComponent<RectTransform>();
LayoutElement graphLayout = graphSurface.GetComponent<LayoutElement>();
graphLayout.flexibleHeight = 1f;
graphLayout.minHeight = 640f;
Image graphImage = graphSurface.GetComponent<Image>();
graphImage.sprite = GetBuiltinSprite();
graphImage.type = Image.Type.Sliced;
graphImage.color = SectionOverlayColor;
connectionLayer = CreateUiObject("Connections", graphSurface.transform, typeof(RectTransform)).GetComponent<RectTransform>();
StretchRect(connectionLayer);
nodeLayer = CreateUiObject("Nodes", graphSurface.transform, typeof(RectTransform)).GetComponent<RectTransform>();
StretchRect(nodeLayer);
RectTransform rightSection = CreateSectionRoot(bodyObject.transform, "Section_Right", 360f);
CreateSectionTitle(rightSection, "노드 상세");
RectTransform detailScroll = CreateScrollView(rightSection, "Scroll_Detail", out detailContent);
LayoutElement detailScrollLayout = detailScroll.gameObject.AddComponent<LayoutElement>();
detailScrollLayout.flexibleHeight = 1f;
detailScrollLayout.minHeight = 300f;
detailText = CreateText(detailContent, "Label_Detail", 19f, TextAlignmentOptions.TopLeft, FontStyles.Normal);
detailText.textWrappingMode = TextWrappingModes.Normal;
detailText.overflowMode = TextOverflowModes.Overflow;
detailText.margin = new Vector4(18f, 8f, 18f, 14f);
detailText.extraPadding = true;
detailText.gameObject.AddComponent<LayoutElement>().minHeight = 300f;
selectNodeButton = CreateButton(rightSection, "Button_SelectNode", "노드 선택", new Vector2(0f, 58f), 21f, FontStyles.Bold, ButtonColor, TextAnchor.MiddleCenter);
selectNodeButtonLabel = selectNodeButton.GetComponentInChildren<TextMeshProUGUI>(true);
CreateDivider(rightSection, "Divider_Actions");
selectionSummaryText = CreateInfoText(rightSection, "Label_SelectionSummary", 132f);
CreateSectionTitle(rightSection, "프리셋");
GameObject presetGrid = CreateUiObject("PresetGrid", rightSection, typeof(RectTransform), typeof(GridLayoutGroup));
GridLayoutGroup presetLayout = presetGrid.GetComponent<GridLayoutGroup>();
presetLayout.cellSize = new Vector2(154f, 56f);
presetLayout.spacing = new Vector2(8f, 8f);
presetLayout.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
presetLayout.constraintCount = 2;
nonePresetButton = CreateButton(presetGrid.transform, "Button_Preset_None", "패시브 없음", Vector2.zero, 18f, FontStyles.Normal, ButtonColor, TextAnchor.MiddleCenter);
defensePresetButton = CreateButton(presetGrid.transform, "Button_Preset_Defense", "방어형 패시브", Vector2.zero, 18f, FontStyles.Normal, ButtonColor, TextAnchor.MiddleCenter);
supportPresetButton = CreateButton(presetGrid.transform, "Button_Preset_Support", "지원형 패시브", Vector2.zero, 18f, FontStyles.Normal, ButtonColor, TextAnchor.MiddleCenter);
attackPresetButton = CreateButton(presetGrid.transform, "Button_Preset_Attack", "공격형 패시브", Vector2.zero, 18f, FontStyles.Normal, ButtonColor, TextAnchor.MiddleCenter);
clearButton = CreateButton(rightSection, "Button_Clear", "전체 초기화", new Vector2(0f, 52f), 18f, FontStyles.Bold, ButtonColor, TextAnchor.MiddleCenter);
}
private static void BuildFooter(Transform parent, out TextMeshProUGUI statusText)
{
GameObject footerObject = CreateUiObject("Footer", parent, typeof(RectTransform), typeof(Image), typeof(LayoutElement), typeof(HorizontalLayoutGroup));
footerObject.GetComponent<LayoutElement>().minHeight = 42f;
Image footerImage = footerObject.GetComponent<Image>();
footerImage.sprite = GetBuiltinSprite();
footerImage.type = Image.Type.Sliced;
footerImage.color = SectionBackgroundColor;
HorizontalLayoutGroup footerLayout = footerObject.GetComponent<HorizontalLayoutGroup>();
footerLayout.padding = new RectOffset(16, 16, 8, 8);
footerLayout.childAlignment = TextAnchor.MiddleLeft;
footerLayout.childControlWidth = true;
footerLayout.childControlHeight = true;
footerLayout.childForceExpandWidth = true;
footerLayout.childForceExpandHeight = true;
statusText = CreateText(footerObject.transform, "Label_Status", 17f, TextAlignmentOptions.MidlineLeft, FontStyles.Normal);
statusText.text = string.Empty;
statusText.textWrappingMode = TextWrappingModes.NoWrap;
statusText.overflowMode = TextOverflowModes.Ellipsis;
footerObject.SetActive(true);
}
private static RectTransform CreateSectionRoot(Transform parent, string name, float preferredWidth)
{
GameObject sectionObject = CreateUiObject(name, parent, typeof(RectTransform), typeof(Image), typeof(LayoutElement), typeof(VerticalLayoutGroup));
RectTransform rect = sectionObject.GetComponent<RectTransform>();
LayoutElement layoutElement = sectionObject.GetComponent<LayoutElement>();
layoutElement.flexibleHeight = 1f;
layoutElement.preferredWidth = preferredWidth;
if (preferredWidth > 0f)
{
layoutElement.minWidth = preferredWidth;
layoutElement.flexibleWidth = 0f;
}
Image sectionImage = sectionObject.GetComponent<Image>();
sectionImage.sprite = GetBuiltinSprite();
sectionImage.type = Image.Type.Sliced;
sectionImage.color = SectionBackgroundColor;
VerticalLayoutGroup layout = sectionObject.GetComponent<VerticalLayoutGroup>();
layout.padding = new RectOffset(16, 16, 16, 16);
layout.spacing = 12f;
layout.childAlignment = TextAnchor.UpperLeft;
layout.childControlWidth = true;
layout.childControlHeight = true;
layout.childForceExpandWidth = true;
layout.childForceExpandHeight = false;
return rect;
}
private static TextMeshProUGUI CreateSectionTitle(RectTransform parent, string text)
{
TextMeshProUGUI label = CreateText(parent, "Label_Title", 28f, TextAlignmentOptions.Left, FontStyles.Bold);
label.text = text;
return label;
}
private static TextMeshProUGUI CreateInfoText(RectTransform parent, string name, float minHeight)
{
GameObject containerObject = CreateUiObject($"{name}_Container", parent, typeof(RectTransform), typeof(Image), typeof(LayoutElement), typeof(VerticalLayoutGroup));
RectTransform containerRect = containerObject.GetComponent<RectTransform>();
containerObject.GetComponent<LayoutElement>().minHeight = minHeight;
Image containerImage = containerObject.GetComponent<Image>();
containerImage.sprite = GetBuiltinSprite();
containerImage.type = Image.Type.Sliced;
containerImage.color = SectionOverlayColor;
VerticalLayoutGroup layout = containerObject.GetComponent<VerticalLayoutGroup>();
layout.padding = new RectOffset(16, 16, 12, 12);
layout.childAlignment = TextAnchor.UpperLeft;
layout.childControlWidth = true;
layout.childControlHeight = true;
layout.childForceExpandWidth = true;
layout.childForceExpandHeight = false;
TextMeshProUGUI label = CreateText(containerRect, name, 18f, TextAlignmentOptions.TopLeft, FontStyles.Normal);
label.textWrappingMode = TextWrappingModes.Normal;
label.overflowMode = TextOverflowModes.Overflow;
label.margin = new Vector4(14f, 6f, 14f, 10f);
label.extraPadding = true;
return label;
}
private static void CreateDivider(Transform parent, string name)
{
GameObject dividerObject = CreateUiObject(name, parent, typeof(RectTransform), typeof(Image), typeof(LayoutElement));
dividerObject.GetComponent<Image>().color = new Color(0f, 0f, 0f, 0.22f);
LayoutElement layout = dividerObject.GetComponent<LayoutElement>();
layout.minHeight = 2f;
layout.preferredHeight = 2f;
}
private static RectTransform CreateScrollView(Transform parent, string name, out RectTransform content)
{
GameObject root = CreateUiObject(name, parent, typeof(RectTransform), typeof(Image), typeof(ScrollRect));
RectTransform rootRect = root.GetComponent<RectTransform>();
Image rootImage = root.GetComponent<Image>();
rootImage.sprite = GetBuiltinSprite();
rootImage.type = Image.Type.Sliced;
rootImage.color = SectionOverlayColor;
ScrollRect scrollRect = root.GetComponent<ScrollRect>();
scrollRect.horizontal = false;
scrollRect.vertical = true;
scrollRect.scrollSensitivity = 30f;
GameObject viewportObject = CreateUiObject("Viewport", root.transform, typeof(RectTransform), typeof(Image), typeof(Mask));
RectTransform viewport = viewportObject.GetComponent<RectTransform>();
StretchRect(viewport);
viewportObject.GetComponent<Image>().color = new Color(1f, 1f, 1f, 0.01f);
viewportObject.GetComponent<Mask>().showMaskGraphic = false;
GameObject contentObject = CreateUiObject("Content", viewportObject.transform, typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter));
content = contentObject.GetComponent<RectTransform>();
content.anchorMin = new Vector2(0f, 1f);
content.anchorMax = new Vector2(1f, 1f);
content.pivot = new Vector2(0f, 1f);
VerticalLayoutGroup contentLayout = contentObject.GetComponent<VerticalLayoutGroup>();
contentLayout.padding = new RectOffset(16, 16, 14, 14);
contentLayout.spacing = 8f;
contentLayout.childAlignment = TextAnchor.UpperLeft;
contentLayout.childControlWidth = true;
contentLayout.childControlHeight = true;
contentLayout.childForceExpandWidth = true;
contentLayout.childForceExpandHeight = false;
ContentSizeFitter fitter = contentObject.GetComponent<ContentSizeFitter>();
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
scrollRect.viewport = viewport;
scrollRect.content = content;
return rootRect;
}
private static Button CreateButton(Transform parent, string name, string labelText, Vector2 size, float fontSize, FontStyles fontStyle, Color color, TextAnchor anchor)
{
GameObject buttonObject = CreateUiObject(name, parent, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button));
RectTransform rect = buttonObject.GetComponent<RectTransform>();
if (size.sqrMagnitude > 0f)
{
rect.sizeDelta = size;
}
Image image = buttonObject.GetComponent<Image>();
image.sprite = GetBuiltinSprite();
image.type = Image.Type.Sliced;
image.color = color;
Button button = buttonObject.GetComponent<Button>();
button.targetGraphic = image;
TextMeshProUGUI label = CreateText(buttonObject.transform, "Label", fontSize, ToTmpAlignment(anchor), fontStyle);
label.text = labelText;
StretchRect(label.rectTransform);
label.color = Color.white;
label.textWrappingMode = TextWrappingModes.Normal;
return button;
}
private static TextMeshProUGUI CreateText(Transform parent, string name, float fontSize, TextAlignmentOptions alignment, FontStyles fontStyle)
{
GameObject textObject = CreateUiObject(name, parent, typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
RectTransform rect = textObject.GetComponent<RectTransform>();
rect.anchorMin = new Vector2(0f, 1f);
rect.anchorMax = new Vector2(1f, 1f);
rect.pivot = new Vector2(0f, 1f);
TextMeshProUGUI text = textObject.GetComponent<TextMeshProUGUI>();
text.font = LoadPreferredFontAsset();
text.fontSize = fontSize;
text.fontStyle = fontStyle;
text.color = TextColor;
text.alignment = alignment;
text.textWrappingMode = TextWrappingModes.Normal;
text.extraPadding = true;
text.raycastTarget = false;
return text;
}
private static TMP_FontAsset LoadPreferredFontAsset()
{
TMP_FontAsset preferredFont = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(PreferredFontAssetPath);
if (preferredFont != null)
return preferredFont;
return TMP_Settings.defaultFontAsset;
}
private static Sprite LoadPassiveNodeSprite(bool special)
{
string assetPath = special ? SpecialIconSpritePath : NormalIconSpritePath;
return AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
}
private static PassivePrototypeCatalogData LoadPassivePrototypeCatalog()
{
return AssetDatabase.LoadAssetAtPath<PassivePrototypeCatalogData>(PassiveCatalogAssetPath);
}
private static Image AddImage(GameObject gameObject, string childName, Color color)
{
GameObject imageObject = CreateUiObject(childName, gameObject.transform, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
Image image = imageObject.GetComponent<Image>();
image.sprite = GetBuiltinSprite();
image.type = Image.Type.Sliced;
image.color = color;
return image;
}
private static GameObject CreateUiObject(string name, Transform parent, params System.Type[] componentTypes)
{
GameObject gameObject = new(name, componentTypes);
gameObject.transform.SetParent(parent, false);
return gameObject;
}
private static void StretchRect(RectTransform rectTransform)
{
rectTransform.anchorMin = Vector2.zero;
rectTransform.anchorMax = Vector2.one;
rectTransform.offsetMin = Vector2.zero;
rectTransform.offsetMax = Vector2.zero;
rectTransform.anchoredPosition = Vector2.zero;
rectTransform.sizeDelta = Vector2.zero;
}
private static void SetSerializedReference(Object target, string propertyName, Object value)
{
SerializedObject serializedObject = new(target);
serializedObject.FindProperty(propertyName).objectReferenceValue = value;
serializedObject.ApplyModifiedPropertiesWithoutUndo();
}
private static GameObject SavePrefab(GameObject root, string path)
{
PrefabUtility.SaveAsPrefabAsset(root, path);
return AssetDatabase.LoadAssetAtPath<GameObject>(path);
}
private static Sprite GetBuiltinSprite()
{
return AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/UISprite.psd");
}
private static TextAlignmentOptions ToTmpAlignment(TextAnchor anchor)
{
return anchor switch
{
TextAnchor.MiddleCenter => TextAlignmentOptions.Center,
TextAnchor.MiddleLeft => TextAlignmentOptions.MidlineLeft,
_ => TextAlignmentOptions.Center,
};
}
}
}