diff --git a/NightclubArcadia/Assets/Dialogue/NightclubArcadia.yarnproject b/NightclubArcadia/Assets/Dialogue/NightclubArcadia.yarnproject index 8f682c9..e6bbc95 100644 --- a/NightclubArcadia/Assets/Dialogue/NightclubArcadia.yarnproject +++ b/NightclubArcadia/Assets/Dialogue/NightclubArcadia.yarnproject @@ -28,8 +28,8 @@ "y": 2287 }, "Chevalier Cassian Thal.yarn": { - "x": 1794, - "y": 2275 + "x": 1621, + "y": 2260 }, "Commentary.yarn": { "x": 12, @@ -40,20 +40,20 @@ "y": 12 }, "Chair.yarn": { - "x": 840, - "y": 1072 + "x": 866, + "y": 1104 }, "Debug.yarn": { - "x": -1, - "y": 483 + "x": 14, + "y": 448 }, "SC101.yarn": { "x": 820, "y": -3 }, "SC102.yarn": { - "x": 2734, - "y": 2559 + "x": 2595, + "y": 2682 } } } diff --git a/NightclubArcadia/Assets/Scripts/Dialogue/DialogueInteractable.cs b/NightclubArcadia/Assets/Scripts/Dialogue/DialogueInteractable.cs index d9ac31f..db3005f 100644 --- a/NightclubArcadia/Assets/Scripts/Dialogue/DialogueInteractable.cs +++ b/NightclubArcadia/Assets/Scripts/Dialogue/DialogueInteractable.cs @@ -1,4 +1,5 @@ using UnityEngine; +using UnityEngine.InputSystem; using Yarn.Unity; namespace NightclubArcadia.Dialogue @@ -14,25 +15,39 @@ namespace NightclubArcadia.Dialogue [SerializeField] private GameObject dialogueUI; [SerializeField] private string yarnNodeName = "Bartender_Chat"; - private bool playerInRange = false; + private bool playerInRange; + private InputAction interactAction; + + void Awake() + { + interactAction = new InputAction("Interact"); + interactAction.AddBinding("/e"); + interactAction.AddBinding("/buttonNorth"); + interactAction.performed += _ => + { + if (playerInRange) + { + StartConversation(); + } + }; + } + + void OnEnable() => interactAction?.Enable(); + void OnDisable() => interactAction?.Disable(); void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) + { playerInRange = true; + } } void OnTriggerExit(Collider other) { if (other.CompareTag("Player")) - playerInRange = false; - } - - void Update() - { - if (playerInRange && Input.GetKeyDown(KeyCode.E)) { - StartConversation(); + playerInRange = false; } } diff --git a/NightclubArcadia/Assets/Scripts/Dialogue/DialogueTransitionController.cs b/NightclubArcadia/Assets/Scripts/Dialogue/DialogueTransitionController.cs index 50f543d..579f6a4 100644 --- a/NightclubArcadia/Assets/Scripts/Dialogue/DialogueTransitionController.cs +++ b/NightclubArcadia/Assets/Scripts/Dialogue/DialogueTransitionController.cs @@ -1,4 +1,4 @@ -using StarterAssets; +using NightclubArcadia.UI; using UnityEngine; using Yarn.Unity; @@ -7,33 +7,27 @@ namespace NightclubArcadia.Dialogue /// /// Owns the hand-off between dialogue and the 3D environment: locks the /// player's controls for as long as a conversation is running, and registers - /// the <> Yarn command that hands control back. + /// the <<enter_environment>> Yarn command that hands control back. /// /// The lock is driven by the DialogueRunner's own lifecycle events rather /// than by each interactable, so every entry point — auto-started nodes, /// NPCStandIn, DialogueInteractable — gets the same behaviour without - /// having to remember to do it. + /// having to remember to do it. Control locking is delegated to + /// so the player menu can hold a second + /// independent lock without re-enabling movement mid-conversation. /// public class DialogueTransitionController : MonoBehaviour { [SerializeField] private DialogueRunner dialogueRunner; [SerializeField] private GameObject dialogueUI; [SerializeField] private GameObject playerArmature; - - private ThirdPersonController playerController; - private StarterAssetsInputs playerInputs; - private Animator playerAnimator; - - static readonly int AnimIDSpeed = Animator.StringToHash("Speed"); - static readonly int AnimIDMotionSpeed = Animator.StringToHash("MotionSpeed"); + [SerializeField] private PlayerControlLock controlLock; void Awake() { - if (playerArmature != null) + if (controlLock == null && playerArmature != null) { - playerController = playerArmature.GetComponent(); - playerInputs = playerArmature.GetComponent(); - playerAnimator = playerArmature.GetComponent(); + controlLock = playerArmature.GetComponent(); } // Subscribed in Awake rather than Start: the runner auto-starts its @@ -45,11 +39,16 @@ namespace NightclubArcadia.Dialogue void Start() { + if (controlLock == null && playerArmature != null) + { + controlLock = playerArmature.GetComponent(); + } + // Dialogue is in progress as soon as this controller is active // (including auto-started nodes), so the player stays locked out // until <>. The armature itself stays // active/visible — only its controls are disabled. - SetPlayerControlEnabled(false); + controlLock?.Acquire(this); dialogueRunner.AddCommandHandler("enter_environment", EnterEnvironment); } @@ -63,7 +62,8 @@ namespace NightclubArcadia.Dialogue void OnDialogueStart() { DialogueUIVisibility.Show(dialogueUI); - SetPlayerControlEnabled(false); + UIStateController.Instance?.SetDialogueActive(true); + controlLock?.Acquire(this); } // Safety net: a node that ends without <> must still @@ -74,40 +74,12 @@ namespace NightclubArcadia.Dialogue { // Hide, don't deactivate: deactivating the Canvas breaks TMP's // cached Canvas reference and makes the next conversation throw. - // See DialogueUIVisibility. + // See DialogueUIVisibility / PanelVisibility. DialogueUIVisibility.Hide(dialogueUI); - SetPlayerControlEnabled(true); - } - - void SetPlayerControlEnabled(bool value) - { - if (playerController == null) - { - return; - } - - if (!value) - { - // Clear pending input and the locomotion blend before freezing - // the controller. Disabling it mid-stride otherwise leaves - // Speed at its last value, and the character keeps running on - // the spot for the whole conversation. - if (playerInputs != null) - { - playerInputs.MoveInput(Vector2.zero); - playerInputs.LookInput(Vector2.zero); - playerInputs.SprintInput(false); - playerInputs.JumpInput(false); - } - - if (playerAnimator != null) - { - playerAnimator.SetFloat(AnimIDSpeed, 0f); - playerAnimator.SetFloat(AnimIDMotionSpeed, 0f); - } - } - - playerController.enabled = value; + UIStateController.Instance?.SetDialogueActive(false); + // Release is idempotent on a HashSet — safe when both + // <> and onDialogueComplete fire. + controlLock?.Release(this); } } } diff --git a/NightclubArcadia/Assets/Scripts/Dialogue/DialogueUIVisibility.cs b/NightclubArcadia/Assets/Scripts/Dialogue/DialogueUIVisibility.cs index a7c66af..fba0185 100644 --- a/NightclubArcadia/Assets/Scripts/Dialogue/DialogueUIVisibility.cs +++ b/NightclubArcadia/Assets/Scripts/Dialogue/DialogueUIVisibility.cs @@ -1,23 +1,13 @@ +using NightclubArcadia.UI; using UnityEngine; namespace NightclubArcadia.Dialogue { /// /// Shows and hides the dialogue UI *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. + /// Forwards to — kept as a thin wrapper so + /// existing call sites (NPCStandIn, DialogueInteractable, + /// DialogueTransitionController) continue to compile unchanged. /// public static class DialogueUIVisibility { @@ -27,41 +17,12 @@ namespace NightclubArcadia.Dialogue /// 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 dialogueUI) => SetVisible(dialogueUI, true); + public static bool Show(GameObject dialogueUI) => PanelVisibility.Show(dialogueUI); /// - /// Hides the dialogue UI, leaving the hierarchy active. See class docs. + /// Hides the dialogue UI, leaving the hierarchy active. See + /// . /// - public static void Hide(GameObject dialogueUI) => SetVisible(dialogueUI, false); - - static bool SetVisible(GameObject dialogueUI, bool visible) - { - if (dialogueUI == 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 (!dialogueUI.activeSelf) - { - dialogueUI.SetActive(true); - wasReactivated = true; - } - - var group = dialogueUI.GetComponent(); - if (group == null) - { - group = dialogueUI.AddComponent(); - } - - group.alpha = visible ? 1f : 0f; - group.interactable = visible; - group.blocksRaycasts = visible; - - return wasReactivated; - } + public static void Hide(GameObject dialogueUI) => PanelVisibility.Hide(dialogueUI); } } diff --git a/NightclubArcadia/Assets/Scripts/NPCs/NPCStandIn.cs b/NightclubArcadia/Assets/Scripts/NPCs/NPCStandIn.cs index 7410e4a..e3ba11a 100644 --- a/NightclubArcadia/Assets/Scripts/NPCs/NPCStandIn.cs +++ b/NightclubArcadia/Assets/Scripts/NPCs/NPCStandIn.cs @@ -3,6 +3,7 @@ using UnityEngine.InputSystem; using TMPro; using Yarn.Unity; using NightclubArcadia.Dialogue; +using NightclubArcadia.UI; /// /// Drop this on a sphere primitive to use it as a placeholder NPC: @@ -153,6 +154,11 @@ public class NPCStandIn : MonoBehaviour return; } + if (UIStateController.Instance != null && UIStateController.Instance.IsMenuOpen) + { + return; + } + if (!dialogueRunner.IsDialogueRunning) { StartDialogueAsync();