using NightclubArcadia.UI; using UnityEngine; using Yarn.Unity; 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 <<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, /// DialogueInteractor — gets the same behaviour without /// 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; [SerializeField] private PlayerControlLock controlLock; void Awake() { if (controlLock == null && playerArmature != null) { controlLock = playerArmature.GetComponent(); } // Subscribed in Awake rather than Start: the runner auto-starts its // first node from its own Start(), and script execution order // between the two is not guaranteed. dialogueRunner.onDialogueStart?.AddListener(OnDialogueStart); dialogueRunner.onDialogueComplete?.AddListener(OnDialogueComplete); } 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. controlLock?.Acquire(this); dialogueRunner.AddCommandHandler("enter_environment", EnterEnvironment); } void OnDestroy() { dialogueRunner.onDialogueStart?.RemoveListener(OnDialogueStart); dialogueRunner.onDialogueComplete?.RemoveListener(OnDialogueComplete); } void OnDialogueStart() { DialogueUIVisibility.Show(dialogueUI); UIStateController.Instance?.SetDialogueActive(true); controlLock?.Acquire(this); } // Safety net: a node that ends without <> must still // give the player their controls back. void OnDialogueComplete() => EnterEnvironment(); void EnterEnvironment() { // Hide, don't deactivate: deactivating the Canvas breaks TMP's // cached Canvas reference and makes the next conversation throw. // See DialogueUIVisibility / PanelVisibility. DialogueUIVisibility.Hide(dialogueUI); UIStateController.Instance?.SetDialogueActive(false); // Release is idempotent on a HashSet — safe when both // <> and onDialogueComplete fire. controlLock?.Release(this); } } }