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

329 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}
}