Working regular dialogue and speech bubble. Item conditions need work

This commit is contained in:
Michal Pikulski
2025-09-27 14:08:27 +02:00
parent 4c1be82cf2
commit 665cfa89c6
18 changed files with 3052 additions and 49 deletions

View File

@@ -16,6 +16,7 @@ namespace Dialogue
private RuntimeDialogueNode currentNode;
private int currentLineIndex;
private bool initialized = false;
private SpeechBubble speechBubble;
// Properties
public bool IsActive { get; private set; }
@@ -37,10 +38,33 @@ namespace Dialogue
ItemManager.Instance.OnCorrectItemSlotted += OnAnyItemSlotted;
}
speechBubble = GetComponentInChildren<SpeechBubble>();
if (speechBubble == null)
{
Debug.LogError("SpeechBubble component is missing on Dialogue Component");
}
// Auto-start the dialogue
StartDialogue();
var interactable = GetComponent<Interactable>();
if (interactable != null)
{
interactable.characterArrived.AddListener(OnCharacterArrived);
}
if (HasAnyLines())
{
speechBubble.SetText(". . .");
}
}
private void OnCharacterArrived()
{
speechBubble.SetText(GetCurrentDialogueLine());
}
private void OnDestroy()
{
// Unregister from events
@@ -314,6 +338,49 @@ namespace Dialogue
return false;
}
/// <summary>
/// Checks if the dialogue component has any lines available to serve
/// </summary>
/// <returns>True if there are lines available, false otherwise</returns>
public bool HasAnyLines()
{
if (!initialized)
{
// If not initialized yet but has a dialogue graph, it will have lines when initialized
return dialogueGraph != null;
}
// No lines if dialogue is not active or is completed
if (!IsActive || IsCompleted || currentNode == null)
return false;
// Check if the current node has any lines
if (currentNode.dialogueLines.Count > 0)
{
// If we're not at the end of the lines or we loop through them
if (currentLineIndex < currentNode.dialogueLines.Count - 1 || currentNode.loopThroughLines)
{
return true;
}
// If we're at the end of lines but not waiting for a condition and have a next node
if (!IsWaitingForCondition() && !string.IsNullOrEmpty(currentNode.nextNodeID))
{
// We need to check if the next node would have lines
RuntimeDialogueNode nextNode = dialogueGraph.GetNodeByID(currentNode.nextNodeID);
return nextNode != null && (nextNode.dialogueLines.Count > 0 || nextNode.nodeType != RuntimeDialogueNodeType.End);
}
}
// Special case for conditional nodes waiting on conditions
if (IsWaitingForCondition())
{
return currentNode.dialogueLines.Count > 0;
}
return false;
}
// Editor functionality
public void SetDialogueGraph(RuntimeDialogueGraph graph)
{

View File

@@ -0,0 +1,193 @@
using System;
using System.Collections;
using TMPro;
using UnityEngine;
namespace Dialogue
{
/// <summary>
/// Display mode for the speech bubble text
/// </summary>
public enum TextDisplayMode
{
Instant, // Display all text at once
Typewriter // Display text one character at a time
}
[AddComponentMenu("Apple Hills/Dialogue/Speech Bubble")]
public class SpeechBubble : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI textDisplay;
[SerializeField] private TextDisplayMode displayMode = TextDisplayMode.Typewriter;
[SerializeField] private float typewriterSpeed = 0.05f; // Time between characters in seconds
[SerializeField] private AudioSource typingSoundSource;
[SerializeField] private float typingSoundFrequency = 3; // Play sound every X characters
[SerializeField] private bool useRichText = true; // Whether to respect rich text tags
private Coroutine typewriterCoroutine;
private string currentFullText = string.Empty;
private bool isVisible = false;
private void Awake()
{
}
/// <summary>
/// Show the speech bubble
/// </summary>
public void Show()
{
gameObject.SetActive(true);
isVisible = true;
}
/// <summary>
/// Hide the speech bubble
/// </summary>
public void Hide()
{
gameObject.SetActive(false);
isVisible = false;
// Stop any ongoing typewriter effect
if (typewriterCoroutine != null)
{
StopCoroutine(typewriterCoroutine);
typewriterCoroutine = null;
}
}
/// <summary>
/// Toggle visibility of the speech bubble
/// </summary>
public void Toggle()
{
if (isVisible)
Hide();
else
Show();
}
/// <summary>
/// Set the text to display in the speech bubble
/// </summary>
/// <param name="text">Text to display</param>
public void SetText(string text)
{
if (textDisplay == null)
{
Debug.LogError("SpeechBubble: TextMeshProUGUI component is not assigned!");
return;
}
currentFullText = text;
// Stop any existing typewriter effect
if (typewriterCoroutine != null)
{
StopCoroutine(typewriterCoroutine);
typewriterCoroutine = null;
}
// Display text based on the selected mode
if (displayMode == TextDisplayMode.Instant)
{
textDisplay.text = text;
}
else // Typewriter mode
{
textDisplay.text = string.Empty; // Clear the text initially
typewriterCoroutine = StartCoroutine(TypewriterEffect(text));
}
// Make sure the bubble is visible when setting text
if (!isVisible)
Show();
}
/// <summary>
/// Change the display mode
/// </summary>
/// <param name="mode">New display mode</param>
public void SetDisplayMode(TextDisplayMode mode)
{
displayMode = mode;
// If we're changing modes while text is displayed, refresh it
if (!string.IsNullOrEmpty(currentFullText))
{
SetText(currentFullText);
}
}
/// <summary>
/// Skip the typewriter effect and show the full text immediately
/// </summary>
public void SkipTypewriter()
{
if (typewriterCoroutine != null)
{
StopCoroutine(typewriterCoroutine);
typewriterCoroutine = null;
textDisplay.text = currentFullText;
}
}
/// <summary>
/// Set the speed of the typewriter effect
/// </summary>
/// <param name="charactersPerSecond">Characters per second</param>
public void SetTypewriterSpeed(float charactersPerSecond)
{
if (charactersPerSecond <= 0)
{
Debug.LogError("SpeechBubble: Typewriter speed must be greater than 0!");
return;
}
typewriterSpeed = 1f / charactersPerSecond;
}
/// <summary>
/// Coroutine that gradually reveals text one character at a time
/// </summary>
private IEnumerator TypewriterEffect(string text)
{
int visibleCount = 0;
int characterCount = 0;
while (visibleCount < text.Length)
{
// Skip rich text tags if enabled
if (useRichText && visibleCount < text.Length && text[visibleCount] == '<')
{
// Find the end of the tag
int tagEnd = text.IndexOf('>', visibleCount);
if (tagEnd != -1)
{
// Include the entire tag at once
visibleCount = tagEnd + 1;
textDisplay.text = text.Substring(0, visibleCount);
continue;
}
}
// Reveal the next character
visibleCount++;
characterCount++;
textDisplay.text = text.Substring(0, visibleCount);
// Play typing sound at specified frequency
if (typingSoundSource != null && characterCount % typingSoundFrequency == 0)
{
typingSoundSource.Play();
}
yield return new WaitForSeconds(typewriterSpeed);
}
typewriterCoroutine = null;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cb3605ae81a54d2689504e0cd456ac27
timeCreated: 1758973942