Zabiju je
This commit is contained in:
417
Assets/Scripts/LobbyDisplayUI.cs
Normal file
417
Assets/Scripts/LobbyDisplayUI.cs
Normal file
@@ -0,0 +1,417 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using GeoSus.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Attach to any manager GameObject in create.unity or join loading.unity.
|
||||
/// On Start(), removes all placeholder Art elements from the Canvas and builds
|
||||
/// a proper mobile-portrait lobby screen entirely in code.
|
||||
/// </summary>
|
||||
public class LobbyDisplayUI : MonoBehaviour
|
||||
{
|
||||
// ── Static hub so GameManager_UI can push state updates ──────────────────
|
||||
private static readonly HashSet<LobbyDisplayUI> _all = new HashSet<LobbyDisplayUI>();
|
||||
public static void RefreshAll(LobbyState state)
|
||||
{
|
||||
foreach (var ui in _all) ui._pending = state;
|
||||
}
|
||||
|
||||
// ── Built UI references ───────────────────────────────────────────────────
|
||||
private TMP_Text _codeText;
|
||||
private TMP_Text _countText;
|
||||
private Transform _listContent;
|
||||
private TMP_Text _statusText;
|
||||
private GameObject _startFooter;
|
||||
private GameObject _waitFooter;
|
||||
|
||||
private readonly List<GameObject> _rows = new List<GameObject>();
|
||||
private LobbyState _pending;
|
||||
|
||||
// ── Colour 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_HDR = H("#141927");
|
||||
static readonly Color C_SUBBG = H("#0F1221");
|
||||
static readonly Color C_ROW_A = H("#1A2035");
|
||||
static readonly Color C_ROW_B = H("#161C2E");
|
||||
static readonly Color C_DIVIDER = H("#252A3F");
|
||||
static readonly Color C_ACCENT = H("#3399FF");
|
||||
static readonly Color C_GOLD = H("#FFB800");
|
||||
static readonly Color C_GREEN = H("#2DB84B");
|
||||
static readonly Color C_RED = H("#C43232");
|
||||
static readonly Color C_MUTED = new Color(0.47f, 0.53f, 0.67f);
|
||||
static readonly Color C_WHITE = Color.white;
|
||||
static readonly Color C_SOFT = new Color(0.73f, 0.80f, 0.88f);
|
||||
|
||||
void OnEnable() => _all.Add(this);
|
||||
void OnDisable() => _all.Remove(this);
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
void Start()
|
||||
{
|
||||
var canvasGO = GameObject.Find("Canvas");
|
||||
if (canvasGO == null)
|
||||
{
|
||||
Debug.LogError("[LobbyDisplayUI] No Canvas found in scene!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove all placeholder Art children immediately (before we build)
|
||||
var kill = new List<GameObject>();
|
||||
foreach (Transform child in canvasGO.transform)
|
||||
kill.Add(child.gameObject);
|
||||
foreach (var go in kill)
|
||||
DestroyImmediate(go);
|
||||
|
||||
Build(canvasGO.transform);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
var gm = GameManager.Instance;
|
||||
if (gm?.gameClient?.CurrentLobbyState != null)
|
||||
_pending = gm.gameClient.CurrentLobbyState;
|
||||
|
||||
if (_pending != null && _listContent != null)
|
||||
{
|
||||
Refresh(_pending);
|
||||
_pending = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Full UI construction ──────────────────────────────────────────────────
|
||||
void Build(Transform canvasRoot)
|
||||
{
|
||||
const float HDR_H = 250f;
|
||||
const float SUB_H = 88f;
|
||||
const float FOOT_H = 180f;
|
||||
const float BTN_W = 200f;
|
||||
|
||||
// Fullscreen dark overlay
|
||||
var root = RT("Root", canvasRoot);
|
||||
Stretch(root);
|
||||
Img(root, C_BG);
|
||||
|
||||
// ─── Header bar ───────────────────────────────────────────────────────
|
||||
var header = RT("Header", root);
|
||||
PinTop(header, HDR_H);
|
||||
Img(header, C_HDR);
|
||||
|
||||
// Back (✕) button — left side of header
|
||||
var backBtn = RT("BackBtn", header);
|
||||
backBtn.anchorMin = new Vector2(0f, 0f);
|
||||
backBtn.anchorMax = new Vector2(0f, 1f);
|
||||
backBtn.pivot = new Vector2(0f, 0.5f);
|
||||
backBtn.offsetMin = new Vector2(18f, 22f);
|
||||
backBtn.offsetMax = new Vector2(BTN_W + 18f, -22f);
|
||||
Img(backBtn, C_RED);
|
||||
Btn(backBtn, C_RED, () => GameManager.Instance?.LeaveLobbyButton());
|
||||
TxtChild(backBtn, "✕", 72, C_WHITE, TextAlignmentOptions.Center, bold: true);
|
||||
|
||||
// "LOBBY CODE" micro label — upper-center of header
|
||||
var codeLbl = RT("CodeLbl", header);
|
||||
codeLbl.anchorMin = new Vector2(0.14f, 0.52f);
|
||||
codeLbl.anchorMax = new Vector2(0.86f, 0.97f);
|
||||
codeLbl.offsetMin = codeLbl.offsetMax = Vector2.zero;
|
||||
TmpDirect(codeLbl, "LOBBY CODE", 28, C_MUTED, TextAlignmentOptions.Center, bold: true);
|
||||
|
||||
// Large code value — lower-center of header
|
||||
var codeValRT = RT("CodeVal", header);
|
||||
codeValRT.anchorMin = new Vector2(0.14f, 0.05f);
|
||||
codeValRT.anchorMax = new Vector2(0.86f, 0.52f);
|
||||
codeValRT.offsetMin = codeValRT.offsetMax = Vector2.zero;
|
||||
_codeText = TmpDirect(codeValRT, "------", 76, C_ACCENT, TextAlignmentOptions.Center, bold: true);
|
||||
|
||||
// Copy (⎘) button — right side of header
|
||||
var copyBtn = RT("CopyBtn", header);
|
||||
copyBtn.anchorMin = new Vector2(1f, 0f);
|
||||
copyBtn.anchorMax = new Vector2(1f, 1f);
|
||||
copyBtn.pivot = new Vector2(1f, 0.5f);
|
||||
copyBtn.offsetMin = new Vector2(-(BTN_W + 18f), 22f);
|
||||
copyBtn.offsetMax = new Vector2(-18f, -22f);
|
||||
Img(copyBtn, C_ACCENT);
|
||||
Btn(copyBtn, C_ACCENT, () =>
|
||||
{
|
||||
if (_codeText != null) GUIUtility.systemCopyBuffer = _codeText.text;
|
||||
});
|
||||
TxtChild(copyBtn, "⎘", 60, C_WHITE, TextAlignmentOptions.Center);
|
||||
|
||||
// ─── Player count subtitle bar ─────────────────────────────────────────
|
||||
var subBar = RT("CountBar", root);
|
||||
PinBelowTop(subBar, HDR_H, SUB_H);
|
||||
Img(subBar, C_SUBBG);
|
||||
_countText = TxtChild(subBar, "0 players in lobby", 34, C_MUTED, TextAlignmentOptions.Center);
|
||||
|
||||
// ─── Scrollable player list ────────────────────────────────────────────
|
||||
var scrollArea = RT("PlayerScroll", root);
|
||||
Fill(scrollArea, HDR_H + SUB_H, FOOT_H);
|
||||
BuildScroll(scrollArea);
|
||||
|
||||
// ─── Footer: START GAME (host) or waiting text (others) ───────────────
|
||||
_startFooter = new GameObject("StartFooter");
|
||||
var sfRT = _startFooter.AddComponent<RectTransform>();
|
||||
sfRT.SetParent(root, false);
|
||||
sfRT.localScale = Vector3.one;
|
||||
PinBottom(sfRT, FOOT_H);
|
||||
Img(sfRT, C_SUBBG);
|
||||
|
||||
var startBtnRT = RT("StartBtn", sfRT);
|
||||
Fill(startBtnRT, 20f, 20f, 24f, 24f);
|
||||
Img(startBtnRT, C_GREEN);
|
||||
Btn(startBtnRT, C_GREEN, () => GameManager.Instance?.StartGameButton());
|
||||
TxtChild(startBtnRT, "▶ START GAME", 54, C_WHITE, TextAlignmentOptions.Center, bold: true);
|
||||
_startFooter.SetActive(false);
|
||||
|
||||
_waitFooter = new GameObject("WaitFooter");
|
||||
var wfRT = _waitFooter.AddComponent<RectTransform>();
|
||||
wfRT.SetParent(root, false);
|
||||
wfRT.localScale = Vector3.one;
|
||||
PinBottom(wfRT, FOOT_H);
|
||||
Img(wfRT, C_SUBBG);
|
||||
_statusText = TxtChild(wfRT, "⌛ Waiting for host to start...", 38, C_MUTED,
|
||||
TextAlignmentOptions.Center, italic: true);
|
||||
_waitFooter.SetActive(true);
|
||||
}
|
||||
|
||||
void BuildScroll(RectTransform rt)
|
||||
{
|
||||
var sr = rt.gameObject.AddComponent<ScrollRect>();
|
||||
|
||||
var viewport = RT("Viewport", rt);
|
||||
Stretch(viewport);
|
||||
viewport.gameObject.AddComponent<RectMask2D>();
|
||||
|
||||
var content = RT("Content", viewport);
|
||||
content.anchorMin = new Vector2(0f, 1f);
|
||||
content.anchorMax = new Vector2(1f, 1f);
|
||||
content.pivot = new Vector2(0.5f, 1f);
|
||||
content.sizeDelta = new Vector2(0f, 0f);
|
||||
content.anchoredPosition = Vector2.zero;
|
||||
|
||||
var vlg = content.gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
vlg.childControlWidth = true;
|
||||
vlg.childControlHeight = false;
|
||||
vlg.childForceExpandWidth = true;
|
||||
vlg.childForceExpandHeight = false;
|
||||
vlg.spacing = 2f;
|
||||
vlg.padding = new RectOffset(0, 0, 0, 0);
|
||||
|
||||
var csf = content.gameObject.AddComponent<ContentSizeFitter>();
|
||||
csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
sr.viewport = viewport;
|
||||
sr.content = content;
|
||||
sr.horizontal = false;
|
||||
sr.vertical = true;
|
||||
sr.scrollSensitivity = 80f;
|
||||
sr.movementType = ScrollRect.MovementType.Elastic;
|
||||
sr.elasticity = 0.1f;
|
||||
|
||||
_listContent = content;
|
||||
}
|
||||
|
||||
// ── State refresh ─────────────────────────────────────────────────────────
|
||||
void Refresh(LobbyState state)
|
||||
{
|
||||
if (_codeText != null) _codeText.text = state.JoinCode ?? "------";
|
||||
|
||||
int n = state.Players.Count;
|
||||
if (_countText != null)
|
||||
_countText.text = $"{n} player{(n == 1 ? "" : "s")} in lobby";
|
||||
|
||||
var gm = GameManager.Instance;
|
||||
bool isHost = gm?.gameClient?.IsOwner ?? false;
|
||||
string myId = gm?.gameClient?.ClientUuid ?? "";
|
||||
|
||||
if (_startFooter != null) _startFooter.SetActive(isHost);
|
||||
if (_waitFooter != null) _waitFooter.SetActive(!isHost);
|
||||
|
||||
if (_statusText != null)
|
||||
_statusText.text = state.Phase == GamePhase.Loading
|
||||
? "⏳ Downloading map data..."
|
||||
: "⌛ Waiting for host to start...";
|
||||
|
||||
if (_listContent == null) return;
|
||||
|
||||
foreach (var row in _rows) Destroy(row);
|
||||
_rows.Clear();
|
||||
|
||||
for (int i = 0; i < state.Players.Count; i++)
|
||||
{
|
||||
var p = state.Players[i];
|
||||
bool me = p.ClientUuid == myId;
|
||||
var row = BuildRow(p.DisplayName ?? "???", me, p.IsOwner,
|
||||
i % 2 == 0 ? C_ROW_A : C_ROW_B);
|
||||
row.transform.SetParent(_listContent, false);
|
||||
_rows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
GameObject BuildRow(string playerName, bool isMe, bool isHostPlayer, Color bg)
|
||||
{
|
||||
const float ROW_H = 130f;
|
||||
var go = new GameObject("PlayerRow");
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.sizeDelta = new Vector2(0f, ROW_H);
|
||||
|
||||
var le = go.AddComponent<LayoutElement>();
|
||||
le.minHeight = ROW_H;
|
||||
le.preferredHeight = ROW_H;
|
||||
|
||||
Img(rt, bg);
|
||||
|
||||
// Bottom divider line
|
||||
var divRT = RT("Div", rt);
|
||||
divRT.anchorMin = new Vector2(0f, 0f);
|
||||
divRT.anchorMax = new Vector2(1f, 0f);
|
||||
divRT.pivot = new Vector2(0.5f, 0f);
|
||||
divRT.offsetMin = new Vector2(20f, 0f);
|
||||
divRT.offsetMax = new Vector2(-20f, 2f);
|
||||
Img(divRT, C_DIVIDER);
|
||||
|
||||
float nameLeft = 24f;
|
||||
|
||||
// Crown emoji for lobby host
|
||||
if (isHostPlayer)
|
||||
{
|
||||
var crownRT = RT("Crown", rt);
|
||||
crownRT.anchorMin = new Vector2(0f, 0.5f);
|
||||
crownRT.anchorMax = new Vector2(0f, 0.5f);
|
||||
crownRT.pivot = new Vector2(0f, 0.5f);
|
||||
crownRT.sizeDelta = new Vector2(90f, 90f);
|
||||
crownRT.anchoredPosition = new Vector2(18f, 0f);
|
||||
TmpDirect(crownRT, "👑", 52, C_GOLD, TextAlignmentOptions.Center);
|
||||
nameLeft = 118f;
|
||||
}
|
||||
|
||||
// Player name
|
||||
float nameMaxX = isMe ? 0.68f : 1f;
|
||||
var nameRT = RT("Name", rt);
|
||||
nameRT.anchorMin = new Vector2(0f, 0f);
|
||||
nameRT.anchorMax = new Vector2(nameMaxX, 1f);
|
||||
nameRT.offsetMin = new Vector2(nameLeft, 6f);
|
||||
nameRT.offsetMax = new Vector2(-10f, -6f);
|
||||
var nt = nameRT.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
nt.text = playerName;
|
||||
nt.fontSize = 48;
|
||||
nt.color = isMe ? C_WHITE : C_SOFT;
|
||||
nt.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
nt.fontStyle = isMe ? FontStyles.Bold : FontStyles.Normal;
|
||||
nt.overflowMode = TextOverflowModes.Ellipsis;
|
||||
|
||||
// "YOU" badge
|
||||
if (isMe)
|
||||
{
|
||||
var badgeRT = RT("YouBadge", rt);
|
||||
badgeRT.anchorMin = new Vector2(0.68f, 0.22f);
|
||||
badgeRT.anchorMax = new Vector2(1f, 0.78f);
|
||||
badgeRT.offsetMin = new Vector2(0f, 0f);
|
||||
badgeRT.offsetMax = new Vector2(-20f, 0f);
|
||||
Img(badgeRT, C_ACCENT);
|
||||
TxtChild(badgeRT, "YOU", 30, C_WHITE, TextAlignmentOptions.Center, bold: true);
|
||||
}
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
// ── Layout helpers ────────────────────────────────────────────────────────
|
||||
RectTransform RT(string name, Transform parent)
|
||||
{
|
||||
var go = new GameObject(name);
|
||||
var rt = go.AddComponent<RectTransform>();
|
||||
rt.SetParent(parent, false);
|
||||
rt.localScale = Vector3.one;
|
||||
return rt;
|
||||
}
|
||||
|
||||
void Stretch(RectTransform rt)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero;
|
||||
rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = Vector2.zero;
|
||||
rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
void Fill(RectTransform rt, float top, float bottom, float left = 0f, float right = 0f)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero;
|
||||
rt.anchorMax = Vector2.one;
|
||||
rt.offsetMin = new Vector2(left, bottom);
|
||||
rt.offsetMax = new Vector2(-right, -top);
|
||||
}
|
||||
|
||||
void PinTop(RectTransform rt, float h)
|
||||
{
|
||||
rt.anchorMin = new Vector2(0f, 1f);
|
||||
rt.anchorMax = new Vector2(1f, 1f);
|
||||
rt.pivot = new Vector2(0.5f, 1f);
|
||||
rt.offsetMin = new Vector2(0f, -h);
|
||||
rt.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
void PinBelowTop(RectTransform rt, float fromTop, float h)
|
||||
{
|
||||
rt.anchorMin = new Vector2(0f, 1f);
|
||||
rt.anchorMax = new Vector2(1f, 1f);
|
||||
rt.pivot = new Vector2(0.5f, 1f);
|
||||
rt.offsetMin = new Vector2(0f, -(fromTop + h));
|
||||
rt.offsetMax = new Vector2(0f, -fromTop);
|
||||
}
|
||||
|
||||
void PinBottom(RectTransform rt, float h)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero;
|
||||
rt.anchorMax = new Vector2(1f, 0f);
|
||||
rt.pivot = new Vector2(0.5f, 0f);
|
||||
rt.offsetMin = Vector2.zero;
|
||||
rt.offsetMax = new Vector2(0f, h);
|
||||
}
|
||||
|
||||
// ── Graphic helpers ───────────────────────────────────────────────────────
|
||||
|
||||
/// Adds an Image directly to rt.
|
||||
Image Img(RectTransform rt, Color c)
|
||||
{
|
||||
var img = rt.gameObject.AddComponent<Image>();
|
||||
img.color = c;
|
||||
return img;
|
||||
}
|
||||
|
||||
/// Adds TMP directly to rt — only use when rt has NO Image component.
|
||||
TMP_Text TmpDirect(RectTransform rt, string text, float size, Color color,
|
||||
TextAlignmentOptions align, bool bold = false, bool italic = false)
|
||||
{
|
||||
var tmp = rt.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = text;
|
||||
tmp.fontSize = size;
|
||||
tmp.color = color;
|
||||
tmp.alignment = align;
|
||||
if (bold) tmp.fontStyle |= FontStyles.Bold;
|
||||
if (italic) tmp.fontStyle |= FontStyles.Italic;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/// Creates a stretch-fill child GO with TMP — safe when parent already has Image.
|
||||
TMP_Text TxtChild(RectTransform parent, string text, float size, Color color,
|
||||
TextAlignmentOptions align, bool bold = false, bool italic = false)
|
||||
{
|
||||
var childRT = RT("Txt", parent);
|
||||
Stretch(childRT);
|
||||
return TmpDirect(childRT, text, size, color, align, bold, italic);
|
||||
}
|
||||
|
||||
void Btn(RectTransform rt, Color normal, System.Action onClick)
|
||||
{
|
||||
var btn = rt.gameObject.AddComponent<Button>();
|
||||
btn.targetGraphic = rt.gameObject.GetComponent<Image>();
|
||||
btn.onClick.AddListener(() => onClick());
|
||||
var cols = btn.colors;
|
||||
cols.normalColor = normal;
|
||||
cols.highlightedColor = Color.Lerp(normal, Color.white, 0.3f);
|
||||
cols.pressedColor = Color.Lerp(normal, Color.black, 0.3f);
|
||||
cols.selectedColor = normal;
|
||||
btn.colors = cols;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user