using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using TMPro; #if ENABLE_INPUT_SYSTEM using UnityEngine.InputSystem.UI; #endif public class RetroMainMenuUI : MonoBehaviour { private Canvas m_MenuCanvas; private bool m_MenuActive; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void Bootstrap() { if (Object.FindFirstObjectByType() != null) { return; } GameObject root = new("RetroMainMenuUI"); root.AddComponent(); } private void Awake() { m_MenuActive = true; Time.timeScale = 0f; ApplyMenuCursorState(); BuildMenu(); EnsureEventSystem(); } private void LateUpdate() { if (!m_MenuActive) { return; } // Some gameplay scripts lock the cursor during Start/Update. // Force menu cursor state while the menu is active. ApplyMenuCursorState(); } private void BuildMenu() { Color bgColor = HexToColor("001e26"); Color panelColor = HexToColor("517567"); Color titleColor = HexToColor("f3d58d"); Color TextNormalColor = HexToColor("eb9843"); Color textWarningColor = HexToColor("c12204"); Color shadowColor = HexToColor("520805"); GameObject canvasObject = new("MainMenuCanvas"); Canvas canvas = canvasObject.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.sortingOrder = 10000; m_MenuCanvas = canvas; CanvasScaler scaler = canvasObject.AddComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1920f, 1080f); scaler.matchWidthOrHeight = 0.5f; canvasObject.AddComponent(); // Background GameObject background = CreateImage("Background", canvasObject.transform, bgColor); StretchToFull(background.GetComponent()); // Decorative horizontal lines (scanline aesthetic) CreateLine("TopLine", background.transform, new Rect(0, -60, 0, 4), panelColor, AnchorPreset.TopStretch); CreateLine("BotLine", background.transform, new Rect(0, 60, 0, 4), panelColor, AnchorPreset.BottomStretch); // --- LEFT PANEL --- GameObject leftPanel = new GameObject("LeftPanel", typeof(RectTransform)); leftPanel.transform.SetParent(canvasObject.transform, false); RectTransform leftRect = leftPanel.GetComponent(); leftRect.anchorMin = new Vector2(0.08f, 0.1f); leftRect.anchorMax = new Vector2(0.45f, 0.9f); leftRect.offsetMin = Vector2.zero; leftRect.offsetMax = Vector2.zero; // Title TextMeshProUGUI titleText = CreateTMP("Title", leftPanel.transform, "HEADLESS HAZARD", titleColor, 72, TextAlignmentOptions.BottomLeft); RectTransform titleRect = titleText.GetComponent(); titleRect.anchorMin = new Vector2(0f, 0.85f); titleRect.anchorMax = new Vector2(1f, 1f); titleRect.offsetMin = Vector2.zero; titleRect.offsetMax = Vector2.zero; titleText.fontStyle = FontStyles.Bold; // Title Shadow TextMeshProUGUI titleShadow = CreateTMP("TitleShadow", leftPanel.transform, "HEADLESS HAZARD", shadowColor, 72, TextAlignmentOptions.BottomLeft); RectTransform shadowRect = titleShadow.GetComponent(); shadowRect.anchorMin = new Vector2(0f, 0.85f); shadowRect.anchorMax = new Vector2(1f, 1f); shadowRect.offsetMin = new Vector2(4f, -4f); // apply drop shadow offset shadowRect.offsetMax = new Vector2(4f, -4f); titleShadow.fontStyle = FontStyles.Bold; titleShadow.transform.SetSiblingIndex(0); // push behind title // Subtitle / Decorative Status TextMeshProUGUI subText = CreateTMP("Subtitle", leftPanel.transform, "SYSTEM_BOOT // OS.ACTIVE_ ", panelColor, 20, TextAlignmentOptions.TopLeft); RectTransform subRect = subText.GetComponent(); subRect.anchorMin = new Vector2(0f, 0.80f); subRect.anchorMax = new Vector2(1f, 0.85f); subRect.offsetMin = Vector2.zero; subRect.offsetMax = Vector2.zero; // Button Group GameObject buttonGroup = new("ButtonGroup", typeof(RectTransform), typeof(VerticalLayoutGroup)); buttonGroup.transform.SetParent(leftPanel.transform, false); RectTransform groupRect = buttonGroup.GetComponent(); groupRect.anchorMin = new Vector2(0f, 0f); groupRect.anchorMax = new Vector2(1f, 0.65f); groupRect.offsetMin = Vector2.zero; groupRect.offsetMax = Vector2.zero; VerticalLayoutGroup layout = buttonGroup.GetComponent(); layout.childAlignment = TextAnchor.UpperLeft; layout.spacing = 16f; layout.childControlWidth = true; layout.childControlHeight = false; CreateTextButton(buttonGroup.transform, "> INITIALIZE_PLAY", TextNormalColor, titleColor, () => { Debug.Log("Play clicked."); OnPlayClicked(); }); CreateTextButton(buttonGroup.transform, "> CONFIGURE_PARAMS", TextNormalColor, titleColor, () => { Debug.Log("Options clicked."); }); CreateTextButton(buttonGroup.transform, "> TERMINATE_PROCESS", TextNormalColor, textWarningColor, () => { Debug.Log("Quit clicked."); Application.Quit(); }); // --- RIGHT PANEL (Level Info) --- GameObject rightPanel = new GameObject("RightPanel", typeof(RectTransform)); rightPanel.transform.SetParent(canvasObject.transform, false); RectTransform rightRect = rightPanel.GetComponent(); rightRect.anchorMin = new Vector2(0.55f, 0.4f); rightRect.anchorMax = new Vector2(0.92f, 0.82f); rightRect.offsetMin = Vector2.zero; rightRect.offsetMax = Vector2.zero; // Right side Border lines CreateLine("R_Top", rightPanel.transform, new Rect(0, 0, 0, 2), panelColor, AnchorPreset.TopStretch); CreateLine("R_Bot", rightPanel.transform, new Rect(0, 0, 0, 2), panelColor, AnchorPreset.BottomStretch); CreateLine("R_Left", rightPanel.transform, new Rect(0, 0, 2, 0), panelColor, AnchorPreset.LeftStretch); CreateLine("R_Right", rightPanel.transform, new Rect(0, 0, 2, 0), panelColor, AnchorPreset.RightStretch); // Right Panel Headers TextMeshProUGUI headerText = CreateTMP("LevelHeader", rightPanel.transform, "CURRENT_SECTOR", panelColor, 24, TextAlignmentOptions.TopLeft); headerText.GetComponent().anchorMin = new Vector2(0f, 1f); headerText.GetComponent().anchorMax = new Vector2(1f, 1f); headerText.GetComponent().anchoredPosition = new Vector2(20f, -20f); // Big Level Text TextMeshProUGUI levelText = CreateTMP("LevelNumber", rightPanel.transform, "LEVEL 01", textWarningColor, 140, TextAlignmentOptions.Center); StretchToFull(levelText.GetComponent()); levelText.fontStyle = FontStyles.Bold; // Decorative status TextMeshProUGUI statusText = CreateTMP("LevelStatus", rightPanel.transform, "[ STATUS: OPTIMAL ]", panelColor, 24, TextAlignmentOptions.BottomRight); statusText.GetComponent().anchorMin = new Vector2(0f, 0f); statusText.GetComponent().anchorMax = new Vector2(1f, 0f); statusText.GetComponent().anchoredPosition = new Vector2(-20f, 20f); } private void OnPlayClicked() { m_MenuActive = false; Time.timeScale = 1f; Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; if (m_MenuCanvas != null) { Destroy(m_MenuCanvas.gameObject); } Destroy(gameObject); } private static GameObject CreateTextButton( Transform parent, string label, Color normalColor, Color highlightColor, UnityEngine.Events.UnityAction clickAction) { GameObject buttonObject = new(label, typeof(RectTransform), typeof(TextMeshProUGUI), typeof(Button)); buttonObject.transform.SetParent(parent, false); RectTransform rect = buttonObject.GetComponent(); rect.sizeDelta = new Vector2(0f, 60f); // Height 60, width auto-controlled by LayoutGroup TextMeshProUGUI text = buttonObject.GetComponent(); text.text = label; text.fontSize = 38; text.alignment = TextAlignmentOptions.Left; text.color = Color.white; // Button tint applies on top of white text.textWrappingMode = TextWrappingModes.NoWrap; Button button = buttonObject.GetComponent