Zabiju je 2. Epicky thriller od tvurce Zabiju je
This commit is contained in:
42
Assets/Scripts/GameState.cs
Normal file
42
Assets/Scripts/GameState.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using GeoSus.Client;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Single source of truth for all in-game state on the client.
|
||||
/// Updated exclusively by GameManager_Network; read by GameManager_UI.
|
||||
/// </summary>
|
||||
public class GameState
|
||||
{
|
||||
// ── Phase / Role ──────────────────────────────────────────────────────────
|
||||
public GamePhase Phase { get; set; } = GamePhase.Lobby;
|
||||
public PlayerRole? MyRole { get; set; }
|
||||
public bool IsDead { get; set; }
|
||||
|
||||
// ── Tasks ─────────────────────────────────────────────────────────────────
|
||||
public List<GameTask> MyTasks { get; set; } = new List<GameTask>();
|
||||
public HashSet<string> MyCompletedTaskIds { get; set; } = new HashSet<string>();
|
||||
public int TotalCompleted { get; set; }
|
||||
public int TotalRequired { get; set; }
|
||||
|
||||
// ── Players ───────────────────────────────────────────────────────────────
|
||||
public List<PlayerInfo> Players { get; set; } = new List<PlayerInfo>();
|
||||
|
||||
// ── Meeting ───────────────────────────────────────────────────────────────
|
||||
public MeetingStartedPayload ActiveMeeting { get; set; }
|
||||
public VotingClosedPayload LastVoteResult { get; set; }
|
||||
public HashSet<string> VotedPlayerIds { get; set; } = new HashSet<string>();
|
||||
|
||||
// ── Sabotage ──────────────────────────────────────────────────────────────
|
||||
public SabotageStartedPayload ActiveSabotage { get; set; }
|
||||
|
||||
// ── End game ──────────────────────────────────────────────────────────────
|
||||
public GameEndedPayload GameEndData { get; set; }
|
||||
|
||||
// ── Kill cooldown (tracked by GameManager, reflected here for UI) ─────────
|
||||
public float KillCooldownRemaining { get; set; }
|
||||
|
||||
// ── Notification (toast) ─────────────────────────────────────────────────
|
||||
public string ToastMessage { get; set; }
|
||||
public float ToastExpiry { get; set; } // UnityEngine.Time.time
|
||||
}
|
||||
|
||||
2
Assets/Scripts/GameState.cs.meta
Normal file
2
Assets/Scripts/GameState.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82b3963a05498b68baf483476d0d81f4
|
||||
@@ -3,63 +3,397 @@ using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
/// <summary>
|
||||
/// Attach to a manager GameObject in host lobby.unity.
|
||||
/// Reads radius from the "radius" slider/input and triggers CreateLobby.
|
||||
/// Also wires the "vytvořit" button.
|
||||
/// Attach to any manager GameObject in the create-lobby scene.
|
||||
/// On Start it nukes all Art placeholder children from the Canvas and builds
|
||||
/// a complete mobile-portrait lobby-configuration screen entirely in code.
|
||||
/// </summary>
|
||||
public class HostLobbyUI : MonoBehaviour
|
||||
{
|
||||
[Header("Optional refs (auto-found by name if null)")]
|
||||
public Slider radiusSlider;
|
||||
public TMP_InputField radiusInput;
|
||||
public Button createButton;
|
||||
// ── Colours ───────────────────────────────────────────────────────────────
|
||||
static Color H(string hex) { ColorUtility.TryParseHtmlString(hex, out var c); return c; }
|
||||
static readonly Color C_BG = H("#0D0F1A");
|
||||
static readonly Color C_HDR = H("#141927");
|
||||
static readonly Color C_CARD = H("#111525");
|
||||
static readonly Color C_BORDER = H("#1E2540");
|
||||
static readonly Color C_ACCENT = H("#3399FF");
|
||||
static readonly Color C_GREEN = H("#2DB84B");
|
||||
static readonly Color C_MUTED = new Color(0.47f, 0.53f, 0.67f);
|
||||
static readonly Color C_WHITE = Color.white;
|
||||
static readonly Color C_INPUT = H("#0A0D1A");
|
||||
|
||||
// ── Live values ───────────────────────────────────────────────────────────
|
||||
private float _radius = 500f;
|
||||
private int _impostors = 1;
|
||||
private int _tasks = 5;
|
||||
private string _playerName = "";
|
||||
|
||||
// ── UI refs ───────────────────────────────────────────────────────────────
|
||||
private TMP_Text _radiusValueLabel;
|
||||
private TMP_Text _impostorValueLabel;
|
||||
private TMP_Text _taskValueLabel;
|
||||
private TMP_Text _statusText;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (radiusSlider == null)
|
||||
// Pre-populate from GameManager defaults
|
||||
var gm = GameManager.Instance;
|
||||
if (gm != null)
|
||||
{
|
||||
var go = GameObject.Find("radius");
|
||||
if (go != null) radiusSlider = go.GetComponent<Slider>();
|
||||
}
|
||||
if (radiusInput == null)
|
||||
{
|
||||
var go = GameObject.Find("radius");
|
||||
if (go != null) radiusInput = go.GetComponent<TMP_InputField>();
|
||||
_radius = (float)gm.pendingRadius;
|
||||
_impostors = gm.pendingImpostorCount;
|
||||
_tasks = gm.pendingTaskCount;
|
||||
_playerName = gm.displayName ?? "";
|
||||
}
|
||||
|
||||
if (createButton == null)
|
||||
var canvasGO = GameObject.Find("Canvas");
|
||||
if (canvasGO == null)
|
||||
{
|
||||
// Try all name variants used by the Art team
|
||||
var go = GameObject.Find("stvo\u0159it") // stvořit
|
||||
?? GameObject.Find("stvorit")
|
||||
?? GameObject.Find("vytvo\u0159it") // vytvořit
|
||||
?? GameObject.Find("vytvorit");
|
||||
if (go != null)
|
||||
{
|
||||
createButton = go.GetComponent<Button>();
|
||||
// Disable the Art team's direct scene-changer so only our
|
||||
// wired OnCreateClicked fires (navigation is handled by
|
||||
// HandleCreateLobbyResponse after the server confirms).
|
||||
var sceneChanger = go.GetComponent<CudlikZmenaSceny>();
|
||||
if (sceneChanger != null) sceneChanger.enabled = false;
|
||||
}
|
||||
Debug.LogError("[HostLobbyUI] No Canvas found!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (createButton != null)
|
||||
createButton.onClick.AddListener(OnCreateClicked);
|
||||
// Nuke all Art placeholder children
|
||||
var kill = new System.Collections.Generic.List<GameObject>();
|
||||
foreach (Transform child in canvasGO.transform)
|
||||
kill.Add(child.gameObject);
|
||||
foreach (var go in kill)
|
||||
DestroyImmediate(go);
|
||||
|
||||
// Disable scene-changer components that bypass our logic
|
||||
foreach (var sc in canvasGO.GetComponentsInChildren<CudlikZmenaSceny>(true))
|
||||
sc.enabled = false;
|
||||
|
||||
Build(canvasGO.GetComponent<RectTransform>() ?? canvasGO.AddComponent<RectTransform>());
|
||||
}
|
||||
|
||||
private void OnCreateClicked()
|
||||
// ── Builder ───────────────────────────────────────────────────────────────
|
||||
void Build(RectTransform root)
|
||||
{
|
||||
// Full-screen background
|
||||
var bg = MakeRT("BG", root);
|
||||
Stretch(bg);
|
||||
bg.gameObject.AddComponent<CanvasRenderer>();
|
||||
Img(bg, C_BG);
|
||||
|
||||
// ── Header bar ───────────────────────────────────────────────────────
|
||||
var hdr = MakeRT("Header", root);
|
||||
Anchor(hdr, new Vector2(0,1), new Vector2(1,1), new Vector2(0,-80), new Vector2(0,0));
|
||||
Img(hdr, C_HDR);
|
||||
var hdrTxt = Txt("Create Lobby", hdr, 28, C_WHITE, TextAlignmentOptions.Center, bold: true);
|
||||
Stretch(hdrTxt.rectTransform);
|
||||
|
||||
// ── Scroll body ───────────────────────────────────────────────────────
|
||||
var scroll = MakeRT("Scroll", root);
|
||||
Anchor(scroll, new Vector2(0,0), new Vector2(1,1), new Vector2(0,80), new Vector2(0,-80));
|
||||
var sf = scroll.gameObject.AddComponent<ScrollRect>();
|
||||
sf.horizontal = false;
|
||||
Img(scroll, new Color(0,0,0,0));
|
||||
|
||||
var content = MakeRT("Content", scroll);
|
||||
content.anchorMin = new Vector2(0,1);
|
||||
content.anchorMax = new Vector2(1,1);
|
||||
content.pivot = new Vector2(0.5f, 1);
|
||||
content.offsetMin = Vector2.zero;
|
||||
content.offsetMax = Vector2.zero;
|
||||
var vlg = content.gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
vlg.padding = new RectOffset(16, 16, 12, 12);
|
||||
vlg.spacing = 12;
|
||||
vlg.childForceExpandWidth = true;
|
||||
vlg.childForceExpandHeight = false;
|
||||
var csf = content.gameObject.AddComponent<ContentSizeFitter>();
|
||||
csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
sf.content = content;
|
||||
sf.viewport = scroll;
|
||||
|
||||
// ── Player name card ─────────────────────────────────────────────────
|
||||
AddSectionLabel("PLAYER NAME", content);
|
||||
var nameCard = AddCard(content, 70);
|
||||
var nameInput = MakeInputField("Your name", nameCard, _playerName);
|
||||
Stretch(nameInput.GetComponent<RectTransform>());
|
||||
nameInput.onEndEdit.AddListener(v =>
|
||||
{
|
||||
_playerName = v.Trim();
|
||||
var gm2 = GameManager.Instance;
|
||||
if (gm2 != null) gm2.displayName = _playerName;
|
||||
PlayerPrefs.SetString("PlayerName", _playerName);
|
||||
});
|
||||
|
||||
// ── Radius card ───────────────────────────────────────────────────────
|
||||
AddSectionLabel("PLAY AREA RADIUS", content);
|
||||
var radCard = AddCard(content, 110);
|
||||
var radRT = radCard.GetComponent<RectTransform>();
|
||||
|
||||
var radLbl = MakeRT("RadLbl", radRT);
|
||||
Anchor(radLbl, new Vector2(0,1), new Vector2(1,1), new Vector2(12,-40), new Vector2(-12,-4));
|
||||
var rt = radLbl.gameObject;
|
||||
var radTmp = rt.AddComponent<TextMeshProUGUI>();
|
||||
radTmp.text = RadiusLabel(_radius);
|
||||
radTmp.fontSize = 20; radTmp.color = C_ACCENT;
|
||||
radTmp.alignment = TextAlignmentOptions.Center;
|
||||
_radiusValueLabel = radTmp;
|
||||
|
||||
var slider = MakeRT("RadSlider", radRT).gameObject.AddComponent<Slider>();
|
||||
var srt = slider.GetComponent<RectTransform>();
|
||||
Anchor(srt, new Vector2(0,0), new Vector2(1,0), new Vector2(16, 14), new Vector2(-16, 44));
|
||||
slider.minValue = 100; slider.maxValue = 2000; slider.value = _radius;
|
||||
BuildSliderVisuals(slider, C_ACCENT);
|
||||
slider.onValueChanged.AddListener(v =>
|
||||
{
|
||||
_radius = v;
|
||||
_radiusValueLabel.text = RadiusLabel(v);
|
||||
var gm2 = GameManager.Instance;
|
||||
if (gm2 != null) gm2.pendingRadius = v;
|
||||
});
|
||||
|
||||
// ── Impostor count card ───────────────────────────────────────────────
|
||||
AddSectionLabel("IMPOSTORS", content);
|
||||
var impCard = AddCard(content, 80);
|
||||
BuildStepper(impCard.GetComponent<RectTransform>(), ref _impostors, 1, 4,
|
||||
v => { var gm2 = GameManager.Instance; if (gm2 != null) gm2.pendingImpostorCount = v; },
|
||||
out _impostorValueLabel);
|
||||
|
||||
// ── Task count card ───────────────────────────────────────────────────
|
||||
AddSectionLabel("TASKS PER PLAYER", content);
|
||||
var taskCard = AddCard(content, 80);
|
||||
BuildStepper(taskCard.GetComponent<RectTransform>(), ref _tasks, 1, 15,
|
||||
v => { var gm2 = GameManager.Instance; if (gm2 != null) gm2.pendingTaskCount = v; },
|
||||
out _taskValueLabel);
|
||||
|
||||
// ── Status text ───────────────────────────────────────────────────────
|
||||
var statusCard = AddCard(content, 40);
|
||||
_statusText = Txt("", statusCard.GetComponent<RectTransform>(), 16, C_MUTED, TextAlignmentOptions.Center);
|
||||
Stretch(_statusText.rectTransform);
|
||||
|
||||
// ── Footer create button ──────────────────────────────────────────────
|
||||
var footer = MakeRT("Footer", root);
|
||||
Anchor(footer, new Vector2(0,0), new Vector2(1,0), new Vector2(0,0), new Vector2(0,80));
|
||||
Img(footer, C_HDR);
|
||||
var btnRT = MakeRT("CreateBtn", footer);
|
||||
Anchor(btnRT, new Vector2(0.1f,0.15f), new Vector2(0.9f,0.85f), Vector2.zero, Vector2.zero);
|
||||
var btn = btnRT.gameObject.AddComponent<Button>();
|
||||
var btnImg = btnRT.gameObject.AddComponent<Image>();
|
||||
btnImg.color = C_GREEN;
|
||||
btn.targetGraphic = btnImg;
|
||||
var cb = btn.colors; cb.pressedColor = C_GREEN * 0.7f; btn.colors = cb;
|
||||
var btnTxt = Txt("CREATE LOBBY", btnRT, 22, C_WHITE, TextAlignmentOptions.Center, bold: true);
|
||||
Stretch(btnTxt.rectTransform);
|
||||
btn.onClick.AddListener(OnCreateClicked);
|
||||
|
||||
// ── Back button ───────────────────────────────────────────────────────
|
||||
var backRT = MakeRT("BackBtn", root);
|
||||
Anchor(backRT, new Vector2(0,1), new Vector2(0,1), new Vector2(8,-72), new Vector2(72,-8));
|
||||
var backBtn = backRT.gameObject.AddComponent<Button>();
|
||||
var backImg = backRT.gameObject.AddComponent<Image>();
|
||||
backImg.color = new Color(1,1,1,0);
|
||||
backBtn.targetGraphic = backImg;
|
||||
var backTxt = Txt("←", backRT, 28, C_MUTED, TextAlignmentOptions.Center);
|
||||
Stretch(backTxt.rectTransform);
|
||||
backBtn.onClick.AddListener(() => UnityEngine.SceneManagement.SceneManager.LoadScene("main menu asi idk lol"));
|
||||
}
|
||||
|
||||
// ── Actions ───────────────────────────────────────────────────────────────
|
||||
void OnCreateClicked()
|
||||
{
|
||||
var gm = GameManager.Instance;
|
||||
if (gm == null) return;
|
||||
if (gm == null) { SetStatus("GameManager not found!", Color.red); return; }
|
||||
|
||||
// Read radius from slider or input field
|
||||
if (radiusSlider != null)
|
||||
gm.pendingRadius = radiusSlider.value;
|
||||
else if (radiusInput != null && float.TryParse(radiusInput.text, out float r))
|
||||
gm.pendingRadius = r;
|
||||
if (string.IsNullOrWhiteSpace(_playerName))
|
||||
{
|
||||
SetStatus("Enter a player name first.", Color.yellow);
|
||||
return;
|
||||
}
|
||||
|
||||
gm.pendingRadius = _radius;
|
||||
gm.pendingImpostorCount = _impostors;
|
||||
gm.pendingTaskCount = _tasks;
|
||||
gm.displayName = _playerName;
|
||||
|
||||
SetStatus("Connecting…", C_MUTED);
|
||||
gm.CreateLobbyButton();
|
||||
}
|
||||
|
||||
void SetStatus(string msg, Color col)
|
||||
{
|
||||
if (_statusText == null) return;
|
||||
_statusText.text = msg;
|
||||
_statusText.color = col;
|
||||
}
|
||||
|
||||
// ── UI helpers ────────────────────────────────────────────────────────────
|
||||
static string RadiusLabel(float v) => $"{Mathf.RoundToInt(v)} m";
|
||||
|
||||
void BuildStepper(RectTransform parent, ref int val, int min, int max,
|
||||
System.Action<int> onChange, out TMP_Text label)
|
||||
{
|
||||
int captured = val; // local copy for closures
|
||||
|
||||
// minus
|
||||
var minusRT = MakeRT("Minus", parent);
|
||||
Anchor(minusRT, new Vector2(0,0), new Vector2(0,1), new Vector2(8,8), new Vector2(72,-8));
|
||||
var minusBtn = minusRT.gameObject.AddComponent<Button>();
|
||||
var minusImg = minusRT.gameObject.AddComponent<Image>(); minusImg.color = C_BORDER;
|
||||
minusBtn.targetGraphic = minusImg;
|
||||
var minusTxt = Txt("−", minusRT, 28, C_WHITE, TextAlignmentOptions.Center);
|
||||
Stretch(minusTxt.rectTransform);
|
||||
|
||||
// label
|
||||
var lblRT = MakeRT("Val", parent);
|
||||
Stretch(lblRT);
|
||||
var lbl = Txt(captured.ToString(), lblRT, 26, C_WHITE, TextAlignmentOptions.Center, bold: true);
|
||||
label = lbl;
|
||||
|
||||
// plus
|
||||
var plusRT = MakeRT("Plus", parent);
|
||||
Anchor(plusRT, new Vector2(1,0), new Vector2(1,1), new Vector2(-72,8), new Vector2(-8,-8));
|
||||
var plusBtn = plusRT.gameObject.AddComponent<Button>();
|
||||
var plusImg = plusRT.gameObject.AddComponent<Image>(); plusImg.color = C_ACCENT;
|
||||
plusBtn.targetGraphic = plusImg;
|
||||
var plusTxt = Txt("+", plusRT, 28, C_WHITE, TextAlignmentOptions.Center);
|
||||
Stretch(plusTxt.rectTransform);
|
||||
|
||||
minusBtn.onClick.AddListener(() =>
|
||||
{
|
||||
captured = Mathf.Max(min, captured - 1);
|
||||
lbl.text = captured.ToString();
|
||||
onChange?.Invoke(captured);
|
||||
});
|
||||
plusBtn.onClick.AddListener(() =>
|
||||
{
|
||||
captured = Mathf.Min(max, captured + 1);
|
||||
lbl.text = captured.ToString();
|
||||
onChange?.Invoke(captured);
|
||||
});
|
||||
|
||||
// write back initial
|
||||
val = captured;
|
||||
}
|
||||
|
||||
static void BuildSliderVisuals(Slider s, Color fillColor)
|
||||
{
|
||||
// Background track
|
||||
var bgRT = MakeRT("Background", s.GetComponent<RectTransform>());
|
||||
Stretch(bgRT);
|
||||
bgRT.gameObject.AddComponent<CanvasRenderer>();
|
||||
var bgImg = bgRT.gameObject.AddComponent<Image>(); bgImg.color = new Color(0.15f,0.18f,0.28f);
|
||||
|
||||
// Fill area
|
||||
var fillArea = MakeRT("Fill Area", s.GetComponent<RectTransform>());
|
||||
Anchor(fillArea, new Vector2(0,0.25f), new Vector2(1,0.75f), new Vector2(5,0), new Vector2(-5,0));
|
||||
var fillRT = MakeRT("Fill", fillArea);
|
||||
fillRT.anchorMin = Vector2.zero; fillRT.anchorMax = Vector2.one;
|
||||
fillRT.offsetMin = Vector2.zero; fillRT.offsetMax = Vector2.zero;
|
||||
fillRT.gameObject.AddComponent<CanvasRenderer>();
|
||||
var fillImg = fillRT.gameObject.AddComponent<Image>(); fillImg.color = fillColor;
|
||||
s.fillRect = fillRT;
|
||||
|
||||
// Handle area
|
||||
var handleArea = MakeRT("Handle Slide Area", s.GetComponent<RectTransform>());
|
||||
Stretch(handleArea);
|
||||
var handleRT = MakeRT("Handle", handleArea);
|
||||
handleRT.sizeDelta = new Vector2(28, 28);
|
||||
handleRT.anchorMin = new Vector2(0, 0.5f); handleRT.anchorMax = new Vector2(0, 0.5f);
|
||||
handleRT.anchoredPosition = Vector2.zero;
|
||||
handleRT.gameObject.AddComponent<CanvasRenderer>();
|
||||
var hImg = handleRT.gameObject.AddComponent<Image>(); hImg.color = Color.white;
|
||||
s.handleRect = handleRT;
|
||||
s.targetGraphic = hImg;
|
||||
}
|
||||
|
||||
static void AddSectionLabel(string text, RectTransform parent)
|
||||
{
|
||||
var rt = MakeRT("Lbl_" + text, parent);
|
||||
var le = rt.gameObject.AddComponent<LayoutElement>(); le.preferredHeight = 28;
|
||||
var tmp = rt.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = text; tmp.fontSize = 13; tmp.color = new Color(0.47f,0.53f,0.67f);
|
||||
tmp.fontStyle = FontStyles.Bold; tmp.alignment = TextAlignmentOptions.Left;
|
||||
}
|
||||
|
||||
static RectTransform AddCard(RectTransform parent, float height)
|
||||
{
|
||||
var rt = MakeRT("Card", parent);
|
||||
var le = rt.gameObject.AddComponent<LayoutElement>(); le.preferredHeight = height;
|
||||
var img = rt.gameObject.AddComponent<Image>(); img.color = H("#111525");
|
||||
// border via outline
|
||||
var outline = rt.gameObject.AddComponent<Outline>();
|
||||
outline.effectColor = H("#1E2540");
|
||||
outline.effectDistance = new Vector2(1, -1);
|
||||
return rt;
|
||||
}
|
||||
|
||||
static TMP_InputField MakeInputField(string placeholder, RectTransform parent, string initialValue)
|
||||
{
|
||||
var go = new GameObject("InputField");
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
|
||||
var img = go.AddComponent<Image>(); img.color = H("#0A0D1A");
|
||||
var field = go.AddComponent<TMP_InputField>();
|
||||
|
||||
var textArea = MakeRT("Text Area", rt);
|
||||
Stretch(textArea);
|
||||
textArea.offsetMin = new Vector2(10, 4);
|
||||
textArea.offsetMax = new Vector2(-10, -4);
|
||||
var areaImg = textArea.gameObject.AddComponent<Image>(); areaImg.color = new Color(0,0,0,0);
|
||||
var mask = textArea.gameObject.AddComponent<RectMask2D>();
|
||||
|
||||
var phRT = MakeRT("Placeholder", textArea);
|
||||
Stretch(phRT);
|
||||
var phTmp = phRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
phTmp.text = placeholder; phTmp.fontSize = 20;
|
||||
phTmp.color = new Color(0.47f, 0.53f, 0.67f);
|
||||
phTmp.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
|
||||
var txtRT = MakeRT("Text", textArea);
|
||||
Stretch(txtRT);
|
||||
var txtTmp = txtRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
txtTmp.fontSize = 20; txtTmp.color = Color.white;
|
||||
txtTmp.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
|
||||
field.textViewport = textArea;
|
||||
field.textComponent = txtTmp;
|
||||
field.placeholder = phTmp;
|
||||
field.text = initialValue;
|
||||
field.targetGraphic = img;
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
static RectTransform MakeRT(string name, RectTransform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
return rt;
|
||||
}
|
||||
|
||||
static void Stretch(RectTransform rt)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
static void Anchor(RectTransform rt, Vector2 aMin, Vector2 aMax, Vector2 offMin, Vector2 offMax)
|
||||
{
|
||||
rt.anchorMin = aMin; rt.anchorMax = aMax;
|
||||
rt.offsetMin = offMin; rt.offsetMax = offMax;
|
||||
}
|
||||
|
||||
static Image Img(RectTransform rt, Color c)
|
||||
{
|
||||
rt.gameObject.AddComponent<CanvasRenderer>();
|
||||
var img = rt.gameObject.AddComponent<Image>(); img.color = c; return img;
|
||||
}
|
||||
|
||||
static TextMeshProUGUI Txt(string text, RectTransform parent, float size, Color color,
|
||||
TextAlignmentOptions align, bool bold = false)
|
||||
{
|
||||
var rt = MakeRT("T_" + text, parent);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,276 +1,291 @@
|
||||
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() explicitly from GameManager.OnSceneLoaded BEFORE BindClientScene(),
|
||||
/// so that GameManager_UI can find the newly created elements by name.
|
||||
/// Call BuildNow() from GameManager.OnSceneLoaded BEFORE BindClientScene so GameManager_UI
|
||||
/// can locate named children.
|
||||
///
|
||||
/// 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()
|
||||
/// 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
|
||||
/// MeetingPlayerList – TMP player list (text fallback)
|
||||
/// 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
|
||||
/// KillCooldown – TMP kill-cooldown label
|
||||
/// TaskList – TMP task name list
|
||||
/// TaskProgress – TMP global task progress
|
||||
/// Toast – TMP toast notification
|
||||
/// </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);
|
||||
// ── Palette ───────────────────────────────────────────────────────────────
|
||||
static Color H(string hex) { ColorUtility.TryParseHtmlString(hex, out var c); return c; }
|
||||
static readonly Color C_BG = H("#0D0F1A");
|
||||
static readonly Color C_BAR = H("#141927");
|
||||
static readonly Color C_ACCENT = H("#3399FF");
|
||||
static readonly Color C_GREEN = H("#2DB84B");
|
||||
static readonly Color C_RED = H("#C43232");
|
||||
static readonly Color C_ORANGE = H("#F08C1A");
|
||||
static readonly Color C_YELLOW = H("#FFB800");
|
||||
static readonly Color C_MUTED = new Color(0.47f, 0.53f, 0.67f);
|
||||
static readonly Color C_ROW_A = H("#1A2035");
|
||||
static readonly Color C_ROW_B = H("#161C2E");
|
||||
|
||||
private bool _built;
|
||||
|
||||
// ── Entry points ──────────────────────────────────────────────────────────
|
||||
public void BuildNow() { if (!_built) { _built = true; Build(); } }
|
||||
void Start() { if (!_built) Build(); }
|
||||
|
||||
/// <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);
|
||||
BuildToast(rt);
|
||||
}
|
||||
|
||||
// ── Section builders ──────────────────────────────────────────────────────
|
||||
|
||||
// ── Top bar ───────────────────────────────────────────────────────────────
|
||||
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);
|
||||
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 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);
|
||||
// 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;
|
||||
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)
|
||||
{
|
||||
// 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);
|
||||
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));
|
||||
|
||||
// "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;
|
||||
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);
|
||||
|
||||
// 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;
|
||||
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 = 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;
|
||||
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 = 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);
|
||||
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);
|
||||
|
||||
// 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 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>();
|
||||
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;
|
||||
TxtChild(recBtn,"⊙",42,Color.white,TextAlignmentOptions.Center,bold:true);
|
||||
}
|
||||
|
||||
// ── Action button (DIRECT child so Transform.Find works) ─────────────────
|
||||
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 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>();
|
||||
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);
|
||||
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;
|
||||
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
|
||||
btn.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// ── Sabotage panel (top strip) ────────────────────────────────────────────
|
||||
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 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 = AddChild("SabotageTimer", panel);
|
||||
var timer = Child("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;
|
||||
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) ───────────────────────────────────
|
||||
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 panel = Child("MeetingPanel", parent);
|
||||
Stretch(panel);
|
||||
Img(panel, new Color(0.04f,0.05f,0.14f,0.97f));
|
||||
|
||||
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;
|
||||
// Header
|
||||
var hdr = Child("MeetingHeader", panel);
|
||||
Anchor(hdr, new Vector2(0,0.86f), 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;
|
||||
|
||||
// Scrollable player vote list
|
||||
var scrollArea = Child("_MeetingScroll", panel);
|
||||
Anchor(scrollArea, new Vector2(0,0.22f), new Vector2(1,0.86f), 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.22f), new Vector2(1,0.86f), 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
|
||||
|
||||
// Skip button
|
||||
var skip = Child("SkipButton", panel);
|
||||
Anchor(skip, new Vector2(0.05f,0.04f), new Vector2(0.95f,0.18f), 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 (hidden until voting closes)
|
||||
var resultPanel = Child("VoteResultPanel", panel);
|
||||
Anchor(resultPanel, new Vector2(0,0.04f), new Vector2(1,0.22f), 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)
|
||||
{
|
||||
// DIRECT child, full-screen overlay
|
||||
var panel = AddChild("GameEndPanel", parent);
|
||||
var panel = Child("GameEndPanel", parent);
|
||||
Stretch(panel);
|
||||
AddImage(panel.gameObject, new Color(0f, 0f, 0f, 0.85f));
|
||||
Img(panel, new Color(0,0,0,0.90f));
|
||||
|
||||
var txt = AddChild("GameEndText", panel);
|
||||
Stretch(txt);
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
|
||||
// ── 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 AddChild(string name, RectTransform parent)
|
||||
RectTransform Child(string name, RectTransform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
@@ -279,40 +294,35 @@ public class InGameHUDBuilder : MonoBehaviour
|
||||
return rt;
|
||||
}
|
||||
|
||||
Image AddImage(GameObject go, Color color)
|
||||
Image Img(RectTransform rt, Color c)
|
||||
{
|
||||
var img = go.AddComponent<Image>();
|
||||
img.color = color;
|
||||
var img = rt.gameObject.AddComponent<Image>();
|
||||
img.color = c;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user