chore: Project Restructuring & Theatre
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using Unheimlich.Core;
|
||||
using Unheimlich.Features.Dialogue.Data;
|
||||
using Unheimlich.Features.DetectiveBoard.Data;
|
||||
using Unheimlich.Features.DetectiveBoard;
|
||||
using Unheimlich.Features.Player;
|
||||
using Unheimlich.Features.Dialogue.UI;
|
||||
using Unheimlich.Features.Dialogue;
|
||||
using System.Collections.Generic;
|
||||
using InputManager = Unheimlich.Core.Input.InputManager;
|
||||
|
||||
namespace Unheimlich.Features.Dialogue
|
||||
{
|
||||
public class DialogueManager : Singleton<DialogueManager>
|
||||
{
|
||||
|
||||
[Header("State")]
|
||||
[SerializeField] private bool IsInDialogue = false;
|
||||
|
||||
[Header("Focus Settings")]
|
||||
[SerializeField] private LayerMask _choiceLayer;
|
||||
[SerializeField] private float _focusRange = 5f;
|
||||
[SerializeField] private float _focusTime = 1.5f;
|
||||
[SerializeField] private float _raycastInterval = 0.05f;
|
||||
|
||||
[Header("Focus Stabilization")]
|
||||
[SerializeField] private float _recoveryDuration = 0.5f;
|
||||
[SerializeField] private float _focusRetentionTime = 0.1f;
|
||||
|
||||
[Tooltip("Fallback camera shake intensity if not set in DialogueAnimationSettings")]
|
||||
[SerializeField] private float _fallbackShakeIntensity = 30f;
|
||||
|
||||
public DialogueAnimationSettings CurrentAnimationSettings => _currentAnimationSettings;
|
||||
public bool IsDialogueActive => IsInDialogue;
|
||||
public Transform CurrentChoiceAnchor => _currentChoiceAnchor;
|
||||
public DialogueUI CurrentDialogueUI => _activeDialogueUI;
|
||||
public object CurrentOwner { get; private set; }
|
||||
public DialogueConversation CurrentConversation => _currentConversation;
|
||||
public DialogueNode CurrentNode => _currentNode;
|
||||
|
||||
private bool _blurEnabled;
|
||||
|
||||
public event Action<DialogueChoiceData> OnChoiceMade;
|
||||
public event Action<object> OnDialogueCompleted;
|
||||
|
||||
private DialogueFocusSystem _focusSystem;
|
||||
private Camera _mainCamera;
|
||||
private DialogueAnimationSettings _currentAnimationSettings;
|
||||
private DialogueUI _activeDialogueUI;
|
||||
private Transform _currentChoiceAnchor;
|
||||
|
||||
private DialogueConversation _currentConversation;
|
||||
private DialogueNode _currentNode;
|
||||
private float _autoCloseTimer = 0f;
|
||||
private bool _isWaitingForAutoClose = false;
|
||||
|
||||
protected override void OnAwake() { }
|
||||
|
||||
protected override void OnEnabled()
|
||||
{
|
||||
// With Scene Reload disabled, Start() doesn't run again each session.
|
||||
// We recreate the focus system to clear any stale locked state (_isFocusLocked,
|
||||
// _focusTimer, _currentFocusedHandler) that may have carried over.
|
||||
if (_focusSystem != null)
|
||||
{
|
||||
_mainCamera = Camera.main;
|
||||
InitFocusSystem();
|
||||
|
||||
// Also reset dialogue state in case we exited mid-dialogue
|
||||
IsInDialogue = false;
|
||||
_isWaitingForAutoClose = false;
|
||||
_autoCloseTimer = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_mainCamera = Camera.main;
|
||||
InitFocusSystem();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or recreates the DialogueFocusSystem with the current inspector settings.
|
||||
/// Called from Start and OnEnabled (domain-reload-less re-entry).
|
||||
/// </summary>
|
||||
private void InitFocusSystem()
|
||||
{
|
||||
_focusSystem = new DialogueFocusSystem(
|
||||
this,
|
||||
_mainCamera,
|
||||
PlayerController.Instance?.CameraEffects,
|
||||
_choiceLayer,
|
||||
_focusRange,
|
||||
_focusTime,
|
||||
_raycastInterval,
|
||||
_recoveryDuration,
|
||||
_focusRetentionTime,
|
||||
_fallbackShakeIntensity
|
||||
);
|
||||
|
||||
_focusSystem.OnChoiceMade += (choice) => OnChoiceMade?.Invoke(choice);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsInDialogue)
|
||||
return;
|
||||
|
||||
if (_activeDialogueUI == null)
|
||||
{
|
||||
Debug.LogError("[DialogueManager] IsInDialogue is true but _activeDialogueUI is null. Forcing dialogue end.", this);
|
||||
IsInDialogue = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_activeDialogueUI.AreChoicesVisible())
|
||||
{
|
||||
if (_isWaitingForAutoClose && _currentNode != null && _currentNode.AutoCloseDelay > 0f)
|
||||
{
|
||||
_autoCloseTimer += Time.deltaTime;
|
||||
|
||||
if (_autoCloseTimer >= _currentNode.AutoCloseDelay)
|
||||
{
|
||||
_activeDialogueUI.FadeOutAndHide(() =>
|
||||
{
|
||||
OnDialogueCompleted?.Invoke(CurrentOwner);
|
||||
CleanupDialogue();
|
||||
});
|
||||
|
||||
IsInDialogue = false;
|
||||
_isWaitingForAutoClose = false;
|
||||
|
||||
if (_blurEnabled && DialogueBlurEffectURP.Instance != null)
|
||||
DialogueBlurEffectURP.Instance.HideBlur();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var interactAction = InputManager.GetInteractAction();
|
||||
if (interactAction != null && interactAction.WasPressedThisFrame())
|
||||
{
|
||||
EndDialogue();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_focusSystem?.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public void StartConversation(DialogueConversation conversation, Transform choiceAnchor = null, DialogueAnimationSettings animationSettings = null, DialogueUI customDialogueUI = null, object owner = null, bool showBlur = false)
|
||||
{
|
||||
if (conversation == null)
|
||||
{
|
||||
Debug.LogError("Cannot start conversation: conversation is null");
|
||||
return;
|
||||
}
|
||||
|
||||
DialogueNode firstNode = conversation.GetFirstNode();
|
||||
if (firstNode == null)
|
||||
{
|
||||
Debug.LogError($"Cannot start conversation {conversation.name}: no dialogue nodes found");
|
||||
return;
|
||||
}
|
||||
|
||||
_currentConversation = conversation;
|
||||
_currentNode = firstNode;
|
||||
_currentChoiceAnchor = choiceAnchor;
|
||||
_currentAnimationSettings = animationSettings;
|
||||
_activeDialogueUI = customDialogueUI;
|
||||
CurrentOwner = owner;
|
||||
_blurEnabled = showBlur;
|
||||
|
||||
if (_activeDialogueUI == null)
|
||||
{
|
||||
Debug.LogError("[DialogueManager] StartConversation called with no DialogueUI. Assign a Local Dialogue UI on the DialogueTriggerZone that triggered this conversation.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_blurEnabled && DialogueBlurEffectURP.Instance != null)
|
||||
DialogueBlurEffectURP.Instance.ShowBlur();
|
||||
|
||||
PlayerEvents.TriggerEnterDialogue();
|
||||
ShowCurrentNode();
|
||||
}
|
||||
|
||||
private void ShowCurrentNode()
|
||||
{
|
||||
if (_currentNode == null || _currentConversation == null)
|
||||
return;
|
||||
|
||||
UnlockFocus();
|
||||
IsInDialogue = true;
|
||||
|
||||
if (_currentNode.ClueToUnlock != null)
|
||||
{
|
||||
if (DetectiveBoardManager.Instance != null)
|
||||
{
|
||||
DetectiveBoardManager.Instance.UnlockClue(_currentNode.ClueToUnlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[DialogueManager] Cannot unlock clue '{_currentNode.ClueToUnlock.ClueID}': DetectiveBoardManager not found");
|
||||
}
|
||||
}
|
||||
|
||||
if(_currentChoiceAnchor == null)
|
||||
{
|
||||
Debug.LogWarning("No choice anchor found!");
|
||||
return;
|
||||
}
|
||||
Transform anchor = _currentChoiceAnchor;
|
||||
|
||||
string speakerName = _currentConversation.GetLocalizedSpeakerName();
|
||||
string dialogueText = _currentNode.GetLocalizedDialogueText();
|
||||
DialogueDisplayMode displayMode = _currentConversation.DisplayMode;
|
||||
|
||||
_activeDialogueUI.ShowDialogueBox(speakerName, dialogueText, displayMode);
|
||||
|
||||
_autoCloseTimer = 0f;
|
||||
_isWaitingForAutoClose = false;
|
||||
|
||||
if (_currentNode.Choices != null && _currentNode.Choices.Count > 0)
|
||||
{
|
||||
List<DialogueChoiceData> validChoices = new List<DialogueChoiceData>();
|
||||
|
||||
foreach (var choiceData in _currentNode.Choices)
|
||||
{
|
||||
if (choiceData != null && choiceData.IsValid())
|
||||
validChoices.Add(choiceData);
|
||||
}
|
||||
|
||||
if (validChoices.Count > 0)
|
||||
_activeDialogueUI.ShowChoices(validChoices, anchor, _currentAnimationSettings);
|
||||
}
|
||||
else
|
||||
_isWaitingForAutoClose = true;
|
||||
}
|
||||
|
||||
public void GoToNextNode(DialogueNode nextNode)
|
||||
{
|
||||
if (nextNode == null)
|
||||
{
|
||||
OnDialogueCompleted?.Invoke(CurrentOwner);
|
||||
EndDialogue();
|
||||
return;
|
||||
}
|
||||
|
||||
UnlockFocus();
|
||||
|
||||
if (_activeDialogueUI != null)
|
||||
_activeDialogueUI.HideChoices();
|
||||
|
||||
_currentNode = nextNode;
|
||||
ShowCurrentNode();
|
||||
}
|
||||
|
||||
public void EndDialogue()
|
||||
{
|
||||
IsInDialogue = false;
|
||||
|
||||
if (_activeDialogueUI != null)
|
||||
_activeDialogueUI.HideDialogueBox();
|
||||
|
||||
if (_blurEnabled && DialogueBlurEffectURP.Instance != null)
|
||||
DialogueBlurEffectURP.Instance.HideBlur();
|
||||
|
||||
PlayerEvents.TriggerExitDialogue();
|
||||
CleanupDialogue();
|
||||
}
|
||||
|
||||
private void CleanupDialogue()
|
||||
{
|
||||
_currentChoiceAnchor = null;
|
||||
_currentAnimationSettings = null;
|
||||
_activeDialogueUI = null;
|
||||
CurrentOwner = null;
|
||||
_blurEnabled = false;
|
||||
|
||||
_currentConversation = null;
|
||||
_currentNode = null;
|
||||
_autoCloseTimer = 0f;
|
||||
_isWaitingForAutoClose = false;
|
||||
}
|
||||
|
||||
public void UnlockFocus()
|
||||
{
|
||||
_focusSystem?.UnlockFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user