feat: 패시브 트리 레이아웃 자동화 및 UI 정리

- 패시브 트리 노드 배치를 삼각형 기반 자동 배치 구조로 전환하고 축 및 브릿지 반경을 재정리\n- 패시브 UI 프리팹과 런타임 렌더링을 수정해 노드 겹침, 링크 관통, 상태별 간격 변화, 하단 여백 문제를 정리\n- 프로토타입 패시브 노드, 트리, 프리셋 자산을 재생성해 최신 레이아웃과 확장 노드 구성을 반영
This commit is contained in:
2026-03-27 04:43:14 +09:00
parent 786a38d72d
commit d78e0edabd
45 changed files with 5228 additions and 6276 deletions

View File

@@ -23,6 +23,7 @@ namespace Colosseum.UI
public RectTransform RectTransform;
public Button Button;
public Image Image;
public Image FillImage;
public Image InnerImage;
public Outline Outline;
}
@@ -69,9 +70,10 @@ namespace Colosseum.UI
[SerializeField] private Color statusErrorColor = new Color(1f, 0.52f, 0.45f, 1f);
[SerializeField] private Color lineColor = new Color(0.72f, 0.67f, 0.53f, 0.4f);
[SerializeField] private Color activeLineColor = new Color(0.98f, 0.9f, 0.7f, 0.92f);
[SerializeField] private float graphCenterYOffset = -118f;
private const float LeftPanelWidth = 292f;
private const float RightPanelWidth = 308f;
private const float GraphPadding = 52f;
private const float GraphPadding = 24f;
private const float ConnectionThickness = 7f;
private readonly Dictionary<PassiveNodeData, NodeVisual> nodeVisuals = new();
@@ -399,8 +401,8 @@ namespace Colosseum.UI
Rect canvasRect = canvasRectTransform.rect;
panelRectTransform.sizeDelta = new Vector2(
Mathf.Max(1120f, canvasRect.width - 48f),
Mathf.Max(680f, canvasRect.height - 48f));
Mathf.Max(1160f, canvasRect.width - 24f),
Mathf.Max(720f, canvasRect.height - 24f));
}
private void CreateToggleButton()
@@ -884,6 +886,11 @@ namespace Colosseum.UI
image.sprite = GetNodeSprite(node);
image.type = Image.Type.Simple;
image.preserveAspect = true;
Image fillImage = nodeView.FillImage;
if (fillImage != null)
{
fillImage.type = Image.Type.Sliced;
}
Image innerImage = nodeView.InnerImage;
if (innerImage != null)
{
@@ -913,6 +920,7 @@ namespace Colosseum.UI
RectTransform = rectTransform,
Button = button,
Image = image,
FillImage = fillImage,
InnerImage = innerImage,
Outline = nodeView.Outline != null ? nodeView.Outline : button.GetComponent<Outline>(),
};
@@ -943,7 +951,11 @@ namespace Colosseum.UI
}
StringBuilder builder = new StringBuilder();
builder.AppendLine($"<size=30><b>{focusedNode.DisplayName}</b></size>");
if (!string.IsNullOrWhiteSpace(focusedNode.DisplayName))
{
builder.AppendLine($"<size=30><b>{focusedNode.DisplayName}</b></size>");
}
builder.AppendLine($"<size=18>{PassivePresentationUtility.GetBranchLabel(focusedNode.Branch)} | {PassivePresentationUtility.GetNodeKindLabel(focusedNode.NodeKind)} | 축 {PassivePresentationUtility.GetAxisSummary(focusedNode.AxisMask)}</size>");
builder.AppendLine($"<size=18>비용 {focusedNode.Cost}</size>");
@@ -1091,12 +1103,16 @@ namespace Colosseum.UI
visual.Button.interactable = true;
visual.Image.sprite = GetNodeSprite(node);
visual.Image.color = fillColor;
if (visual.FillImage != null)
{
visual.FillImage.color = GetNodeFillColor(selected, selectable, focused);
}
if (visual.InnerImage != null)
{
visual.InnerImage.sprite = GetInnerNodeSprite();
visual.InnerImage.color = GetInnerNodeColor(selected, selectable, focused);
}
visual.RectTransform.localScale = selected ? new Vector3(1.05f, 1.05f, 1f) : focused ? new Vector3(1.03f, 1.03f, 1f) : Vector3.one;
visual.RectTransform.localScale = Vector3.one;
Outline outline = visual.Outline != null ? visual.Outline : visual.Button.GetComponent<Outline>();
if (outline == null)
@@ -1125,12 +1141,11 @@ namespace Colosseum.UI
private Vector2 GetGraphAnchoredPosition(Vector2 layoutPosition)
{
Rect rect = graphRectTransform != null ? graphRectTransform.rect : new Rect(0f, 0f, 640f, 480f);
float halfWidth = Mathf.Max(0f, rect.width * 0.5f - GraphPadding);
float halfHeight = Mathf.Max(0f, rect.height * 0.5f - GraphPadding);
float halfExtent = Mathf.Max(0f, Mathf.Min(rect.width, rect.height) * 0.5f - GraphPadding);
return new Vector2(
Mathf.Clamp(layoutPosition.x, -1f, 1f) * halfWidth,
Mathf.Clamp(layoutPosition.y, -1f, 1f) * halfHeight);
Mathf.Clamp(layoutPosition.x, -1f, 1f) * halfExtent,
Mathf.Clamp(layoutPosition.y, -1f, 1f) * halfExtent + graphCenterYOffset);
}
private static string BuildConnectionKey(PassiveNodeData leftNode, PassiveNodeData rightNode)
@@ -1142,7 +1157,7 @@ namespace Colosseum.UI
private Sprite GetNodeSprite(PassiveNodeData node)
{
bool useSpecialSprite = node != null && (node.NodeKind == PassiveNodeKind.Hub || node.NodeKind == PassiveNodeKind.Bridge || node.NodeKind == PassiveNodeKind.Capstone);
bool useSpecialSprite = node != null && (node.NodeKind == PassiveNodeKind.Bridge || node.NodeKind == PassiveNodeKind.Capstone);
Sprite fallbackSprite = useSpecialSprite ? normalNodeSprite : specialNodeSprite;
Sprite preferredSprite = useSpecialSprite ? specialNodeSprite : normalNodeSprite;
return preferredSprite != null ? preferredSprite : fallbackSprite;
@@ -1170,6 +1185,20 @@ namespace Colosseum.UI
return new Color(0.3f, 0.3f, 0.32f, 0.34f);
}
private static Color GetNodeFillColor(bool selected, bool selectable, bool focused)
{
if (selected)
return new Color(0.09f, 0.09f, 0.11f, 0.96f);
if (focused)
return new Color(0.08f, 0.08f, 0.10f, 0.94f);
if (selectable)
return new Color(0.07f, 0.07f, 0.09f, 0.92f);
return new Color(0.06f, 0.06f, 0.08f, 0.90f);
}
private Color GetNodeBaseColor(PassiveNodeData node)
{
if (node.Branch == PassiveNodeBranch.Bridge)
@@ -1198,19 +1227,21 @@ namespace Colosseum.UI
{
return node.NodeKind switch
{
PassiveNodeKind.Hub => new Vector2(88f, 88f),
PassiveNodeKind.Capstone => new Vector2(78f, 78f),
PassiveNodeKind.Bridge => new Vector2(64f, 64f),
_ => new Vector2(70f, 70f),
PassiveNodeKind.Hub => new Vector2(78f, 78f),
PassiveNodeKind.Capstone => new Vector2(70f, 70f),
PassiveNodeKind.Bridge => new Vector2(56f, 56f),
_ => new Vector2(62f, 62f),
};
}
private static float GetConnectionInset(PassiveNodeData node)
{
Vector2 size = GetNodeSize(node);
float radius = Mathf.Min(size.x, size.y) * 0.42f;
float radius = Mathf.Min(size.x, size.y) * 0.5f + ConnectionThickness * 0.5f + 2f;
if (node != null && node.NodeKind == PassiveNodeKind.Bridge)
radius += 3f;
radius += 4f;
else if (node != null && (node.NodeKind == PassiveNodeKind.Hub || node.NodeKind == PassiveNodeKind.Capstone))
radius += 2f;
return radius;
}
@@ -1380,7 +1411,13 @@ namespace Colosseum.UI
Transform footerTransform = statusText.transform.parent;
if (footerTransform != null)
{
footerTransform.gameObject.SetActive(hasMessage);
Image footerImage = footerTransform.GetComponent<Image>();
if (footerImage != null)
{
Color color = sectionBackgroundColor;
color.a = hasMessage ? sectionBackgroundColor.a : 0f;
footerImage.color = color;
}
}
statusText.text = hasMessage ? lastStatusMessage : string.Empty;