feat: add RetroMainMenuUI script and associated meta file for main menu functionality
This commit is contained in:
363
Assets/Code/Scripts/UI/RetroMainMenuUI.cs
Normal file
363
Assets/Code/Scripts/UI/RetroMainMenuUI.cs
Normal file
@@ -0,0 +1,363 @@
|
||||
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<RetroMainMenuUI>() != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject root = new("RetroMainMenuUI");
|
||||
root.AddComponent<RetroMainMenuUI>();
|
||||
}
|
||||
|
||||
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>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 10000;
|
||||
m_MenuCanvas = canvas;
|
||||
|
||||
CanvasScaler scaler = canvasObject.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(1920f, 1080f);
|
||||
scaler.matchWidthOrHeight = 0.5f;
|
||||
|
||||
canvasObject.AddComponent<GraphicRaycaster>();
|
||||
|
||||
// Background
|
||||
GameObject background = CreateImage("Background", canvasObject.transform, bgColor);
|
||||
StretchToFull(background.GetComponent<RectTransform>());
|
||||
|
||||
// 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<RectTransform>();
|
||||
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<RectTransform>();
|
||||
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<RectTransform>();
|
||||
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<RectTransform>();
|
||||
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<RectTransform>();
|
||||
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<VerticalLayoutGroup>();
|
||||
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<RectTransform>();
|
||||
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<RectTransform>().anchorMin = new Vector2(0f, 1f);
|
||||
headerText.GetComponent<RectTransform>().anchorMax = new Vector2(1f, 1f);
|
||||
headerText.GetComponent<RectTransform>().anchoredPosition = new Vector2(20f, -20f);
|
||||
|
||||
// Big Level Text
|
||||
TextMeshProUGUI levelText = CreateTMP("LevelNumber", rightPanel.transform, "LEVEL 01", textWarningColor, 140, TextAlignmentOptions.Center);
|
||||
StretchToFull(levelText.GetComponent<RectTransform>());
|
||||
levelText.fontStyle = FontStyles.Bold;
|
||||
|
||||
// Decorative status
|
||||
TextMeshProUGUI statusText = CreateTMP("LevelStatus", rightPanel.transform, "[ STATUS: OPTIMAL ]", panelColor, 24, TextAlignmentOptions.BottomRight);
|
||||
statusText.GetComponent<RectTransform>().anchorMin = new Vector2(0f, 0f);
|
||||
statusText.GetComponent<RectTransform>().anchorMax = new Vector2(1f, 0f);
|
||||
statusText.GetComponent<RectTransform>().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<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(0f, 60f); // Height 60, width auto-controlled by LayoutGroup
|
||||
|
||||
TextMeshProUGUI text = buttonObject.GetComponent<TextMeshProUGUI>();
|
||||
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<Button>();
|
||||
button.targetGraphic = text;
|
||||
button.transition = Selectable.Transition.ColorTint;
|
||||
|
||||
ColorBlock colors = button.colors;
|
||||
colors.normalColor = normalColor;
|
||||
colors.highlightedColor = highlightColor;
|
||||
colors.pressedColor = highlightColor;
|
||||
colors.selectedColor = highlightColor;
|
||||
colors.disabledColor = Color.gray;
|
||||
colors.colorMultiplier = 1f;
|
||||
colors.fadeDuration = 0.1f;
|
||||
button.colors = colors;
|
||||
|
||||
button.onClick.AddListener(clickAction);
|
||||
|
||||
return buttonObject;
|
||||
}
|
||||
|
||||
private static TextMeshProUGUI CreateTMP(string name, Transform parent, string textStr, Color color, float size, TextAlignmentOptions align)
|
||||
{
|
||||
GameObject go = new GameObject(name, typeof(RectTransform), typeof(TextMeshProUGUI));
|
||||
go.transform.SetParent(parent, false);
|
||||
TextMeshProUGUI tmp = go.GetComponent<TextMeshProUGUI>();
|
||||
tmp.text = textStr;
|
||||
tmp.color = color;
|
||||
tmp.fontSize = size;
|
||||
tmp.alignment = align;
|
||||
tmp.textWrappingMode = TextWrappingModes.NoWrap;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
enum AnchorPreset { TopStretch, BottomStretch, LeftStretch, RightStretch }
|
||||
private static GameObject CreateLine(string name, Transform parent, Rect details, Color color, AnchorPreset preset)
|
||||
{
|
||||
GameObject line = CreateImage(name, parent, color);
|
||||
RectTransform rect = line.GetComponent<RectTransform>();
|
||||
|
||||
switch (preset)
|
||||
{
|
||||
case AnchorPreset.TopStretch:
|
||||
rect.anchorMin = new Vector2(0, 1);
|
||||
rect.anchorMax = new Vector2(1, 1);
|
||||
rect.sizeDelta = new Vector2(details.width, details.height);
|
||||
rect.anchoredPosition = new Vector2(details.x, details.y);
|
||||
break;
|
||||
case AnchorPreset.BottomStretch:
|
||||
rect.anchorMin = new Vector2(0, 0);
|
||||
rect.anchorMax = new Vector2(1, 0);
|
||||
rect.sizeDelta = new Vector2(details.width, details.height);
|
||||
rect.anchoredPosition = new Vector2(details.x, details.y);
|
||||
break;
|
||||
case AnchorPreset.LeftStretch:
|
||||
rect.anchorMin = new Vector2(0, 0);
|
||||
rect.anchorMax = new Vector2(0, 1);
|
||||
rect.sizeDelta = new Vector2(details.width, details.height);
|
||||
rect.anchoredPosition = new Vector2(details.x, details.y);
|
||||
break;
|
||||
case AnchorPreset.RightStretch:
|
||||
rect.anchorMin = new Vector2(1, 0);
|
||||
rect.anchorMax = new Vector2(1, 1);
|
||||
rect.sizeDelta = new Vector2(details.width, details.height);
|
||||
rect.anchoredPosition = new Vector2(details.x, details.y);
|
||||
break;
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
private static GameObject CreateImage(string name, Transform parent, Color color)
|
||||
{
|
||||
GameObject imageObject = new(name, typeof(RectTransform), typeof(Image));
|
||||
imageObject.transform.SetParent(parent, false);
|
||||
Image image = imageObject.GetComponent<Image>();
|
||||
image.color = color;
|
||||
return imageObject;
|
||||
}
|
||||
|
||||
private static void StretchToFull(RectTransform rect)
|
||||
{
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.offsetMin = Vector2.zero;
|
||||
rect.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
private static Color HexToColor(string hex)
|
||||
{
|
||||
if (ColorUtility.TryParseHtmlString("#" + hex, out Color color))
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
return Color.magenta;
|
||||
}
|
||||
|
||||
private static void ApplyMenuCursorState()
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
}
|
||||
|
||||
private static void EnsureEventSystem()
|
||||
{
|
||||
EventSystem[] allEventSystems = Object.FindObjectsByType<EventSystem>(FindObjectsSortMode.None);
|
||||
|
||||
EventSystem eventSystem;
|
||||
if (allEventSystems.Length == 0)
|
||||
{
|
||||
GameObject eventSystemObject = new("EventSystem", typeof(EventSystem));
|
||||
eventSystem = eventSystemObject.GetComponent<EventSystem>();
|
||||
}
|
||||
else
|
||||
{
|
||||
eventSystem = allEventSystems[0];
|
||||
|
||||
for (int i = 1; i < allEventSystems.Length; i++)
|
||||
{
|
||||
if (allEventSystems[i] != null)
|
||||
{
|
||||
Destroy(allEventSystems[i].gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EnsureCompatibleInputModule(eventSystem.gameObject);
|
||||
}
|
||||
|
||||
private static void EnsureCompatibleInputModule(GameObject eventSystemObject)
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
StandaloneInputModule standaloneModule = eventSystemObject.GetComponent<StandaloneInputModule>();
|
||||
if (standaloneModule != null)
|
||||
{
|
||||
Destroy(standaloneModule);
|
||||
}
|
||||
|
||||
if (eventSystemObject.GetComponent<InputSystemUIInputModule>() == null)
|
||||
{
|
||||
eventSystemObject.AddComponent<InputSystemUIInputModule>();
|
||||
}
|
||||
#else
|
||||
if (eventSystemObject.GetComponent<StandaloneInputModule>() == null)
|
||||
{
|
||||
eventSystemObject.AddComponent<StandaloneInputModule>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
2
Assets/Code/Scripts/UI/RetroMainMenuUI.cs.meta
Normal file
2
Assets/Code/Scripts/UI/RetroMainMenuUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa612753289203548aa7ed790c42847e
|
||||
Reference in New Issue
Block a user