Working on dialogue manager

This commit is contained in:
Hazim Bin Ijaz 2025-08-30 00:39:34 +05:00
parent 7bec58ece8
commit 7962b96976
10 changed files with 392 additions and 2 deletions

View File

@ -191096,7 +191096,7 @@ Transform:
m_GameObject: {fileID: 1236565186}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 141.0367, y: 26.4137, z: 389.6348}
m_LocalPosition: {x: 140.95, y: 26.4137, z: 389.6348}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
@ -198170,7 +198170,7 @@ Transform:
m_GameObject: {fileID: 1282845310}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: 2.3, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8f4c15fc4e9fb7a40b4e9a3abced59cc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
[CreateAssetMenu(menuName = "Dialogue/Dialogue Asset", fileName = "NewDialogue")]
public class DialogueAsset : ScriptableObject
{
[Tooltip("Start from this node id when dialogue begins")]
public int startNodeId = 0;
public List<Node> nodes = new();
[Serializable]
public class Node
{
public int id = 0;
public string speakerName;
public Sprite speakerPortrait;
[Tooltip("Shown in order. If more than one, player taps/presses to advance within the same node.")]
[TextArea(2, 4)] public List<string> lines = new();
[Tooltip("Invoked when this node is entered (before showing text).")]
public UnityEvent onEnter;
[Tooltip("If no choices and nextNodeId < 0, dialogue ends after this node.")]
public int nextNodeId = -1;
public List<Choice> choices = new();
}
[Serializable]
public class Choice
{
[TextArea(1, 2)] public string text;
[Tooltip("Go to this node when chosen. If < 0, ends dialogue.")]
public int nextNodeId = -1;
[Header("Conditions / Effects")]
[Tooltip("If non-empty, this choice is only visible when the flag is present in DialogueManager.")]
public string requireFlag;
[Tooltip("If non-empty, set this flag on choose (stored in DialogueManager).")]
public string setFlag;
[Tooltip("Optional SFX or other hook.")]
public UnityEvent onChoose;
}
public bool TryGetNode(int id, out Node node)
{
node = nodes.Find(n => n.id == id);
return node != null;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8603d6a0b297c554fbd9be8adf7daecf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,131 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DialogueManager : MonoBehaviour
{
public static DialogueManager Instance { get; private set; }
[Header("Hook up the UI component here")]
public DialogueUI ui;
[Header("Flags saved during play (simple global state)")]
public HashSet<string> flags = new();
DialogueAsset _active;
DialogueAsset.Node _node;
int _lineIndex;
bool _isTyping;
Coroutine _typeRoutine;
void Awake()
{
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
}
// Public API -------------
public bool IsRunning => _active != null;
public void StartDialogue(DialogueAsset asset)
{
if (asset == null) return;
_active = asset;
GoToNode(asset.startNodeId);
ui.Show(true);
}
public void StopDialogue()
{
_active = null;
_node = null;
ui.Show(false);
}
public void Next() // Called by UI "Continue" button / input
{
if (!IsRunning || _node == null) return;
if (_isTyping)
{
ui.CompleteTypeThisFrame();
_isTyping = false;
return;
}
// Advance line within node
_lineIndex++;
if (_lineIndex < _node.lines.Count)
{
StartTyping(_node.lines[_lineIndex]);
}
else
{
// Lines finished -> show choices or auto-next / end
var visibleChoices = ui.BuildVisibleChoices(_node.choices, flags);
if (visibleChoices.Count > 0)
{
ui.ShowChoices(visibleChoices, OnChoicePicked);
}
else
{
if (_node.nextNodeId >= 0) GoToNode(_node.nextNodeId);
else StopDialogue();
}
}
}
// Internal -------------
void GoToNode(int id)
{
if (!_active.TryGetNode(id, out _node))
{
Debug.LogWarning($"Dialogue node {id} not found.");
StopDialogue();
return;
}
_lineIndex = 0;
_node.onEnter?.Invoke();
ui.BindSpeaker(_node.speakerName, _node.speakerPortrait);
if (_node.lines.Count == 0)
{
// Empty node: immediately resolve choices/next
var visibleChoices = ui.BuildVisibleChoices(_node.choices, flags);
if (visibleChoices.Count > 0) ui.ShowChoices(visibleChoices, OnChoicePicked);
else if (_node.nextNodeId >= 0) GoToNode(_node.nextNodeId);
else StopDialogue();
}
else
{
StartTyping(_node.lines[_lineIndex]);
}
}
void StartTyping(string text)
{
ui.HideChoices();
if (_typeRoutine != null) StopCoroutine(_typeRoutine);
_typeRoutine = StartCoroutine(TypeRoutine(text));
}
IEnumerator TypeRoutine(string text)
{
_isTyping = true;
yield return ui.TypeText(text);
_isTyping = false;
}
void OnChoicePicked(DialogueAsset.Choice choice)
{
choice?.onChoose?.Invoke();
if (!string.IsNullOrEmpty(choice.setFlag)) flags.Add(choice.setFlag);
if (choice == null || choice.nextNodeId < 0) { StopDialogue(); return; }
GoToNode(choice.nextNodeId);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c11bb4d5be167a240beb9566d35772ae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,114 @@
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class DialogueUI : MonoBehaviour
{
[Header("References")]
public CanvasGroup root; // Whole dialogue panel
public TMP_Text speakerNameText;
public Image speakerPortraitImage;
public TMP_Text bodyText;
[Header("Choices")]
public Transform choicesParent;
public Button choiceButtonPrefab;
[Header("Typing")]
[Range(0.005f, 0.1f)] public float charDelay = 0.02f;
string _targetFull;
bool _completeThisFrame;
void Reset()
{
root = GetComponent<CanvasGroup>();
}
public void Show(bool on)
{
if (!root) return;
root.alpha = on ? 1f : 0f;
root.interactable = on;
root.blocksRaycasts = on;
if (!on)
{
bodyText?.SetText(string.Empty);
HideChoices();
}
}
public void BindSpeaker(string speakerName, Sprite portrait)
{
if (speakerNameText) speakerNameText.text = speakerName ?? "";
if (speakerPortraitImage)
{
speakerPortraitImage.sprite = portrait;
speakerPortraitImage.enabled = portrait != null;
}
}
public IEnumerator TypeText(string text)
{
_targetFull = text ?? string.Empty;
bodyText.text = string.Empty;
_completeThisFrame = false;
foreach (char c in _targetFull)
{
if (_completeThisFrame) break;
bodyText.text += c;
yield return new WaitForSeconds(charDelay);
}
bodyText.text = _targetFull;
_completeThisFrame = false;
}
public void CompleteTypeThisFrame()
{
_completeThisFrame = true;
}
public List<DialogueAsset.Choice> BuildVisibleChoices(
List<DialogueAsset.Choice> all,
HashSet<string> flags)
{
var list = new List<DialogueAsset.Choice>();
if (all == null) return list;
foreach (var c in all)
{
if (string.IsNullOrEmpty(c.requireFlag) || flags.Contains(c.requireFlag))
list.Add(c);
}
return list;
}
public void ShowChoices(
List<DialogueAsset.Choice> choices,
Action<DialogueAsset.Choice> onPick)
{
HideChoices();
foreach (var c in choices)
{
var btn = Instantiate(choiceButtonPrefab, choicesParent);
var label = btn.GetComponentInChildren<TMP_Text>();
if (label) label.text = c.text;
btn.onClick.AddListener(() => onPick?.Invoke(c));
}
}
public void HideChoices()
{
if (!choicesParent) return;
for (int i = choicesParent.childCount - 1; i >= 0; i--)
Destroy(choicesParent.GetChild(i).gameObject);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 88612b9886a695c4faf4af1fa2800a2e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
using UnityEngine;
public class NPCDialogueTrigger : MonoBehaviour
{
public DialogueAsset dialogue;
public string interactKey = "e";
public float interactRange = 2.2f;
Transform _player;
void Start()
{
var p = GameObject.FindGameObjectWithTag("Player");
_player = p ? p.transform : null;
}
void Update()
{
if (DialogueManager.Instance && DialogueManager.Instance.IsRunning) return;
if (_player == null) return;
if (Vector3.Distance(transform.position, _player.position) <= interactRange)
{
if (Input.GetKeyDown(interactKey.ToLower()))
{
DialogueManager.Instance.StartDialogue(dialogue);
}
}
}
void OnDrawGizmosSelected()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, interactRange);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cafcb3d2d5194794089b630c916b6084
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: