319 lines
13 KiB
C#
319 lines
13 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
|
|
/// <summary>
|
|
/// Programmatically builds the complete in-game HUD inside the InGame canvas (Client.unity).
|
|
///
|
|
/// Call BuildNow() explicitly from GameManager.OnSceneLoaded BEFORE BindClientScene(),
|
|
/// so that GameManager_UI can find the newly created elements by name.
|
|
///
|
|
/// Named GameObjects created as direct children of InGame canvas (required by Transform.Find):
|
|
/// • ActionButton — Button + TMP child; shown/hidden by GameManager_Tasks.UpdateProximity()
|
|
/// • SabotagePanel — warning banner at top (contains "SabotageTimer" TMP_Text)
|
|
/// • MeetingPanel — voting/meeting overlay (populated by GameManager_UI.ShowMeetingPanel)
|
|
/// • GameEndPanel — end-of-game overlay (contains "GameEndText" TMP_Text)
|
|
///
|
|
/// Named TMP_Text descendants (found by GameManager_UI.FindTMP — any depth):
|
|
/// • KillCooldown — shown to impostors during kill cooldown
|
|
/// • TaskList — crewmate task names
|
|
/// • TaskProgress — global task completion "X/Y tasks"
|
|
/// • SabotageTimer — countdown inside SabotagePanel
|
|
/// • GameEndText — win/lose result text inside GameEndPanel
|
|
/// • (Role already exists in the scene)
|
|
///
|
|
/// Additional elements managed by this script:
|
|
/// • RecenterBtn — calls MapCameraController.Instance.Recenter()
|
|
/// </summary>
|
|
public class InGameHUDBuilder : MonoBehaviour
|
|
{
|
|
// ── Color palette ─────────────────────────────────────────────────────────
|
|
private static readonly Color C_BG = new Color(0.05f, 0.06f, 0.12f, 0.80f);
|
|
private static readonly Color C_BAR_BG = new Color(0.03f, 0.04f, 0.08f, 0.90f);
|
|
private static readonly Color C_ACCENT = new Color(0.20f, 0.60f, 1.00f, 1.00f);
|
|
private static readonly Color C_GREEN = new Color(0.18f, 0.75f, 0.30f, 1.00f);
|
|
private static readonly Color C_RED = new Color(0.76f, 0.19f, 0.19f, 1.00f);
|
|
private static readonly Color C_ORANGE = new Color(0.95f, 0.55f, 0.10f, 1.00f);
|
|
|
|
private bool _built;
|
|
|
|
// ── Entry points ──────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Called from GameManager.OnSceneLoaded before BindClientScene.</summary>
|
|
public void BuildNow()
|
|
{
|
|
if (_built) return;
|
|
_built = true;
|
|
Build();
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
if (!_built) Build(); // safety fallback
|
|
}
|
|
|
|
// ── Build ─────────────────────────────────────────────────────────────────
|
|
void Build()
|
|
{
|
|
var rt = GetComponent<RectTransform>();
|
|
if (rt == null) return;
|
|
|
|
// ── Top bar: role is already in scene, add kill-cooldown ─────────────
|
|
BuildTopBar(rt);
|
|
|
|
// ── Right task panel ──────────────────────────────────────────────────
|
|
BuildTaskPanel(rt);
|
|
|
|
// ── Task progress (above bottom bar) ─────────────────────────────────
|
|
BuildTaskProgress(rt);
|
|
|
|
// ── Bottom bar: action button + recenter ──────────────────────────────
|
|
BuildBottomBar(rt);
|
|
|
|
// ── Action button (DIRECT child — Transform.Find requirement) ─────────
|
|
BuildActionButton(rt);
|
|
|
|
// ── Sabotage panel (DIRECT child) ─────────────────────────────────────
|
|
BuildSabotagePanel(rt);
|
|
|
|
// ── Meeting panel (DIRECT child) ──────────────────────────────────────
|
|
BuildMeetingPanel(rt);
|
|
|
|
// ── Game-end panel (DIRECT child) ─────────────────────────────────────
|
|
BuildGameEndPanel(rt);
|
|
}
|
|
|
|
// ── Section builders ──────────────────────────────────────────────────────
|
|
|
|
void BuildTopBar(RectTransform parent)
|
|
{
|
|
// Thin semi-transparent header at very top
|
|
var bar = AddChild("_TopBar", parent);
|
|
Anchor(bar, new Vector2(0f, 1f), new Vector2(1f, 1f));
|
|
bar.sizeDelta = new Vector2(0f, 90f);
|
|
bar.anchoredPosition = new Vector2(0f, 0f);
|
|
bar.pivot = new Vector2(0.5f, 1f);
|
|
AddImage(bar.gameObject, C_BAR_BG);
|
|
|
|
// Kill cooldown (right side) — starts hidden
|
|
var cd = AddChild("KillCooldown", bar);
|
|
Anchor(cd, new Vector2(0.5f, 0f), new Vector2(1f, 1f));
|
|
cd.offsetMin = new Vector2(0f, 6f);
|
|
cd.offsetMax = new Vector2(-12f, -6f);
|
|
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);
|
|
}
|
|
|
|
void BuildTaskPanel(RectTransform parent)
|
|
{
|
|
// Right-side floating panel (always visible during game)
|
|
var panel = AddChild("_TaskPanel", parent);
|
|
Anchor(panel, new Vector2(1f, 0.35f), new Vector2(1f, 0.88f));
|
|
panel.pivot = new Vector2(1f, 0.5f);
|
|
panel.sizeDelta = new Vector2(280f, 0f);
|
|
panel.anchoredPosition = Vector2.zero;
|
|
AddImage(panel.gameObject, C_BG);
|
|
|
|
// "MY TASKS" header
|
|
var hdr = AddChild("_Header", panel);
|
|
Anchor(hdr, new Vector2(0f, 1f), new Vector2(1f, 1f));
|
|
hdr.pivot = new Vector2(0.5f, 1f);
|
|
hdr.sizeDelta = new Vector2(0f, 44f);
|
|
hdr.anchoredPosition = Vector2.zero;
|
|
AddImage(hdr.gameObject, C_ACCENT * new Color(1, 1, 1, 0.6f));
|
|
var hdrTmp = AddTextChild(hdr, "_HeaderTxt", "MY TASKS", 26, FontStyles.Bold, TextAlignmentOptions.Center);
|
|
hdrTmp.color = Color.white;
|
|
|
|
// Task list body
|
|
var body = AddChild("TaskList", panel);
|
|
Anchor(body, new Vector2(0f, 0f), new Vector2(1f, 1f));
|
|
body.offsetMin = new Vector2(8f, 8f);
|
|
body.offsetMax = new Vector2(-8f, -48f);
|
|
var taskTmp = body.gameObject.AddComponent<TextMeshProUGUI>();
|
|
taskTmp.text = "";
|
|
taskTmp.fontSize = 22;
|
|
taskTmp.color = Color.white;
|
|
taskTmp.alignment = TextAlignmentOptions.TopLeft;
|
|
}
|
|
|
|
void BuildTaskProgress(RectTransform parent)
|
|
{
|
|
var prog = AddChild("TaskProgress", parent);
|
|
Anchor(prog, new Vector2(0f, 0f), new Vector2(1f, 0f));
|
|
prog.pivot = new Vector2(0.5f, 0f);
|
|
prog.sizeDelta = new Vector2(-20f, 40f);
|
|
prog.anchoredPosition = new Vector2(0f, 120f); // above bottom bar
|
|
var tmp = prog.gameObject.AddComponent<TextMeshProUGUI>();
|
|
tmp.text = "";
|
|
tmp.fontSize = 28;
|
|
tmp.color = Color.white;
|
|
tmp.fontStyle = FontStyles.Bold;
|
|
tmp.alignment = TextAlignmentOptions.Center;
|
|
}
|
|
|
|
void BuildBottomBar(RectTransform parent)
|
|
{
|
|
var bar = AddChild("_BottomBar", parent);
|
|
Anchor(bar, new Vector2(0f, 0f), new Vector2(1f, 0f));
|
|
bar.pivot = new Vector2(0.5f, 0f);
|
|
bar.sizeDelta = new Vector2(0f, 110f);
|
|
bar.anchoredPosition = Vector2.zero;
|
|
AddImage(bar.gameObject, C_BAR_BG);
|
|
|
|
// Recenter button (bottom-right of bar)
|
|
var recBtn = AddChild("_RecenterBtn", bar);
|
|
Anchor(recBtn, new Vector2(0.82f, 0.08f), new Vector2(0.98f, 0.92f));
|
|
var recBg = AddImage(recBtn.gameObject, C_ACCENT);
|
|
var recButton = recBtn.gameObject.AddComponent<Button>();
|
|
var recColors = recButton.colors;
|
|
recColors.pressedColor = new Color(0.1f, 0.4f, 0.8f);
|
|
recButton.colors = recColors;
|
|
recButton.targetGraphic = recBg;
|
|
recButton.onClick.AddListener(() => MapCameraController.Instance?.Recenter());
|
|
var recTxt = AddTextChild(recBtn, "_RecTxt", "⊙", 42, FontStyles.Bold, TextAlignmentOptions.Center);
|
|
recTxt.color = Color.white;
|
|
}
|
|
|
|
void BuildActionButton(RectTransform parent)
|
|
{
|
|
// MUST be a DIRECT child so Transform.Find("ActionButton") works
|
|
var btn = AddChild("ActionButton", parent);
|
|
Anchor(btn, new Vector2(0.15f, 0f), new Vector2(0.80f, 0f));
|
|
btn.pivot = new Vector2(0.5f, 0f);
|
|
btn.sizeDelta = new Vector2(0f, 90f);
|
|
btn.anchoredPosition = new Vector2(0f, 12f);
|
|
|
|
var bg = AddImage(btn.gameObject, C_GREEN);
|
|
var button = btn.gameObject.AddComponent<Button>();
|
|
var colors = button.colors;
|
|
colors.normalColor = C_GREEN;
|
|
colors.pressedColor = new Color(0.12f, 0.55f, 0.22f);
|
|
button.colors = colors;
|
|
button.targetGraphic = bg;
|
|
|
|
// TMP child named "Text" so GetComponentInChildren<TMP_Text> finds it
|
|
var txtRt = AddChild("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); // hidden until proximity detected
|
|
}
|
|
|
|
void BuildSabotagePanel(RectTransform parent)
|
|
{
|
|
// DIRECT child
|
|
var panel = AddChild("SabotagePanel", parent);
|
|
Anchor(panel, new Vector2(0f, 0.88f), new Vector2(1f, 1f));
|
|
panel.offsetMin = new Vector2(0f, -10f);
|
|
panel.offsetMax = new Vector2(0f, -80f);
|
|
AddImage(panel.gameObject, C_RED * new Color(1, 1, 1, 0.88f));
|
|
|
|
var timer = AddChild("SabotageTimer", panel);
|
|
Stretch(timer);
|
|
var tmp = timer.gameObject.AddComponent<TextMeshProUGUI>();
|
|
tmp.text = "SABOTAGE!";
|
|
tmp.fontSize = 48;
|
|
tmp.fontStyle = FontStyles.Bold;
|
|
tmp.color = Color.white;
|
|
tmp.alignment = TextAlignmentOptions.Center;
|
|
|
|
panel.gameObject.SetActive(false);
|
|
}
|
|
|
|
void BuildMeetingPanel(RectTransform parent)
|
|
{
|
|
// DIRECT child — populated by GameManager_UI.ShowMeetingPanel at runtime
|
|
var panel = AddChild("MeetingPanel", parent);
|
|
Anchor(panel, new Vector2(0.05f, 0.10f), new Vector2(0.95f, 0.90f));
|
|
AddImage(panel.gameObject, new Color(0.04f, 0.05f, 0.14f, 0.96f));
|
|
|
|
var title = AddChild("_MeetingTitle", panel);
|
|
Anchor(title, new Vector2(0f, 0.85f), new Vector2(1f, 1f));
|
|
var titleTmp = title.gameObject.AddComponent<TextMeshProUGUI>();
|
|
titleTmp.text = "EMERGENCY MEETING";
|
|
titleTmp.fontSize = 44;
|
|
titleTmp.fontStyle = FontStyles.Bold;
|
|
titleTmp.color = C_ORANGE;
|
|
titleTmp.alignment = TextAlignmentOptions.Center;
|
|
|
|
panel.gameObject.SetActive(false);
|
|
}
|
|
|
|
void BuildGameEndPanel(RectTransform parent)
|
|
{
|
|
// DIRECT child, full-screen overlay
|
|
var panel = AddChild("GameEndPanel", parent);
|
|
Stretch(panel);
|
|
AddImage(panel.gameObject, new Color(0f, 0f, 0f, 0.85f));
|
|
|
|
var txt = AddChild("GameEndText", panel);
|
|
Stretch(txt);
|
|
var tmp = txt.gameObject.AddComponent<TextMeshProUGUI>();
|
|
tmp.text = "";
|
|
tmp.fontSize = 72;
|
|
tmp.fontStyle = FontStyles.Bold;
|
|
tmp.color = Color.white;
|
|
tmp.alignment = TextAlignmentOptions.Center;
|
|
|
|
panel.gameObject.SetActive(false);
|
|
}
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
|
|
RectTransform AddChild(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 AddImage(GameObject go, Color color)
|
|
{
|
|
var img = go.AddComponent<Image>();
|
|
img.color = color;
|
|
return img;
|
|
}
|
|
|
|
TextMeshProUGUI AddTextChild(RectTransform parent, string name, string text,
|
|
float size, FontStyles style, TextAlignmentOptions align)
|
|
{
|
|
var rt = AddChild(name, parent);
|
|
Stretch(rt);
|
|
var tmp = rt.gameObject.AddComponent<TextMeshProUGUI>();
|
|
tmp.text = text;
|
|
tmp.fontSize = size;
|
|
tmp.fontStyle = style;
|
|
tmp.alignment = align;
|
|
tmp.color = Color.white;
|
|
return tmp;
|
|
}
|
|
|
|
void Anchor(RectTransform rt, Vector2 min, Vector2 max)
|
|
{
|
|
rt.anchorMin = min;
|
|
rt.anchorMax = max;
|
|
rt.offsetMin = Vector2.zero;
|
|
rt.offsetMax = Vector2.zero;
|
|
}
|
|
|
|
void Stretch(RectTransform rt)
|
|
{
|
|
rt.anchorMin = Vector2.zero;
|
|
rt.anchorMax = Vector2.one;
|
|
rt.offsetMin = Vector2.zero;
|
|
rt.offsetMax = Vector2.zero;
|
|
}
|
|
}
|