399 lines
29 KiB
C#
399 lines
29 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLEquipGui.Gacha.cs — 장비 상자(열쇠) 시트 · 결과 화면 · 확률/천장 팝업 (#825 연출을 v3 Layer Lab 스킨으로 이식 · 2026-10-12)
|
||
//
|
||
// 진입 = 허브 상단 열쇠 알약의 「+」. 바닥 시트(상자 흔들림 → 등급 색 번쩍임 → 결과 카드 등장 → 전설(S) 별 튀기) · 확률·천장 바.
|
||
// 로직은 WLEquipGacha(Draw · LastResults · Box). 치수·시간은 json `gacha`.
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
namespace WL.Equip
|
||
{
|
||
public partial class WLEquipGui
|
||
{
|
||
GameObject _boxPopup, _resultView, _ratesPopup;
|
||
RectTransform _chest, _resultGrid;
|
||
Image _chestImg, _chestGlow, _flash, _resultGlow, _epicBarFill, _sBarFill;
|
||
TextMeshProUGUI _boxInfo, _boxKeys, _boxLog, _resultTitle, _resultHint, _ratesPity;
|
||
Button _box1Btn, _box10Btn, _keyBtn, _ratesBtn, _againBtn, _resultCloseBtn;
|
||
readonly List<Cell> _resultCards = new List<Cell>();
|
||
readonly List<TextMeshProUGUI> _resultNames = new List<TextMeshProUGUI>();
|
||
readonly List<GameObject> _ratesRows = new List<GameObject>();
|
||
readonly List<Image> _ratesPills = new List<Image>();
|
||
readonly List<TextMeshProUGUI> _ratesNames = new List<TextMeshProUGUI>(), _ratesPcts = new List<TextMeshProUGUI>();
|
||
Coroutine _drawCo; bool _revealing, _skipReveal; int _lastDrawCount = 1;
|
||
public static int DrawsAnimated, ResultsShown;
|
||
|
||
// ── 시트 ──
|
||
void BuildBoxPopup()
|
||
{
|
||
_boxPopup = new GameObject("GachaSheet", typeof(RectTransform)); _boxPopup.layer = UILayer; _boxPopup.transform.SetParent(_root, false);
|
||
var prt = (RectTransform)_boxPopup.transform; Stretch(prt);
|
||
var dim = NewImage(prt, "Dim", null, Col("dim", new Color(0f, 0f, 0f, 0.7f)), Image.Type.Simple); Stretch(dim.rectTransform); dim.raycastTarget = true;
|
||
var dimBtn = dim.gameObject.AddComponent<Button>(); dimBtn.targetGraphic = dim; dimBtn.onClick.AddListener(CloseSheet);
|
||
var sheetFill = NewImage(prt, "SheetFill", Spr("innerBox"), Col("popupFill", new Color(0.14f, 0.11f, 0.17f, 0.98f))); sheetFill.raycastTarget = true;
|
||
Place(sheetFill.rectTransform, new Vector2(0.5f, 0f), new Vector2(0f, G.sheetBottom + 14f), new Vector2(G.sheetW - 28f, G.sheetH - 28f), new Vector2(0.5f, 0f));
|
||
var sheet = NewImage(prt, "Sheet", Spr("sheetBg"), Color.white);
|
||
var srt = sheet.rectTransform; srt.anchorMin = srt.anchorMax = new Vector2(0.5f, 0f); srt.pivot = new Vector2(0.5f, 0f);
|
||
srt.anchoredPosition = new Vector2(0f, G.sheetBottom); srt.sizeDelta = new Vector2(G.sheetW, G.sheetH);
|
||
var title = NewText(srt, "Title", G.titleFontPx, TextAlignmentOptions.Midline, TextCol()); title.text = "장비 상자"; title.fontStyle = FontStyles.Bold;
|
||
Place(title.rectTransform, new Vector2(0.5f, 1f), new Vector2(0f, -10f), new Vector2(G.sheetW - 240f, G.titleH - 20f), new Vector2(0.5f, 1f));
|
||
var close = NewIconButton(srt, "Close", "closeIcon", L.closeBtnW, CloseSheet, "closeBtnBg");
|
||
Place((RectTransform)close.transform, new Vector2(1f, 1f), new Vector2(-14f, -14f), new Vector2(L.closeBtnW, L.closeBtnH), new Vector2(1f, 1f));
|
||
_boxKeys = NewPill(srt, "Keys", "keyIcon", new Vector2(0f, 1f), new Vector2(G.edge + H.pillW + H.pillIcon * 0.3f, -G.titleH), false, null);
|
||
_keyBtn = NewButton(srt, "열쇠 +" + _cfg.cheatKeyGrant + " (테스트)", Spr("btnNeutral"), Color.white, G.cheatBtnW, G.cheatBtnH,
|
||
() => Act(() => { WLInventory.AddKeys(_cfg.cheatKeyGrant); WLInventory.Commit("열쇠 +" + _cfg.cheatKeyGrant); return true; }), G.infoFontPx - 6f, TextCol());
|
||
Place((RectTransform)_keyBtn.transform, new Vector2(1f, 1f), new Vector2(-G.edge, -G.titleH), new Vector2(G.cheatBtnW, G.cheatBtnH), new Vector2(1f, 1f));
|
||
_keyBtn.gameObject.SetActive(_cfg.cheatGrantEnabled != 0);
|
||
_chestGlow = NewImage(srt, "Glow", Spr("glow"), new Color(1f, 0.9f, 0.5f, 0.35f), Image.Type.Simple);
|
||
Place(_chestGlow.rectTransform, Vector2.zero, new Vector2(G.chestX, G.chestY), new Vector2(G.glowSize, G.glowSize), new Vector2(0.5f, 0.5f));
|
||
_chestImg = NewImage(srt, "Chest", Spr("chestIcon"), Color.white, Image.Type.Simple); _chestImg.preserveAspect = true;
|
||
Place(_chestImg.rectTransform, Vector2.zero, new Vector2(G.chestX, G.chestY), new Vector2(G.chestSize, G.chestSize), new Vector2(0.5f, 0.5f));
|
||
_chest = _chestImg.rectTransform;
|
||
_boxInfo = NewText(srt, "Info", G.infoFontPx, TextAlignmentOptions.TopLeft, TextCol()); _boxInfo.enableWordWrapping = true;
|
||
Place(_boxInfo.rectTransform, Vector2.zero, new Vector2(G.infoX, G.infoY), new Vector2(G.infoW, G.infoH), new Vector2(0f, 0f));
|
||
_boxLog = NewText(srt, "Log", G.infoFontPx - 6f, TextAlignmentOptions.BottomLeft, Col("textDim", Color.white)); _boxLog.enableWordWrapping = true;
|
||
Place(_boxLog.rectTransform, Vector2.zero, new Vector2(G.edge, G.drawBtnBottom + G.drawBtnH + 8f), new Vector2(G.sheetW - G.edge * 2f, G.logH), new Vector2(0f, 0f));
|
||
_ratesBtn = NewIconButton(srt, "Rates", "ratesIcon", G.ratesBtn, OpenRates);
|
||
Place((RectTransform)_ratesBtn.transform, new Vector2(1f, 0f), new Vector2(-G.edge, G.drawBtnBottom), new Vector2(G.ratesBtn, G.ratesBtn), new Vector2(1f, 0f));
|
||
_box1Btn = NewButton(srt, "1회", Spr("draw1Btn"), Color.white, G.drawBtnW, G.drawBtnH, () => StartDraw(1), G.drawFontPx, DarkCol());
|
||
Place((RectTransform)_box1Btn.transform, new Vector2(1f, 0f), new Vector2(-(G.edge + G.ratesBtn + G.drawGap), G.drawBtnBottom), new Vector2(G.drawBtnW, G.drawBtnH), new Vector2(1f, 0f));
|
||
_box10Btn = NewButton(srt, "10회", Spr("draw10Btn"), Color.white, G.drawBtnW, G.drawBtnH, () => StartDraw(10), G.drawFontPx, TextCol());
|
||
Place((RectTransform)_box10Btn.transform, new Vector2(1f, 0f), new Vector2(-(G.edge + G.ratesBtn + G.drawGap * 2f + G.drawBtnW), G.drawBtnBottom), new Vector2(G.drawBtnW, G.drawBtnH), new Vector2(1f, 0f));
|
||
_flash = NewImage(prt, "Flash", null, new Color(1f, 1f, 1f, 0f), Image.Type.Simple); Stretch(_flash.rectTransform);
|
||
_boxPopup.SetActive(false);
|
||
}
|
||
|
||
void OpenSheet() { if (_boxPopup == null) return; _popup.SetActive(false); _boxPopup.SetActive(true); if (_chestImg != null) _chestImg.sprite = Spr("chestIcon"); RefreshBox(); }
|
||
void CloseSheet() { if (_drawCo != null) return; _boxPopup.SetActive(false); }
|
||
|
||
void StartDraw(int count)
|
||
{
|
||
if (_drawCo != null || _revealing) return;
|
||
if (!isActiveAndEnabled) return; // 꺼진 화면에서는 코루틴을 시작할 수 없다(2026-10-12 「WL_EquipGui is inactive」 에러)
|
||
var bd = WLEquipGacha.Box(_cfg.defaultBoxId);
|
||
if (bd == null) { _boxLog.text = "상자 표 없음"; return; }
|
||
if (WLInventory.GetKeys() < bd.keyCost * count) { _boxLog.text = "열쇠가 부족합니다 (" + bd.keyCost * count + " 필요)"; return; }
|
||
_drawCo = StartCoroutine(Co_Draw(count, false));
|
||
}
|
||
|
||
/// <summary>상자 흔들림 → 뽑기 → 뚜껑 열림 + 등급 색 번쩍임 → 결과 화면. instant = 연출 생략(프로브).</summary>
|
||
IEnumerator Co_Draw(int count, bool instant)
|
||
{
|
||
_lastDrawCount = count;
|
||
SetEnabled(_box1Btn, false, "draw1Btn"); SetEnabled(_box10Btn, false, "draw10Btn");
|
||
if (!instant) yield return Co_Shake(_chest, G.shakeSec, G.shakeDeg);
|
||
int done = 0;
|
||
try { done = WLEquipGacha.Draw(_cfg.defaultBoxId, count); }
|
||
catch (System.Exception ex) { Debug.LogError("[WL-825] 뽑기 예외 — " + ex); }
|
||
RefreshBox();
|
||
if (done <= 0 || WLEquipGacha.LastResults.Count == 0) { _drawCo = null; RefreshBox(); yield break; }
|
||
int best = 1;
|
||
for (int i = 0; i < WLEquipGacha.LastResults.Count; i++) best = Mathf.Max(best, WLEquipGacha.LastResults[i].tier);
|
||
if (_chestImg != null && Spr("chestOpenIcon") != null) _chestImg.sprite = Spr("chestOpenIcon");
|
||
if (!instant) { DrawsAnimated++; yield return Co_Flash(WLEquipIcons.TierColor(best), G.flashSec); }
|
||
ShowResult(WLEquipGacha.LastResults, best, instant);
|
||
_drawCo = null;
|
||
RefreshBox();
|
||
}
|
||
|
||
IEnumerator Co_Shake(RectTransform rt, float sec, float deg)
|
||
{
|
||
if (rt == null || sec <= 0f) yield break;
|
||
float t = 0f;
|
||
while (t < sec)
|
||
{
|
||
t += Time.unscaledDeltaTime;
|
||
float k = Mathf.Clamp01(t / sec);
|
||
rt.localRotation = Quaternion.Euler(0f, 0f, Mathf.Sin(t * 45f) * deg * (1f - k * 0.5f));
|
||
rt.localScale = Vector3.one * (1f + 0.18f * Mathf.Sin(k * Mathf.PI));
|
||
yield return null;
|
||
}
|
||
rt.localRotation = Quaternion.identity; rt.localScale = Vector3.one;
|
||
}
|
||
|
||
IEnumerator Co_Flash(Color c, float sec)
|
||
{
|
||
if (_flash == null || sec <= 0f) yield break;
|
||
float t = 0f;
|
||
while (t < sec)
|
||
{
|
||
t += Time.unscaledDeltaTime;
|
||
float k = Mathf.Clamp01(t / sec);
|
||
float a = k < 0.3f ? k / 0.3f : 1f - (k - 0.3f) / 0.7f;
|
||
_flash.color = new Color(c.r, c.g, c.b, a * 0.9f);
|
||
yield return null;
|
||
}
|
||
_flash.color = new Color(c.r, c.g, c.b, 0f);
|
||
}
|
||
|
||
// ── 결과 화면 ──
|
||
void BuildResultView()
|
||
{
|
||
_resultView = new GameObject("GachaResult", typeof(RectTransform)); _resultView.layer = UILayer; _resultView.transform.SetParent(_root, false);
|
||
var prt = (RectTransform)_resultView.transform; Stretch(prt);
|
||
var dim = NewImage(prt, "Dim", null, new Color(0f, 0f, 0f, 0.88f), Image.Type.Simple); Stretch(dim.rectTransform); dim.raycastTarget = true;
|
||
var dimBtn = dim.gameObject.AddComponent<Button>(); dimBtn.targetGraphic = dim; dimBtn.onClick.AddListener(() => { if (_revealing) _skipReveal = true; });
|
||
_resultGlow = NewImage(prt, "Glow", Spr("glow"), new Color(1f, 0.85f, 0.4f, 0.45f), Image.Type.Simple);
|
||
Place(_resultGlow.rectTransform, new Vector2(0.5f, 0.5f), new Vector2(0f, G.resultGlowY), new Vector2(G.resultGlow, G.resultGlow));
|
||
_resultTitle = NewText(prt, "Title", G.resultTitleFontPx, TextAlignmentOptions.Midline, Color.white); _resultTitle.fontStyle = FontStyles.Bold;
|
||
_resultTitle.outlineWidth = 0.2f; _resultTitle.outlineColor = new Color32(40, 25, 10, 255);
|
||
Place(_resultTitle.rectTransform, new Vector2(0.5f, 1f), new Vector2(0f, -G.resultTitleTop), new Vector2(960f, G.resultTitleFontPx + 24f));
|
||
var cards = new GameObject("Cards", typeof(RectTransform), typeof(GridLayoutGroup), typeof(ContentSizeFitter)); cards.layer = UILayer; cards.transform.SetParent(prt, false);
|
||
_resultGrid = (RectTransform)cards.transform;
|
||
Place(_resultGrid, new Vector2(0.5f, 0.5f), new Vector2(0f, G.resultGridY), Vector2.zero);
|
||
var grid = cards.GetComponent<GridLayoutGroup>(); grid.spacing = new Vector2(G.resultGap, G.resultGap); grid.childAlignment = TextAnchor.MiddleCenter;
|
||
grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount; grid.constraintCount = Mathf.Max(1, (int)G.resultCols);
|
||
var fit = cards.GetComponent<ContentSizeFitter>(); fit.horizontalFit = ContentSizeFitter.FitMode.PreferredSize; fit.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||
_againBtn = NewButton(prt, "다시 뽑기", Spr("againBtn"), Color.white, G.againBtnW, G.againBtnH, () => { if (_revealing) return; HideResult(); StartDraw(_lastDrawCount); }, G.drawFontPx, DarkCol());
|
||
Place((RectTransform)_againBtn.transform, new Vector2(0.5f, 0f), new Vector2(0f, G.againY), new Vector2(G.againBtnW, G.againBtnH), new Vector2(0.5f, 0.5f));
|
||
_resultCloseBtn = NewButton(prt, "확인", Spr("closeTextBtn"), Color.white, G.closeBtnW, G.closeBtnH, () => { if (!_revealing) HideResult(); }, G.infoFontPx, TextCol());
|
||
Place((RectTransform)_resultCloseBtn.transform, new Vector2(0.5f, 0f), new Vector2(0f, G.closeY), new Vector2(G.closeBtnW, G.closeBtnH), new Vector2(0.5f, 0.5f));
|
||
_resultHint = NewText(prt, "Hint", G.resultNameFontPx, TextAlignmentOptions.Midline, new Color(1f, 1f, 1f, 0.75f)); _resultHint.text = "화면을 누르면 건너뛰기";
|
||
Place(_resultHint.rectTransform, new Vector2(0.5f, 0f), new Vector2(0f, G.closeY), new Vector2(700f, G.resultNameFontPx + 16f), new Vector2(0.5f, 0.5f));
|
||
_resultView.SetActive(false);
|
||
}
|
||
|
||
void ShowResult(List<WLDrawResult> res, int bestTier, bool instant)
|
||
{
|
||
if (_resultView == null) return;
|
||
ResultsShown++;
|
||
_resultView.SetActive(true);
|
||
int n = res.Count; bool big = n == 1;
|
||
float cs = big ? G.resultCardBig : G.resultCard;
|
||
var grid = _resultGrid.GetComponent<GridLayoutGroup>();
|
||
grid.cellSize = new Vector2(cs, cs + G.resultNameFontPx + 10f);
|
||
grid.constraintCount = big ? 1 : Mathf.Max(1, (int)G.resultCols);
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
if (i >= _resultCards.Count)
|
||
{
|
||
var holder = NewRT(_resultGrid, "Card" + i);
|
||
var c = NewCell(holder, "Cell", cs, L.cellIconInset, G.resultNameFontPx, false, false, null);
|
||
c.root.anchorMin = new Vector2(0f, 1f); c.root.anchorMax = new Vector2(1f, 1f); c.root.pivot = new Vector2(0.5f, 1f); c.root.anchoredPosition = Vector2.zero; c.root.sizeDelta = new Vector2(0f, cs);
|
||
var nm = NewText(holder, "Name", G.resultNameFontPx - 2f, TextAlignmentOptions.Top, TextCol());
|
||
nm.rectTransform.anchorMin = new Vector2(0f, 0f); nm.rectTransform.anchorMax = new Vector2(1f, 0f); nm.rectTransform.pivot = new Vector2(0.5f, 0f); nm.rectTransform.anchoredPosition = Vector2.zero; nm.rectTransform.sizeDelta = new Vector2(0f, G.resultNameFontPx + 6f);
|
||
nm.overflowMode = TextOverflowModes.Ellipsis;
|
||
_resultCards.Add(c); _resultNames.Add(nm);
|
||
}
|
||
var card = _resultCards[i];
|
||
var holderRt = (RectTransform)card.root.parent;
|
||
var le = holderRt.GetComponent<LayoutElement>() ?? holderRt.gameObject.AddComponent<LayoutElement>();
|
||
le.preferredWidth = cs; le.minWidth = cs; le.preferredHeight = cs + G.resultNameFontPx + 10f;
|
||
card.root.sizeDelta = new Vector2(0f, cs);
|
||
holderRt.gameObject.SetActive(true);
|
||
BindResultCard(card, _resultNames[i], res[i]);
|
||
holderRt.localScale = Vector3.zero;
|
||
}
|
||
for (int i = n; i < _resultCards.Count; i++) _resultCards[i].root.parent.gameObject.SetActive(false);
|
||
var bd = WLEquipGacha.Box(_cfg.defaultBoxId);
|
||
bool gotS = bd != null && bestTier >= bd.sTier;
|
||
var bc = WLEquipIcons.TierColor(bestTier);
|
||
_resultTitle.text = gotS ? "<color=#" + ColorUtility.ToHtmlStringRGB(bc) + ">" + WLEquipGrades.TierLabel(bestTier, false) + " 등장!</color>" : "획득!";
|
||
_resultGlow.color = new Color(bc.r, bc.g, bc.b, 0.45f);
|
||
DrawLabel(_againBtn, "다시 뽑기 ×" + n, (bd != null ? bd.keyCost : 1) * n);
|
||
_skipReveal = instant;
|
||
StartCoroutine(Co_Reveal(n));
|
||
}
|
||
|
||
void BindResultCard(Cell c, TextMeshProUGUI name, WLDrawResult r)
|
||
{
|
||
BindCellJson(c, r.json, 1);
|
||
c.level.text = WLEquipGrades.TierLabel(r.tier, false) + (string.IsNullOrEmpty(r.why) ? "" : " " + r.why);
|
||
c.lvBg.rectTransform.sizeDelta = new Vector2(c.size * (string.IsNullOrEmpty(r.why) ? 0.5f : 0.9f), c.lvBg.rectTransform.sizeDelta.y); // 「에픽 (확정)」 이 잘리지 않게
|
||
SetOn(c.lvBg, true);
|
||
if (name != null) name.text = r.json != null ? r.json.Item_name : "";
|
||
}
|
||
|
||
IEnumerator Co_Reveal(int n)
|
||
{
|
||
_revealing = true;
|
||
_againBtn.gameObject.SetActive(false); _resultCloseBtn.gameObject.SetActive(false); _resultHint.gameObject.SetActive(!_skipReveal);
|
||
yield return null;
|
||
for (int i = 0; i < n && i < _resultCards.Count; i++)
|
||
{
|
||
var holder = (RectTransform)_resultCards[i].root.parent;
|
||
if (_skipReveal) { holder.localScale = Vector3.one; continue; }
|
||
yield return Co_Pop(holder, G.popSec);
|
||
if (i < WLEquipGacha.LastResults.Count && WLEquipGacha.LastResults[i].isS) StartCoroutine(Co_Stars(holder));
|
||
float w = 0f; while (w < G.revealStagger && !_skipReveal) { w += Time.unscaledDeltaTime; yield return null; }
|
||
}
|
||
for (int i = 0; i < n && i < _resultCards.Count; i++) _resultCards[i].root.parent.localScale = Vector3.one;
|
||
_revealing = false; _skipReveal = false;
|
||
_resultHint.gameObject.SetActive(false);
|
||
var bd = WLEquipGacha.Box(_cfg.defaultBoxId); int kc = bd != null ? bd.keyCost : 1;
|
||
SetEnabled(_againBtn, WLInventory.GetKeys() >= kc * _lastDrawCount, "againBtn");
|
||
_againBtn.gameObject.SetActive(true); _resultCloseBtn.gameObject.SetActive(true);
|
||
}
|
||
|
||
IEnumerator Co_Pop(RectTransform rt, float sec)
|
||
{
|
||
float t = 0f;
|
||
while (t < sec && !_skipReveal)
|
||
{
|
||
t += Time.unscaledDeltaTime; float k = Mathf.Clamp01(t / sec);
|
||
float s = k < 0.6f ? Mathf.Lerp(0f, 1.15f, k / 0.6f) : Mathf.Lerp(1.15f, 1f, (k - 0.6f) / 0.4f);
|
||
rt.localScale = Vector3.one * s;
|
||
yield return null;
|
||
}
|
||
rt.localScale = Vector3.one;
|
||
}
|
||
|
||
/// <summary>S급(전설 이상) 카드 — 별이 사방으로 튀어 사라진다.</summary>
|
||
IEnumerator Co_Stars(RectTransform around)
|
||
{
|
||
int n = Mathf.Max(0, (int)G.starCount);
|
||
if (n == 0 || Spr("star") == null || around == null) yield break;
|
||
var stars = new List<Image>(n); var dirs = new List<Vector2>(n);
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
var img = NewImage(around, "Star", Spr("star"), new Color(1f, 0.95f, 0.5f, 1f), Image.Type.Simple); img.preserveAspect = true;
|
||
Place(img.rectTransform, new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(G.starSize, G.starSize));
|
||
float a = (i + Random.value * 0.5f) / n * Mathf.PI * 2f;
|
||
stars.Add(img); dirs.Add(new Vector2(Mathf.Cos(a), Mathf.Sin(a)));
|
||
}
|
||
float t = 0f;
|
||
while (t < G.starSec)
|
||
{
|
||
t += Time.unscaledDeltaTime; float k = Mathf.Clamp01(t / G.starSec);
|
||
for (int i = 0; i < stars.Count; i++)
|
||
{
|
||
if (stars[i] == null) continue;
|
||
stars[i].rectTransform.anchoredPosition = dirs[i] * (G.starFly * Mathf.Sin(k * Mathf.PI * 0.5f));
|
||
stars[i].rectTransform.localScale = Vector3.one * (1f - k * 0.6f);
|
||
stars[i].color = new Color(1f, 0.95f, 0.5f, 1f - k);
|
||
}
|
||
yield return null;
|
||
}
|
||
for (int i = 0; i < stars.Count; i++) if (stars[i] != null) Destroy(stars[i].gameObject);
|
||
}
|
||
|
||
void HideResult()
|
||
{
|
||
if (_resultView != null) _resultView.SetActive(false);
|
||
_revealing = false; _skipReveal = false;
|
||
if (_chestImg != null) _chestImg.sprite = Spr("chestIcon");
|
||
RefreshBox();
|
||
}
|
||
|
||
// ── 확률·천장 팝업 ──
|
||
void BuildRatesPopup()
|
||
{
|
||
_ratesPopup = new GameObject("RatesPopup", typeof(RectTransform)); _ratesPopup.layer = UILayer; _ratesPopup.transform.SetParent(_root, false);
|
||
var prt = (RectTransform)_ratesPopup.transform; Stretch(prt);
|
||
var dim = NewImage(prt, "Dim", null, Col("dim", new Color(0f, 0f, 0f, 0.7f)), Image.Type.Simple); Stretch(dim.rectTransform); dim.raycastTarget = true;
|
||
var dimBtn = dim.gameObject.AddComponent<Button>(); dimBtn.targetGraphic = dim; dimBtn.onClick.AddListener(() => _ratesPopup.SetActive(false));
|
||
var box = NewPopupBox(prt, "Box", new Vector2(G.ratesW, G.ratesH));
|
||
var brt = box.rectTransform;
|
||
var t = NewText(brt, "Title", L.popupNameFontPx, TextAlignmentOptions.Midline, TextCol()); t.text = "확률 · 천장"; t.fontStyle = FontStyles.Bold;
|
||
Place(t.rectTransform, new Vector2(0.5f, 1f), new Vector2(0f, -16f), new Vector2(G.ratesW - 240f, L.popupTitleH - 30f), new Vector2(0.5f, 1f));
|
||
var closeBtn = NewIconButton(brt, "Close", "closeIcon", L.closeBtnW, () => _ratesPopup.SetActive(false), "closeBtnBg");
|
||
Place((RectTransform)closeBtn.transform, new Vector2(1f, 1f), new Vector2(-14f, -14f), new Vector2(L.closeBtnW, L.closeBtnH), new Vector2(1f, 1f));
|
||
var body = NewRT(brt, "Body");
|
||
body.anchorMin = new Vector2(0f, 0f); body.anchorMax = new Vector2(1f, 1f); body.offsetMin = new Vector2(40f, 40f); body.offsetMax = new Vector2(-40f, -L.popupTitleH - 10f);
|
||
var vl = body.gameObject.AddComponent<VerticalLayoutGroup>();
|
||
vl.padding = new RectOffset(0, 0, 0, 0); vl.spacing = 10f; vl.childAlignment = TextAnchor.UpperCenter;
|
||
vl.childControlWidth = true; vl.childControlHeight = true; vl.childForceExpandWidth = true; vl.childForceExpandHeight = false;
|
||
for (int i = 0; i < 7; i++)
|
||
{
|
||
var row = NewRow(body, "Rate" + i, G.ratesRowH, 14f);
|
||
var hl = row.GetComponent<HorizontalLayoutGroup>(); hl.childAlignment = TextAnchor.MiddleLeft;
|
||
var pill = NewImage(row, "Pill", Spr("gradeLabel"), Color.white); Pref(pill.rectTransform, G.ratesPillW, G.ratesRowH - 16f);
|
||
var pl = NewText(pill.rectTransform, "Label", G.ratesFontPx - 4f, TextAlignmentOptions.Midline, Color.white); pl.fontStyle = FontStyles.Bold; Stretch(pl.rectTransform);
|
||
var name = NewText(row, "Name", G.ratesFontPx, TextAlignmentOptions.MidlineLeft, TextCol()); Flex(name.rectTransform, 1f);
|
||
var pct = NewText(row, "Pct", G.ratesFontPx, TextAlignmentOptions.MidlineRight, TextCol()); pct.fontStyle = FontStyles.Bold; Pref(pct.rectTransform, 200f, G.ratesRowH);
|
||
_ratesRows.Add(row.gameObject); _ratesPills.Add(pill); _ratesNames.Add(name); _ratesPcts.Add(pct);
|
||
}
|
||
_ratesPity = NewText(body, "Pity", G.ratesFontPx, TextAlignmentOptions.TopLeft, TextCol()); _ratesPity.enableWordWrapping = true; Pref(_ratesPity.rectTransform, 0f, G.ratesFontPx * 2f + 28f);
|
||
_epicBarFill = NewBar(body, "EpicBar");
|
||
_sBarFill = NewBar(body, "SBar");
|
||
var note = NewText(body, "Note", G.ratesFontPx - 6f, TextAlignmentOptions.TopLeft, Col("textDim", Color.white)); note.enableWordWrapping = true;
|
||
note.text = "확률·확정 주기·천장은 제안값(PD 확정 전) · 10회마다 에픽 이상 확정 · 전설 천장은 자연 전설이 나오면 다시 셉니다 · 불멸·신화는 합성 승급으로만";
|
||
var nle = note.gameObject.AddComponent<LayoutElement>(); nle.flexibleHeight = 1f; nle.minHeight = 80f;
|
||
_ratesPopup.SetActive(false);
|
||
}
|
||
|
||
Image NewBar(RectTransform parent, string name)
|
||
{
|
||
var bg = NewImage(parent, name, Spr("barBg"), Spr("barBg") != null ? Color.white : new Color(0f, 0f, 0f, 0.25f)); Pref(bg.rectTransform, 0f, G.barH);
|
||
var fill = NewImage(bg.rectTransform, "Fill", Spr("barFill"), new Color(1f, 0.75f, 0.2f, 1f));
|
||
fill.rectTransform.anchorMin = Vector2.zero; fill.rectTransform.anchorMax = new Vector2(0f, 1f); fill.rectTransform.pivot = new Vector2(0f, 0.5f);
|
||
fill.rectTransform.offsetMin = new Vector2(4f, 4f); fill.rectTransform.offsetMax = new Vector2(-4f, -4f);
|
||
return fill;
|
||
}
|
||
|
||
static void SetBar(Image fill, float ratio)
|
||
{
|
||
if (fill == null) return;
|
||
ratio = Mathf.Clamp01(ratio);
|
||
fill.gameObject.SetActive(ratio > 0.02f);
|
||
fill.rectTransform.anchorMax = new Vector2(ratio, 1f);
|
||
}
|
||
|
||
void OpenRates() { if (_ratesPopup == null) return; _ratesPopup.SetActive(true); RefreshRates(); }
|
||
|
||
void RefreshRates()
|
||
{
|
||
var bd = WLEquipGacha.Box(_cfg.defaultBoxId); var d = WLInventory.Data;
|
||
int shown = 0;
|
||
if (bd != null && bd.tierWeights != null)
|
||
{
|
||
int total = 0; for (int i = 0; i < bd.tierWeights.Count; i++) total += Mathf.Max(0, bd.tierWeights[i].weight);
|
||
for (int i = 0; i < bd.tierWeights.Count && shown < _ratesRows.Count; i++)
|
||
{
|
||
var tw = bd.tierWeights[i];
|
||
_ratesRows[shown].SetActive(true);
|
||
_ratesPills[shown].color = TierCol(tw.tier);
|
||
var pl = _ratesPills[shown].GetComponentInChildren<TextMeshProUGUI>(); if (pl != null) pl.text = WLEquipGrades.TierLabel(tw.tier, false);
|
||
_ratesNames[shown].text = WLEquipGrades.TierLabel(tw.tier, false) + (tw.tier >= bd.sTier ? " <size=75%>(S급)</size>" : "");
|
||
_ratesPcts[shown].text = total > 0 ? (100.0 * tw.weight / total).ToString("F1") + "%" : "-";
|
||
shown++;
|
||
}
|
||
}
|
||
for (int i = shown; i < _ratesRows.Count; i++) _ratesRows[i].SetActive(false);
|
||
if (bd == null || d == null) { _ratesPity.text = "상자 표 없음"; return; }
|
||
int toEpic = bd.epicEvery > 0 ? bd.epicEvery - (d.boxDraws % bd.epicEvery) : 0;
|
||
int toS = bd.sPity > 0 ? Mathf.Max(0, bd.sPity - d.sinceS) : 0;
|
||
_ratesPity.text = (bd.epicEvery > 0 ? WLEquipGrades.TierLabel(bd.epicTier, false) + " 확정까지 " + toEpic + "회 (" + bd.epicEvery + "회마다)\n" : "")
|
||
+ (bd.sPity > 0 ? WLEquipGrades.TierLabel(bd.sTier, false) + " 천장까지 " + toS + "회 (" + bd.sPity + "회)" : "");
|
||
SetBar(_epicBarFill, bd.epicEvery > 0 ? 1f - (float)toEpic / bd.epicEvery : 0f);
|
||
SetBar(_sBarFill, bd.sPity > 0 ? 1f - (float)toS / bd.sPity : 0f);
|
||
}
|
||
|
||
void DrawLabel(Button b, string head, int keys) { if (b != null) SetButtonLabel(b, head + "\n<size=70%>열쇠 " + keys + "</size>"); }
|
||
|
||
void RefreshBox()
|
||
{
|
||
if (_boxPopup == null || !_boxPopup.activeSelf) return;
|
||
var bd = WLEquipGacha.Box(_cfg.defaultBoxId); int kc = bd != null ? bd.keyCost : 1; int keys = WLInventory.GetKeys();
|
||
_boxKeys.text = keys.ToString();
|
||
_boxInfo.text = WLEquipGacha.InfoLine(_cfg.defaultBoxId).Replace(" · ", "\n");
|
||
_boxLog.text = string.IsNullOrEmpty(WLEquipGacha.LastLog) ? "" : "최근: " + WLEquipGacha.LastLog;
|
||
DrawLabel(_box1Btn, "1회", kc); DrawLabel(_box10Btn, "10회", kc * 10);
|
||
bool busy = _drawCo != null;
|
||
SetEnabled(_box1Btn, !busy && bd != null && keys >= kc, "draw1Btn");
|
||
SetEnabled(_box10Btn, !busy && bd != null && keys >= kc * 10, "draw10Btn");
|
||
if (_ratesPopup != null && _ratesPopup.activeSelf) RefreshRates();
|
||
}
|
||
|
||
// 프로브용
|
||
public void ShowBoxForProbe() { if (!gameObject.activeSelf) Open(); OpenSheet(); }
|
||
/// <summary>프로브용 뽑기 — 화면이 닫혀 있으면 먼저 연다(닫힌 GameObject 에서 StartCoroutine 은 에러).</summary>
|
||
public void DrawForProbe(int count, bool instant)
|
||
{
|
||
if (!gameObject.activeSelf) { Open(); OpenSheet(); }
|
||
if (!isActiveAndEnabled) return;
|
||
if (_drawCo == null && !_revealing) _drawCo = StartCoroutine(Co_Draw(count, instant));
|
||
}
|
||
public void ShowRatesForProbe() { OpenRates(); }
|
||
public void HideResultForProbe() { HideResult(); }
|
||
public bool IsBusyForProbe { get { return _drawCo != null || _revealing; } }
|
||
public bool ResultVisibleForProbe { get { return _resultView != null && _resultView.activeSelf; } }
|
||
public bool SheetVisibleForProbe { get { return _boxPopup != null && _boxPopup.activeSelf; } }
|
||
}
|
||
}
|