feat: 패시브 트리 프로토타입 구현

- 패시브 트리/노드/프리셋 데이터와 카탈로그 참조 구조를 추가하고 Resources 의존을 Data/Passives 자산 구조로 정리
- 플레이어 런타임, 전투 계수, 프리셋 적용, 멀티플레이 동기화 경로에 패시브 적용 로직 연결
- 프리팹 기반 패시브 트리 UI와 노드 아이콘/프리셋/상세 패널 흐름을 추가하고 HUD에 연동
- 패시브 디버그/부트스트랩 메뉴와 UI 프리팹 재생성 경로를 추가
This commit is contained in:
2026-03-26 22:59:39 +09:00
parent 13d1949ded
commit 8d1e97d01a
89 changed files with 10848 additions and 68 deletions

View File

@@ -0,0 +1,630 @@
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 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(70f, 70f);
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 innerObject = CreateUiObject("InnerIcon", root.transform, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
RectTransform innerRect = innerObject.GetComponent<RectTransform>();
StretchRect(innerRect);
innerRect.offsetMin = new Vector2(10f, 10f);
innerRect.offsetMax = new Vector2(-10f, -10f);
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, "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(1380f, 820f);
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 = 760f;
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 = 560f;
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(false);
}
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,
};
}
}
}