diff --git a/Assets/Code/Scripts/Player/PlayerInputHandler.cs b/Assets/Code/Scripts/Player/PlayerInputHandler.cs index b768a2d..5ca9d8d 100644 --- a/Assets/Code/Scripts/Player/PlayerInputHandler.cs +++ b/Assets/Code/Scripts/Player/PlayerInputHandler.cs @@ -4,6 +4,7 @@ using UnityEngine.InputSystem; public class PlayerInputController : MonoBehaviour { public InputActionAsset InputActions; + public bool InputEnabled { get; private set; } = true; private InputAction m_moveAction; private InputAction m_lookAction; @@ -44,6 +45,17 @@ public class PlayerInputController : MonoBehaviour private void Update() { + if (!InputEnabled) + { + MoveAmount = Vector2.zero; + LookAmount = Vector2.zero; + ShiftPressed = false; + JumpPressed = false; + ThrowPressed = false; + HeadInteractionPressed = false; + return; + } + MoveAmount = m_moveAction.ReadValue(); LookAmount = m_lookAction.ReadValue(); @@ -52,4 +64,9 @@ public class PlayerInputController : MonoBehaviour ThrowPressed = m_throwAction.WasPressedThisFrame(); HeadInteractionPressed = m_headInteractAction.WasPressedThisFrame(); } + + public void SetInputEnabled(bool enabled) + { + InputEnabled = enabled; + } } \ No newline at end of file diff --git a/Assets/Code/Scripts/Player/PlayerLook.cs b/Assets/Code/Scripts/Player/PlayerLook.cs index 5f11fae..b254db9 100644 --- a/Assets/Code/Scripts/Player/PlayerLook.cs +++ b/Assets/Code/Scripts/Player/PlayerLook.cs @@ -16,9 +16,9 @@ public class PlayerLook : MonoBehaviour private void Awake() { - m_rigidbody = GetComponent(); - input = GetComponent(); - headController = GetComponent(); + m_rigidbody = GetComponent(); + input = GetComponent(); + headController = GetComponent(); } private void FixedUpdate() diff --git a/Assets/Code/Scripts/Player/RobotBootSequence.cs b/Assets/Code/Scripts/Player/RobotBootSequence.cs new file mode 100644 index 0000000..09f9610 --- /dev/null +++ b/Assets/Code/Scripts/Player/RobotBootSequence.cs @@ -0,0 +1,264 @@ +using System.Collections; +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +public class RobotBootSequence : MonoBehaviour +{ + [Header("References")] + public PlayerInputController InputController; + public Transform CameraTransform; + + [Header("Timing")] + public bool PlayOnStart = true; + [Min(0.1f)] public float BootDuration = 2.4f; + [Min(0.1f)] public float CharacterPerSecond = 40f; + [Min(0f)] public float LinePause = 0.35f; + [Min(0f)] public float DelayBeforeReveal = 0.4f; + + [Header("Motion")] + public Vector2 StartYawPitch = new Vector2(-30f, -20f); + public float RollWobble = 2.5f; + public float WobbleFrequency = 16f; + public AnimationCurve EaseCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); + + [Header("Boot Text")] + public Color BootTextColor = new Color(0.62f, 1f, 0.7f, 1f); + public string[] BootLines = + { + "UNIT SB-3954 | preparing startup . . .", + "verification of OS-5 . . . 4 . . . 3 . . . 2 . . . 1", + "system integrity: OK", + "motor bus: OK", + "vision pipeline: ONLINE", + "SYSTEM OK" + }; + + [Header("Optional Audio")] + public AudioSource BootAudioSource; + + private bool m_IsPlaying; + + private struct BootUI + { + public Canvas Canvas; + public RectTransform LeftPanel; + public RectTransform RightPanel; + public TextMeshProUGUI Text; + } + + private void Awake() + { + if (InputController == null) + { + InputController = GetComponent(); + } + } + + private void Start() + { + if (PlayOnStart) + { + StartCoroutine(PlayBootSequence()); + } + } + + [ContextMenu("Play Boot Sequence")] + public void PlayBootSequenceFromMenu() + { + if (!Application.isPlaying || m_IsPlaying) + { + return; + } + + StartCoroutine(PlayBootSequence()); + } + + public IEnumerator PlayBootSequence() + { + if (m_IsPlaying) + { + yield break; + } + + m_IsPlaying = true; + + if (InputController != null) + { + InputController.SetInputEnabled(false); + } + + if (BootAudioSource != null) + { + BootAudioSource.Play(); + } + + if (CameraTransform == null) + { + m_IsPlaying = false; + if (InputController != null) + { + InputController.SetInputEnabled(true); + } + yield break; + } + + Quaternion gameplayRotation = CameraTransform.localRotation; + Quaternion fromRotation = Quaternion.Euler(StartYawPitch.y, StartYawPitch.x, 0f) * gameplayRotation; + CameraTransform.localRotation = fromRotation; + + BootUI bootUI = CreateBootUI(); + + yield return StartCoroutine(PlayBootText(bootUI.Text)); + + if (DelayBeforeReveal > 0f) + { + yield return new WaitForSeconds(DelayBeforeReveal); + } + + float elapsed = 0f; + while (elapsed < BootDuration) + { + elapsed += Time.deltaTime; + float t = Mathf.Clamp01(elapsed / BootDuration); + float eased = EaseCurve.Evaluate(t); + + float wobbleFade = 1f - eased; + float roll = Mathf.Sin(Time.time * WobbleFrequency) * RollWobble * wobbleFade; + Quaternion wobbleRotation = Quaternion.Euler(0f, 0f, roll); + + CameraTransform.localRotation = Quaternion.Slerp(fromRotation, gameplayRotation, eased) * wobbleRotation; + + RectTransform rootRect = bootUI.Canvas.GetComponent(); + float halfWidth = rootRect.rect.width * 0.5f; + float leftTarget = -(halfWidth + 24f); + float rightTarget = halfWidth + 24f; + bootUI.LeftPanel.anchoredPosition = new Vector2(Mathf.Lerp(0f, leftTarget, eased), 0f); + bootUI.RightPanel.anchoredPosition = new Vector2(Mathf.Lerp(0f, rightTarget, eased), 0f); + + Color textColor = bootUI.Text.color; + textColor.a = 1f - eased; + bootUI.Text.color = textColor; + + yield return null; + } + + CameraTransform.localRotation = gameplayRotation; + + if (bootUI.Canvas != null) + { + Destroy(bootUI.Canvas.gameObject); + } + + if (InputController != null) + { + InputController.SetInputEnabled(true); + } + + m_IsPlaying = false; + } + + private IEnumerator PlayBootText(TextMeshProUGUI label) + { + if (label == null || BootLines == null || BootLines.Length == 0) + { + yield break; + } + + label.text = string.Empty; + float charDelay = CharacterPerSecond <= 0f ? 0f : 1f / CharacterPerSecond; + + for (int i = 0; i < BootLines.Length; i++) + { + string line = BootLines[i]; + for (int c = 0; c < line.Length; c++) + { + label.text += line[c]; + if (charDelay > 0f) + { + yield return new WaitForSeconds(charDelay); + } + } + + if (i < BootLines.Length - 1) + { + label.text += "\n"; + } + + if (LinePause > 0f) + { + yield return new WaitForSeconds(LinePause); + } + } + } + + private BootUI CreateBootUI() + { + BootUI ui = new BootUI(); + + GameObject canvasGO = new GameObject("RobotBootCanvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster)); + Canvas canvas = canvasGO.GetComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + canvas.sortingOrder = 5000; + + CanvasScaler scaler = canvasGO.GetComponent(); + scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + scaler.referenceResolution = new Vector2(1920f, 1080f); + scaler.matchWidthOrHeight = 0.5f; + + RectTransform root = canvasGO.GetComponent(); + root.anchorMin = Vector2.zero; + root.anchorMax = Vector2.one; + root.offsetMin = Vector2.zero; + root.offsetMax = Vector2.zero; + + RectTransform leftPanel = CreatePanel("LeftPanel", root, true); + RectTransform rightPanel = CreatePanel("RightPanel", root, false); + TextMeshProUGUI label = CreateBootLabel(root); + + ui.Canvas = canvas; + ui.LeftPanel = leftPanel; + ui.RightPanel = rightPanel; + ui.Text = label; + + return ui; + } + + private RectTransform CreatePanel(string panelName, RectTransform parent, bool isLeft) + { + GameObject panelGO = new GameObject(panelName, typeof(RectTransform), typeof(Image)); + RectTransform rect = panelGO.GetComponent(); + rect.SetParent(parent, false); + rect.anchorMin = isLeft ? new Vector2(0f, 0f) : new Vector2(0.5f, 0f); + rect.anchorMax = isLeft ? new Vector2(0.5f, 1f) : new Vector2(1f, 1f); + rect.pivot = new Vector2(0.5f, 0.5f); + rect.offsetMin = Vector2.zero; + rect.offsetMax = Vector2.zero; + rect.anchoredPosition = Vector2.zero; + + Image image = panelGO.GetComponent(); + image.color = Color.black; + + return rect; + } + + private TextMeshProUGUI CreateBootLabel(RectTransform parent) + { + GameObject textGO = new GameObject("BootText", typeof(RectTransform), typeof(TextMeshProUGUI)); + RectTransform rect = textGO.GetComponent(); + rect.SetParent(parent, false); + rect.anchorMin = new Vector2(0.13f, 0.5f); + rect.anchorMax = new Vector2(0.13f, 0.5f); + rect.pivot = new Vector2(0f, 0.5f); + rect.sizeDelta = new Vector2(980f, 380f); + + TextMeshProUGUI text = textGO.GetComponent(); + text.text = string.Empty; + text.fontSize = 40f; + text.alignment = TextAlignmentOptions.Left; + text.color = BootTextColor; + text.textWrappingMode = TextWrappingModes.Normal; + + return text; + } +} diff --git a/Assets/Code/Scripts/Player/RobotBootSequence.cs.meta b/Assets/Code/Scripts/Player/RobotBootSequence.cs.meta new file mode 100644 index 0000000..e0ad01c --- /dev/null +++ b/Assets/Code/Scripts/Player/RobotBootSequence.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ef6855cd57b4f94b47f410d47e89ff1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Code/Scripts/Rendering.meta b/Assets/Code/Scripts/Rendering.meta new file mode 100644 index 0000000..3510470 --- /dev/null +++ b/Assets/Code/Scripts/Rendering.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 97a4ae8015df4732ac9524441048a765 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs b/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs new file mode 100644 index 0000000..2f3816e --- /dev/null +++ b/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs @@ -0,0 +1,123 @@ +// using UnityEngine; +// using UnityEngine.Rendering; +// using UnityEngine.Rendering.RenderGraphModule; +// using UnityEngine.Rendering.RenderGraphModule.Util; +// using UnityEngine.Rendering.Universal; + +// public class CRTRendererFeature : ScriptableRendererFeature +// { +// [System.Serializable] +// public class CRTSettings +// { +// public bool EffectEnabled = true; +// public RenderPassEvent PassEvent = RenderPassEvent.AfterRenderingPostProcessing; +// public Shader CRTShader; + +// [Range(0f, 1f)] public float Intensity = 0.65f; +// [Range(0f, 2f)] public float ScanlineDensity = 1.2f; +// [Range(0f, 1f)] public float ScanlineStrength = 0.18f; +// [Range(0f, 0.2f)] public float Curvature = 0.04f; +// [Range(0f, 1f)] public float VignetteStrength = 0.28f; +// [Range(0f, 0.05f)] public float ChromaticAberration = 0.004f; +// [Range(0f, 0.2f)] public float NoiseStrength = 0.03f; +// [Range(0f, 0.1f)] public float FlickerStrength = 0.015f; +// } + +// class CRTPass : ScriptableRenderPass +// { +// private Material m_Material; +// private CRTSettings m_Settings; + +// public void Setup(Material material, CRTSettings settings) +// { +// m_Material = material; +// m_Settings = settings; +// renderPassEvent = settings.PassEvent; +// requiresIntermediateTexture = true; +// } + +// public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) +// { +// if (m_Material == null || m_Settings == null || !m_Settings.EffectEnabled) +// { +// return; +// } + +// UniversalResourceData resourceData = frameData.Get(); +// if (resourceData.isActiveTargetBackBuffer) +// { +// return; +// } + +// m_Material.SetFloat("_Intensity", m_Settings.Intensity); +// m_Material.SetFloat("_ScanlineDensity", m_Settings.ScanlineDensity); +// m_Material.SetFloat("_ScanlineStrength", m_Settings.ScanlineStrength); +// m_Material.SetFloat("_Curvature", m_Settings.Curvature); +// m_Material.SetFloat("_VignetteStrength", m_Settings.VignetteStrength); +// m_Material.SetFloat("_ChromaticAberration", m_Settings.ChromaticAberration); +// m_Material.SetFloat("_NoiseStrength", m_Settings.NoiseStrength); +// m_Material.SetFloat("_FlickerStrength", m_Settings.FlickerStrength); + +// TextureHandle source = resourceData.activeColorTexture; +// TextureDesc destinationDesc = renderGraph.GetTextureDesc(source); +// destinationDesc.name = "CameraColor-CRT"; +// destinationDesc.clearBuffer = false; + +// TextureHandle destination = renderGraph.CreateTexture(destinationDesc); +// RenderGraphUtils.BlitMaterialParameters blitParams = new(source, destination, m_Material, 0); +// renderGraph.AddBlitPass(blitParams, "CRT Effect"); + +// resourceData.cameraColor = destination; +// } + +// public void Dispose() +// { +// // RenderGraph path does not allocate persistent RTHandles in this pass. +// } +// } + +// public CRTSettings Settings = new(); + +// private CRTPass m_Pass; +// private Material m_Material; + +// public override void Create() +// { +// if (Settings.CRTShader == null) +// { +// Settings.CRTShader = Shader.Find("Hidden/HeadlessHazard/CRT"); +// } + +// if (Settings.CRTShader != null) +// { +// m_Material = CoreUtils.CreateEngineMaterial(Settings.CRTShader); +// } + +// m_Pass ??= new CRTPass(); +// } + +// public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) +// { +// if (m_Material == null || !Settings.EffectEnabled) +// { +// return; +// } + +// if (renderingData.cameraData.cameraType != CameraType.Game) +// { +// return; +// } + +// m_Pass.Setup(m_Material, Settings); +// renderer.EnqueuePass(m_Pass); +// } + +// protected override void Dispose(bool disposing) +// { +// m_Pass?.Dispose(); +// m_Pass = null; + +// CoreUtils.Destroy(m_Material); +// m_Material = null; +// } +// } diff --git a/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs.meta b/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs.meta new file mode 100644 index 0000000..7bcc2e6 --- /dev/null +++ b/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f2de7a6cfbd47c8bc740d43bb991205 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Code/Scripts/UI.meta b/Assets/Code/Scripts/UI.meta new file mode 100644 index 0000000..a85f6a0 --- /dev/null +++ b/Assets/Code/Scripts/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e88664529cd503644b2b92f055895969 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Code/Scripts/UI/MainMenuUI.cs b/Assets/Code/Scripts/UI/MainMenuUI.cs new file mode 100644 index 0000000..0b6a67b --- /dev/null +++ b/Assets/Code/Scripts/UI/MainMenuUI.cs @@ -0,0 +1,379 @@ +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(); + + UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded; + } + + private void OnDestroy() + { + UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded; + } + + private void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode) + { + // Check again when the scene finishes loading to remove any baked-in duplicate EventSystems + 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."); +#if UNITY_EDITOR + UnityEditor.EditorApplication.isPlaying = false; +#else + Application.Quit(); +#endif + }); + + + // --- 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