Files
GeoSusGame/Assets/Scripts/InGameHUDBuilder.cs
Bandwidth d886f97e14 GeoSus
2026-04-26 20:49:32 +02:00

506 lines
24 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 UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections.Generic;
/// <summary>
/// Programmatically builds the complete in-game HUD inside the InGame canvas (Client.unity).
///
/// Call BuildNow() from GameManager.OnSceneLoaded BEFORE BindClientScene so GameManager_UI
/// can locate named children.
///
/// Named elements expected by GameManager_UI (Transform.Find / FindTMP):
/// ActionButton proxim action button
/// SabotagePanel top-of-screen sabotage banner
/// SabotageTimer TMP countdown text inside SabotagePanel
/// MeetingPanel full-screen voting overlay
/// MeetingHeader TMP title (event type)
/// MeetingPhaseLabel TMP sub-phase label (DISCUSSION / VOTING / RESULTS / ARRIVAL)
/// MeetingPhaseProgressBar Image filled, drains with countdown
/// MeetingPhaseCountdown TMP "0:24" countdown text
/// MeetingPlayerList TMP player list (text fallback)
/// MyVoteIndicator TMP "You voted for: X" strip
/// SkipButton skip-vote button
/// VoteResultPanel sub-panel shown after voting
/// VoteResult TMP result text
/// GameEndPanel full-screen end-of-game overlay
/// GameEndText TMP result text
/// ReconnectOverlay full-screen "reconnecting..." overlay
/// ReconnectMessage TMP "Reconnecting..." headline
/// ReconnectSubtext TMP secondary message
/// KillCooldown TMP kill-cooldown label
/// TaskList TMP task name list
/// TaskProgress TMP global task progress
/// Toast TMP toast notification
/// </summary>
public class InGameHUDBuilder : MonoBehaviour
{
// ── Palette ───────────────────────────────────────────────────────────────
// Aliases to UITheme so this file's existing local references keep working
// without a wholesale rename pass. New code should call UITheme.* directly;
// the C_* names stay as a transitional crutch for in-flight edits.
static readonly Color C_BG = UITheme.Bg;
static readonly Color C_BAR = UITheme.Surface;
static readonly Color C_ACCENT = UITheme.Accent;
static readonly Color C_GREEN = UITheme.Success;
static readonly Color C_RED = UITheme.Danger;
static readonly Color C_ORANGE = UITheme.Warning;
static readonly Color C_YELLOW = UITheme.Caution;
static readonly Color C_MUTED = UITheme.TextLo;
static readonly Color C_ROW_A = UITheme.RowA;
static readonly Color C_ROW_B = UITheme.RowB;
private bool _built;
// Reference resolution constants kept here as public surface so external
// callers (other UI scripts that grew to use these) don't break. Forward
// to UITheme so there's still one source of truth.
public const float kReferenceWidth = UITheme.ReferenceWidth;
public const float kReferenceHeight = UITheme.ReferenceHeight;
public const float kMatchWidthHeight = UITheme.MatchWidthOrHeight;
public void BuildNow() { if (!_built) { _built = true; Build(); } }
void Start() { if (!_built) Build(); }
void Build()
{
var rt = GetComponent<RectTransform>();
if (rt == null) return;
// Wire up emoji fallback before any TMP component renders. Idempotent
// and cheap on subsequent calls.
UITheme.EnsureEmojiFontFallback();
// Make sure the host canvas has a CanvasScaler with our reference
// resolution. Without this, RectTransform offsets are interpreted as
// raw pixels and the layout looks correct only on whichever device
// the project was last opened against.
ConfigureCanvasScaler(GetComponentInParent<Canvas>());
// Apply Screen.safeArea so iOS notches and Android punch-hole cameras
// don't eat the top/bottom bar. We anchor the host RectTransform to
// the safe rectangle so all child anchors inherit the inset.
rt.anchorMin = Vector2.zero;
rt.anchorMax = Vector2.one;
rt.offsetMin = Vector2.zero;
rt.offsetMax = Vector2.zero;
ApplySafeArea(rt);
BuildTopBar(rt);
BuildTaskPanel(rt);
BuildTaskProgress(rt);
BuildBottomBar(rt);
BuildActionButton(rt);
BuildSabotagePanel(rt);
BuildMeetingPanel(rt);
BuildGameEndPanel(rt);
BuildReconnectOverlay(rt);
BuildSpectatePanel(rt);
BuildToast(rt);
}
/// <summary>
/// Apply the project's standard CanvasScaler config to a Canvas. Idempotent -
/// adds the component if missing, otherwise updates settings in-place.
/// </summary>
public static void ConfigureCanvasScaler(Canvas canvas)
{
if (canvas == null) return;
var scaler = canvas.GetComponent<CanvasScaler>()
?? canvas.gameObject.AddComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(kReferenceWidth, kReferenceHeight);
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
scaler.matchWidthOrHeight = kMatchWidthHeight;
scaler.referencePixelsPerUnit = 100f;
}
/// <summary>
/// Anchors the given RectTransform to the screen's safe rectangle, so all
/// children inherit the inset. Called once at build time; the safe area
/// rarely changes after the app launches and a full HUD rebuild on rotation
/// would be the simpler way to handle that case.
/// </summary>
public static void ApplySafeArea(RectTransform rt)
{
if (rt == null) return;
var safe = Screen.safeArea;
var screenSize = new Vector2(Screen.width, Screen.height);
if (screenSize.x <= 0 || screenSize.y <= 0) return;
Vector2 aMin = safe.position;
Vector2 aMax = safe.position + safe.size;
aMin.x /= screenSize.x; aMin.y /= screenSize.y;
aMax.x /= screenSize.x; aMax.y /= screenSize.y;
rt.anchorMin = aMin;
rt.anchorMax = aMax;
rt.offsetMin = Vector2.zero;
rt.offsetMax = Vector2.zero;
}
// ── Top bar ───────────────────────────────────────────────────────────────
void BuildTopBar(RectTransform parent)
{
var bar = Child("_TopBar", parent);
Anchor(bar, new Vector2(0,1), new Vector2(1,1), new Vector2(0,-90f), Vector2.zero);
bar.pivot = new Vector2(0.5f,1f);
Img(bar, C_BAR);
// Kill cooldown right half, hidden by default
var cd = Child("KillCooldown", bar);
cd.anchorMin = new Vector2(0.5f, 0); cd.anchorMax = Vector2.one;
cd.offsetMin = new Vector2(0, 6); cd.offsetMax = new Vector2(-12, -6);
var cdTmp = cd.gameObject.AddComponent<TextMeshProUGUI>();
cdTmp.text = ""; cdTmp.fontSize = 32; cdTmp.color = C_ORANGE;
cdTmp.fontStyle = FontStyles.Bold; cdTmp.alignment = TextAlignmentOptions.MidlineRight;
cd.gameObject.SetActive(false);
}
// ── Task panel (right side) ───────────────────────────────────────────────
void BuildTaskPanel(RectTransform parent)
{
var panel = Child("_TaskPanel", parent);
panel.anchorMin = new Vector2(1,0.35f); panel.anchorMax = new Vector2(1,0.88f);
panel.pivot = new Vector2(1,0.5f); panel.sizeDelta = new Vector2(280,0);
Img(panel, new Color(0.05f,0.06f,0.12f,0.85f));
var hdr = Child("_Hdr", panel);
Anchor(hdr, new Vector2(0,1), new Vector2(1,1), new Vector2(0,-44), Vector2.zero);
hdr.pivot = new Vector2(0.5f,1f);
Img(hdr, new Color(0.2f,0.6f,1f,0.5f));
TxtChild(hdr,"MY TASKS",26,Color.white,TextAlignmentOptions.Center,bold:true);
var body = Child("TaskList", panel);
body.anchorMin = Vector2.zero; body.anchorMax = Vector2.one;
body.offsetMin = new Vector2(8,8); body.offsetMax = new Vector2(-8,-48);
var t = body.gameObject.AddComponent<TextMeshProUGUI>();
t.text = ""; t.fontSize = 22; t.color = Color.white; t.alignment = TextAlignmentOptions.TopLeft;
t.enableWordWrapping = true;
}
// ── Task progress (above bottom bar) ─────────────────────────────────────
void BuildTaskProgress(RectTransform parent)
{
var prog = Child("TaskProgress", parent);
Anchor(prog, new Vector2(0,0), new Vector2(1,0), new Vector2(-20,120), new Vector2(20,160));
var t = prog.gameObject.AddComponent<TextMeshProUGUI>();
t.text = ""; t.fontSize = 28; t.color = Color.white;
t.fontStyle = FontStyles.Bold; t.alignment = TextAlignmentOptions.Center;
}
// ── Bottom bar ────────────────────────────────────────────────────────────
void BuildBottomBar(RectTransform parent)
{
var bar = Child("_BottomBar", parent);
Anchor(bar, Vector2.zero, new Vector2(1,0), Vector2.zero, new Vector2(0,110));
bar.pivot = new Vector2(0.5f,0);
Img(bar, C_BAR);
var recBtn = Child("_RecenterBtn", bar);
recBtn.anchorMin = new Vector2(0.82f,0.08f); recBtn.anchorMax = new Vector2(0.98f,0.92f);
var recBg = Img(recBtn, C_ACCENT);
var recButton = recBtn.gameObject.AddComponent<Button>();
recButton.targetGraphic = recBg;
recButton.onClick.AddListener(() => MapCameraController.Instance?.Recenter());
TxtChild(recBtn,"⊙",42,Color.white,TextAlignmentOptions.Center,bold:true);
}
// ── Action button (DIRECT child so Transform.Find works) ─────────────────
void BuildActionButton(RectTransform parent)
{
var btn = Child("ActionButton", parent);
Anchor(btn, new Vector2(0.15f,0), new Vector2(0.80f,0), new Vector2(0,12), new Vector2(0,102));
btn.pivot = new Vector2(0.5f,0);
var bg = Img(btn, C_GREEN);
var button = btn.gameObject.AddComponent<Button>();
button.targetGraphic = bg;
var txtRt = Child("Text", btn);
Stretch(txtRt);
var tmp = txtRt.gameObject.AddComponent<TextMeshProUGUI>();
tmp.text = "ACTION"; tmp.fontSize = 44; tmp.fontStyle = FontStyles.Bold;
tmp.color = Color.white; tmp.alignment = TextAlignmentOptions.Center;
btn.gameObject.SetActive(false);
}
// ── Sabotage panel (top strip) ────────────────────────────────────────────
void BuildSabotagePanel(RectTransform parent)
{
var panel = Child("SabotagePanel", parent);
panel.anchorMin = new Vector2(0,1); panel.anchorMax = new Vector2(1,1);
panel.pivot = new Vector2(0.5f,1f); panel.sizeDelta = new Vector2(0,80);
Img(panel, new Color(0.76f,0.19f,0.19f,0.92f));
var timer = Child("SabotageTimer", panel);
Stretch(timer);
var t = timer.gameObject.AddComponent<TextMeshProUGUI>();
t.text = "SABOTAGE!"; t.fontSize = 48; t.fontStyle = FontStyles.Bold;
t.color = Color.white; t.alignment = TextAlignmentOptions.Center;
panel.gameObject.SetActive(false);
}
// ── Meeting panel (full screen overlay) ───────────────────────────────────
//
// Layout (vertical, top to bottom):
// 0.90 - 1.00 MeetingHeader - "EMERGENCY MEETING" / "BODY REPORTED"
// 0.83 - 0.90 MeetingPhaseLabel - "ARRIVAL" / "DISCUSSION" / "VOTING" / "RESULTS"
// 0.79 - 0.83 MeetingPhaseProgressBar - thin fill bar that drains as countdown runs
// 0.74 - 0.79 MeetingPhaseCountdown - "0:24" countdown text
// 0.18 - 0.74 _MeetingScroll - vote rows (or VoteResultPanel when resolved)
// 0.14 - 0.18 MyVoteIndicator - "You voted for: X" or "Voting hasn't started"
// 0.04 - 0.14 SkipButton - skip vote (will move into the row list in P2.7)
void BuildMeetingPanel(RectTransform parent)
{
var panel = Child("MeetingPanel", parent);
Stretch(panel);
Img(panel, new Color(0.04f,0.05f,0.14f,0.97f));
// Title
var hdr = Child("MeetingHeader", panel);
Anchor(hdr, new Vector2(0,0.90f), new Vector2(1,1), Vector2.zero, Vector2.zero);
var hdrTmp = hdr.gameObject.AddComponent<TextMeshProUGUI>();
hdrTmp.text = "EMERGENCY MEETING"; hdrTmp.fontSize = 52;
hdrTmp.fontStyle = FontStyles.Bold; hdrTmp.color = C_ORANGE;
hdrTmp.alignment = TextAlignmentOptions.Center;
// Sub-phase label (DISCUSSION / VOTING / RESULTS / ARRIVAL)
var phaseLbl = Child("MeetingPhaseLabel", panel);
Anchor(phaseLbl, new Vector2(0,0.83f), new Vector2(1,0.90f), Vector2.zero, Vector2.zero);
var phaseLblTmp = phaseLbl.gameObject.AddComponent<TextMeshProUGUI>();
phaseLblTmp.text = ""; phaseLblTmp.fontSize = 32;
phaseLblTmp.fontStyle = FontStyles.Bold; phaseLblTmp.color = C_ACCENT;
phaseLblTmp.alignment = TextAlignmentOptions.Center;
// Phase progress bar (drains as countdown elapses)
var progBg = Child("MeetingPhaseProgressBg", panel);
Anchor(progBg, new Vector2(0.10f,0.79f), new Vector2(0.90f,0.83f), Vector2.zero, Vector2.zero);
Img(progBg, new Color(0.10f,0.13f,0.22f,1f));
var progFill = Child("MeetingPhaseProgressBar", progBg);
progFill.anchorMin = new Vector2(0,0); progFill.anchorMax = new Vector2(1,1);
progFill.offsetMin = Vector2.zero; progFill.offsetMax = Vector2.zero;
var fillImg = progFill.gameObject.AddComponent<Image>();
fillImg.color = C_ACCENT;
fillImg.type = Image.Type.Filled;
fillImg.fillMethod = Image.FillMethod.Horizontal;
fillImg.fillAmount = 0f;
// Countdown text under the bar
var cd = Child("MeetingPhaseCountdown", panel);
Anchor(cd, new Vector2(0,0.74f), new Vector2(1,0.79f), Vector2.zero, Vector2.zero);
var cdTmp = cd.gameObject.AddComponent<TextMeshProUGUI>();
cdTmp.text = ""; cdTmp.fontSize = 28;
cdTmp.color = new Color(0.8f,0.85f,0.95f);
cdTmp.alignment = TextAlignmentOptions.Center;
// Scrollable player vote list
var scrollArea = Child("_MeetingScroll", panel);
Anchor(scrollArea, new Vector2(0,0.18f), new Vector2(1,0.74f), Vector2.zero, Vector2.zero);
BuildMeetingScroll(scrollArea);
// Text fallback (hidden by default; shown if scroll build fails)
var fallback = Child("MeetingPlayerList", panel);
Anchor(fallback, new Vector2(0,0.18f), new Vector2(1,0.74f), new Vector2(8,0), new Vector2(-8,0));
var fallbackTmp = fallback.gameObject.AddComponent<TextMeshProUGUI>();
fallbackTmp.text = ""; fallbackTmp.fontSize = 28; fallbackTmp.color = Color.white;
fallbackTmp.alignment = TextAlignmentOptions.TopLeft;
fallback.gameObject.SetActive(false); // hidden - scroll list used instead
// "Your vote: X" indicator strip
var myVote = Child("MyVoteIndicator", panel);
Anchor(myVote, new Vector2(0.05f,0.14f), new Vector2(0.95f,0.18f), Vector2.zero, Vector2.zero);
var myVoteTmp = myVote.gameObject.AddComponent<TextMeshProUGUI>();
myVoteTmp.text = ""; myVoteTmp.fontSize = 26;
myVoteTmp.color = new Color(0.73f,0.8f,0.88f);
myVoteTmp.alignment = TextAlignmentOptions.Center;
// Skip button (will be merged into the vote list as a row in P2.7)
var skip = Child("SkipButton", panel);
Anchor(skip, new Vector2(0.05f,0.04f), new Vector2(0.95f,0.14f), Vector2.zero, Vector2.zero);
var skipBg = Img(skip, C_MUTED);
var skipBtn = skip.gameObject.AddComponent<Button>();
skipBtn.targetGraphic = skipBg;
skipBtn.onClick.AddListener(() => GameManager.Instance?.CastVote(null));
TxtChild(skip, "⏭ SKIP", 36, Color.white, TextAlignmentOptions.Center, bold: true);
// Vote-result sub-panel - now sized to *replace* the scroll area when
// results arrive, instead of squeezing into the bottom strip alongside
// skip/my-vote (which caused the previous overlap).
var resultPanel = Child("VoteResultPanel", panel);
Anchor(resultPanel, new Vector2(0,0.18f), new Vector2(1,0.74f), Vector2.zero, Vector2.zero);
Img(resultPanel, new Color(0.05f,0.05f,0.15f,0.95f));
var resultText = Child("VoteResult", resultPanel);
Stretch(resultText);
var rtTmp = resultText.gameObject.AddComponent<TextMeshProUGUI>();
rtTmp.text = ""; rtTmp.fontSize = 34; rtTmp.color = C_YELLOW;
rtTmp.fontStyle = FontStyles.Bold; rtTmp.alignment = TextAlignmentOptions.Center;
resultPanel.gameObject.SetActive(false);
panel.gameObject.SetActive(false);
}
void BuildMeetingScroll(RectTransform rt)
{
var sr = rt.gameObject.AddComponent<ScrollRect>();
var vp = Child("Viewport", rt);
Stretch(vp);
vp.gameObject.AddComponent<RectMask2D>();
var content = Child("MeetingContent", vp);
content.anchorMin = new Vector2(0,1); content.anchorMax = new Vector2(1,1);
content.pivot = new Vector2(0.5f,1);
var vlg = content.gameObject.AddComponent<VerticalLayoutGroup>();
vlg.childControlWidth = true; vlg.childControlHeight = false;
vlg.childForceExpandWidth = true; vlg.spacing = 4;
var csf = content.gameObject.AddComponent<ContentSizeFitter>();
csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
sr.viewport = vp; sr.content = content;
sr.horizontal = false; sr.vertical = true; sr.scrollSensitivity = 60;
}
// ── Game-end panel ────────────────────────────────────────────────────────
void BuildGameEndPanel(RectTransform parent)
{
var panel = Child("GameEndPanel", parent);
Stretch(panel);
Img(panel, new Color(0,0,0,0.90f));
// Result text (upper half)
var txt = Child("GameEndText", panel);
Anchor(txt, new Vector2(0,0.4f), new Vector2(1,0.9f), Vector2.zero, Vector2.zero);
var tmp = txt.gameObject.AddComponent<TextMeshProUGUI>();
tmp.text = ""; tmp.fontSize = 72; tmp.fontStyle = FontStyles.Bold;
tmp.color = Color.white; tmp.alignment = TextAlignmentOptions.Center;
// "Return to lobby" button owner only (GameManager_UI shows/hides it)
var retBtn = Child("ReturnToLobbyButton", panel);
Anchor(retBtn, new Vector2(0.15f,0.22f), new Vector2(0.85f,0.36f), Vector2.zero, Vector2.zero);
var retBg = Img(retBtn, C_GREEN);
var retButton = retBtn.gameObject.AddComponent<Button>();
retButton.targetGraphic = retBg;
retButton.onClick.AddListener(() => GameManager.Instance?.gameClient?.ReturnToLobby());
TxtChild(retBtn, "▶ RETURN TO LOBBY", 38, Color.white, TextAlignmentOptions.Center, bold: true);
retBtn.gameObject.SetActive(false); // shown only for host
// "Leave" button
var leaveBtn = Child("LeaveGameButton", panel);
Anchor(leaveBtn, new Vector2(0.3f,0.08f), new Vector2(0.7f,0.20f), Vector2.zero, Vector2.zero);
var leaveBg = Img(leaveBtn, C_RED);
var leaveButton = leaveBtn.gameObject.AddComponent<Button>();
leaveButton.targetGraphic = leaveBg;
leaveButton.onClick.AddListener(() => GameManager.Instance?.LeaveLobbyButton());
TxtChild(leaveBtn, "✕ LEAVE", 34, Color.white, TextAlignmentOptions.Center, bold: true);
panel.gameObject.SetActive(false);
}
// ── Reconnect overlay (visible while the socket is dropping/reconnecting) ─
void BuildReconnectOverlay(RectTransform parent)
{
var panel = Child("ReconnectOverlay", parent);
Stretch(panel);
Img(panel, new Color(0.04f,0.05f,0.14f,0.90f));
var msg = Child("ReconnectMessage", panel);
Anchor(msg, new Vector2(0,0.45f), new Vector2(1,0.65f), Vector2.zero, Vector2.zero);
var msgTmp = msg.gameObject.AddComponent<TextMeshProUGUI>();
msgTmp.text = "Reconnecting..."; msgTmp.fontSize = 56;
msgTmp.fontStyle = FontStyles.Bold; msgTmp.color = C_YELLOW;
msgTmp.alignment = TextAlignmentOptions.Center;
var sub = Child("ReconnectSubtext", panel);
Anchor(sub, new Vector2(0,0.35f), new Vector2(1,0.45f), Vector2.zero, Vector2.zero);
var subTmp = sub.gameObject.AddComponent<TextMeshProUGUI>();
subTmp.text = "Server keeps your slot for up to 60 seconds.";
subTmp.fontSize = 28; subTmp.color = new Color(0.73f,0.8f,0.88f);
subTmp.alignment = TextAlignmentOptions.Center;
panel.gameObject.SetActive(false);
}
// ── Spectate panel (visible after death; dim banner so the player still
// sees the live map but understands they're spectating) ────────────────
void BuildSpectatePanel(RectTransform parent)
{
var panel = Child("SpectatePanel", parent);
// Top strip only - we don't want to occlude the map. Just enough to
// communicate "you're dead, watching live."
panel.anchorMin = new Vector2(0, 1); panel.anchorMax = new Vector2(1, 1);
panel.pivot = new Vector2(0.5f, 1f); panel.sizeDelta = new Vector2(0, 96);
Img(panel, new Color(0f, 0f, 0f, 0.65f));
var label = Child("SpectateLabel", panel);
Stretch(label);
var t = label.gameObject.AddComponent<TextMeshProUGUI>();
UITheme.StyleText(t, UITheme.FontTitle, UITheme.TextHi,
TextAlignmentOptions.Center, bold: true);
t.text = "👻 YOU ARE DEAD - SPECTATING";
var sub = Child("SpectateSub", panel);
Anchor(sub, new Vector2(0, 0), new Vector2(1, 0.45f), Vector2.zero, Vector2.zero);
var st = sub.gameObject.AddComponent<TextMeshProUGUI>();
UITheme.StyleText(st, UITheme.FontSmall, UITheme.TextMid,
TextAlignmentOptions.Center);
st.text = "Crew can finish tasks as ghosts. Impostors cannot kill you.";
panel.gameObject.SetActive(false);
}
// ── Toast notification ────────────────────────────────────────────────────
void BuildToast(RectTransform parent)
{
var toast = Child("Toast", parent);
Anchor(toast, new Vector2(0.05f,0.88f), new Vector2(0.95f,0.94f), Vector2.zero, Vector2.zero);
Img(toast, new Color(0.1f,0.1f,0.2f,0.92f));
TxtChild(toast, "", 30, C_YELLOW, TextAlignmentOptions.Center, bold: true);
toast.gameObject.SetActive(false);
}
// ── Helpers ───────────────────────────────────────────────────────────────
RectTransform Child(string name, RectTransform parent)
{
var go = new GameObject(name);
var rt = go.AddComponent<RectTransform>();
rt.SetParent(parent, false);
rt.localScale = Vector3.one;
return rt;
}
Image Img(RectTransform rt, Color c)
{
var img = rt.gameObject.AddComponent<Image>();
img.color = c;
return img;
}
void Stretch(RectTransform rt)
{
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
}
// min/max by anchor + absolute offset corners
void Anchor(RectTransform rt, Vector2 aMin, Vector2 aMax, Vector2 offsetMin, Vector2 offsetMax)
{
rt.anchorMin = aMin; rt.anchorMax = aMax;
rt.offsetMin = offsetMin; rt.offsetMax = offsetMax;
}
TextMeshProUGUI TxtChild(RectTransform parent, string text, float size, Color color,
TextAlignmentOptions align, bool bold = false)
{
var rt = Child("Txt", parent);
Stretch(rt);
var tmp = rt.gameObject.AddComponent<TextMeshProUGUI>();
tmp.text = text; tmp.fontSize = size; tmp.color = color; tmp.alignment = align;
if (bold) tmp.fontStyle = FontStyles.Bold;
return tmp;
}
}