400 lines
18 KiB
C#
400 lines
18 KiB
C#
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
|
||
/// <summary>
|
||
/// 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
|
||
{
|
||
// ── 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()
|
||
{
|
||
// Pre-populate from GameManager defaults
|
||
var gm = GameManager.Instance;
|
||
if (gm != null)
|
||
{
|
||
_radius = (float)gm.pendingRadius;
|
||
_impostors = gm.pendingImpostorCount;
|
||
_tasks = gm.pendingTaskCount;
|
||
_playerName = gm.displayName ?? "";
|
||
}
|
||
|
||
var canvasGO = GameObject.Find("Canvas");
|
||
if (canvasGO == null)
|
||
{
|
||
Debug.LogError("[HostLobbyUI] No Canvas found!");
|
||
return;
|
||
}
|
||
|
||
// 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>());
|
||
}
|
||
|
||
// ── 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) { SetStatus("GameManager not found!", Color.red); return; }
|
||
|
||
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;
|
||
}
|
||
}
|