Runtime UI under Scripts/UI: a UIStateController owning menu/dialogue state, a PlayerControlLock so opening a panel suppresses world input, panel visibility helpers, and the menu input binding. Views: the dialogue stream presenter and its options presenter and message row, the portrait HUD, and the player menu with character, journal, and tab-bar views. Skills gains SkillsPanelView and SkillRowView, which is why the Skills asmdef now references UnityEngine.UI. Prefabs/UI holds the authored canvases (dialogue stream, HUD, player menu) and their rows. UILayerBootstrap remains a hand-added runtime fallback for scenes where those prefabs are not instantiated; it deliberately does not auto-spawn. Editor/UILayerSetup.cs is the one-shot menu that builds and wires those prefabs into a scene, with UILayerSetupAutoRun reminding after a script reload. Art/UI/Placeholder carries the placeholder portrait the HUD falls back to. Co-Authored-By: Claude Opus 5 <[email protected]>
68 lines
2.5 KiB
C#
68 lines
2.5 KiB
C#
using UnityEngine;
|
|
|
|
namespace NightclubArcadia.UI
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public static class PanelVisibility
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public static bool Show(GameObject panel) => SetVisible(panel, true);
|
|
|
|
/// <summary>
|
|
/// Hides the panel, leaving the hierarchy active. See class docs.
|
|
/// </summary>
|
|
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<CanvasGroup>();
|
|
if (group == null)
|
|
{
|
|
group = panel.AddComponent<CanvasGroup>();
|
|
}
|
|
|
|
group.alpha = visible ? 1f : 0f;
|
|
group.interactable = visible;
|
|
group.blocksRaycasts = visible;
|
|
|
|
return wasReactivated;
|
|
}
|
|
}
|
|
}
|