Advanced POC - portals, dialogue system + floating words, room loading

This commit is contained in:
2026-01-26 20:31:27 +01:00
parent b5ca4763b3
commit 203b00578a
426 changed files with 123482 additions and 29 deletions
@@ -0,0 +1,12 @@
namespace Unheimlich.Interactions.Dialogues
{
/// <summary>
/// Serializable data structure representing a single dialogue choice option.
/// Links choice text to the next dialogue node via ID.
/// </summary>
[System.Serializable]
public class DialogueChoice
{
public string ChoiceText; // Text on the choice button
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e7a6b90d8597f824a9451b58900227b9
@@ -0,0 +1,84 @@
using UnityEngine;
using Unheimlich.Data.Dialogues;
using Unheimlich.Managers;
using Unheimlich.UI;
namespace Unheimlich.Interactions.Dialogues
{
public class DialogueStateHandler
{
public bool IsCompleted { get; private set; }
public bool HasPausedDialogue => _pausedConversation != null && _pausedNode != null;
private DialogueConversation _pausedConversation;
private DialogueNode _pausedNode;
private Transform _pausedChoiceAnchor;
private DialogueAnimationSettings _pausedAnimationSettings;
private DialogueUI _pausedDialogueUI;
private object _owner;
public DialogueStateHandler(object owner)
{
_owner = owner;
}
public void PauseDialogue()
{
if (DialogueManager.Instance == null || !DialogueManager.Instance.IsDialogueActive)
return;
DialogueManager dialogueManager = DialogueManager.Instance;
_pausedConversation = dialogueManager.CurrentConversation;
_pausedNode = dialogueManager.CurrentNode;
_pausedChoiceAnchor = dialogueManager.CurrentChoiceAnchor;
_pausedAnimationSettings = dialogueManager.CurrentAnimationSettings;
_pausedDialogueUI = dialogueManager.CurrentDialogueUI;
DialogueManager.Instance.EndDialogue();
}
public void ResumeDialogue()
{
if (!HasPausedDialogue || DialogueManager.Instance == null)
return;
DialogueManager.Instance.StartConversation(
_pausedConversation,
_pausedChoiceAnchor,
_pausedAnimationSettings,
_pausedDialogueUI,
_owner
);
ClearPausedDialogue();
}
public void MarkAsCompleted()
{
IsCompleted = true;
ClearPausedDialogue();
}
public void AllowRetrigger()
{
IsCompleted = false;
}
public void Reset()
{
IsCompleted = false;
ClearPausedDialogue();
}
private void ClearPausedDialogue()
{
_pausedConversation = null;
_pausedNode = null;
_pausedChoiceAnchor = null;
_pausedAnimationSettings = null;
_pausedDialogueUI = null;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b328dc63103543940898cfa2dbd4ec36
@@ -0,0 +1,185 @@
using UnityEngine;
using Unheimlich.Data.Dialogues;
using Unheimlich.Managers;
using Unheimlich.UI;
namespace Unheimlich.Interactions.Dialogues
{
/// <summary>
/// Trigger zone that starts a dialogue sequence when player enters.
/// Supports pause/resume on exit/enter and optional one-time triggering.
/// </summary>
[RequireComponent(typeof(Collider))]
public class DialogueTriggerZone : MonoBehaviour
{
[Header("Dialogue Reference")]
[Tooltip("DialogueConversation to use for this trigger zone")]
[SerializeField] private DialogueConversation _conversation;
[Header("Trigger Settings")]
[Tooltip("Can the dialogue be retriggered after completion")]
[SerializeField] private bool _isRetriggerable = false;
[SerializeField] private bool _requirePlayerTag = true;
[SerializeField] private string _playerTag = "Player";
[Header("Distance Check")]
[Tooltip("Pause dialogue if player exits the trigger zone")]
[SerializeField] private bool _pauseOnExit = true;
[Header("Floating Word Anchor")]
[SerializeField] private Transform _floatingWordAnchor;
[Header("Animation Settings")]
[SerializeField] private DialogueAnimationSettings _animationSettings;
[Header("UI Reference")]
[Tooltip("Local DialogueUI for this trigger zone. Uses DialogueManager's UI if null")]
[SerializeField] private DialogueUI _localDialogueUI;
[Header("Blur Effect")]
[Tooltip("Enable blur effect when dialogue starts")]
[SerializeField] private bool _enableBlurEffect = true;
private bool _hasTriggered = false;
private Collider _triggerCollider;
private DialogueStateHandler _dialogueState;
private void Awake()
{
_triggerCollider = GetComponent<Collider>();
_dialogueState = new DialogueStateHandler(this);
if (!_triggerCollider.isTrigger)
{
Debug.LogWarning($"{gameObject.name}: Collider should be marked as Trigger!", this);
_triggerCollider.isTrigger = true;
}
if (_floatingWordAnchor == null)
_floatingWordAnchor = transform;
}
private void OnTriggerEnter(Collider other)
{
if (_requirePlayerTag && !other.CompareTag(_playerTag))
return;
if (_dialogueState.IsCompleted && !_isRetriggerable)
return;
if (_dialogueState.HasPausedDialogue)
{
_dialogueState.ResumeDialogue();
if (_enableBlurEffect && DialogueBlurEffectURP.Instance != null)
DialogueBlurEffectURP.Instance.ShowBlur();
}
else if (!_hasTriggered || (_isRetriggerable && _dialogueState.IsCompleted))
{
TriggerDialogue();
}
}
private void OnTriggerExit(Collider other)
{
if (_requirePlayerTag && !other.CompareTag(_playerTag))
return;
if (_pauseOnExit && DialogueManager.Instance != null && DialogueManager.Instance.IsDialogueActive)
{
if ((Object)DialogueManager.Instance.CurrentOwner == this)
{
_dialogueState.PauseDialogue();
if (_enableBlurEffect && DialogueBlurEffectURP.Instance != null)
DialogueBlurEffectURP.Instance.HideBlur();
}
}
}
private void TriggerDialogue()
{
if (DialogueManager.Instance == null)
{
Debug.LogError("DialogueManager not found in scene!", this);
return;
}
if (_conversation == null)
{
Debug.LogError($"{gameObject.name}: No conversation assigned!", this);
return;
}
_hasTriggered = true;
if (_enableBlurEffect && DialogueBlurEffectURP.Instance != null)
{
DialogueBlurEffectURP.Instance.ShowBlur();
}
DialogueManager.Instance.StartConversation(
_conversation,
_floatingWordAnchor,
_animationSettings,
_localDialogueUI,
this
);
DialogueManager.Instance.OnDialogueCompleted += OnDialogueCompleted;
}
private void OnDialogueCompleted(object owner)
{
if ((Object)owner == this)
{
_dialogueState.MarkAsCompleted();
if (_isRetriggerable)
{
_hasTriggered = false;
_dialogueState.AllowRetrigger();
}
if (_enableBlurEffect && DialogueBlurEffectURP.Instance != null)
DialogueBlurEffectURP.Instance.HideBlur();
}
if (DialogueManager.Instance != null)
DialogueManager.Instance.OnDialogueCompleted -= OnDialogueCompleted;
}
public void ResetTrigger()
{
_hasTriggered = false;
_dialogueState.Reset();
}
private void OnDestroy()
{
if (DialogueManager.Instance != null)
DialogueManager.Instance.OnDialogueCompleted -= OnDialogueCompleted;
}
private void OnDrawGizmosSelected()
{
Collider col = GetComponent<Collider>();
if (col != null)
{
Gizmos.color = _hasTriggered ? Color.gray : Color.yellow;
Gizmos.matrix = transform.localToWorldMatrix;
if (col is BoxCollider box)
Gizmos.DrawWireCube(box.center, box.size);
else if (col is SphereCollider sphere)
Gizmos.DrawWireSphere(sphere.center, sphere.radius);
}
if (_floatingWordAnchor != null)
{
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(_floatingWordAnchor.position, 0.2f);
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f33be20afd5d4c843a800ba1d8dd8437
@@ -0,0 +1,142 @@
using UnityEngine;
using Unheimlich.Interactions.Core;
using Unheimlich.Data.Dialogues;
using Unheimlich.Managers;
using Unheimlich.Gameplay.Player;
using Unheimlich.UI;
namespace Unheimlich.Interactions.Dialogues
{
/// <summary>
/// NPC interactable that starts dialogue conversations.
/// Rotates to face player during dialogue and pauses if player moves too far away.
/// </summary>
public class NPCDialogueTrigger : InteractableBase
{
[Header("Dialogue Reference")]
[Tooltip("DialogueConversation to use for this NPC")]
public DialogueConversation Conversation;
[Header("Trigger Settings")]
[Tooltip("Can the dialogue be retriggered after completion")]
[SerializeField] private bool _isRetriggerable = true;
[Header("Floating Word Setting")]
[SerializeField] private Transform _floatingWordAnchor;
[Header("Animation Settings")]
[SerializeField] private DialogueAnimationSettings _animationSettings;
[Header("UI Reference")]
[Tooltip("Local DialogueUI for this NPC. Uses DialogueManager's UI if null")]
[SerializeField] private DialogueUI _localDialogueUI;
[Header("Distance Settings")]
[Tooltip("Maximum distance before dialogue pauses")]
[SerializeField] private float _maxDialogueDistance = 5f;
[Header("Blur Effect")]
[Tooltip("Enable blur effect when dialogue starts")]
[SerializeField] private bool _enableBlurEffect = false;
public Transform FloatingWordAnchor => _floatingWordAnchor;
private bool _isDialogueActive = false;
private DialogueStateHandler _dialogueState;
private void Awake()
{
_dialogueState = new DialogueStateHandler(this);
}
private void Start()
{
InteractionText = "Talk";
}
private void Update()
{
if (_isDialogueActive && Player.Instance != null)
{
float distance = Vector3.Distance(transform.position, Player.Instance.transform.position);
if (distance > _maxDialogueDistance && DialogueManager.Instance != null && DialogueManager.Instance.IsDialogueActive)
{
if ((Object)DialogueManager.Instance.CurrentOwner == this)
{
_dialogueState.PauseDialogue();
_isDialogueActive = false;
}
}
else
LookAtPlayer();
}
}
public override void Interact(GameObject interactor)
{
if (DialogueManager.Instance == null)
return;
if (_dialogueState.IsCompleted && !_isRetriggerable)
return;
if (_dialogueState.HasPausedDialogue)
{
_dialogueState.ResumeDialogue();
DialogueManager.Instance.OnDialogueCompleted += OnDialogueCompleted;
_isDialogueActive = true;
if (_enableBlurEffect && DialogueBlurEffectURP.Instance != null)
DialogueBlurEffectURP.Instance.ShowBlur();
}
else
{
if (Conversation == null)
{
Debug.LogWarning($"NPC {gameObject.name} has no conversation configured!");
return;
}
DialogueManager.Instance.StartConversation(Conversation, _floatingWordAnchor, _animationSettings, _localDialogueUI, this);
DialogueManager.Instance.OnDialogueCompleted += OnDialogueCompleted;
_isDialogueActive = true;
if (_enableBlurEffect && DialogueBlurEffectURP.Instance != null)
DialogueBlurEffectURP.Instance.ShowBlur();
}
}
private void OnDialogueCompleted(object owner)
{
if ((Object)owner == this)
{
_dialogueState.MarkAsCompleted();
if (_isRetriggerable)
_dialogueState.AllowRetrigger();
_isDialogueActive = false;
}
if (DialogueManager.Instance != null)
DialogueManager.Instance.OnDialogueCompleted -= OnDialogueCompleted;
}
private void OnDestroy()
{
if (DialogueManager.Instance != null)
DialogueManager.Instance.OnDialogueCompleted -= OnDialogueCompleted;
}
public void LookAtPlayer()
{
if (Player.Instance == null) return;
Vector3 lookAtPosition = Player.Instance.transform.position;
lookAtPosition.y = transform.position.y;
transform.LookAt(lookAtPosition);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8696519b7eabaab4dbe80f2a4744cea0