461 lines
23 KiB
C#
461 lines
23 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
using UnityEngine.UI;
|
||
|
||
namespace TKS.OutGame
|
||
{
|
||
/// <summary>
|
||
/// 편성(img08 · 기획 S12 v3) — 프리셋 3 · 전열 3(윗줄)·후열 2(아랫줄) 자리 = 영웅 목록 카드(축소) · 하단 보유 목록(장수 ▼ 전환 · 같은 카드 4열 · 출전) ·
|
||
/// 탭(자리 고르기 → 카드 탭 = 넣기 · 같은 자리 두 번 = 빼기 · 다른 자리 = 맞바꾸기) · 끌기(자리 ↔ 자리 · 카드 → 자리) · 저장 = 그 프리셋을 출전 편성으로.
|
||
/// 빈 칸 허용 · 최소 1 · 편성 제약 없음(D4).
|
||
/// </summary>
|
||
public class FormationScreen : OutGameScreen
|
||
{
|
||
int preset;
|
||
readonly List<string> work = new List<string>();
|
||
bool dirty;
|
||
int selected = -1;
|
||
bool soldiers;
|
||
|
||
readonly List<Button> presetBtns = new List<Button>();
|
||
readonly List<RectTransform> slots = new List<RectTransform>();
|
||
RectTransform list;
|
||
ScrollRect scroll;
|
||
RectTransform ghost;
|
||
|
||
public IList<string> Working
|
||
{
|
||
get { return work; }
|
||
}
|
||
|
||
public int Preset
|
||
{
|
||
get { return preset; }
|
||
}
|
||
|
||
public bool Dirty
|
||
{
|
||
get { return dirty; }
|
||
}
|
||
|
||
// Fable 제안서 §3(2026-11-09 · PD 「편성 메뉴와 영웅 목록 프레임 구분 · 디자인 요소 · 카드 목록 완성도」) — 본문 좌표 = 절대 y − 224
|
||
// 섹션 A 「출전 편성」 패널(절대 236~1010) · 홈 16 · 섹션 B 「보유 영웅」 패널(1026~1760) · 자리 = 목록 카드 × SeatScale · 보유 = × ListScale
|
||
const float SeatScale = 0.62f;
|
||
const float ListScale = 0.72f;
|
||
const int GhostMin = 8; // 보유 격자 최소 칸(2줄 · 빈 칸 = 고스트 카드)
|
||
const float SeatPitch = 222f; // 자리 중심 간격(198 + 24)
|
||
const float SeatCenterX = 608f; // 자리 줄 가운데(절대 x · 왼쪽 라벨 칸만큼 오른쪽)
|
||
const float PanelX = 24f, PanelW = 1032f;
|
||
const float PanelA_Y = 12f, PanelA_H = 760f; // 절대 236~996(Fable 검수 결함 4)
|
||
const float PanelB_Y = 804f, PanelB_H = 708f; // 절대 1028~1736(Fable 재검수 결함 4 · 그림자 아래 탭 바까지 바탕 틈 ≈ 29 · PD 2026-11-09 「고쳐」)
|
||
const float GridBottomAbs = 1730f; // 격자 아래 끝(절대 · B 필드 안쪽 1730)
|
||
const float FrontY = 182f, BackY = 471f; // 자리 위 끝(패널 A 기준 · 밴드 412~691 / 701~980 가운데)
|
||
Text countText;
|
||
Text filterText;
|
||
Text ownedCount;
|
||
Text powerText;
|
||
Image saveImg;
|
||
Text saveText;
|
||
Image saveDot;
|
||
readonly List<GameObject> presetTails = new List<GameObject>();
|
||
|
||
static RectTransform At(Transform p, string name, float x, float y, float w, float h)
|
||
{
|
||
return UiKit.Rect(name, p, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(x, -y), new Vector2(w, h));
|
||
}
|
||
|
||
public override void Build()
|
||
{
|
||
int n = Root.Battle.Config.playerSpawnPoints.Length;
|
||
int front = Mathf.Clamp(S.Db.Economy.FormationFront, 0, n);
|
||
// 바탕 = 영웅 목록과 같음(진남 + 재화 바 뒤 띠) · 헤더 오른쪽 사선 칸 = 출전 n / 칸 수
|
||
UiKit.Paint(UiKit.Fill("ListBg", Rt), UiKit.Col("list_bg", UiKit.Bg), false);
|
||
UiKit.Paint(UiKit.TopBand("ListTop", Rt, 0f, OutGameRoot.TopBarH), UiKit.Col("band_top", UiKit.Bg), false);
|
||
var head = Header("편성", "", true);
|
||
var ob = UiKit.Rect("Oblique", head, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), Vector2.zero, new Vector2(239f, OutGameRoot.HeaderH - 7f));
|
||
UiKit.Skin(ob, "oblique", UiKit.Col("oblique", UiKit.White));
|
||
countText = UiKit.Title(UiKit.Label(ob, "", 40, UiKit.Col("count_text", UiKit.Dark), TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(12f, 0f), new Vector2(220f, 80f)));
|
||
countText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
// 본문 = 1080 폭 틀(낮은 화면에서도 가운데)
|
||
var body = UiKit.Rect("Mock", Body(), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), Vector2.zero, new Vector2(OutGameRoot.Reference.x, OutGameRoot.BodyH));
|
||
|
||
// ===== 섹션 A 「출전 편성」 =====
|
||
RectTransform a;
|
||
UiKit.SectionPanel(body, "PanelA", PanelX, PanelA_Y, PanelW, PanelA_H, true, true, out a);
|
||
UiKit.SectionTitle(a, "출전 편성");
|
||
// 제약 칩(ⓘ = 편성 안내 팝업 · 안내 문장 이관)
|
||
var lim = At(a, "Limit", 592f, 12f, 230f, 56f);
|
||
UiKit.Skin(lim, "chip", UiKit.Col("chip_dark", UiKit.Dark), true);
|
||
UiKit.Layer(lim, "Line", "chip_line", UiKit.Col("chip_line", Color.clear), 4f, 4f, 4f, 4f);
|
||
UiKit.Label(lim, "편성 제약 없음", 26, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(18f, 0f), new Vector2(170f, 50f)).horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
UiKit.Icon(lim, "info", new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-16f, 0f), 32f);
|
||
lim.gameObject.AddComponent<Button>().onClick.AddListener(ShowGuide);
|
||
// 저장(녹 · 바뀐 것 없으면 비활성 색 · 바뀌면 알림 점)
|
||
var sv = At(a, "Save", 832f, 14f, 180f, 56f);
|
||
saveImg = UiKit.Skin(sv, UISkin.Has("btn_s_green") ? "btn_s_green" : "button", UiKit.White, true);
|
||
sv.gameObject.AddComponent<Button>().onClick.AddListener(SaveNow);
|
||
saveText = UiKit.Title(UiKit.Label(sv, "저장", 34, UiKit.Col("text_on_green", UiKit.Dark), TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 3f), new Vector2(170f, 50f)));
|
||
saveDot = UiKit.Icon(sv, "new_dot", new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-6f, -6f), 20f);
|
||
// 프리셋 탭 3 + 출전 꼬리 칩
|
||
for (int i = 0; i < S.Save.presets.Count; i++)
|
||
{
|
||
int k = i;
|
||
var b = SubTab("Preset" + i, a, "프리셋 " + (i + 1), new Vector2(20f + i * 212f, -92f), new Vector2(200f, 72f), () => LoadPreset(k));
|
||
presetBtns.Add(b);
|
||
var tail = UiKit.Skin(UiKit.Rect("Active", b.transform, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-8f, 4f), new Vector2(64f, 30f)), "tail", UiKit.Col("tail_red", UiKit.Red));
|
||
UiKit.Title(UiKit.FillLabel(tail.transform, "출전", 18, UiKit.White), true);
|
||
presetTails.Add(tail.gameObject);
|
||
}
|
||
// 총 전투력 칩(편성 변화가 숫자로 바로 보임)
|
||
var pw = At(a, "Power", 736f, 96f, 276f, 64f);
|
||
UiKit.Skin(pw, "chip", UiKit.Col("chip_dark", UiKit.Dark));
|
||
UiKit.Icon(pw, "sword", new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(12f, 0f), 44f);
|
||
UiKit.Label(pw, "전투력", 24, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleLeft, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(62f, 0f), new Vector2(90f, 40f));
|
||
powerText = UiKit.Title(UiKit.Label(pw, "", 40, UiKit.White, TextAnchor.MiddleRight, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-18f, 2f), new Vector2(150f, 56f)), true);
|
||
powerText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
// 열 밴드 2 · 구분선 · 전열/후열 칩
|
||
UiKit.Skin(At(a, "FrontBand", 20f, 176f, 992f, 279f), "row_band", UiKit.Col("row_band", Color.clear));
|
||
UiKit.Skin(At(a, "Divider", 76f, 457f, 880f, 8f), "divider", UiKit.Col("divider", Color.clear));
|
||
UiKit.Skin(At(a, "BackBand", 20f, 465f, 992f, 279f), "row_band", UiKit.Col("row_band", Color.clear));
|
||
// 라벨 칸 세로 띠(라벨 쪽을 면으로 분리 · 검수 결함 7)
|
||
UiKit.Skin(At(a, "FrontStrip", 20f, 176f, 156f, 279f), "row_band", UiKit.Col("row_label_strip", Color.clear));
|
||
UiKit.Skin(At(a, "BackStrip", 20f, 465f, 156f, 279f), "row_band", UiKit.Col("row_label_strip", Color.clear));
|
||
Pill(a, "전열", UiKit.Col("row_front", UiKit.Red), 16f, 176f + 139.5f - 30f);
|
||
Pill(a, "후열", UiKit.Col("row_back", UiKit.Dark), 16f, 465f + 139.5f - 30f);
|
||
// 자리 = 받침(빈 자리에도) + 카드 칸
|
||
float sw = CollectionCards.CellSize.x * SeatScale, sh = CollectionCards.CellSize.y * SeatScale;
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
bool f = i < front;
|
||
int col = f ? i : i - front;
|
||
int cnt = f ? front : n - front;
|
||
float cx = SeatCenterX - PanelX + (col - (cnt - 1) / 2f) * SeatPitch;
|
||
float top = f ? FrontY : BackY;
|
||
var plate = At(a, "Plate" + i, cx - sw / 2f - 8f, top - 8f, sw + 16f, sh + 16f);
|
||
UiKit.Skin(plate, "seat_plate", UiKit.Col("seat_plate", UiKit.Dark));
|
||
var pl = UiKit.Rect("Light", plate, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -4f), new Vector2(sw, 10f));
|
||
UiKit.Skin(pl, "panel_light", UiKit.Col("seat_light", Color.clear)).enabled = UISkin.Has("panel_light");
|
||
var s = At(a, "Slot" + i, cx - sw / 2f, top, sw, sh);
|
||
var hit = s.gameObject.AddComponent<Image>();
|
||
hit.color = Color.clear;
|
||
hit.raycastTarget = true;
|
||
var td = s.gameObject.AddComponent<TapDrag>();
|
||
td.AnyDirection = true;
|
||
int k = i;
|
||
td.OnTap = () => TapSlot(k);
|
||
td.OnDragStart = e => StartGhost(work[k], e);
|
||
td.OnDragMove = MoveGhost;
|
||
td.OnDragEnd = e => DropFromSlot(k, e);
|
||
slots.Add(s);
|
||
}
|
||
|
||
// ===== 섹션 B 「보유 영웅」 =====
|
||
RectTransform b2;
|
||
UiKit.SectionPanel(body, "PanelB", PanelX, PanelB_Y, PanelW, PanelB_H, false, false, out b2);
|
||
var tb = UiKit.SectionTitle(b2, "보유 영웅");
|
||
var cnt2 = UiKit.Skin(UiKit.Rect("Count", tb, new Vector2(0f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(300f, 2f), new Vector2(44f, 46f)), "badge_circle", UiKit.Col("grade_blue", UiKit.Dark));
|
||
cnt2.preserveAspect = true;
|
||
ownedCount = UiKit.Title(UiKit.FillLabel(cnt2.transform, "", 26, UiKit.White), true);
|
||
// 「장수 ▼」 필터 칩
|
||
var pick = At(b2, "Filter", 812f, 6f, 200f, 56f);
|
||
UiKit.Skin(pick, "chip", UiKit.Col("chip_dark", UiKit.Dark), true);
|
||
UiKit.Layer(pick, "Line", "chip_line", UiKit.Col("chip_line", Color.clear), 4f, 4f, 4f, 4f);
|
||
pick.gameObject.AddComponent<Button>().onClick.AddListener(() =>
|
||
{
|
||
soldiers = !soldiers;
|
||
Refresh();
|
||
});
|
||
filterText = UiKit.Title(UiKit.Label(pick, "", 30, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(-18f, 2f), new Vector2(140f, 50f)));
|
||
UiKit.Icon(pick, "arrow_down", new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-22f, 0f), 28f);
|
||
// 격자 4열(절대 x 54~1026 · y 1108~1730) + 위·아래 페이드
|
||
var cell = CollectionCards.CellSize * ListScale;
|
||
float gridTop = 1110f - 224f, gridBottom = OutGameRoot.BodyH - (GridBottomAbs - 224f);
|
||
list = UiKit.ScrollGrid("List", body, 54f, 54f, gridTop, gridBottom, cell, new Vector2(17f, 16f), 4, out scroll);
|
||
UiKit.ScrollFades(body, gridTop, gridBottom, 40f, 30f);
|
||
}
|
||
|
||
/// <summary>편성 안내(제약 칩 ⓘ · 화면의 안내 문장을 이관).</summary>
|
||
void ShowGuide()
|
||
{
|
||
Root.Modal("편성 안내", "편성 제약 없음 · 빈 칸 허용 · 최소 1명\n\n자리 탭 → 카드 탭 = 넣기 · 교체\n같은 자리 두 번 탭 = 빼기\n끌기 = 자리 ↔ 자리 · 카드 → 자리\n저장 = 이 프리셋을 출전 편성으로", new OutGameRoot.ModalButton("확인", UiKit.Dark, null));
|
||
}
|
||
|
||
/// <summary>서브탭(팩 Button_Round10 + 윗면 광택 · 선택 = 붉은 옻칠).</summary>
|
||
static Button SubTab(string name, Transform parent, string label, Vector2 pos, Vector2 size, System.Action onClick)
|
||
{
|
||
var img = UiKit.Skin(UiKit.Rect(name, parent, new Vector2(0f, 1f), new Vector2(0f, 1f), pos, size), UISkin.Has("subtab") ? "subtab" : "button", UiKit.Col("subtab_off", UiKit.Paper), true);
|
||
var b = img.gameObject.AddComponent<Button>();
|
||
b.onClick.AddListener(() => onClick());
|
||
UiKit.Title(UiKit.FillLabel(img.transform, label, 32, UiKit.White, TextAnchor.MiddleCenter, true, 8f), true);
|
||
return b;
|
||
}
|
||
|
||
static void Pill(RectTransform panel, string label, Color c, float x, float y)
|
||
{
|
||
// 전열·후열 라벨 = 평행사변형 칩 120×54(Fable §3-3)
|
||
var p = UISkin.Has("para_orange")
|
||
? UiKit.Skin(At(panel, "Row_" + label, x, y, 140f, 60f), "para_orange", c)
|
||
: UiKit.Paint(At(panel, "Row_" + label, x, y, 140f, 60f), c);
|
||
UiKit.Title(UiKit.FillLabel(p.transform, label, 30, UiKit.White), true);
|
||
}
|
||
|
||
public override void OnShow()
|
||
{
|
||
LoadPreset(S.Save.presetIdx, true);
|
||
}
|
||
|
||
public override bool OnBack()
|
||
{
|
||
if (dirty) Root.Toast("저장하지 않은 편성 변경은 취소됩니다");
|
||
dirty = false;
|
||
return false;
|
||
}
|
||
|
||
public void LoadPreset(int idx, bool quiet = false)
|
||
{
|
||
if (dirty && !quiet) Root.Toast("저장하지 않은 변경 취소");
|
||
preset = Mathf.Clamp(idx, 0, S.Save.presets.Count - 1);
|
||
work.Clear();
|
||
work.AddRange(S.Save.presets[preset].slots);
|
||
dirty = false;
|
||
selected = -1;
|
||
Refresh();
|
||
}
|
||
|
||
int Filled()
|
||
{
|
||
int n = 0;
|
||
foreach (var id in work)
|
||
if (!string.IsNullOrEmpty(id)) n++;
|
||
return n;
|
||
}
|
||
|
||
public void TapSlot(int i)
|
||
{
|
||
if (selected < 0)
|
||
{
|
||
selected = i;
|
||
}
|
||
else if (selected == i)
|
||
{
|
||
if (!string.IsNullOrEmpty(work[i]))
|
||
{
|
||
if (Filled() > 1)
|
||
{
|
||
work[i] = "";
|
||
dirty = true;
|
||
}
|
||
else Root.Toast("최소 1명은 출전해야 합니다");
|
||
}
|
||
selected = -1;
|
||
}
|
||
else
|
||
{
|
||
Swap(selected, i);
|
||
selected = -1;
|
||
}
|
||
Refresh();
|
||
}
|
||
|
||
public void TapCard(string id)
|
||
{
|
||
int at = work.IndexOf(id);
|
||
if (at >= 0)
|
||
{
|
||
if (Filled() > 1)
|
||
{
|
||
work[at] = "";
|
||
dirty = true;
|
||
}
|
||
else Root.Toast("최소 1명은 출전해야 합니다");
|
||
}
|
||
else
|
||
{
|
||
int target = selected >= 0 ? selected : work.IndexOf("");
|
||
if (target < 0) Root.Toast("빈 자리가 없습니다 — 자리를 먼저 탭해 고른 뒤 카드를 탭(교체)");
|
||
else
|
||
{
|
||
work[target] = id;
|
||
dirty = true;
|
||
}
|
||
}
|
||
selected = -1;
|
||
Refresh();
|
||
}
|
||
|
||
/// <summary>id 를 자리 slot 에 넣는다(이미 다른 자리에 있으면 맞바꿈).</summary>
|
||
public void Place(string id, int slot)
|
||
{
|
||
if (slot < 0 || slot >= work.Count || string.IsNullOrEmpty(id)) return;
|
||
int at = work.IndexOf(id);
|
||
if (at >= 0) Swap(at, slot);
|
||
else
|
||
{
|
||
work[slot] = id;
|
||
dirty = true;
|
||
}
|
||
Refresh();
|
||
}
|
||
|
||
public void Swap(int a, int b)
|
||
{
|
||
if (a == b) return;
|
||
var t = work[a];
|
||
work[a] = work[b];
|
||
work[b] = t;
|
||
dirty = true;
|
||
}
|
||
|
||
public void SaveNow()
|
||
{
|
||
var why = S.SavePreset(preset, work);
|
||
if (why != null)
|
||
{
|
||
Root.Toast(why);
|
||
return;
|
||
}
|
||
dirty = false;
|
||
Root.Toast("프리셋 " + (preset + 1) + " 저장 · 출전 편성으로 지정");
|
||
Refresh();
|
||
}
|
||
|
||
// ---------- 끌기 ----------
|
||
|
||
void StartGhost(string id, PointerEventData e)
|
||
{
|
||
if (string.IsNullOrEmpty(id)) return;
|
||
if (ghost != null) Object.Destroy(ghost.gameObject);
|
||
ghost = UiKit.Portrait("Ghost", Root.TopRoot, S.Def(id), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(170f, 170f), true, 2.1f, UiKit.Red);
|
||
ghost.gameObject.name = "Ghost_" + id;
|
||
UiLayer.Nest(ghost.gameObject, UiRate.Frame); // 끄는 동안 매 프레임 이동 = 자기 캔버스(PD 2026-11-10)
|
||
MoveGhost(e);
|
||
}
|
||
|
||
void MoveGhost(PointerEventData e)
|
||
{
|
||
if (ghost == null) return;
|
||
Vector2 lp;
|
||
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(Root.TopRoot, e.position, Root.Cam, out lp)) ghost.anchoredPosition = lp;
|
||
}
|
||
|
||
int SlotAt(Vector2 screen)
|
||
{
|
||
for (int i = 0; i < slots.Count; i++)
|
||
if (RectTransformUtility.RectangleContainsScreenPoint(slots[i], screen, Root.Cam)) return i;
|
||
return -1;
|
||
}
|
||
|
||
void DropFromSlot(int from, PointerEventData e)
|
||
{
|
||
EndGhost();
|
||
int to = SlotAt(e.position);
|
||
if (to < 0 || to == from) return;
|
||
Swap(from, to);
|
||
selected = -1;
|
||
Refresh();
|
||
}
|
||
|
||
void DropCard(string id, PointerEventData e)
|
||
{
|
||
EndGhost();
|
||
int to = SlotAt(e.position);
|
||
if (to >= 0) Place(id, to);
|
||
}
|
||
|
||
void EndGhost()
|
||
{
|
||
if (ghost == null) return;
|
||
ghost.gameObject.SetActive(false);
|
||
Object.Destroy(ghost.gameObject);
|
||
ghost = null;
|
||
}
|
||
|
||
// ---------- 그리기 ----------
|
||
|
||
public override void Refresh()
|
||
{
|
||
for (int i = 0; i < presetBtns.Count; i++)
|
||
{
|
||
bool on = i == preset;
|
||
presetBtns[i].GetComponent<Image>().color = on ? UiKit.Red : UiKit.Col("subtab_off", UiKit.Paper);
|
||
var t = UiKit.LabelOf(presetBtns[i]);
|
||
t.color = UiKit.White;
|
||
t.text = "프리셋 " + (i + 1) + (on && dirty ? "*" : "");
|
||
presetTails[i].SetActive(i == S.Save.presetIdx);
|
||
}
|
||
countText.text = "출전 " + Filled() + "/" + work.Count;
|
||
float power = 0f;
|
||
foreach (var id in work)
|
||
if (!string.IsNullOrEmpty(id) && S.Def(id) != null) power += S.StatsOf(id).power;
|
||
powerText.text = power.ToString("N0");
|
||
// 저장 = 바뀐 것이 있을 때만 녹(+ 알림 점)
|
||
// 비활성 = 회색 스프라이트(녹에 회색을 곱하면 녹이 남음 · 검수 결함 3) · 광택 없음 · 글자 #5A5E70
|
||
UISkin.Apply(saveImg, dirty || !UISkin.Has("btn_s_gray") ? "btn_s_green" : "btn_s_gray");
|
||
saveImg.color = UiKit.White;
|
||
saveText.color = UiKit.Col(dirty ? "text_on_green" : "disabled_text", UiKit.Dark);
|
||
if (saveDot != null) saveDot.gameObject.SetActive(dirty);
|
||
|
||
// 자리 = 목록 카드(배율) · 빈 자리 = 원본 빈 카드 + 초록 플러스 뱃지 · 고른 자리 = 노란 포커스 테두리(깜빡)
|
||
for (int i = 0; i < slots.Count; i++)
|
||
{
|
||
var s = slots[i];
|
||
UiKit.Clear(s);
|
||
var id = i < work.Count ? work[i] : "";
|
||
var h = S.Def(id);
|
||
if (h != null) CollectionCards.CardIn(s, S, h, false, null, SeatScale);
|
||
else
|
||
{
|
||
CollectionCards.EmptyIn(s, SeatScale);
|
||
var badge = UiKit.Skin(UiKit.Rect("Plus", s, new Vector2(1f, 0f), new Vector2(0.5f, 0.5f), new Vector2(-24f, 24f), new Vector2(48f, 50f)), "badge_circle", UiKit.Col("ok", UiKit.Red));
|
||
badge.preserveAspect = true;
|
||
UiKit.Icon(badge.transform, "plus_glyph", new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 1f), 26f);
|
||
}
|
||
if (i == selected)
|
||
{
|
||
var fx = UiKit.Layer(s, "Select", "focus_line", UiKit.Col("select_line", UiKit.Star), -6f, -6f, -6f, -6f);
|
||
fx.gameObject.AddComponent<UiPulse>();
|
||
}
|
||
}
|
||
|
||
// 보유 목록 = 영웅 목록 카드(배율 · 레벨 높은 순) · 출전 중 = 꼬리 칩 + 35 % 덮개 + 초록 체크 뱃지
|
||
filterText.text = soldiers ? "병사" : "장수";
|
||
UiKit.Clear(list);
|
||
var owned = new List<HeroDb.Hero>(S.Owned(soldiers));
|
||
ownedCount.text = owned.Count.ToString();
|
||
owned.Sort((a, b) =>
|
||
{
|
||
int la = S.Save.Level(a.id) != null ? S.Save.Level(a.id).lv : 1, lb = S.Save.Level(b.id) != null ? S.Save.Level(b.id).lv : 1;
|
||
return lb.CompareTo(la);
|
||
});
|
||
var cellSize = CollectionCards.CellSize * ListScale;
|
||
foreach (var h in owned)
|
||
{
|
||
var id = h.id;
|
||
bool on = work.Contains(id);
|
||
var holder = UiKit.Rect("C_" + id, list, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, cellSize);
|
||
var card = CollectionCards.CardIn(holder, S, h, on, null, ListScale);
|
||
if (on)
|
||
{
|
||
var dim = UiKit.Layer(card.transform, "Dim", "card_bg", UiKit.Col("seat_dim", Color.clear), 2f, 80f, 2f, 70f); // 초상만(이름·레벨·별 제외)
|
||
dim.transform.SetSiblingIndex(2); // 바탕·테두리 위 · 글자·뱃지 아래
|
||
var ck = UiKit.Skin(UiKit.Rect("Check", card.transform, new Vector2(1f, 0f), new Vector2(0.5f, 0.5f), new Vector2(-47f, 86f), new Vector2(67f, 70f)), "check_circle", UiKit.Col("ok", UiKit.Red));
|
||
ck.preserveAspect = true;
|
||
UiKit.Icon(ck.transform, "check_badge", new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 2f), 39f);
|
||
}
|
||
var td = card.gameObject.AddComponent<TapDrag>();
|
||
td.OnTap = () => TapCard(id);
|
||
td.OnDragStart = e => StartGhost(id, e);
|
||
td.OnDragMove = MoveGhost;
|
||
td.OnDragEnd = e => DropCard(id, e);
|
||
}
|
||
// 빈 칸 = 고스트 카드(영웅 목록과 같은 빈 카드)로 최소 2줄(8칸)을 채움(Fable 검수 결함 5)
|
||
for (int i = owned.Count; i < GhostMin; i++)
|
||
{
|
||
var holder = UiKit.Rect("Ghost" + i, list, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, cellSize);
|
||
CollectionCards.EmptyIn(holder, ListScale).raycastTarget = false;
|
||
}
|
||
}
|
||
}
|
||
}
|