using StarterAssets; 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 <> 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. /// 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"); void Awake() { if (playerArmature != null) { playerController = playerArmature.GetComponent(); playerInputs = playerArmature.GetComponent(); playerAnimator = 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() { // 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); dialogueRunner.AddCommandHandler("enter_environment", EnterEnvironment); } void OnDestroy() { dialogueRunner.onDialogueStart?.RemoveListener(OnDialogueStart); dialogueRunner.onDialogueComplete?.RemoveListener(OnDialogueComplete); } void OnDialogueStart() { DialogueUIVisibility.Show(dialogueUI); SetPlayerControlEnabled(false); } // 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. 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; } } }