diff --git a/Assets/Data/UI/ui_theme.csv b/Assets/Data/UI/ui_theme.csv index 0f425f9a..17efdd21 100644 --- a/Assets/Data/UI/ui_theme.csv +++ b/Assets/Data/UI/ui_theme.csv @@ -428,3 +428,59 @@ color,alert_red,,,,,,#FF243E,알림 원(수량) skin,btn_s_gray,Button/Button01_s_Gray.png,,2.1,,,,Secondary 회색 h56(비활성 · 검수 결함 3) color,row_label_strip,,,,,,#FFFFFF0A,편성 라벨 칸 세로 띠(흰 4 %) font,title,Assets/UI/GUIPro/ResourcesData/Fonts/Sen-ExtraBold.ttf,,,,,,제목·숫자(라틴 Sen · 한글 = 폴백 Malgun Gothic · PD 결정 전) +color,text_on_yellow,,,,,,#1E1E2A,노랑 Primary 위 글자(Fable §5-8) +color,ribbon_glow,,,,,,#F3B72EB3,결과 리본 뒤 금빛(α 0.7 · Fable §4-2 α 0.35 는 어두운 덮개 위에서 안 보여 올림) +color,row_on_field,,,,,,#3F4262,필드 위 행 판(필드보다 밝게 · 팝업 교환 행) +skin,btn_l_yellow,Button/Button01_l_Yellow.png,,1.3,,,,Primary 노랑 h110(확인·10연·열기 · Fable §5-5) +skin,btn_l_red,Button/Button01_l_White_Bg.png,Button/Button01_l_White_Light1.png,1.35,5;5;5;18,0.35,,Primary 붉은 h104(출정 · 틴트 red) +skin,title_divider_l,Label/Title_Line01_Divider_Left.png,,1,,,,소제목 왼쪽 선(Fable §5-2) +skin,title_divider_r,Label/Title_Line01_Divider_Right.png,,1,,,,소제목 오른쪽 선 +icon,star_on_l,IconMisc/Icon_Star_On.png,,,,,,큰 별 켜짐(결과 · star_off 와 한 벌 289 px) +icon,x_small,IconMisc/Icon_Close02.png,,,,,,미달 표시(틴트 disabled) +icon,badge_first,Label/Label_Badge01_Yellow.png,,,,,,첫 클리어 뱃지 +icon,badge_hot,Label/Label_Badge01_Red.png,,,,,,픽업·×2 뱃지 +icon,ad,IconMisc/Icon_Going.png,,,,,,광고 보기 글리프(임시) +skin,banner_pattern,Frame/BannerFrame00_01~02_Pattern.png,,1,,,,배너 아래 문양 띠(Tiled · Fable §4-4) +color,banner_pattern,,,,,,#FFFFFF14,배너 아래 문양(흰 8 %) +icon,lv_badge_y,Slider/Slider_Level02_Icon_Badge_Yellow.png,,,,,,천장 게이지 배지(노랑) +color,chest_bronze,,,,,,#B0784D,청동 상자 카드 틴트(구 TreasureScreen.ChestColor 상수 → 테마 · C45) +color,chest_bronze_bg,,,,,,#FF8A1E,청동 상자 칸 bg(= gear_orange_bg) +color,chest_bronze_deco,,,,,,#FFA43CE6,청동 상자 칸 deco(= gear_orange_deco) +color,chest_bronze_light,,,,,,#FFD36BD9,청동 상자 칸 light(= gear_orange_light) +color,chest_bronze_glow,,,,,,#FFD98A59,청동 상자 칸 glow(= gear_orange_glow) +color,chest_silver,,,,,,#A8ADB8,은 상자 카드 틴트(구 TreasureScreen.ChestColor 상수 → 테마 · C45) +color,chest_silver_bg,,,,,,#617E8A,은 상자 칸 bg(= gear_white_bg) +color,chest_silver_deco,,,,,,#718E99E6,은 상자 칸 deco(= gear_white_deco) +color,chest_silver_light,,,,,,#82A0ABD9,은 상자 칸 light(= gear_white_light) +color,chest_silver_glow,,,,,,#FFFFFF59,은 상자 칸 glow(= gear_white_glow) +color,chest_gold,,,,,,#E0B338,금 상자 카드 틴트(구 TreasureScreen.ChestColor 상수 → 테마 · C45) +color,chest_gold_bg,,,,,,#FFC900,금 상자 칸 bg(= gear_gold_bg) +color,chest_gold_deco,,,,,,#FFDE00E6,금 상자 칸 deco(= gear_gold_deco) +color,chest_gold_light,,,,,,#FEFD4ED9,금 상자 칸 light(= gear_gold_light) +color,chest_gold_glow,,,,,,#F3FF3159,금 상자 칸 glow(= gear_gold_glow) +color,chest_general,,,,,,#9E4038,명장 상자 카드 틴트(구 TreasureScreen.ChestColor 상수 → 테마 · C45) +color,chest_general_bg,,,,,,#DF2F38,명장 상자 칸 bg(= tre_myth_bg) +color,chest_general_deco,,,,,,#FD474BE6,명장 상자 칸 deco(= tre_myth_deco) +color,chest_general_light,,,,,,#FF8F9CD9,명장 상자 칸 light(= tre_myth_light) +color,chest_general_glow,,,,,,#FF9DA459,명장 상자 칸 glow(= tre_myth_glow) +color,chest_gem,,,,,,#4DA691,보옥 상자 카드 틴트(구 TreasureScreen.ChestColor 상수 → 테마 · C45) +color,chest_gem_bg,,,,,,#1DC056,보옥 상자 칸 bg(= gear_green_bg) +color,chest_gem_deco,,,,,,#34D934E6,보옥 상자 칸 deco(= gear_green_deco) +color,chest_gem_light,,,,,,#B2F11FD9,보옥 상자 칸 light(= gear_green_light) +color,chest_gem_glow,,,,,,#C0FF5159,보옥 상자 칸 glow(= gear_green_glow) +skin,card3_bg,Frame/CardFrame03_n_Bg.png,,1,,,,상자 카드 바탕(틴트 · Fable §4-5) +skin,card3_inner,Frame/CardFrame03_n_InnerBg.png,,1,,,,상자 카드 안쪽 +skin,card3_light,Frame/CardFrame03_n_Light.png,,1,,,,상자 카드 광택 +color,card3_inner,,,,,,#00000040,상자 카드 안쪽(검정 25 %) +color,card3_light,,,,,,#FFFFFF80,상자 카드 광택(흰 50 %) +color,chest_glow,,,,,,#F3B72E73,상자 뒤 빛(α 0.45) +skin,banner_point,Frame/BannerFrame05_Single.png,,3,,,,상자 포인트 띠(h90 · 장식 비율 유지) +color,banner_point,,,,,,#2C2F4A,상자 포인트 띠 틴트 +skin,bar4_bg,Slider/Slider_Basic04_Bg.png,,1.5,,,,가는 진행 바 바탕 +skin,bar4_fill,Slider/Slider_Basic04_Fill_Orange.png,,1.5,,,,가는 진행 바 채움 +icon,tre_stat_hp,Icon_ItemIcons/128/ItemIcon_Heart_Red.Png,,,,,,보물 임시 아이콘 체력(최종 = 보물 그림 N3) +icon,tre_stat_atk,Icon_ItemIcons/128/ItemIcon_Gear_Sword.Png,,,,,,보물 임시 아이콘 공격 +icon,tre_stat_def,Icon_ItemIcons/128/ItemIcon_Gear_Armor_Top.Png,,,,,,보물 임시 아이콘 방어 +icon,tre_stat_crit,Icon_ItemIcons/128/ItemIcon_Damage.Png,,,,,,보물 임시 아이콘 치명 +icon,tre_stat_range,Icon_ItemIcons/128/ItemIcon_Gear_Boots.Png,,,,,,보물 임시 아이콘 사거리 +icon,tre_stat_splash,Icon_ItemIcons/128/ItemIcon_Battle.Png,,,,,,보물 임시 아이콘 광역 diff --git a/Assets/Resources/UI/UITheme.asset b/Assets/Resources/UI/UITheme.asset index 9b15d434..72838893 100644 --- a/Assets/Resources/UI/UITheme.asset +++ b/Assets/Resources/UI/UITheme.asset @@ -499,6 +499,72 @@ MonoBehaviour: color: {r: 1, g: 0.14117648, b: 0.24313726, a: 1} - id: row_label_strip color: {r: 1, g: 1, b: 1, a: 0.039215688} + - id: text_on_yellow + color: {r: 0.11764706, g: 0.11764706, b: 0.16470589, a: 1} + - id: ribbon_glow + color: {r: 0.9529412, g: 0.7176471, b: 0.18039216, a: 0.7019608} + - id: row_on_field + color: {r: 0.24705882, g: 0.25882354, b: 0.38431373, a: 1} + - id: banner_pattern + color: {r: 1, g: 1, b: 1, a: 0.078431375} + - id: chest_bronze + color: {r: 0.6901961, g: 0.47058824, b: 0.3019608, a: 1} + - id: chest_bronze_bg + color: {r: 1, g: 0.5411765, b: 0.11764706, a: 1} + - id: chest_bronze_deco + color: {r: 1, g: 0.6431373, b: 0.23529412, a: 0.9019608} + - id: chest_bronze_light + color: {r: 1, g: 0.827451, b: 0.41960785, a: 0.8509804} + - id: chest_bronze_glow + color: {r: 1, g: 0.8509804, b: 0.5411765, a: 0.34901962} + - id: chest_silver + color: {r: 0.65882355, g: 0.6784314, b: 0.72156864, a: 1} + - id: chest_silver_bg + color: {r: 0.38039216, g: 0.49411765, b: 0.5411765, a: 1} + - id: chest_silver_deco + color: {r: 0.44313726, g: 0.5568628, b: 0.6, a: 0.9019608} + - id: chest_silver_light + color: {r: 0.50980395, g: 0.627451, b: 0.67058825, a: 0.8509804} + - id: chest_silver_glow + color: {r: 1, g: 1, b: 1, a: 0.34901962} + - id: chest_gold + color: {r: 0.8784314, g: 0.7019608, b: 0.21960784, a: 1} + - id: chest_gold_bg + color: {r: 1, g: 0.7882353, b: 0, a: 1} + - id: chest_gold_deco + color: {r: 1, g: 0.87058824, b: 0, a: 0.9019608} + - id: chest_gold_light + color: {r: 0.99607843, g: 0.99215686, b: 0.30588236, a: 0.8509804} + - id: chest_gold_glow + color: {r: 0.9529412, g: 1, b: 0.19215687, a: 0.34901962} + - id: chest_general + color: {r: 0.61960787, g: 0.2509804, b: 0.21960784, a: 1} + - id: chest_general_bg + color: {r: 0.8745098, g: 0.18431373, b: 0.21960784, a: 1} + - id: chest_general_deco + color: {r: 0.99215686, g: 0.2784314, b: 0.29411766, a: 0.9019608} + - id: chest_general_light + color: {r: 1, g: 0.56078434, b: 0.6117647, a: 0.8509804} + - id: chest_general_glow + color: {r: 1, g: 0.6156863, b: 0.6431373, a: 0.34901962} + - id: chest_gem + color: {r: 0.3019608, g: 0.6509804, b: 0.5686275, a: 1} + - id: chest_gem_bg + color: {r: 0.11372549, g: 0.7529412, b: 0.3372549, a: 1} + - id: chest_gem_deco + color: {r: 0.20392157, g: 0.8509804, b: 0.20392157, a: 0.9019608} + - id: chest_gem_light + color: {r: 0.69803923, g: 0.94509804, b: 0.12156863, a: 0.8509804} + - id: chest_gem_glow + color: {r: 0.7529412, g: 1, b: 0.31764707, a: 0.34901962} + - id: card3_inner + color: {r: 0, g: 0, b: 0, a: 0.2509804} + - id: card3_light + color: {r: 1, g: 1, b: 1, a: 0.5019608} + - id: chest_glow + color: {r: 0.9529412, g: 0.7176471, b: 0.18039216, a: 0.4509804} + - id: banner_point + color: {r: 0.17254902, g: 0.18431373, b: 0.2901961, a: 1} skins: - id: card pack: {fileID: 21300000, guid: 3070a6f23e6ca4273897acf1b7e8c5ce, type: 3} @@ -1193,6 +1259,83 @@ MonoBehaviour: ppu: 2.1 inset: {x: 0, y: 0, z: 0, w: 0} overlayAlpha: 1 + - id: btn_l_yellow + pack: {fileID: 21300000, guid: 6cdadcc1a39954748abf7d3d4351e1e6, type: 3} + samguk: {fileID: 0} + overlay: {fileID: 0} + ppu: 1.3 + inset: {x: 0, y: 0, z: 0, w: 0} + overlayAlpha: 1 + - id: btn_l_red + pack: {fileID: 21300000, guid: 3eba92f7629b145ae9366b196000a3f3, type: 3} + samguk: {fileID: 0} + overlay: {fileID: 21300000, guid: b802653e90c694dd0966490b86866a22, type: 3} + ppu: 1.35 + inset: {x: 5, y: 5, z: 5, w: 18} + overlayAlpha: 0.35 + - id: title_divider_l + pack: {fileID: 21300000, guid: 878668f672fc94e3b92df41daa6fb966, type: 3} + samguk: {fileID: 0} + overlay: {fileID: 0} + ppu: 1 + inset: {x: 0, y: 0, z: 0, w: 0} + overlayAlpha: 1 + - id: title_divider_r + pack: {fileID: 21300000, guid: 6c3ea799aad7c48919b36b752f77ead4, type: 3} + samguk: {fileID: 0} + overlay: {fileID: 0} + ppu: 1 + inset: {x: 0, y: 0, z: 0, w: 0} + overlayAlpha: 1 + - id: banner_pattern + pack: {fileID: 21300000, guid: b75949b3a23a049fb814d9cb21aa2c39, type: 3} + samguk: {fileID: 0} + overlay: {fileID: 0} + ppu: 1 + inset: {x: 0, y: 0, z: 0, w: 0} + overlayAlpha: 1 + - id: card3_bg + pack: {fileID: 21300000, guid: 12e2f59e45c9e4f6280b38d49ef68605, type: 3} + samguk: {fileID: 0} + overlay: {fileID: 0} + ppu: 1 + inset: {x: 0, y: 0, z: 0, w: 0} + overlayAlpha: 1 + - id: card3_inner + pack: {fileID: 21300000, guid: 74ae39be6091048c7a16b3cd8712ce2c, type: 3} + samguk: {fileID: 0} + overlay: {fileID: 0} + ppu: 1 + inset: {x: 0, y: 0, z: 0, w: 0} + overlayAlpha: 1 + - id: card3_light + pack: {fileID: 21300000, guid: 6cd33b9fcf759447fa159757f293591b, type: 3} + samguk: {fileID: 0} + overlay: {fileID: 0} + ppu: 1 + inset: {x: 0, y: 0, z: 0, w: 0} + overlayAlpha: 1 + - id: banner_point + pack: {fileID: 21300000, guid: 0311dcd69620e47f4b965eb345ef9b35, type: 3} + samguk: {fileID: 0} + overlay: {fileID: 0} + ppu: 3 + inset: {x: 0, y: 0, z: 0, w: 0} + overlayAlpha: 1 + - id: bar4_bg + pack: {fileID: 21300000, guid: 3c166fa4e3ee74616b471b7c2332ab9b, type: 3} + samguk: {fileID: 0} + overlay: {fileID: 0} + ppu: 1.5 + inset: {x: 0, y: 0, z: 0, w: 0} + overlayAlpha: 1 + - id: bar4_fill + pack: {fileID: 21300000, guid: 26d89dd2d7ae148e4acefd7fb973eb82, type: 3} + samguk: {fileID: 0} + overlay: {fileID: 0} + ppu: 1.5 + inset: {x: 0, y: 0, z: 0, w: 0} + overlayAlpha: 1 icons: - id: cur_gold pack: {fileID: 21300000, guid: 6d5687fb0dd3a428d9cd26a6f36c6d78, type: 3} @@ -1538,6 +1681,54 @@ MonoBehaviour: pack: {fileID: 21300000, guid: c0c390b014f3f4b0781cfca1e2ebe6f0, type: 3} samguk: {fileID: 0} tint: {r: 1, g: 1, b: 1, a: 1} + - id: star_on_l + pack: {fileID: 21300000, guid: 9fdf2423376c44500a69de40dd83ef49, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} + - id: x_small + pack: {fileID: 21300000, guid: f1a76f95358d94ca080a3e48691f8e5f, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} + - id: badge_first + pack: {fileID: 21300000, guid: 77ef9d3b0b12a4a4d954bfce8b142152, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} + - id: badge_hot + pack: {fileID: 21300000, guid: e9be1374ca9ae47fdb7208668217021c, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} + - id: ad + pack: {fileID: 21300000, guid: 23684c39a5232480abc423f4c39c550d, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} + - id: lv_badge_y + pack: {fileID: 21300000, guid: 9fa652a2cd39b4cfcaf06285cfff1483, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} + - id: tre_stat_hp + pack: {fileID: 21300000, guid: ae898e04b74ec47d8bb33582fcf24b12, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} + - id: tre_stat_atk + pack: {fileID: 21300000, guid: 41bc376691e7a4aef8398c3f84ee249f, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} + - id: tre_stat_def + pack: {fileID: 21300000, guid: 3a3a5d970e2d94998818421e4e0e384b, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} + - id: tre_stat_crit + pack: {fileID: 21300000, guid: 5864b32a142be4809ab2ccc225cae6bf, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} + - id: tre_stat_range + pack: {fileID: 21300000, guid: e07c16cced13542f59c7400aff52d8a7, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} + - id: tre_stat_splash + pack: {fileID: 21300000, guid: d031f4b21493c4841914d4257119a4e3, type: 3} + samguk: {fileID: 0} + tint: {r: 1, g: 1, b: 1, a: 1} - id: tile_bridge pack: {fileID: 0} samguk: {fileID: 21300000, guid: 8e5bf7eb1e300474785ff2781eeb5093, type: 3} diff --git a/Assets/Scripts/OutGame/UI/BattleResultPanel.cs b/Assets/Scripts/OutGame/UI/BattleResultPanel.cs index ad08950f..6a5da559 100644 --- a/Assets/Scripts/OutGame/UI/BattleResultPanel.cs +++ b/Assets/Scripts/OutGame/UI/BattleResultPanel.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.UI; @@ -5,21 +6,33 @@ using UnityEngine.UI; namespace TKS.OutGame { /// - /// 전투 결과 패널(기획서 §6) — 별 3(클리어 · 전원 생존 · n턴 이내) · 골드 · 경험(출전 전원) · 보옥(첫 클리어 · 보스) · 장비 드랍. - /// 전투 HUD 의 승리/패배 판(sortingOrder 100) 위에 덮는다 · 탭 처리는 기존 그대로(SlingshotInput → 월드맵 복귀). + /// 전투 결과 패널(기획서 §6 · 비주얼 = Fable 제안서 §4-2 #1104) — 별 3(받침 원 + 등장 튀김) · 조건 행 3 · 보상 타일(골드 · 경험 · 보옥 · 장비 · 상자) · + /// 버튼 = 「다시」(Tertiary · 같은 스테이지 재도전) + 「확인」(Primary 노랑) · 승리면 리본 뒤 금빛. + /// 전투 HUD 의 승리/패배 판(sortingOrder 100) 위에 덮는다 · 화면 아무 데나 탭 = 기존 그대로(SlingshotInput → 월드맵 복귀 · 「다시」 위만 제외). /// public class BattleResultPanel { + // 패널 배치(UI px · 패널 왼쪽 위 기준) + const float PW = 920f; + const float PH = 1180f; + const float Side = 60f; + const float RowW = PW - Side * 2f; + const float RowH = 64f; + const float Tile = 132f; + const float BtnY = PH - 40f - 110f; + readonly OutGameRoot root; readonly GameObject go; readonly Text title; - readonly Text stars; - readonly Text conds; - readonly Text rewards; - readonly Text hint; readonly Image ribbon; + readonly Image glow; readonly RectTransform starRow; readonly Image[] starImg = new Image[3]; + readonly RectTransform body; + readonly Text plain; + readonly Button retry; + readonly Button ok; + string rewardsText = ""; public bool Visible { @@ -28,7 +41,7 @@ namespace TKS.OutGame public string RewardsText { - get { return rewards.text; } + get { return rewardsText; } } public BattleResultPanel(OutGameRoot r, RectTransform canvas) @@ -40,35 +53,47 @@ namespace TKS.OutGame Image panel; if (UISkin.Has("popup")) { - // 팩 팝업 틀 + 제목 리본(승리 = 붉은 · 패배 = 회색) + 별 아이콘 3 - panel = UiKit.Skin(UiKit.Rect("Panel", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 20f), new Vector2(920f, 1180f)), "popup", UiKit.Paper); + // 리본 뒤 금빛(승리만 · 팩 Glow_Circle01 · 호흡) → 팩 팝업 틀 → 제목 리본(승리 붉은 · 패배 회색) + if (UISkin.Has("glow_circle")) + { + glow = UiKit.Skin(UiKit.Rect("Glow", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 20f + PH * 0.5f + 90f), new Vector2(1240f, 1240f)), "glow_circle", UiKit.Col("ribbon_glow", Color.clear)); + var pulse = glow.gameObject.AddComponent(); + pulse.min = 0.65f; + pulse.period = 1.6f; + } + panel = UiKit.Skin(UiKit.Rect("Panel", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 20f), new Vector2(PW, PH)), "popup", UiKit.Paper); var rib = UiKit.Rect("Ribbon", panel.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 0.5f), new Vector2(0f, 10f), new Vector2(1000f, 200f)); ribbon = UiKit.Skin(rib, "ribbon", Color.white); title = UiKit.Title(UiKit.FillLabel(rib, "", 88, UiKit.White, TextAnchor.MiddleCenter, true, 150f), true); title.rectTransform.offsetMin = new Vector2(150f, 34f); - starRow = UiKit.Rect("StarRow", panel.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -150f), new Vector2(600f, 190f)); + // 별 3 = 받침 원(Circle78 chip_dark) + 별(가운데 크게 · 위로) · 0.15 s 간격 튀김 + starRow = UiKit.At(panel.transform, "StarRow", 0f, 120f, PW, 190f); for (int i = 0; i < 3; i++) { - float s = i == 1 ? 190f : 150f; - starImg[i] = UiKit.Icon(starRow, "star_on", new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2((i - 1) * 180f, i == 1 ? 14f : -10f), s); + bool mid = i == 1; + float plate = mid ? 150f : 120f; + var pos = new Vector2((i - 1) * 190f, mid ? 12f : -14f); + UiKit.Skin(UiKit.Rect("Plate" + i, starRow, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), pos, new Vector2(plate, plate)), "circle_frame", UiKit.Col("chip_dark", UiKit.Dark)); + starImg[i] = UiKit.Icon(starRow, "star_on_l", new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), pos + new Vector2(0f, 4f), mid ? 132f : 106f); + if (starImg[i] == null) continue; + UiKit.DropShadow(starImg[i], 5f); + starImg[i].gameObject.AddComponent().delay = 0.15f * i; } + body = UiKit.Fill("Body", panel.transform); + retry = UiKit.SkinBtn(panel.transform, "Retry", "round08", UiKit.Col("menu_btn", UiKit.Dark), "다시", 40, UiKit.White, Side, BtnY, 260f, 110f, () => root.RetryStage()); + ok = UiKit.SkinBtn(panel.transform, "Ok", UISkin.Has("btn_l_yellow") ? "btn_l_yellow" : "button", UISkin.Has("btn_l_yellow") ? UiKit.White : UiKit.Coin, "확인", 46, UiKit.Col("text_on_yellow", UiKit.Ink), PW - Side - 520f, BtnY, 520f, 110f, null); } else { - panel = UiKit.Box("Panel", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 40f), new Vector2(920f, 1180f), UiKit.Paper); + panel = UiKit.Box("Panel", dim, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 40f), new Vector2(PW, PH), UiKit.Paper); UiKit.Border(panel, UiKit.Line, 4f); var head = UiKit.TopBand("Head", panel.transform, 0f, 170f); UiKit.Paint(head, UiKit.Dark); title = UiKit.FillLabel(head, "", 96, UiKit.White); + plain = UiKit.Label(panel.transform, "", 36, UiKit.Ink, TextAnchor.UpperLeft, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -200f), new Vector2(760f, 860f), false); + plain.lineSpacing = 1.15f; + UiKit.Label(panel.transform, "화면을 탭하면 계속", 34, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 36f), new Vector2(800f, 60f), false); } - stars = UiKit.Label(panel.transform, "", 120, UiKit.Star, TextAnchor.MiddleCenter, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -190f), new Vector2(800f, 150f)); - conds = UiKit.Label(panel.transform, "", 34, UiKit.Ink, TextAnchor.UpperLeft, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -350f), new Vector2(760f, 180f), false); - conds.lineSpacing = 1.15f; - var line = UiKit.Box("Line", panel.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -540f), new Vector2(800f, 4f), UiKit.Slot, false); - line.raycastTarget = false; - rewards = UiKit.Label(panel.transform, "", 38, UiKit.Ink, TextAnchor.UpperLeft, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -566f), new Vector2(760f, 460f), false); - rewards.lineSpacing = 1.15f; - hint = UiKit.Label(panel.transform, "화면을 탭하면 월드맵으로", 34, UiKit.InkSoft, TextAnchor.MiddleCenter, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 36f), new Vector2(800f, 60f), false); go.SetActive(false); } @@ -77,61 +102,155 @@ namespace TKS.OutGame if (go.activeSelf != on) go.SetActive(on); } + /// 화면 좌표가 「다시」 버튼 위인가 — 화면 탭(월드맵 복귀)을 이 버튼에서는 건너뛴다. + public bool PointerOnRetry(Vector2 screen) + { + if (retry == null || !go.activeInHierarchy || !retry.gameObject.activeSelf) return false; + var c = go.GetComponentInParent(); + return RectTransformUtility.RectangleContainsScreenPoint((RectTransform)retry.transform, screen, c != null ? c.worldCamera : null); + } + public void Fill(StageReward r) { var s = root.Session; - hint.text = "화면을 탭하면 월드맵으로"; + rewardsText = r.win ? RewardText(s, r) : "보상 없음"; title.text = (r.win ? "승리" : "패배") + " " + r.chapter + "-" + r.stage + (r.boss ? " 보스" : "") + ""; - stars.text = r.win ? UiKit.Stars(r.stars, 3) : UiKit.Stars(0, 3); ShowStars(r.win ? r.stars : 0, r.win); + SetButtons(true); + if (body == null) + { + plain.text = (r.win ? "별 " + UiKit.Stars(r.stars, 3) + "\n\n" : "아군이 전멸했습니다\n\n") + rewardsText; + return; + } + UiKit.Clear(body); + float y = 330f; if (r.win) { - conds.text = Mark(r.conds[0]) + " 클리어\n" + - Mark(r.conds[1]) + " 아군 전원 생존\n" + - Mark(r.conds[2]) + " " + r.starTurns + "턴 이내 클리어 (" + r.turns + "턴)"; - rewards.text = RewardText(s, r); + string[] cond = { "스테이지 클리어", "아군 전원 생존", r.starTurns + "턴 이내 클리어 (" + r.turns + "턴)" }; + for (int i = 0; i < 3; i++) + UiKit.CondRow(body, "Cond" + i, Side, y + i * (RowH + 8f), RowW, RowH, r.conds[i] ? "check" : "x_small", r.conds[i], cond[i], 30); } else { - conds.text = "아군이 전멸했습니다\n군량 " + r.stamina + " 소모 · 별 없음"; - rewards.text = "보상 없음\n영웅 레벨업 · 승급 · 편성을 바꿔 다시 도전"; + UiKit.CondRow(body, "Cond0", Side, y, RowW, RowH, "x_small", false, "아군이 전멸했습니다", 30); + UiKit.CondRow(body, "Cond1", Side, y + RowH + 8f, RowW, RowH, "cur_stamina", false, "군량 " + r.stamina + " 소모 · 별 없음", 30, false); + } + y = 566f; + UiKit.SubTitle(body, "보상", Side, y, RowW); + y += 64f; + if (!r.win) + { + var none = UiKit.Title(UiKit.Label(body, "보상 없음", 40, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Side, -y - 10f), new Vector2(RowW, 70f))); + none.horizontalOverflow = HorizontalWrapMode.Overflow; + UiKit.Label(body, "영웅 레벨업 · 승급 · 편성을 바꿔 다시 도전", 30, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Side, -y - 90f), new Vector2(RowW, 50f), false); + return; + } + Tiles(s, r, y); + Notes(s, r, y + Tile + 60f); + } + + /// 보상 타일 행(가운데 정렬 · 칸 아래 이름) — 골드 · 경험 · 보옥(있을 때) · 장비(등급 칸 · 있을 때) · 상자(있을 때). + void Tiles(OutGameSession s, StageReward r, float y) + { + var list = new List(); // 아이콘 · 수량 · 칸 색 접두 · 이름 + list.Add(new[] { "cur_gold", r.gold.ToString("N0"), "gear_white_", "골드" }); + list.Add(new[] { "cur_exp", r.exp.ToString("N0"), "gear_white_", "경험 · " + r.expTo.Count + "명" }); + if (r.gem > 0) list.Add(new[] { "cur_gem", r.gem.ToString("N0"), "gear_blue_", "보옥" }); + if (!string.IsNullOrEmpty(r.gearId)) + { + var g = s.Db.Gear.Get(r.gearId); + string grade = g != null && !string.IsNullOrEmpty(g.grade) ? g.grade : "white"; + list.Add(new[] { g != null ? "gear_" + g.kind : "cur_gear", "", "gear_" + grade + "_", g != null ? g.nameKo : r.gearId }); + } + if (!string.IsNullOrEmpty(r.chest) && s.Db.Chests != null && s.Db.Chests.Get(r.chest) != null) + list.Add(new[] { "chest_" + r.chest, "", "gear_purple_", s.Db.Chests.Get(r.chest).nameKo }); + float gap = list.Count >= 5 ? 22f : 30f; + float x = (PW - list.Count * Tile - (list.Count - 1) * gap) * 0.5f; + for (int i = 0; i < list.Count; i++) + { + var e = list[i]; + float tx = x + i * (Tile + gap); + UiKit.RewardTile(body, "Tile" + i, tx, y, Tile, e[2], e[0], e[1], e[2] != "gear_white_"); + var n = UiKit.Label(body, e[3], 26, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.UpperCenter, new Vector2(0f, 1f), new Vector2(0.5f, 1f), new Vector2(tx + Tile * 0.5f, -y - Tile - 12f), new Vector2(Tile + gap, 36f), false); + n.horizontalOverflow = HorizontalWrapMode.Overflow; } } - /// 연무장 결과(탑 · 던전 · 투기장 · 적장 토벌 · PD #1093 C) — 별 없음 · 결과 줄 · 탭 = 연무장. + /// 타일 아래 한두 줄 — 첫 클리어/반복 배율 · 레벨업 가능 영웅 · 소탕. + void Notes(OutGameSession s, StageReward r, float y) + { + var e = s.Db.Economy; + var sb = new StringBuilder(r.first ? "첫 클리어 보상" + (r.boss ? " + 보스" : "") : "반복 보상 ×" + e.RepeatRewardMult); + if (r.sweep) sb.Append(" · 소탕(군량 ").Append(r.stamina).Append(")"); + var ready = ReadyNames(s, r); + if (ready.Length > 0) sb.Append("\n레벨업 가능 ").Append(ready).Append(""); + var t = UiKit.Label(body, sb.ToString(), 30, UiKit.White, TextAnchor.UpperCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Side, -y), new Vector2(RowW, 100f), false); + t.lineSpacing = 1.2f; + } + + /// 연무장 결과(탑 · 던전 · 투기장 · 적장 토벌 · PD #1093 C) — 별 없음 · 결과 행 · 탭 = 연무장. public void FillMode(ModeResult r) { - hint.text = "화면을 탭하면 연무장으로"; + SetButtons(false); + rewardsText = r != null ? string.Join("\n", r.lines.ToArray()) : ""; if (r == null) { title.text = "연무장"; - stars.text = conds.text = rewards.text = ""; + ShowStars(-1, true); + if (body != null) UiKit.Clear(body); + else plain.text = ""; return; } + bool win = r.win || r.kind == ArenaRules.Raid; title.text = r.kind == ArenaRules.Raid ? "토벌 종료" : r.win ? "승리" : "패배"; - stars.text = ""; - ShowStars(-1, r.win || r.kind == ArenaRules.Raid); - conds.text = "" + r.title + ""; - rewards.text = string.Join("\n", r.lines.ToArray()); + ShowStars(-1, win); + if (body == null) + { + plain.text = "" + r.title + "\n\n" + rewardsText; + return; + } + UiKit.Clear(body); + var head = UiKit.Title(UiKit.Label(body, r.title, 44, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Side, -150f), new Vector2(RowW, 110f)), true); + head.horizontalOverflow = HorizontalWrapMode.Overflow; + UiKit.SubTitle(body, "결과", Side, 290f, RowW); + float y = 354f; + for (int i = 0; i < r.lines.Count && y + RowH <= BtnY - 20f; i++, y += RowH + 8f) + UiKit.CondRow(body, "Line" + i, Side, y, RowW, RowH, null, true, r.lines[i], 30); } - /// 별 아이콘(n < 0 = 숨김) · 리본 색(승리 붉은 · 패배 회색) · 테마가 없으면 글자 별만. + /// 스테이지 = 「다시」 + 「확인」 · 연무장 = 「확인」 가운데. + void SetButtons(bool stage) + { + if (retry == null) return; + retry.gameObject.SetActive(stage); + ((RectTransform)ok.transform).anchoredPosition = new Vector2(stage ? PW - Side - 520f : (PW - 520f) * 0.5f, -BtnY); + } + + /// 별 아이콘(n < 0 = 숨김) · 리본 색(승리 붉은 · 패배 회색) · 금빛 = 승리만. void ShowStars(int n, bool win) { + if (glow != null) glow.gameObject.SetActive(win); if (starRow == null) return; - stars.gameObject.SetActive(false); starRow.gameObject.SetActive(n >= 0); for (int i = 0; i < 3; i++) - if (starImg[i] != null) starImg[i].sprite = UISkin.Icon(i < n ? "star_on" : "star_off") ?? starImg[i].sprite; + if (starImg[i] != null) starImg[i].sprite = UISkin.Icon(i < n ? "star_on_l" : "star_off") ?? starImg[i].sprite; if (ribbon != null && !UISkin.Apply(ribbon, win ? "ribbon" : "ribbon_gray")) ribbon.color = win ? Color.white : UiKit.Disabled; } - static string Mark(bool ok) + static string ReadyNames(OutGameSession s, StageReward r) { - return ok ? "○" : "×"; + var ready = new StringBuilder(); + foreach (var id in r.expTo) + { + var lv = s.Save.Level(id); + if (HeroRules.CanLevelUp(s.Db, s.Save, lv) != null) continue; + if (ready.Length > 0) ready.Append(" · "); + ready.Append(s.Def(id).nameKo); + } + return ready.ToString(); } - /// 보상 줄(결과 화면 · 소탕 팝업 공용). + /// 보상 줄(소탕 팝업 · 테마 없을 때 결과 화면 공용). public static string RewardText(OutGameSession s, StageReward r) { var e = s.Db.Economy; @@ -147,14 +266,7 @@ namespace TKS.OutGame else sb.Append("\n장비 드랍 없음"); if (!string.IsNullOrEmpty(r.chest) && s.Db.Chests != null && s.Db.Chests.Get(r.chest) != null) sb.Append("\n").Append(s.Db.Chests.Get(r.chest).nameKo).Append(" 획득 (보물 > 상자)"); if (r.sweep) sb.Append("\n군량 ").Append(r.stamina).Append(" 소모 · 소탕(전투 없음)"); - var ready = new StringBuilder(); - foreach (var id in r.expTo) - { - var lv = s.Save.Level(id); - if (HeroRules.CanLevelUp(s.Db, s.Save, lv) != null) continue; - if (ready.Length > 0) ready.Append(" · "); - ready.Append(s.Def(id).nameKo); - } + var ready = ReadyNames(s, r); if (ready.Length > 0) sb.Append("\n레벨업 가능: ").Append(ready).Append(""); return sb.ToString(); } diff --git a/Assets/Scripts/OutGame/UI/OutGameRoot.cs b/Assets/Scripts/OutGame/UI/OutGameRoot.cs index 86f84063..08a4cc1a 100644 --- a/Assets/Scripts/OutGame/UI/OutGameRoot.cs +++ b/Assets/Scripts/OutGame/UI/OutGameRoot.cs @@ -193,6 +193,8 @@ namespace TKS.OutGame { wm.TopInset = TopBarH; wm.PopupInfo = PopupInfo; + wm.PopupFill = PopupFill; + wm.CostOf = si => EconomyRules.StageCost(Session.Db, Session.Save, b.Config.stages[si]); wm.TryEnter = TryEnter; wm.StarsOf = si => EconomyRules.StarsOf(Session.Save, b.Config.stages[si]); wm.CanSweep = si => EconomyRules.CanSweep(Session.Save, b.Config.stages[si]); @@ -221,6 +223,7 @@ namespace TKS.OutGame BuildTabBar(); BuildCurrencyBar(); result = new BattleResultPanel(this, (RectTransform)resultCanvas.transform); + if (s != null) s.HoldResultTap = p => result.PointerOnRetry(p); mainCanvas.gameObject.SetActive(false); } @@ -625,11 +628,16 @@ namespace TKS.OutGame StaminaModal(null); } - void StaminaModal(string why) + void StaminaModal(string why, bool good = false) { var e = Session.Db.Economy; var d = Session.Save; EconomyRules.RollDaily(d, Session.Now); + if (UISkin.Has("popup") && UISkin.Has("panel_field")) + { + StaminaPopup(why, good); + return; + } string body = (why != null ? why + "\n\n" : "") + "군량 " + d.stamina + " / " + EconomyRules.StaminaMax(Session.Db, d) + " · " + (EconomyRules.RegenSec(Session.Db, d) / 60f).ToString("0.#") + "분마다 1 회복(오프라인 포함)\n" + "스테이지 " + e.StageCost + " · 보스 " + e.BossCost + " 소모"; @@ -649,6 +657,73 @@ namespace TKS.OutGame new ModalButton("닫기", UiKit.Dark, null)); } + /// + /// 군량 충전 팝업(Fable 제안서 §4-3 · #1104) — 팝업 안 필드(panel_field 인셋 20) · 현재 군량 블록 · 교환 행 2(보옥 → 군량 · 광고 → 군량) + /// = 비용 아이콘 + 숫자 → 화살표 → 군량 아이콘 + 「+n」 · 행 오른쪽 Secondary 버튼(하늘 · 녹) · 닫기 = 제목 띠 X 만. + /// + void StaminaPopup(string why, bool good) + { + var e = Session.Db.Economy; + var d = Session.Save; + const float H = 760f; + var content = ModalCustom("군량 충전", H); + var panel = content.parent; + content.gameObject.SetActive(false); + var field = UiKit.Layer(panel, "Field", "panel_field", UiKit.Col("panel_field", UiKit.Dark), 20f, 126f, 20f, 20f).rectTransform; + float w = 940f - 40f; + // 현재 군량(아이콘 96 · 값 56 Sen · 회복 · 소모) + var ic = UiKit.Icon(field, "cur_stamina", new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(92f, -90f), 104f); + UiKit.DropShadow(ic, 5f); + var now = UiKit.Title(UiKit.Label(field, d.stamina + " / " + EconomyRules.StaminaMax(Session.Db, d) + "", 60, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(170f, -26f), new Vector2(400f, 70f)), true); + now.horizontalOverflow = HorizontalWrapMode.Overflow; + int left = Session.StaminaSecondsToNext(); + string regen = (EconomyRules.RegenSec(Session.Db, d) / 60f).ToString("0.#") + "분마다 1 회복" + (left > 0 ? " · 다음 " + (left / 60).ToString("00") + ":" + (left % 60).ToString("00") : " · 가득"); + UiKit.Label(field, regen, 28, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(172f, -100f), new Vector2(w - 190f, 40f), false); + UiKit.InfoChip(field, "Cost", w - 24f - 300f, 34f, 300f, 52f, null, "스테이지 " + e.StageCost + " · 보스 " + e.BossCost, 26); + // 안내 줄(구매 결과 · 부족) + if (!string.IsNullOrEmpty(why)) + { + var wt = UiKit.Title(UiKit.Label(field, why, 30, good ? UiKit.Col("ok", UiKit.Ok) : UiKit.Col("alert_red", UiKit.Red), TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f, -160f), new Vector2(w - 48f, 50f)), true); + wt.horizontalOverflow = HorizontalWrapMode.Overflow; + } + UiKit.SubTitle(field, "충전", 24f, 222f, w - 48f); + ExchangeRow(field, 0, 286f, w, "cur_gem", e.GemStaminaCost.ToString("N0"), e.GemStaminaAmount, "오늘 " + d.adsUsed.gemStamina + " / " + e.GemStaminaDaily, + d.adsUsed.gemStamina < e.GemStaminaDaily, "btn_sky", "구매", () => + { + var r = Session.BuyStamina(); + StaminaModal(r ?? "군량 +" + e.GemStaminaAmount, r == null); + if (Map != null) Map.RefreshPopup(); + }); + ExchangeRow(field, 1, 286f + 160f, w, "ad", "광고", e.AdStamina, "오늘 " + d.adsUsed.stamina + " / " + e.AdStaminaDaily + " · 광고 SDK 미연동(시험 지급)", + d.adsUsed.stamina < e.AdStaminaDaily, "btn_s_green", "보기", () => + { + var r = Session.AdStamina(); + StaminaModal(r ?? "군량 +" + e.AdStamina, r == null); + if (Map != null) Map.RefreshPopup(); + }); + } + + /// 교환 행(필드 위 밝은 판 h140) — 왼쪽 [비용 아이콘 + 값] → [군량 + n] · 아래 오늘 횟수 · 오른쪽 Secondary 버튼(다 쓰면 회색). + void ExchangeRow(Transform field, int i, float y, float w, string costIcon, string cost, int gain, string note, bool can, string skin, string label, Action onClick) + { + const float Pad = 24f, Rh = 140f; + var row = UiKit.Skin(UiKit.At(field, "Row" + i, Pad, y, w - Pad * 2f, Rh), "panel12", UiKit.Col("row_on_field", UiKit.Paper)).transform; + var a = UiKit.Icon(row, costIcon, new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(60f, -52f), costIcon == "ad" ? 44f : 64f); + if (a != null) UiKit.DropShadow(a, 3f); + var at = UiKit.Title(UiKit.Label(row, cost, 40, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(100f, -22f), new Vector2(140f, 60f)), true); + at.horizontalOverflow = HorizontalWrapMode.Overflow; + float ax = 108f + Mathf.Ceil(at.preferredWidth) + 22f; + var arrow = UiKit.Icon(row, "arrow_next", new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(ax + 12f, -52f), 40f); + if (arrow != null) arrow.color = UiKit.Col("text_soft", UiKit.InkSoft); + var b = UiKit.Icon(row, "cur_stamina", new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(ax + 72f, -52f), 64f); + if (b != null) UiKit.DropShadow(b, 3f); + var bt = UiKit.Title(UiKit.Label(row, "+" + gain, 40, UiKit.Col("ok", UiKit.Ok), TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(ax + 112f, -22f), new Vector2(140f, 60f)), true); + bt.horizontalOverflow = HorizontalWrapMode.Overflow; + UiKit.Label(row, note, 24, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(30f, -96f), new Vector2(w - 380f, 36f), false); + var btn = UiKit.SkinBtn(row, "Btn", can || !UISkin.Has("btn_gray") ? skin : "btn_gray", UiKit.White, label, 38, can ? UiKit.White : UiKit.Col("disabled_text", UiKit.Disabled), w - Pad * 2f - 24f - 250f, 20f, 250f, 100f, onClick); + if (skin == "btn_s_green" && can) UiKit.BtnCost(btn, "ad", "", 36f, 24); + } + // ---------- 월드맵 연결 ---------- string PopupInfo(int si) @@ -678,6 +753,111 @@ namespace TKS.OutGame return sb.ToString(); } + /// + /// 월드맵 스테이지 팝업 본문(Fable 제안서 §4-3 · #1104 · 필드 안 860 폭) — 제목 · 장소 → 「적 구성」 병종 원 아이콘 ×n + 장수 칩 → + /// 「보상」 타일(골드 · 경험 · 보옥(첫 클리어) · 장비 드랍 % · 첫 클리어 뱃지) → 「별 조건」 행 3(별 아이콘). + /// + void PopupFill(int si, bool locked, RectTransform field) + { + var s = Battle.Config.stages[si]; + var db = Session.Db; + var d = Session.Save; + var e = db.Economy; + var row = db.Rewards.For(s.chapter); + const float W = 860f, Pad = 24f, Tile = 132f; + var soft = UiKit.Col("text_soft", UiKit.InkSoft); + var head = UiKit.Title(UiKit.Label(field, s.title, 38, UiKit.White, TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Pad, -18f), new Vector2(W - Pad * 2f, 56f)), true); + head.horizontalOverflow = HorizontalWrapMode.Overflow; + if (!string.IsNullOrEmpty(s.place)) + UiKit.Label(field, s.place + (string.IsNullOrEmpty(s.bgTheme) ? "" : " · " + s.bgTheme), 26, soft, TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Pad, -76f), new Vector2(W - Pad * 2f, 34f), false); + + // 적 구성 = 병종 원(type 색 Circle78 68 + 병종 아이콘 40) + 「×n」 · 가운데 정렬 + float y = 124f; + UiKit.SubTitle(field, "적 구성", Pad, y, W - Pad * 2f); + var counts = new int[UnitTypeInfo.Count]; + var lead = new List(); + foreach (var en in s.enemies) + { + counts[(int)en.type]++; + if (en.isBoss || en.isGeneral) lead.Add((en.isBoss ? "보스 " : "") + en.name); + } + var kinds = new List(); + for (int t = 0; t < counts.Length; t++) + if (counts[t] > 0) kinds.Add(t); + const float Ew = 172f; + float ex = (W - kinds.Count * Ew) * 0.5f; + for (int k = 0; k < kinds.Count; k++) + { + var ut = (UnitType)kinds[k]; + float cx = ex + k * Ew + 44f; + var circ = UiKit.Skin(UiKit.Rect("Type" + k, field, new Vector2(0f, 1f), new Vector2(0.5f, 0.5f), new Vector2(cx, -y - 94f), new Vector2(80f, 80f)), "circle_frame", UiKit.TypeColor(ut)); + var ti = UiKit.Icon(circ.transform, UiKit.TypeIconId(ut), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 2f), 52f); + if (ti != null) + { + ti.color = UiKit.White; + UiKit.DropShadow(ti, 2f); + } + var n = UiKit.Title(UiKit.Label(field, "×" + counts[kinds[k]], 36, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 0.5f), new Vector2(cx + 50f, -y - 94f), new Vector2(100f, 50f)), true); + n.horizontalOverflow = HorizontalWrapMode.Overflow; + } + y += 140f; + if (lead.Count > 0) + { + string names = string.Join(" · ", lead.ToArray()); + float cw = Mathf.Min(W - Pad * 2f, 120f + names.Length * 26f); + UiKit.InfoChip(field, "Lead", (W - cw) * 0.5f, y, cw, 48f, "badge_hot", names, 26); + y += 64f; + } + + // 보상 타일 + UiKit.SubTitle(field, "보상", Pad, y, W - Pad * 2f); + y += 56f; + var st = d.Stage(s.chapter, s.stage, false); + bool first = st == null || st.clears == 0; + bool boss = s.kind == StageKind.Boss; + if (row != null) + { + int gold = Mathf.RoundToInt((row.goldBase + s.stage * e.GoldPerStage) * (first ? e.FirstClearGoldMult : e.RepeatRewardMult)); + int exp = Mathf.RoundToInt(row.expBase * (first ? 1f : e.RepeatRewardMult)); + float drop = boss ? e.BossGearDropPct : row.gearDropPct; + var tiles = new List(); // 아이콘 · 수량 · 칸 색 · 이름 · 첫 뱃지 + tiles.Add(new[] { "cur_gold", gold.ToString("N0"), "gear_white_", first ? "골드" : "골드 ×" + e.RepeatRewardMult, first ? "1" : "" }); + tiles.Add(new[] { "cur_exp", exp.ToString("N0"), "gear_white_", "경험 · 전원", "" }); + if (first) tiles.Add(new[] { "cur_gem", (row.gemFirst + (boss ? row.gemBoss : 0)).ToString(), "gear_blue_", "보옥", "1" }); + string gg = string.IsNullOrEmpty(row.gearGradeMax) ? "white" : row.gearGradeMax; + tiles.Add(new[] { "cur_gear", Mathf.RoundToInt(drop * 100f) + "%", "gear_" + gg + "_", "장비 ~" + UiKit.GradeKo(gg), "" }); + const float Gap = 30f; + float tx0 = (W - tiles.Count * Tile - (tiles.Count - 1) * Gap) * 0.5f; + for (int i = 0; i < tiles.Count; i++) + { + var tl = tiles[i]; + float tx = tx0 + i * (Tile + Gap); + var cell = UiKit.RewardTile(field, "Tile" + i, tx, y, Tile, tl[2], tl[0], tl[1], tl[2] != "gear_white_"); + if (tl[4] == "1") + { + var b = UiKit.Icon(cell, "badge_first", new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-8f, -6f), 58f); + if (b != null) UiKit.Title(UiKit.FillLabel(b.transform, "첫", 24, UiKit.White), true); + } + var nm = UiKit.Label(field, tl[3], 24, soft, TextAnchor.UpperCenter, new Vector2(0f, 1f), new Vector2(0.5f, 1f), new Vector2(tx + Tile * 0.5f, -y - Tile - 10f), new Vector2(Tile + Gap, 34f), false); + nm.horizontalOverflow = HorizontalWrapMode.Overflow; + } + } + y += Tile + 64f; + + // 별 조건 3행(별 아이콘 = 지금까지 받은 별 수만큼 켜짐) + UiKit.SubTitle(field, "별 조건", Pad, y, W - Pad * 2f); + y += 56f; + int stars = st != null ? st.stars : 0; + string[] cond = { "스테이지 클리어", "아군 전원 생존", (row != null ? row.starTurns : 0) + "턴 이내 클리어" }; + for (int i = 0; i < 3; i++) + UiKit.CondRow(field, "Star" + i, Pad, y + i * 64f, W - Pad * 2f, 58f, i < stars ? "star_on" : "star_off", i < stars, cond[i], 28, false, "row_on_field"); + y += 3 * 64f; + if (locked) + UiKit.Label(field, "앞 스테이지를 먼저 깨세요", 28, UiKit.Col("alert_red", UiKit.Red), TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Pad, -y), new Vector2(W - Pad * 2f, 40f)); + else if (stars >= 3) + UiKit.Label(field, "별 3 = 소탕 가능(군량만 · 전투 없음)", 26, UiKit.Col("ok", UiKit.Ok), TextAnchor.MiddleCenter, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(Pad, -y), new Vector2(W - Pad * 2f, 40f), false); + } + bool TryEnter(int si) { if (Session.TryPayEntry(Battle.Config.stages[si])) return true; @@ -714,6 +894,14 @@ namespace TKS.OutGame result.Fill(r); } + /// 결과 「다시」(#1104) — 월드맵이 있으면 같은 노드 재출전(군량 확인) · 없으면 화면 탭과 같음. + public void RetryStage() + { + if (Battle == null || (Battle.Phase != BattlePhase.StageClear && Battle.Phase != BattlePhase.Defeat)) return; + if (Map != null && Map.HasData && Battle.Mode == null) Map.RetryLast(); + else Battle.ContinueAfterResult(); + } + // ---------- 연무장 전투(PD #1093 C) ---------- /// 연무장 모드 전투 시작 — 아웃게임 화면을 닫고 HUD·조작을 켠 뒤 BattleManager.StartMode · 결과 탭 = 연무장 returnPage 로. diff --git a/Assets/Scripts/OutGame/UI/TradeScreen.cs b/Assets/Scripts/OutGame/UI/TradeScreen.cs index 58b798e1..52d58f36 100644 --- a/Assets/Scripts/OutGame/UI/TradeScreen.cs +++ b/Assets/Scripts/OutGame/UI/TradeScreen.cs @@ -68,19 +68,112 @@ namespace TKS.OutGame public override void Build() { Tab = 4; + if (UISkin.On) UiKit.Paint(UiKit.Fill("ListBg", Rt), UiKit.Col("list_bg", UiKit.Bg), false); // 목록형 바탕(Fable §5-3) Header("거래", "뽑기 · 상자 · 상점 · 패스", true); var body = Body(); string[] names = { "영웅·유닛 뽑기", "보물 상자", "상점·패스" }; for (int i = 0; i < names.Length; i++) { int k = i; - tabs.Add(UiKit.Btn("Tab" + i, body, names[i], 32, UiKit.Paper, UiKit.Ink, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f + i * 348f, -18f), new Vector2(332f, 84f), () => SetPage(k))); + tabs.Add(UISkin.On + ? UiKit.SubTab(body, "Tab" + i, names[i], 24f + i * 348f, 18f, 332f, 84f, () => SetPage(k)) + : UiKit.Btn("Tab" + i, body, names[i], 32, UiKit.Paper, UiKit.Ink, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(24f + i * 348f, -18f), new Vector2(332f, 84f), () => SetPage(k))); pages.Add(UiKit.Fill("Page" + i, body, 0f, 0f, 118f, 0f)); } - BuildGacha(pages[0]); + if (UISkin.Has("panel_field") && UISkin.Has("btn_l_yellow")) BuildGachaKit(pages[0]); + else BuildGacha(pages[0]); BuildShop(pages[2]); } + // ---------- 뽑기(Fable 제안서 §4-4 · #1104) ---------- + + const float GaugeW = 820f; + Text pityBadge; + Text tenCost; + Text singleCost; + + /// + /// 뽑기 = 배너(틀 유지 · 아래 문양 띠 흰 8 % · 오른쪽 위 「픽업」 뱃지) → 천장 섹션(레벨형 게이지 + 노랑 배지) → + /// 확률 섹션(제목 띠 · 행 3 = 별 아이콘 ×n + 바 + % Sen) → 1회(Secondary 하늘 + 보옥 비용) · 10연(Primary 노랑 + 「첫 무료」 꼬리 칩) · 각주 = ⓘ 팝업. + /// + void BuildGachaKit(RectTransform p) + { + var g = S.Db.Gacha; + // 배너 + var banner = UiKit.At(p, "Banner", 24f, 12f, 1032f, 470f); + UiKit.Skin(banner, "banner", Color.white); + if (UISkin.Has("banner_pattern")) + { + var pat = UiKit.Skin(UiKit.At(banner, "Pattern", 18f, 470f - 112f, 1032f - 36f, 90f), "banner_pattern", UiKit.Col("banner_pattern", Color.clear)); + pat.type = Image.Type.Tiled; + } + UiKit.InfoChip(banner, "Period", 30f, 26f, 220f, 52f, null, "픽업 · 2주", 28); + UiKit.Title(UiKit.Label(banner, "영웅·유닛 뽑기", 60, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(36f, -100f), new Vector2(600f, 90f)), true); + UiKit.Label(banner, "모든 영웅·병사 유닛\n= 뽑기로 획득", 32, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.UpperLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(36f, -200f), new Vector2(520f, 110f), false); + var free = UiKit.Skin(UiKit.At(banner, "Free", 36f, 470f - 104f, 300f, 64f), "tail", UiKit.Col("tail_red", UiKit.Red)); + UiKit.Title(UiKit.FillLabel(free.transform, "첫 10연 무료", 30, UiKit.White), true); + freeChip = free.gameObject; + pickupArt = UiKit.Rect("PickupArt", banner, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-20f, 22f), new Vector2(440f, 420f)); + var hot = UiKit.Icon(banner, "badge_hot", new Vector2(1f, 1f), new Vector2(0.5f, 0.5f), new Vector2(-34f, -30f), 120f); + if (hot != null) + { + UiKit.DropShadow(hot, 4f); + UiKit.Title(UiKit.FillLabel(hot.transform, "픽업", 30, UiKit.White), true); + } + + // 천장 게이지 + RectTransform pr; + var pf = UiKit.SectionPanel(p, "Pity", 24f, 504f, 1032f, 156f, false, false, out pr); + UiKit.Title(UiKit.Label(pf, "천장", 34, UiKit.White, TextAnchor.MiddleLeft, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(26f, -12f), new Vector2(200f, 50f)), true); + pityText = UiKit.Label(pf, "", 28, UiKit.Col("text_soft", UiKit.InkSoft), TextAnchor.MiddleRight, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-26f, -12f), new Vector2(620f, 50f), false); + pityBar = UiKit.Gauge(pf, "Gauge", 20f, 52f, GaugeW + 60f, "lv_badge_y", "lv_fill", out pityBadge); + + // 확률 공개 + RectTransform probRoot; + var prob = UiKit.SectionPanel(p, "Prob", 24f, 682f, 1032f, 330f, false, false, out probRoot); + UiKit.SectionTitle(probRoot, "확률 공개"); + var info = UiKit.Rect("Info", probRoot, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-16f, -12f), new Vector2(64f, 64f)); + UiKit.Skin(info, "badge_circle", UiKit.Col("chip_dark", UiKit.Dark), true); + var ii = UiKit.Icon(info, "info", new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 34f); + if (ii == null) UiKit.Title(UiKit.FillLabel(info, "i", 34, UiKit.White), true); + info.gameObject.AddComponent