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

386 lines
23 KiB
C#
Raw Normal View History

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