Working on dialogue manager
This commit is contained in:
parent
7bec58ece8
commit
7962b96976
@ -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: []
|
||||
|
8
Assets/Scripts/Dialogues.meta
Normal file
8
Assets/Scripts/Dialogues.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f4c15fc4e9fb7a40b4e9a3abced59cc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
57
Assets/Scripts/Dialogues/DialogueAsset.cs
Normal file
57
Assets/Scripts/Dialogues/DialogueAsset.cs
Normal 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;
|
||||
}
|
||||
}
|
11
Assets/Scripts/Dialogues/DialogueAsset.cs.meta
Normal file
11
Assets/Scripts/Dialogues/DialogueAsset.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8603d6a0b297c554fbd9be8adf7daecf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
131
Assets/Scripts/Dialogues/DialogueManager.cs
Normal file
131
Assets/Scripts/Dialogues/DialogueManager.cs
Normal 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);
|
||||
}
|
||||
}
|
11
Assets/Scripts/Dialogues/DialogueManager.cs.meta
Normal file
11
Assets/Scripts/Dialogues/DialogueManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c11bb4d5be167a240beb9566d35772ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
114
Assets/Scripts/Dialogues/DialogueUI.cs
Normal file
114
Assets/Scripts/Dialogues/DialogueUI.cs
Normal 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);
|
||||
}
|
||||
}
|
11
Assets/Scripts/Dialogues/DialogueUI.cs.meta
Normal file
11
Assets/Scripts/Dialogues/DialogueUI.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88612b9886a695c4faf4af1fa2800a2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
36
Assets/Scripts/Dialogues/NPCDialogueTrigger.cs
Normal file
36
Assets/Scripts/Dialogues/NPCDialogueTrigger.cs
Normal 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);
|
||||
}
|
||||
}
|
11
Assets/Scripts/Dialogues/NPCDialogueTrigger.cs.meta
Normal file
11
Assets/Scripts/Dialogues/NPCDialogueTrigger.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cafcb3d2d5194794089b630c916b6084
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
x
Reference in New Issue
Block a user