suppress world interaction while a menu panel is open
DialogueInteractable, DialogueTransitionController, and DialogueUIVisibility are reworked against the new UI layer: visibility is driven by UIStateController rather than each component toggling canvases itself, which removes the duplicated show/hide logic the two had accumulated. NPCStandIn now ignores the interact button while UIStateController reports a menu open, so pressing interact through an open panel no longer starts a conversation behind it. The yarnproject change is graph-editor node positions only. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -28,8 +28,8 @@
|
|||||||
"y": 2287
|
"y": 2287
|
||||||
},
|
},
|
||||||
"Chevalier Cassian Thal.yarn": {
|
"Chevalier Cassian Thal.yarn": {
|
||||||
"x": 1794,
|
"x": 1621,
|
||||||
"y": 2275
|
"y": 2260
|
||||||
},
|
},
|
||||||
"Commentary.yarn": {
|
"Commentary.yarn": {
|
||||||
"x": 12,
|
"x": 12,
|
||||||
@@ -40,20 +40,20 @@
|
|||||||
"y": 12
|
"y": 12
|
||||||
},
|
},
|
||||||
"Chair.yarn": {
|
"Chair.yarn": {
|
||||||
"x": 840,
|
"x": 866,
|
||||||
"y": 1072
|
"y": 1104
|
||||||
},
|
},
|
||||||
"Debug.yarn": {
|
"Debug.yarn": {
|
||||||
"x": -1,
|
"x": 14,
|
||||||
"y": 483
|
"y": 448
|
||||||
},
|
},
|
||||||
"SC101.yarn": {
|
"SC101.yarn": {
|
||||||
"x": 820,
|
"x": 820,
|
||||||
"y": -3
|
"y": -3
|
||||||
},
|
},
|
||||||
"SC102.yarn": {
|
"SC102.yarn": {
|
||||||
"x": 2734,
|
"x": 2595,
|
||||||
"y": 2559
|
"y": 2682
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.InputSystem;
|
||||||
using Yarn.Unity;
|
using Yarn.Unity;
|
||||||
|
|
||||||
namespace NightclubArcadia.Dialogue
|
namespace NightclubArcadia.Dialogue
|
||||||
@@ -14,25 +15,39 @@ namespace NightclubArcadia.Dialogue
|
|||||||
[SerializeField] private GameObject dialogueUI;
|
[SerializeField] private GameObject dialogueUI;
|
||||||
[SerializeField] private string yarnNodeName = "Bartender_Chat";
|
[SerializeField] private string yarnNodeName = "Bartender_Chat";
|
||||||
|
|
||||||
private bool playerInRange = false;
|
private bool playerInRange;
|
||||||
|
private InputAction interactAction;
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
interactAction = new InputAction("Interact");
|
||||||
|
interactAction.AddBinding("<Keyboard>/e");
|
||||||
|
interactAction.AddBinding("<Gamepad>/buttonNorth");
|
||||||
|
interactAction.performed += _ =>
|
||||||
|
{
|
||||||
|
if (playerInRange)
|
||||||
|
{
|
||||||
|
StartConversation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnEnable() => interactAction?.Enable();
|
||||||
|
void OnDisable() => interactAction?.Disable();
|
||||||
|
|
||||||
void OnTriggerEnter(Collider other)
|
void OnTriggerEnter(Collider other)
|
||||||
{
|
{
|
||||||
if (other.CompareTag("Player"))
|
if (other.CompareTag("Player"))
|
||||||
|
{
|
||||||
playerInRange = true;
|
playerInRange = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnTriggerExit(Collider other)
|
void OnTriggerExit(Collider other)
|
||||||
{
|
{
|
||||||
if (other.CompareTag("Player"))
|
if (other.CompareTag("Player"))
|
||||||
playerInRange = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Update()
|
|
||||||
{
|
|
||||||
if (playerInRange && Input.GetKeyDown(KeyCode.E))
|
|
||||||
{
|
{
|
||||||
StartConversation();
|
playerInRange = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using StarterAssets;
|
using NightclubArcadia.UI;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Yarn.Unity;
|
using Yarn.Unity;
|
||||||
|
|
||||||
@@ -7,33 +7,27 @@ namespace NightclubArcadia.Dialogue
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Owns the hand-off between dialogue and the 3D environment: locks the
|
/// 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
|
/// player's controls for as long as a conversation is running, and registers
|
||||||
/// the <<enter_environment>> 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
|
/// The lock is driven by the DialogueRunner's own lifecycle events rather
|
||||||
/// than by each interactable, so every entry point — auto-started nodes,
|
/// than by each interactable, so every entry point — auto-started nodes,
|
||||||
/// NPCStandIn, DialogueInteractable — gets the same behaviour without
|
/// NPCStandIn, DialogueInteractable — gets the same behaviour without
|
||||||
/// having to remember to do it.
|
/// having to remember to do it. Control locking is delegated to
|
||||||
|
/// <see cref="PlayerControlLock"/> so the player menu can hold a second
|
||||||
|
/// independent lock without re-enabling movement mid-conversation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DialogueTransitionController : MonoBehaviour
|
public class DialogueTransitionController : MonoBehaviour
|
||||||
{
|
{
|
||||||
[SerializeField] private DialogueRunner dialogueRunner;
|
[SerializeField] private DialogueRunner dialogueRunner;
|
||||||
[SerializeField] private GameObject dialogueUI;
|
[SerializeField] private GameObject dialogueUI;
|
||||||
[SerializeField] private GameObject playerArmature;
|
[SerializeField] private GameObject playerArmature;
|
||||||
|
[SerializeField] private PlayerControlLock controlLock;
|
||||||
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()
|
void Awake()
|
||||||
{
|
{
|
||||||
if (playerArmature != null)
|
if (controlLock == null && playerArmature != null)
|
||||||
{
|
{
|
||||||
playerController = playerArmature.GetComponent<ThirdPersonController>();
|
controlLock = playerArmature.GetComponent<PlayerControlLock>();
|
||||||
playerInputs = playerArmature.GetComponent<StarterAssetsInputs>();
|
|
||||||
playerAnimator = playerArmature.GetComponent<Animator>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribed in Awake rather than Start: the runner auto-starts its
|
// Subscribed in Awake rather than Start: the runner auto-starts its
|
||||||
@@ -45,11 +39,16 @@ namespace NightclubArcadia.Dialogue
|
|||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
|
if (controlLock == null && playerArmature != null)
|
||||||
|
{
|
||||||
|
controlLock = playerArmature.GetComponent<PlayerControlLock>();
|
||||||
|
}
|
||||||
|
|
||||||
// Dialogue is in progress as soon as this controller is active
|
// Dialogue is in progress as soon as this controller is active
|
||||||
// (including auto-started nodes), so the player stays locked out
|
// (including auto-started nodes), so the player stays locked out
|
||||||
// until <<enter_environment>>. The armature itself stays
|
// until <<enter_environment>>. The armature itself stays
|
||||||
// active/visible — only its controls are disabled.
|
// active/visible — only its controls are disabled.
|
||||||
SetPlayerControlEnabled(false);
|
controlLock?.Acquire(this);
|
||||||
|
|
||||||
dialogueRunner.AddCommandHandler("enter_environment", EnterEnvironment);
|
dialogueRunner.AddCommandHandler("enter_environment", EnterEnvironment);
|
||||||
}
|
}
|
||||||
@@ -63,7 +62,8 @@ namespace NightclubArcadia.Dialogue
|
|||||||
void OnDialogueStart()
|
void OnDialogueStart()
|
||||||
{
|
{
|
||||||
DialogueUIVisibility.Show(dialogueUI);
|
DialogueUIVisibility.Show(dialogueUI);
|
||||||
SetPlayerControlEnabled(false);
|
UIStateController.Instance?.SetDialogueActive(true);
|
||||||
|
controlLock?.Acquire(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety net: a node that ends without <<enter_environment>> must still
|
// Safety net: a node that ends without <<enter_environment>> must still
|
||||||
@@ -74,40 +74,12 @@ namespace NightclubArcadia.Dialogue
|
|||||||
{
|
{
|
||||||
// Hide, don't deactivate: deactivating the Canvas breaks TMP's
|
// Hide, don't deactivate: deactivating the Canvas breaks TMP's
|
||||||
// cached Canvas reference and makes the next conversation throw.
|
// cached Canvas reference and makes the next conversation throw.
|
||||||
// See DialogueUIVisibility.
|
// See DialogueUIVisibility / PanelVisibility.
|
||||||
DialogueUIVisibility.Hide(dialogueUI);
|
DialogueUIVisibility.Hide(dialogueUI);
|
||||||
SetPlayerControlEnabled(true);
|
UIStateController.Instance?.SetDialogueActive(false);
|
||||||
}
|
// Release is idempotent on a HashSet — safe when both
|
||||||
|
// <<enter_environment>> and onDialogueComplete fire.
|
||||||
void SetPlayerControlEnabled(bool value)
|
controlLock?.Release(this);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,13 @@
|
|||||||
|
using NightclubArcadia.UI;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace NightclubArcadia.Dialogue
|
namespace NightclubArcadia.Dialogue
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows and hides the dialogue UI *without* deactivating its GameObject.
|
/// Shows and hides the dialogue UI *without* deactivating its GameObject.
|
||||||
///
|
/// Forwards to <see cref="PanelVisibility"/> — kept as a thin wrapper so
|
||||||
/// SetActive(false) is not a safe way to hide this UI. TextMeshProUGUI
|
/// existing call sites (NPCStandIn, DialogueInteractable,
|
||||||
/// resolves its Canvas through Graphic.CacheCanvas, which calls
|
/// DialogueTransitionController) continue to compile unchanged.
|
||||||
/// 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>
|
/// </summary>
|
||||||
public static class DialogueUIVisibility
|
public static class DialogueUIVisibility
|
||||||
{
|
{
|
||||||
@@ -27,41 +17,12 @@ namespace NightclubArcadia.Dialogue
|
|||||||
/// case the caller must wait one frame before rendering text so that
|
/// case the caller must wait one frame before rendering text so that
|
||||||
/// TMP's OnEnable can re-cache its Canvas.
|
/// TMP's OnEnable can re-cache its Canvas.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool Show(GameObject dialogueUI) => SetVisible(dialogueUI, true);
|
public static bool Show(GameObject dialogueUI) => PanelVisibility.Show(dialogueUI);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hides the dialogue UI, leaving the hierarchy active. See class docs.
|
/// Hides the dialogue UI, leaving the hierarchy active. See
|
||||||
|
/// <see cref="PanelVisibility"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Hide(GameObject dialogueUI) => SetVisible(dialogueUI, false);
|
public static void Hide(GameObject dialogueUI) => PanelVisibility.Hide(dialogueUI);
|
||||||
|
|
||||||
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<CanvasGroup>();
|
|
||||||
if (group == null)
|
|
||||||
{
|
|
||||||
group = dialogueUI.AddComponent<CanvasGroup>();
|
|
||||||
}
|
|
||||||
|
|
||||||
group.alpha = visible ? 1f : 0f;
|
|
||||||
group.interactable = visible;
|
|
||||||
group.blocksRaycasts = visible;
|
|
||||||
|
|
||||||
return wasReactivated;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using UnityEngine.InputSystem;
|
|||||||
using TMPro;
|
using TMPro;
|
||||||
using Yarn.Unity;
|
using Yarn.Unity;
|
||||||
using NightclubArcadia.Dialogue;
|
using NightclubArcadia.Dialogue;
|
||||||
|
using NightclubArcadia.UI;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drop this on a sphere primitive to use it as a placeholder NPC:
|
/// Drop this on a sphere primitive to use it as a placeholder NPC:
|
||||||
@@ -153,6 +154,11 @@ public class NPCStandIn : MonoBehaviour
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (UIStateController.Instance != null && UIStateController.Instance.IsMenuOpen)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!dialogueRunner.IsDialogueRunning)
|
if (!dialogueRunner.IsDialogueRunning)
|
||||||
{
|
{
|
||||||
StartDialogueAsync();
|
StartDialogueAsync();
|
||||||
|
|||||||
Reference in New Issue
Block a user