VSClone/Assets/Scripts/View/GameApp.Units.cs

329 lines
23 KiB
C#
Raw Normal View History

using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using VS.Sim;
// 유닛 메뉴(2026-10-29 PD) — 클래시 로얄 「전투 덱」처럼 위 = 덱(프리셋 1·2·3 · 6칸) · 아래 = 유닛 목록(등급 색 카드 · 참고 = 수집 카드 화면)
// 카드를 누르면 유닛 상세(등급 · 이름 · 그림 · 능력치 · 특징 · 덱에 넣기/빼기) · 덱에 빈 칸이 없으면 바꿀 칸을 고른다
// 전투에는 열린 칸(전투 「강화」의 칸 열기)만 나감 · 등급 = battle.json grades(일반 → 고급 → 희귀 → 에픽 → 전설 → 불멸 → 신화 · 별 수 = 등급)
namespace VS.View
{
public partial class GameApp
{
public RectTransform UnitRoot;
RectTransform unitDeckGrid, unitCollGrid, unitDetail, unitSwapBar;
Text unitDeckInfo, unitCollTitle, unitSwapText;
readonly List<Button> presetTabs = new List<Button>();
int swapUnit; // 0 = 아님 · 아니면 덱에 넣을 유닛 id(바꿀 칸 고르는 중)
int detailUnit; // 상세에 띄운 유닛 id
Image udTag, udPortrait, udRole, udTraitIcon; Text udTagText, udName, udCost, udStatus, udTraits, udMainLabel; Button udMain, udPrev, udNext; GameObject udFly;
readonly List<Text> udStatValues = new List<Text>();
const float CardW = 320f, CardH = 350f, CardGap = 24f;
void BuildUnitScreen()
{
UnitRoot = UI.Rect("Units", Root).Fill(0, TabH, 0, 0);
UI.Img(UnitRoot, "Bg", Art.Square, Skin.On ? Pal.Navy : (Color)new Color32(46, 40, 70, 255)).rectTransform.Fill();
if (Skin.Pattern != null) UI.Img(UnitRoot, "Pattern", Skin.Pattern, new Color(1f, 1f, 1f, 0.05f)).rectTransform.Fill();
// 목록(스크롤): 덱 머리줄 · 덱 6칸 · 평균 마나 · 목록 머리줄 · 유닛 카드
var sr = UI.Scroll(UnitRoot, "List", out var content, 14f);
((RectTransform)sr.transform).Fill();
var head = UI.Rect("DeckHead", content); UI.SetLayoutHeight(head, 110);
UI.Txt(head, "T", "전투 덱", 54, Color.white, TextAnchor.MiddleLeft, true).rectTransform.Place(new Vector2(0, 0.5f), new Vector2(0, 0.5f), new Vector2(24, 0), new Vector2(420, 90), new Vector2(0, 0.5f));
for (int i = 0; i < G.DeckPresets; i++)
{
int p = i;
var b = UI.TextBtn(head, "P" + i, (i + 1).ToString(), Pal.Gray, 44, () => SelectPreset(p), out _);
b.GetComponent<RectTransform>().Place(new Vector2(1, 0.5f), new Vector2(1, 0.5f), new Vector2(-24 - (G.DeckPresets - 1 - i) * 126, 0), new Vector2(110, 96), new Vector2(1, 0.5f));
presetTabs.Add(b);
}
unitDeckGrid = Grid(content, "DeckGrid");
UI.SetLayoutHeight(unitDeckGrid, GridH(T.Battle.slotMax));
unitDeckInfo = UI.Txt(content, "DeckInfo", "", 32, new Color32(214, 222, 255, 255), TextAnchor.MiddleCenter, true);
UI.SetLayoutHeight(unitDeckInfo, 64);
unitCollTitle = UI.Txt(content, "CollHead", "", 46, Color.white, TextAnchor.MiddleLeft, true);
UI.SetLayoutHeight(unitCollTitle, 96);
unitCollGrid = Grid(content, "CollGrid");
// 바꿀 칸 고르기 띠(덱이 꽉 찼을 때 「덱에 넣기」)
var bar = UI.Img(UnitRoot, "SwapBar", Skin.Round, new Color(0.12f, 0.1f, 0.24f, 0.96f));
unitSwapBar = bar.rectTransform; unitSwapBar.Place(new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 16), new Vector2(-32, 130), new Vector2(0.5f, 0));
unitSwapText = UI.Txt(unitSwapBar, "T", "", 38, Pal.Yellow, TextAnchor.MiddleLeft, true); unitSwapText.rectTransform.Fill(30, 8, 260, 8);
var cancel = UI.TextBtn(unitSwapBar, "Cancel", "취소", Pal.Gray, 40, () => { swapUnit = 0; ShowUnits(); }, out _);
cancel.GetComponent<RectTransform>().Place(new Vector2(1, 0.5f), new Vector2(1, 0.5f), new Vector2(-24, 0), new Vector2(210, 100), new Vector2(1, 0.5f));
BuildUnitDetail();
UnitRoot.gameObject.SetActive(false);
}
RectTransform Grid(Transform parent, string name)
{
var g = UI.Rect(name, parent);
var gl = g.gameObject.AddComponent<GridLayoutGroup>();
gl.cellSize = new Vector2(CardW, CardH); gl.spacing = new Vector2(CardGap, CardGap); gl.childAlignment = TextAnchor.UpperCenter;
gl.constraint = GridLayoutGroup.Constraint.FixedColumnCount; gl.constraintCount = 3;
return g;
}
static float GridH(int n) { int rows = Mathf.Max(1, (n + 2) / 3); return rows * CardH + (rows - 1) * CardGap; }
static void ClearChildren(Transform t)
{
var old = new List<GameObject>();
foreach (Transform ch in t) old.Add(ch.gameObject);
foreach (var o in old) { o.SetActive(false); Kill(o); }
}
/// 덱 + 유닛 목록 다시 그리기
public void ShowUnits()
{
var c = T.Battle; var deck = G.ActiveDeck;
for (int i = 0; i < presetTabs.Count; i++) UI.SetBtnColor(presetTabs[i], i == G.DeckSel ? Pal.Yellow : Pal.Gray);
ClearChildren(unitDeckGrid);
for (int i = 0; i < deck.Length; i++) DeckSlot(i, deck);
unitDeckInfo.text = $"평균 마나 {G.DeckAvgCost:0.0} · 열린 칸 {G.BattleSlots}/{c.slotMax} — 열린 칸 유닛만 전투에 나갑니다";
ClearChildren(unitCollGrid);
foreach (var u in c.units) CollCard(u);
UI.SetLayoutHeight(unitCollGrid, GridH(c.units.Count));
unitCollTitle.text = $" 유닛 목록 <size=34><color=#C8D2FF>보유 {c.units.Count}/{c.units.Count}</color></size>";
unitCollTitle.supportRichText = true;
unitSwapBar.gameObject.SetActive(swapUnit != 0);
if (swapUnit != 0) unitSwapText.text = $"「{c.UnitDef(swapUnit)?.name}」 넣을 덱 칸을 누르세요";
}
public void SelectPreset(int p) { G.SelectDeck(p); swapUnit = 0; ShowUnits(); }
/// 덱 칸 — 유닛 카드 / 빈 칸 · 잠긴 칸(강화 칸 열기 전) = 어둡게 + 자물쇠 · 바꿀 칸 고르는 중 = 노란 테
void DeckSlot(int i, int[] deck)
{
var u = T.Battle.UnitDef(deck[i]);
int slot = i;
Button b;
if (u != null) b = UnitCard(unitDeckGrid, "Deck" + i, u, () => PickDeckSlot(slot));
else
{
b = UI.Btn(unitDeckGrid, "Deck" + i, Skin.Round, new Color32(28, 30, 52, 255), () => PickDeckSlot(slot));
UI.Img(b.transform, "In", Skin.RoundSoft, new Color(1f, 1f, 1f, 0.08f)).rectTransform.Fill(7, 7, 7, 7);
UI.Txt(b.transform, "T", "빈 칸", 42, new Color(1f, 1f, 1f, 0.6f), TextAnchor.MiddleCenter, true).rectTransform.Fill();
}
UI.Txt(b.transform, "No", (i + 1).ToString(), 30, new Color(1f, 1f, 1f, 0.85f), TextAnchor.MiddleCenter, true).rectTransform.Place(new Vector2(1, 1), new Vector2(1, 1), new Vector2(-26, -30), new Vector2(40, 40));
if (i >= G.BattleSlots)
{
UI.Img(b.transform, "Dim", Skin.RoundSoft, new Color(0.05f, 0.05f, 0.1f, 0.62f)).rectTransform.Fill(7, 7, 7, 7);
UI.Icon(b.transform, "Lock", Skin.Icon("lock"), Color.white).rectTransform.Place(new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0, 24), new Vector2(84, 112));
UI.Txt(b.transform, "LT", "잠긴 칸", 30, Color.white, TextAnchor.MiddleCenter, true).rectTransform.Place(new Vector2(0, 0.5f), new Vector2(1, 0.5f), new Vector2(0, -62), new Vector2(0, 44));
}
if (swapUnit != 0) b.GetComponent<Image>().color = Pal.Yellow;
}
/// 덱 칸 누름 — 바꿀 칸 고르는 중이면 그 칸에 넣기 · 유닛이면 상세 · 잠긴 빈 칸이면 강화 · 열린 빈 칸이면 안내
public void PickDeckSlot(int slot)
{
if (swapUnit != 0) { G.DeckPut(slot, swapUnit); swapUnit = 0; ShowUnits(); return; }
var u = T.Battle.UnitDef(G.ActiveDeck[slot]);
if (u != null) OpenUnitDetail(u.id);
else if (slot >= G.BattleSlots) OpenBattleUpgrade();
else Toast("아래 목록에서 유닛을 눌러 「덱에 넣기」");
}
void CollCard(BUnitDef u)
{
int id = u.id;
var b = UnitCard(unitCollGrid, "Unit" + id, u, () => { swapUnit = 0; OpenUnitDetail(id); });
int pos = G.DeckPos(id);
if (pos < 0) return;
var tag = UI.Img(b.transform, "InDeck", Skin.S("Label_Parallelogram_Green") ?? Skin.Pill, Color.white);
tag.rectTransform.Place(new Vector2(1, 1), new Vector2(1, 1), new Vector2(-8, -10), new Vector2(126, 50), new Vector2(1, 1));
UI.Txt(tag.transform, "T", $"덱 {pos + 1}", 28, Color.white, TextAnchor.MiddleCenter, true).rectTransform.Fill(18, 2, 18, 2);
}
/// 유닛 카드 — 짙은 테 · 안 = 등급 색(신화 = 무지개) · 왼쪽 위 이름 + 역할 · 가운데 그림 · 왼쪽 아래 마나 · 오른쪽 아래 별(= 등급)
Button UnitCard(Transform parent, string name, BUnitDef u, UnityAction click)
{
var b = UI.Btn(parent, name, Skin.Round, new Color32(28, 30, 52, 255), click);
var rt = (RectTransform)b.transform;
var mask = UI.Img(rt, "Mask", Skin.RoundSoft, Color.white); mask.rectTransform.Fill(7, 7, 7, 7);
mask.gameObject.AddComponent<Mask>().showMaskGraphic = false;
UI.Img(mask.transform, "Grade", GradeBg(u.grade), Color.white).rectTransform.Fill();
UI.Img(mask.transform, "Shade", Art.Square, new Color(0f, 0f, 0f, 0.26f)).rectTransform.Place(new Vector2(0, 0), new Vector2(1, 0), Vector2.zero, new Vector2(0, 78), new Vector2(0.5f, 0));
UI.Icon(rt, "Face", UnitSprite(u.id), Color.white).rectTransform.Fill(36, 76, 36, 66);
UI.Txt(rt, "Name", u.name, 36, Color.white, TextAnchor.MiddleLeft, true).rectTransform.Place(new Vector2(0, 1), new Vector2(1, 1), new Vector2(4, -12), new Vector2(-40, 52), new Vector2(0.5f, 1));
var role = UI.Img(rt, "Role", Skin.CirclePlain, new Color(0.1f, 0.1f, 0.18f, 0.7f));
role.rectTransform.Place(new Vector2(0, 1), new Vector2(0, 1), new Vector2(16, -66), new Vector2(54, 54), new Vector2(0, 1));
UI.Icon(role.transform, "I", RoleIcon(u), Color.white).rectTransform.Fill(9, 9, 9, 9);
var gem = UI.Icon(rt, "Gem", Skin.Gem ?? ManaDrop(), Color.white); gem.rectTransform.Place(new Vector2(0, 0), new Vector2(0, 0), new Vector2(48, 40), new Vector2(72, 66));
UI.Txt(gem.transform, "C", u.cost.ToString(), 36, Color.white, TextAnchor.MiddleCenter, true).rectTransform.Fill(0, 4, 0, 0);
var stars = UI.Rect("Stars", rt).Place(new Vector2(1, 0), new Vector2(1, 0), new Vector2(-14, 40), new Vector2(210, 34), new Vector2(1, 0.5f));
var hl = stars.gameObject.AddComponent<HorizontalLayoutGroup>();
hl.childAlignment = TextAnchor.MiddleRight; hl.spacing = -3; hl.childControlWidth = false; hl.childControlHeight = false; hl.childForceExpandWidth = false; hl.childForceExpandHeight = false;
for (int i = 0; i < Mathf.Max(1, u.grade); i++) UI.Icon(stars, "S" + i, Skin.Icon("star"), IsRainbow(u.grade) ? Color.HSVToRGB(i / 7f, 0.5f, 1f) : Color.white).rectTransform.sizeDelta = new Vector2(32, 32);
return b;
}
Sprite RoleIcon(BUnitDef u) => Skin.S(u.projectile ? "RoleIcon_Bow" : "RoleIcon_Sword") ?? Skin.Icon("pk");
bool IsRainbow(int grade) { var gd = T.Battle.Grade(grade); return gd != null && gd.color == "rainbow"; }
/// 등급 대표 색(battle.json) — 신화(무지개) = 분홍빛 보라
Color GradeColor(int grade)
{
var gd = T.Battle.Grade(grade);
if (gd == null) return Color.gray;
if (gd.color == "rainbow") return new Color32(224, 112, 255, 255);
return ColorUtility.TryParseHtmlString(gd.color, out var col) ? col : Color.gray;
}
/// 등급 바탕 — 위 밝게 → 아래 짙게 · 신화 = 대각선 무지개
Sprite GradeBg(int grade)
{
string key = "grade" + grade;
if (bcache.TryGetValue(key, out var s)) return s;
bool rainbow = IsRainbow(grade); Color baseC = GradeColor(grade);
const int W = 80, H = 100;
var r = new Raster(W, H);
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
{
float t = y / (float)(H - 1);
Color c = rainbow ? Color.HSVToRGB(Mathf.Repeat((x + (H - y)) / (float)(W + H), 1f), 0.42f, 1f) : Color.Lerp(baseC * 0.74f, Color.Lerp(baseC, Color.white, 0.22f), t);
c.a = 1f; r.Px[y * W + x] = c;
}
s = r.Sprite(); bcache[key] = s; return s;
}
static string RainbowText(string s)
{
var sb = new StringBuilder();
for (int i = 0; i < s.Length; i++) sb.Append($"<color=#{ColorUtility.ToHtmlStringRGB(Color.HSVToRGB(i / (float)Mathf.Max(1, s.Length) * 0.8f, 0.5f, 1f))}>{s[i]}</color>");
return sb.ToString();
}
// ---------------- 유닛 상세(참고 = 수집 카드 상세 화면) ----------------
void BuildUnitDetail()
{
unitDetail = UI.Rect("Detail", UnitRoot).Fill();
UI.Img(unitDetail, "Bg", Art.Square, new Color32(98, 110, 156, 255), true).rectTransform.Fill(); // 뒤 목록 누름 막기
if (Skin.Pattern != null) UI.Img(unitDetail, "Pattern", Skin.Pattern, new Color(1f, 1f, 1f, 0.07f)).rectTransform.Fill();
// 등급 띠 · 이름
udTag = UI.Img(unitDetail, "Grade", Skin.S("Label_Parallelogram_White_Bg") ?? Skin.Pill, Color.white);
udTag.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(0, -64), new Vector2(230, 64));
udTagText = UI.Txt(udTag.transform, "T", "", 34, Color.white, TextAnchor.MiddleCenter, true); udTagText.supportRichText = true; udTagText.rectTransform.Fill(26, 4, 26, 4);
udName = UI.Txt(unitDetail, "Name", "", 84, Color.white, TextAnchor.MiddleCenter, true);
udName.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(0, -152), new Vector2(900, 110));
// 그림 + 그림자 · 좌우 화살표 = 앞뒤 유닛
UI.Img(unitDetail, "Shadow", Art.SoftCircle, new Color(0f, 0f, 0f, 0.28f)).rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(0, -652), new Vector2(480, 110));
udPortrait = UI.Icon(unitDetail, "Portrait", null, Color.white);
udPortrait.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(0, -440), new Vector2(460, 460));
udPrev = ArrowBtn(unitDetail, "Prev", "Icon_Arrow_Prev1", "<", () => StepUnit(-1));
udPrev.GetComponent<RectTransform>().Place(new Vector2(0, 1), new Vector2(0, 1), new Vector2(84, -440), new Vector2(84, 164));
udNext = ArrowBtn(unitDetail, "Next", "Icon_Arrow_Next1", ">", () => StepUnit(1));
udNext.GetComponent<RectTransform>().Place(new Vector2(1, 1), new Vector2(1, 1), new Vector2(-84, -440), new Vector2(84, 164));
// 역할 · 마나 · 비행
var role = UI.Img(unitDetail, "Role", Skin.CirclePlain, new Color(0.1f, 0.1f, 0.18f, 0.75f));
role.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(-70, -724), new Vector2(72, 72));
udRole = UI.Icon(role.transform, "I", null, Color.white); udRole.rectTransform.Fill(12, 12, 12, 12);
var gem = UI.Icon(unitDetail, "Gem", Skin.Gem ?? ManaDrop(), Color.white);
gem.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(16, -724), new Vector2(80, 74));
udCost = UI.Txt(gem.transform, "C", "", 40, Color.white, TextAnchor.MiddleCenter, true); udCost.rectTransform.Fill(0, 4, 0, 0);
var fly = UI.Img(unitDetail, "Fly", Skin.S("Label_Parallelogram_Blue") ?? Skin.Pill, Color.white);
fly.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(140, -724), new Vector2(150, 60));
UI.Txt(fly.transform, "T", "비행", 32, Color.white, TextAnchor.MiddleCenter, true).rectTransform.Fill(22, 2, 22, 2);
udFly = fly.gameObject;
// 덱 상태(참고 화면의 레벨 막대 자리)
var st = UI.Img(unitDetail, "Status", Skin.Pill, new Color32(40, 44, 70, 235));
st.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(0, -806), new Vector2(620, 64));
udStatus = UI.Txt(st.transform, "T", "", 34, Color.white, TextAnchor.MiddleCenter, true); udStatus.rectTransform.Fill(24, 4, 24, 4);
// 능력치 6칸(3줄 × 2)
string[] labels = { "체력", "공격력", "초당 피해", "공격 간격", "사거리", "이동 속도" };
Sprite[] icons = { Skin.Icon("heart"), Skin.Icon("pk"), Skin.Icon("fire"), Skin.Icon("afk"), Skin.S("RoleIcon_Bow") ?? Skin.Icon("pk"), Skin.S("RoleIcon_Boots") ?? Skin.Icon("auto") };
for (int i = 0; i < labels.Length; i++)
{
var cell = UI.Img(unitDetail, "Stat" + i, Skin.RoundSoft, new Color(1f, 1f, 1f, 0.16f));
cell.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(i % 2 == 0 ? -232 : 232, -906 - i / 2 * 128), new Vector2(440, 112));
UI.Icon(cell.transform, "I", icons[i], Color.white).rectTransform.Place(new Vector2(0, 0.5f), new Vector2(0, 0.5f), new Vector2(56, 0), new Vector2(72, 72));
UI.Txt(cell.transform, "L", labels[i], 28, new Color32(225, 232, 255, 255), TextAnchor.MiddleLeft, true).rectTransform.Place(new Vector2(0, 1), new Vector2(1, 1), new Vector2(60, -6), new Vector2(-130, 36), new Vector2(0.5f, 1));
var v = UI.Txt(cell.transform, "V", "", 46, Color.white, TextAnchor.MiddleLeft, true);
v.rectTransform.Place(new Vector2(0, 0), new Vector2(1, 0), new Vector2(60, 8), new Vector2(-130, 60), new Vector2(0.5f, 0));
udStatValues.Add(v);
}
// 특징(참고 화면의 Ability 자리) — battle.json 값에서 만든 설명
var ab = UI.Img(unitDetail, "Traits", Skin.RoundSoft, new Color(1f, 1f, 1f, 0.16f));
ab.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(0, -1352), new Vector2(904, 214));
UI.Txt(ab.transform, "H", "특징", 36, Color.white, TextAnchor.MiddleCenter, true).rectTransform.Place(new Vector2(0, 1), new Vector2(1, 1), new Vector2(0, -6), new Vector2(0, 46), new Vector2(0.5f, 1));
var tf = UI.Img(ab.transform, "IconBg", Skin.Round, new Color32(80, 190, 90, 255));
tf.rectTransform.Place(new Vector2(0, 0.5f), new Vector2(0, 0.5f), new Vector2(30, -18), new Vector2(124, 124), new Vector2(0, 0.5f));
udTraitIcon = UI.Icon(tf.transform, "I", null, Color.white); udTraitIcon.rectTransform.Fill(20, 20, 20, 20);
udTraits = UI.Txt(ab.transform, "T", "", 30, Color.white, TextAnchor.UpperLeft, true);
udTraits.rectTransform.Place(new Vector2(0, 0), new Vector2(1, 1), new Vector2(86, -26), new Vector2(-220, -70), new Vector2(0.5f, 0.5f));
// 덱에 넣기 / 덱에서 빼기 · 뒤로
udMain = UI.TextBtn(unitDetail, "Main", "", Pal.Green, 50, OnUnitMain, out udMainLabel);
udMain.GetComponent<RectTransform>().Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(0, -1550), new Vector2(480, 150));
var back = UI.Btn(unitDetail, "Back", Skin.Dark, Color.white, CloseUnitDetail);
back.GetComponent<RectTransform>().Place(new Vector2(0, 0), new Vector2(0, 0), new Vector2(-20, 24), new Vector2(210, 128), new Vector2(0, 0));
UI.Icon(back.transform, "I", Skin.S("Icon_Back"), Color.white).rectTransform.Fill(56, 22, 44, 22);
unitDetail.gameObject.SetActive(false);
}
public void OpenUnitDetail(int id)
{
unitDetail.gameObject.SetActive(true);
ShowUnitDetail(id);
}
public void CloseUnitDetail()
{
unitDetail.gameObject.SetActive(false);
ShowUnits();
}
void StepUnit(int d)
{
var units = T.Battle.units; int i = units.FindIndex(u => u.id == detailUnit) + d;
if (i >= 0 && i < units.Count) ShowUnitDetail(units[i].id);
}
void ShowUnitDetail(int id)
{
var c = T.Battle; var u = c.UnitDef(id);
if (u == null) return;
detailUnit = id;
var gd = c.Grade(u.grade); bool rainbow = IsRainbow(u.grade);
udTag.color = rainbow ? (Color)new Color32(44, 40, 70, 255) : GradeColor(u.grade);
udTagText.text = gd == null ? "" : rainbow ? RainbowText(gd.name) : gd.name;
udName.text = u.name;
udPortrait.sprite = UnitSprite(u.id);
udRole.sprite = RoleIcon(u); udTraitIcon.sprite = RoleIcon(u);
udCost.text = u.cost.ToString();
udFly.SetActive(u.flying);
int pos = G.DeckPos(id);
udStatus.text = pos < 0 ? "덱에 없음" : $"덱 {G.DeckSel + 1} · {pos + 1}번 칸" + (pos >= G.BattleSlots ? " (잠긴 칸)" : "");
string[] vals = { $"{u.hp:0}", $"{u.damage:0}", $"{u.damage / Mathf.Max(0.01f, u.interval):0.#}", $"{u.interval:0.#}초", $"{u.range:0.#}칸", $"{u.speed:0.##}칸/초" };
for (int i = 0; i < vals.Length && i < udStatValues.Count; i++) udStatValues[i].text = vals[i];
var tr = new List<string> { u.projectile ? $"원거리 — {u.range:0.#}칸 밖에서 투사체를 쏩니다" : $"근접 — 붙어서 공격합니다(사거리 {u.range:0.#}칸)" };
if (u.flying) tr.Add("비행 — 강과 바위·언덕 위로 곧장 날아갑니다");
tr.Add($"마나 {u.cost} · 크기 {u.radius * 2f:0.#}칸");
udTraits.text = string.Join("\n", tr);
udMainLabel.text = pos >= 0 ? "덱에서 빼기" : "덱에 넣기";
UI.SetBtnColor(udMain, pos >= 0 ? Pal.Red : Pal.Green);
int idx = c.units.IndexOf(u);
udPrev.gameObject.SetActive(idx > 0); udNext.gameObject.SetActive(idx < c.units.Count - 1);
}
/// 덱에 넣기(빈 칸 → 바로 · 꽉 찼으면 목록으로 돌아가 바꿀 칸 고르기) / 덱에서 빼기
void OnUnitMain()
{
int id = detailUnit;
if (G.DeckPos(id) >= 0) { G.DeckRemove(id); ShowUnitDetail(id); return; }
if (G.DeckAdd(id)) { ShowUnitDetail(id); return; }
BeginUnitSwap(id);
}
public void BeginUnitSwap(int id)
{
swapUnit = id;
unitDetail.gameObject.SetActive(false);
ShowUnits();
}
}
}