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

386 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.Numerics;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using VS.Sim;
using Vector2 = UnityEngine.Vector2;
using Vector3 = UnityEngine.Vector3;
// 메인 화면 — 원작 GamePlayUI 배치(1080×1920): 상단 좌 프로필 · 상단 우 코인/초당/티켓 + 세로 버튼 · 좌 하이프 막대 · 하단 채팅 · 후원 받기 · 하단 5버튼
namespace VS.View
{
public partial class GameApp
{
RectTransform stage, roomRect;
Image roomBg, idolImg, idolShadow;
readonly Image[] props = new Image[11];
readonly int[] propStage = new int[11];
Text coinText, gpsText, ticketText, nameText, rankText, followerText, viewText, donateAmount, hypeLabel, boostText;
Image followerFill, hypeFill;
RectTransform hypeBar, chatContent, verticalButtons;
Button donateBtn, newHouseBtn, afkBtn;
Image donateDot;
GameObject hypeGlow;
int idolFrame; float idolTimer, blinkTimer, poseTimer;
// 방 소품 자리 (방 영역 정규 좌표 x, y, 크기) — 클론 배치(원작 아트 좌표 아님)
static readonly float[,] Slot =
{
{ 0.20f, 0.20f, 0.30f }, { 0.82f, 0.20f, 0.27f }, { 0.13f, 0.56f, 0.24f }, { 0.33f, 0.13f, 0.17f },
{ 0.64f, 0.30f, 0.32f }, { 0.66f, 0.47f, 0.20f }, { 0.88f, 0.45f, 0.19f }, { 0.50f, 0.06f, 0.48f },
{ 0.50f, 0.88f, 0.20f }, { 0.42f, 0.22f, 0.14f }, { 0f, 0f, 0f },
};
void BuildHud()
{
// 무대(방 + 스트리머) — 탭 영역
StreamRoot = UI.Rect("Stream", Root).Fill(0, TabH, 0, 0);
var tapArea = UI.Img(StreamRoot, "Stage", Art.Square, new Color(0, 0, 0, 0), true);
stage = tapArea.rectTransform.Fill(0, 0, 0, 0);
var et = tapArea.gameObject.AddComponent<EventTrigger>();
var down = new EventTrigger.Entry { eventID = EventTriggerType.PointerDown };
down.callback.AddListener(e => OnStagePointerDown(((PointerEventData)e).position));
et.triggers.Add(down);
var up = new EventTrigger.Entry { eventID = EventTriggerType.PointerUp };
up.callback.AddListener(e => OnStagePointerUp());
et.triggers.Add(up);
roomBg = UI.Img(stage, "Room", null, Color.white);
roomRect = roomBg.rectTransform.Place(new Vector2(0, 0), new Vector2(1, 1), Vector2.zero, Vector2.zero);
roomRect.offsetMin = new Vector2(0, 560); roomRect.offsetMax = new Vector2(0, 0);
for (int i = 0; i < 10; i++)
{
var im = UI.Img(roomRect, "Prop" + i, null, Color.white);
im.preserveAspect = true;
im.rectTransform.anchorMin = im.rectTransform.anchorMax = new Vector2(Slot[i, 0], Slot[i, 1]);
im.rectTransform.sizeDelta = new Vector2(1080 * Slot[i, 2], 1080 * Slot[i, 2]);
props[i] = im;
}
idolShadow = UI.Img(roomRect, "IdolShadow", Art.SoftCircle, new Color(0, 0, 0, 0.25f));
idolShadow.rectTransform.Place(new Vector2(0.5f, 0.1f), new Vector2(0.5f, 0.1f), Vector2.zero, new Vector2(420, 90));
idolImg = UI.Img(roomRect, "Idol", null, Color.white);
idolImg.preserveAspect = true;
idolImg.rectTransform.Place(new Vector2(0.5f, 0.1f), new Vector2(0.5f, 0.1f), new Vector2(0, 270), new Vector2(460, 644));
hypeGlow = UI.Img(roomRect, "HypeGlow", Art.SoftCircle, new Color(1f, 0.55f, 0.85f, 0.35f)).gameObject;
((RectTransform)hypeGlow.transform).Place(new Vector2(0.5f, 0.1f), new Vector2(0.5f, 0.1f), new Vector2(0, 330), new Vector2(900, 900));
hypeGlow.transform.SetSiblingIndex(idolImg.transform.GetSiblingIndex());
hypeGlow.SetActive(false);
var hud = UI.Rect("Hud", StreamRoot).Fill();
BuildTopLeft(hud);
BuildTopRight(hud);
BuildHypeBar(hud);
BuildBottom(hud);
BuildBattleScreen();
BuildTabs();
FxLayer = UI.Rect("Fx", Root).Fill();
PopupLayer = UI.Rect("Popups", Root).Fill();
RebuildStage();
}
void BuildTopLeft(RectTransform hud)
{
var card = UI.Img(hud, "StreamerProfile", Skin.Round, new Color(0.17f, 0.19f, 0.32f, 0.92f));
card.rectTransform.Place(new Vector2(0, 1), new Vector2(0, 1), new Vector2(36, -40), new Vector2(560, 210), new Vector2(0, 1));
var av = UI.Img(card.transform, "Avatar", Skin.ProfileFrame, Pal.Pink);
av.rectTransform.Place(new Vector2(0, 0.5f), new Vector2(0, 0.5f), new Vector2(105, 0), new Vector2(170, 170));
var avIn = UI.Img(av.transform, "Face", Skin.ProfileInner, Color.white);
avIn.rectTransform.Fill(12, 12, 12, 12);
var mask = avIn.gameObject.AddComponent<Mask>(); mask.showMaskGraphic = true;
var face = UI.Img(avIn.transform, "IdolFace", null, Color.white);
face.preserveAspect = true;
face.rectTransform.Place(new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0, -150), new Vector2(330, 462));
avatarFace = face;
nameText = UI.Txt(card.transform, "Name", "", 42, Color.white, TextAnchor.MiddleLeft, true);
nameText.rectTransform.Place(new Vector2(0, 1), new Vector2(1, 1), new Vector2(206, -40), new Vector2(-230, 56), new Vector2(0, 0.5f));
var rk = UI.Img(card.transform, "Rank", Skin.S("Label_Parallelogram_Purple") ?? Skin.Pill, Skin.On ? Color.white : Pal.Purple);
rk.rectTransform.Place(new Vector2(0, 0.5f), new Vector2(0, 0.5f), new Vector2(206, 4), new Vector2(200, 50), new Vector2(0, 0.5f));
rankText = UI.Txt(rk.transform, "T", "", 30, Color.white); rankText.rectTransform.Fill(6, 2, 6, 2);
var bar = UI.Bar(card.transform, "BarFollower", new Color32(230, 232, 246, 255), Pal.Pink, out followerFill);
bar.rectTransform.Place(new Vector2(0, 0), new Vector2(1, 0), new Vector2(206, 40), new Vector2(-236, 40), new Vector2(0, 0.5f));
followerText = UI.Txt(bar.transform, "T", "", 24, Color.white, TextAnchor.MiddleCenter, true); followerText.rectTransform.Fill(4, 0, 4, 0);
// FrameView (팔로워 아이콘 + 수)
var fv = UI.Img(hud, "FrameView", Skin.Counter, Skin.On ? Skin.CounterTint : new Color(0.15f, 0.1f, 0.25f, 0.65f));
fv.rectTransform.Place(new Vector2(0, 1), new Vector2(0, 1), new Vector2(36, -266), new Vector2(300, 70), new Vector2(0, 1));
var fvIcon = UI.Icon(fv.transform, "Icon", Skin.Icon("view"), Color.white);
fvIcon.rectTransform.Place(new Vector2(0, 0.5f), new Vector2(0, 0.5f), new Vector2(36, 0), new Vector2(64, 64));
viewText = UI.Txt(fv.transform, "T", "", 36, Color.white, TextAnchor.MiddleLeft); viewText.rectTransform.Fill(80, 0, 12, 0);
boostText = UI.Txt(hud, "Boosts", "", 30, Color.white, TextAnchor.UpperLeft, true);
boostText.rectTransform.Place(new Vector2(0, 1), new Vector2(0, 1), new Vector2(40, -350), new Vector2(600, 170), new Vector2(0, 1));
}
Image avatarFace;
Text MakeCounter(RectTransform hud, string name, string icon, float y, out Image frame)
{
frame = UI.Img(hud, name, Skin.Counter, Skin.On ? Skin.CounterTint : new Color(0.15f, 0.1f, 0.25f, 0.7f));
frame.rectTransform.Place(new Vector2(1, 1), new Vector2(1, 1), new Vector2(-190, y), new Vector2(330, 80), new Vector2(1, 1));
var ic = UI.Icon(frame.transform, "Icon", Skin.Icon(icon), Color.white);
ic.rectTransform.Place(new Vector2(0, 0.5f), new Vector2(0, 0.5f), new Vector2(30, 0), new Vector2(84, 84));
var t = UI.Txt(frame.transform, "T", "0", 40, Color.white, TextAnchor.MiddleRight); t.rectTransform.Fill(80, 0, 22, 0);
return t;
}
void BuildTopRight(RectTransform hud)
{
coinText = MakeCounter(hud, "FrameCoin", "coin", -40, out _);
gpsText = MakeCounter(hud, "FrameCoin/s", "coin", -130, out _);
ticketText = MakeCounter(hud, "FrameTicket", "ticket", -220, out var tf);
var tb = tf.gameObject.AddComponent<Button>(); tb.onClick.AddListener(() => OpenTicketShop());
tf.raycastTarget = true;
if (Skin.On) // 티켓 칸 오른쪽 「+」 (상점)
{
var plus = UI.Img(tf.transform, "Plus", Skin.CounterBtn, Color.white);
plus.rectTransform.Place(new Vector2(1, 0.5f), new Vector2(1, 0.5f), new Vector2(-4, 2), new Vector2(66, 68));
UI.Icon(plus.transform, "I", Skin.Plus, Color.white).rectTransform.Fill(18, 22, 18, 16);
ticketText.rectTransform.Fill(80, 0, 80, 0);
}
verticalButtons = UI.Rect("GirdButtonsVertical", hud).Place(new Vector2(1, 1), new Vector2(1, 1), new Vector2(-24, -40), new Vector2(140, 1400), new Vector2(1, 1));
var vl = verticalButtons.gameObject.AddComponent<VerticalLayoutGroup>();
vl.spacing = 22; vl.childAlignment = TextAnchor.UpperCenter; vl.childControlHeight = false; vl.childControlWidth = false; vl.childForceExpandHeight = false;
SideBtn("ButtonSetting", "gear", () => OpenSettings());
afkBtn = SideBtn("ButtonClaimAfk", "afk", () => OpenAfkPopup(false));
newHouseBtn = SideBtn("ButtonNewHouse", "house", () => OpenBuyNewHouse());
SideBtn("ButtonTicketShop", "shop", () => OpenTicketShop());
BuildGiftSlots(verticalButtons);
}
Button SideBtn(string name, string icon, UnityEngine.Events.UnityAction a)
{
var b = UI.Btn(verticalButtons, name, Skin.Dark, Color.white, a);
b.GetComponent<RectTransform>().sizeDelta = new Vector2(128, 128);
var ic = UI.Icon(b.transform, "Icon", Skin.Icon(icon), Color.white);
ic.rectTransform.Fill(16, 20, 16, 14);
return b;
}
void BuildHypeBar(RectTransform hud)
{
var bg = UI.Img(hud, "BarHype", Skin.Round, new Color(0.17f, 0.19f, 0.32f, 0.85f));
hypeBar = bg.rectTransform.Place(new Vector2(0, 0.5f), new Vector2(0, 0.5f), new Vector2(70, 120), new Vector2(84, 560));
hypeFill = UI.Img(bg.transform, "FillHype", Skin.On ? Skin.RoundSoft : Skin.Pill, Pal.Pink);
hypeFill.type = Image.Type.Filled; hypeFill.fillMethod = Image.FillMethod.Vertical; hypeFill.fillOrigin = 0;
hypeFill.rectTransform.Fill(12, 12, 12, 12);
var ic = UI.Icon(bg.transform, "Fire", Skin.Icon("fire"), Color.white);
ic.rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(0, 30), new Vector2(110, 110));
hypeLabel = UI.Txt(bg.transform, "Label", "", 26, Color.white, TextAnchor.MiddleCenter, true);
hypeLabel.rectTransform.Place(new Vector2(0.5f, 0), new Vector2(0.5f, 0), new Vector2(0, -36), new Vector2(160, 50));
}
void BuildBottom(RectTransform hud)
{
var frame = UI.Rect("FrameBanner", hud).Place(new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 0), new Vector2(0, 560), new Vector2(0.5f, 0));
var bgb = UI.Img(frame, "Bg", Skin.BottomBar, Skin.On ? new Color(1f, 1f, 1f, 0.96f) : new Color(0.16f, 0.1f, 0.26f, 0.92f));
bgb.rectTransform.Place(new Vector2(0, 0), new Vector2(1, 0), Vector2.zero, new Vector2(0, 190), new Vector2(0.5f, 0));
var btns = UI.Rect("FrameButtons", frame).Place(new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 20), new Vector2(-40, 150), new Vector2(0.5f, 0));
var hl = btns.gameObject.AddComponent<HorizontalLayoutGroup>();
hl.spacing = 18; hl.childAlignment = TextAnchor.MiddleCenter; hl.childControlWidth = true; hl.childControlHeight = true; hl.childForceExpandWidth = true;
BottomBtn(btns, "ButtonCloset", "closet", L("UI.Closet.Title", "의상"), Pal.Pink, () => OpenCloset());
BottomBtn(btns, "ButtonHouse", "house", L("UI.House.Title", "방"), Pal.Orange, () => OpenHouse());
BottomBtn(btns, "ButtonSkill", "skill", L("UI.Skill.Title", "스킬"), Pal.Purple, () => OpenSkill());
BottomBtn(btns, "ButtonBoosterHeart", "x2", "x2", Pal.Red, () => OpenBoosterX2());
BottomBtn(btns, "ButtonBoosterAuto", "auto", L("Gameplay.Booster.AutoClick.Title", "자동"), Pal.Blue, () => OpenBoosterAuto());
// 채팅
var chat = UI.Rect("BarChat", frame).Place(new Vector2(0, 0), new Vector2(1, 0), new Vector2(-60, 200), new Vector2(-300, 340), new Vector2(0.5f, 0));
chat.gameObject.AddComponent<RectMask2D>();
chatContent = UI.Rect("ChatContent", chat);
chatContent.anchorMin = new Vector2(0, 0); chatContent.anchorMax = new Vector2(1, 0); chatContent.pivot = new Vector2(0.5f, 0);
chatContent.anchoredPosition = Vector2.zero; chatContent.sizeDelta = new Vector2(0, 340);
var vl = chatContent.gameObject.AddComponent<VerticalLayoutGroup>();
vl.childAlignment = TextAnchor.LowerLeft; vl.spacing = 6; vl.childControlHeight = true; vl.childControlWidth = true; vl.childForceExpandHeight = false;
// 후원 받기 · 퀘스트
donateBtn = UI.TextBtn(frame, "ButtonClaimDonate", "", Pal.Green, 36, () => OpenClaimDonate(), out donateAmount);
donateBtn.GetComponent<RectTransform>().Place(new Vector2(1, 0), new Vector2(1, 0), new Vector2(-150, 300), new Vector2(260, 150));
donateAmount.rectTransform.Fill(14, 10, 14, 44);
var lbl = UI.Txt(donateBtn.transform, "Title", L("UI.Gameplay.ClaimDonate", "후원 받기"), 30, Color.white, TextAnchor.MiddleCenter, true);
lbl.rectTransform.Place(new Vector2(0, 1), new Vector2(1, 1), new Vector2(0, -26), new Vector2(-10, 44));
donateDot = UI.Dot(donateBtn.transform, "DotRed");
var task = UI.Btn(frame, "ButtonTask", Skin.Dark, Color.white, () => OpenTasks());
task.GetComponent<RectTransform>().Place(new Vector2(1, 0), new Vector2(1, 0), new Vector2(-150, 470), new Vector2(128, 128));
UI.Icon(task.transform, "Icon", Skin.Icon("task"), Color.white).rectTransform.Fill(16, 20, 16, 14);
taskDot = UI.Dot(task.transform, "DotRed", 34);
}
Image taskDot;
void BottomBtn(RectTransform parent, string name, string icon, string label, Color c, UnityEngine.Events.UnityAction a)
{
var sp = Skin.Button(c);
var b = UI.Btn(parent, name, sp ?? Skin.Round, sp != null ? Color.white : c, a);
UI.Icon(b.transform, "Icon", Skin.Icon(icon), Color.white).rectTransform.Place(new Vector2(0.5f, 1), new Vector2(0.5f, 1), new Vector2(0, -48), new Vector2(84, 84));
var t = UI.Txt(b.transform, "Label", label, 28, Color.white, TextAnchor.MiddleCenter, true);
t.rectTransform.Place(new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 32), new Vector2(-8, 40));
}
public string L(string key, string fallback) => T.Loc.Has(key) ? T.Loc.Get(key) : fallback;
public string ItemName(int house, ItemRow it) => T.Loc.Get($"Content.Room.House{house + 1}.{it.Index}.Name", it.Name);
public string SkillName(ItemRow it) => T.Loc.Get($"Content.Skill.{it.Index}.Name", T.Loc.Get("Content.Skill." + it.Name.Replace(" Skill", "") + ".Name", it.Name));
// ---------------- 무대 갱신 ----------------
public void RebuildStage()
{
for (int i = 0; i < 11; i++) propStage[i] = -99;
RefreshStage();
SetIdolFrame(idolFrame);
}
/// RoomVisualController.GetVisualIndex RVA 0x24C6C80 (문턱 24 · 49 = .cctor RVA 0x24C7904 · 최대 레벨 = 3)
public static int VisualIndex(int level, int maxLevel)
{
if (level < 0) return -1;
if (maxLevel >= 0 && level >= maxLevel) return 3;
if (level < 49) return level >= 24 ? 1 : 0;
return 2;
}
void RefreshStage()
{
int h = G.S.house;
var room = G.Room;
int roomStage = -1;
var roomItem = G.RoomItem(10);
if (roomItem != null) roomStage = VisualIndex(G.EffRoomLevel(10), roomItem.Levels.Count - 1);
if (propStage[10] != roomStage) { propStage[10] = roomStage; roomBg.sprite = Art.RoomBg(h, roomStage); }
for (int i = 0; i < 10; i++)
{
var it = room.Items.Find(x => x.Index == i);
int st = it == null ? -1 : VisualIndex(G.EffRoomLevel(i), it.Levels.Count - 1);
if (propStage[i] == st) continue;
bool grew = propStage[i] != -99 && st > propStage[i];
propStage[i] = st;
props[i].gameObject.SetActive(st >= 0);
if (st >= 0) props[i].sprite = Art.Prop(h, i, it.Name, st);
if (grew) Pop(props[i].rectTransform);
}
}
readonly List<(RectTransform rt, float t)> pops = new List<(RectTransform, float)>();
void Pop(RectTransform rt) { pops.Add((rt, 0f)); }
// ---------------- 갱신 ----------------
public void RefreshAll()
{
if (G == null || coinText == null) return;
coinText.text = Fmt.Num(G.Coin);
gpsText.text = Fmt.Num(G.PassGps()) + "/s";
ticketText.text = G.S.ticket.ToString();
nameText.text = G.S.idolName;
var rk = G.CurrentRank();
rankText.text = rk != null ? T.Loc.Get($"Content.Rank.{rk.Index}.Name", rk.Rank) : "";
var next = G.NextRank();
if (next != null)
{
var cur = rk != null ? Big.P(rk.Follower) : BigInteger.Zero;
var nx = Big.P(next.Follower);
double f = nx > cur ? (double)(G.Follower - cur) / (double)(nx - cur) : 1;
followerFill.fillAmount = Mathf.Clamp01((float)f);
followerText.text = $"{Fmt.Num(G.Follower)} / {Fmt.Num(nx)}";
}
else { followerFill.fillAmount = 1; followerText.text = Fmt.Num(G.Follower); }
var claim = G.DonateClaimable;
donateAmount.text = Fmt.Num(claim);
donateBtn.interactable = claim > 0;
donateDot.gameObject.SetActive(claim > 0);
hypeFill.fillAmount = G.Hype;
hypeLabel.text = G.HypePlaying ? "HYPE x" + T.Cfg.remote.HypeMulti.ToString("0.#") : Mathf.RoundToInt(G.Hype * 100) + "%";
newHouseBtn.gameObject.SetActive(G.HasNextHouse && G.NextHouseRankReq() <= G.S.rankIndex);
RefreshSystemsHud();
RefreshStage();
RefreshOpenPopup();
if (G.RankUpPending && !PopupOpen && !Headless) OpenRankUp();
TryOpenStory();
}
void SetIdolFrame(int f)
{
int outfit = G.S.closetCurrent;
if (PreviewOutfit >= 0) outfit = PreviewOutfit;
idolImg.sprite = Art.Idol(outfit, f);
if (avatarFace != null) avatarFace.sprite = Art.Idol(outfit, 0);
}
public int PreviewOutfit = -1;
// ---------------- 연출 ----------------
void UpdateHudFx(float dt)
{
// 스트리머 프레임 (원작 IdolController: 대기 깜빡임 2 s/0.1 s · 하이프 프레임 0.5 s · 포즈 0.6 s)
var ic = T.Cfg.idol;
if (G.HypePlaying)
{
idolTimer += dt;
if (idolTimer >= ic._timeBetweenHypeFrame) { idolTimer = 0; idolFrame = idolFrame >= 3 && idolFrame < 6 ? idolFrame + 1 : 3; }
}
else if (poseTimer > 0) { poseTimer -= dt; idolFrame = 2; }
else
{
blinkTimer += dt;
idolFrame = blinkTimer > ic._timeBetweenIdleBlink ? 1 : 0;
if (blinkTimer > ic._timeBetweenIdleBlink + ic._idleBlinkDuration) blinkTimer = 0;
}
SetIdolFrame(idolFrame);
hypeGlow.SetActive(G.HypePlaying);
if (G.HypePlaying) hypeGlow.transform.localScale = Vector3.one * (1f + 0.06f * Mathf.Sin(Time.unscaledTime * 8f));
// 탭 펀치(원작 InputHandler 스프링: 강도 0.07 · 강성 180 · 감쇠 12)
var inp = T.Cfg.input;
float acc = -inp._punchSpringStiffness * punch - inp._punchSpringDamping * punchVel;
punchVel += acc * dt; punch += punchVel * dt;
idolImg.rectTransform.localScale = new Vector3(1f + punch, 1f - punch * 0.6f, 1f);
for (int i = pops.Count - 1; i >= 0; i--)
{
var (rt, t) = pops[i];
t += dt;
if (rt == null || t > 0.4f) { if (rt != null) rt.localScale = Vector3.one; pops.RemoveAt(i); continue; }
rt.localScale = Vector3.one * (1f + 0.25f * Mathf.Sin(t / 0.4f * Mathf.PI));
pops[i] = (rt, t);
}
for (int i = floats.Count - 1; i >= 0; i--)
{
var fl = floats[i];
fl.t += dt;
if (fl.t > fl.life) { Kill(fl.rt.gameObject); floats.RemoveAt(i); continue; }
fl.rt.anchoredPosition += fl.vel * dt;
var g = fl.rt.GetComponent<CanvasGroup>(); g.alpha = 1f - fl.t / fl.life;
floats[i] = fl;
}
UpdateChat(dt);
}
float punch, punchVel;
class Floater { public RectTransform rt; public Vector2 vel; public float t, life; }
readonly List<Floater> floats = new List<Floater>();
public void FloatText(Vector2 local, string s, Color c, int size = 48, Sprite icon = null, bool box = false)
{
var rt = UI.Rect("Float", FxLayer);
rt.anchorMin = rt.anchorMax = new Vector2(0.5f, 0.5f);
rt.anchoredPosition = local; rt.sizeDelta = new Vector2(box ? 820 : 420, 90);
rt.gameObject.AddComponent<CanvasGroup>().blocksRaycasts = false;
if (box) UI.Img(rt, "Bg", Skin.Toast, new Color(0.15f, 0.17f, 0.3f, 0.88f)).rectTransform.Fill(0, -8, 0, -8);
var t = UI.Txt(rt, "T", s, size, c, TextAnchor.MiddleCenter, true); t.rectTransform.Fill();
if (icon != null) UI.Img(rt, "I", icon, Color.white).rectTransform.Place(new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(-150, 0), new Vector2(64, 64));
floats.Add(new Floater { rt = rt, vel = new Vector2(Random.Range(-40f, 40f), 260f), life = 1.1f });
}
Vector2 ScreenToRoot(Vector2 screen)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(FxLayer, screen, Cam, out var lp);
return lp;
}
void OnTapVisual(Vector2 screenPos, bool coin)
{
var lp = ScreenToRoot(screenPos);
var h = UI.Icon(FxLayer, "Heart", Skin.Icon("heart"), Color.white);
h.rectTransform.anchorMin = h.rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
h.rectTransform.anchoredPosition = lp; h.rectTransform.sizeDelta = new Vector2(90, 90);
h.gameObject.AddComponent<CanvasGroup>().blocksRaycasts = false;
floats.Add(new Floater { rt = h.rectTransform, vel = new Vector2(Random.Range(-80f, 80f), 320f), life = 0.8f });
if (coin) { punchVel += T.Cfg.input._punchTapForce * T.Cfg.input._punchScaleIntensity * 10f; poseTimer = T.Cfg.input._returnIdleDelay; }
}
}
}