using UnityEngine; namespace NightclubArcadia.UI { /// /// Shows and hides a UI panel *without* deactivating its GameObject. /// /// SetActive(false) is not a safe way to hide this UI. TextMeshProUGUI /// resolves its Canvas through Graphic.CacheCanvas, which calls /// GetComponentsInParent(includeInactive: false) and then requires /// canvas.isActiveAndEnabled. Once the Canvas GameObject has been /// deactivated, that lookup yields null, and TMP.GetTextInfo does: /// /// if (m_canvas == null) m_canvas = this.canvas; // still null /// GenerateTextMesh(); // m_canvas.worldCamera -> NRE /// /// GenerateTextMesh dereferences m_canvas unguarded, so the very next line /// Yarn tries to type throws NullReferenceException. Keeping the hierarchy /// active and driving a CanvasGroup instead means the cached Canvas /// reference stays valid across any number of enter/exit dialogue cycles. /// public static class PanelVisibility { /// /// Makes the panel visible and interactive. Returns true if the /// GameObject had to be re-activated (legacy scene state), in which /// case the caller must wait one frame before rendering text so that /// TMP's OnEnable can re-cache its Canvas. /// public static bool Show(GameObject panel) => SetVisible(panel, true); /// /// Hides the panel, leaving the hierarchy active. See class docs. /// public static void Hide(GameObject panel) => SetVisible(panel, false); static bool SetVisible(GameObject panel, bool visible) { if (panel == null) { return false; } // The UI may still be deactivated by an older saved scene state or // by code that predates this helper. Recover from that, and tell // the caller a frame of grace is needed. var wasReactivated = false; if (!panel.activeSelf) { panel.SetActive(true); wasReactivated = true; } var group = panel.GetComponent(); if (group == null) { group = panel.AddComponent(); } group.alpha = visible ? 1f : 0f; group.interactable = visible; group.blocksRaycasts = visible; return wasReactivated; } } }