Receber por email
← Voltar para o catálogo

dialogue-system

Dialogue tree patterns — ScriptableObject graph, node types (text, choice, condition, event), typewriter effect, localization-ready. Load when implementing NPC conversations.

20estrelas
Atualizado há 6 meses

Ver no GitHub ↗ Licença: MIT

Como adicionar

/plugin marketplace add XeldarAlz/everything-claude-unity

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/dialogue-system/svg)](https://www.skillteca.com.br/skills/dialogue-system)
#ai

Skills relacionadas

Ver todas de Marketing →

Alerta por categoria

Receba novas skills de Marketing toda segunda

1 email curto, só com as skills novas de Marketing. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

Dialogue System

Patterns for building a node-based dialogue tree system: define conversations as ScriptableObject graphs, process them at runtime with a DialogueRunner, display text with a typewriter effect, and integrate with quest/state systems through condition and event nodes.

Node Architecture

Dialogue is a directed graph of nodes. Each node has a unique ID and a type that determines its behavior.

Base Node

using UnityEngine;

public enum DialogueNodeType
{
    Text,
    Choice,
    Condition,
    Event
}

[System.Serializable]
public class DialogueNode
{
    public string nodeId;
    public DialogueNodeType nodeType;

    // Text node fields
    public string speakerName;
    public string speakerKey;       // Localization key for speaker name
    public Sprite speakerPortrait;
    public string text;
    public string textKey;          // Localization key: "dialogue.npc_greeting.001"
    public string nextNodeId;

    // Choice node fields
    public DialogueChoice[] choices;

    // Condition node fields
    public string conditionKey;     // Game state variable to check
    public string trueNodeId;
    public string falseNodeId;

    // Event node fields
    public string eventName;        // Event to trigger
    public string eventParameter;
    public string eventNextNodeId;
}

Choice Data

[System.Serializable]
public class DialogueChoice
{
    public string choiceText;
    public string choiceKey;        // Localization key
    public string nextNodeId;

    // Optional: conditions for showing this choice
    public string requiredConditionKey;
    public bool hideIfUnavailable;  // false = show grayed out; true = hide entirely
}

Dialogue Tree (ScriptableObject)

using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName = "New Dialogue", menuName = "Dialogue/Dialogue Tree")]
public class DialogueTree : ScriptableObject
{
    public string dialogueId;
    public string entryNodeId;
    public List<DialogueNode> nodes = new();

    private Dictionary<string, DialogueNode> _lookup;

    public DialogueNode GetNode(string nodeId)
    {
        if (_lookup == null) BuildLookup();
        _lookup.TryGetValue(nodeId, out var node);
        return node;
    }

    public DialogueNode GetEntryNode()
    {
        return GetNode(entryNodeId);
    }

    private void BuildLookup()
    {
        _lookup = new Dictionary<string, DialogueNode>();
        foreach (var node in nodes)
        {
            if (string.IsNullOrEmpty(node.nodeId)) continue;
            _lookup[node.nodeId] = node;
        }
    }

    private void OnEnable()
    {
        _lookup = null; // Force rebuild on load
    }
}

Speaker Definition

Keep speaker data (name, portrait variations, voice settings) in a separate ScriptableObject so multiple dialogues can share the same speaker.

using UnityEngine;

[CreateAssetMenu(fileName = "New Speaker", menuName = "Dialogue/Speaker")]
public class SpeakerDefinition : ScriptableObject
{
    public string speakerId;
    public string displayName;
    public string nameLocKey;
    public Sprite defaultPortrait;
    public Sprite[] emotionPortraits;   // Index by enum: Happy, Sad, Angry, etc.
    public Color nameColor = Color.white;

    [Header("Voice")]
    public AudioClip talkSound;         // Blip sound per character
    public float talkPitch = 1f;
}

public enum SpeakerEmotion
{
    Neutral,
    Happy,
    Sad,
    Angry,
    Surprised,
    Thinking
}

Dialogue Runner

The runtime component that processes the dialogue tree node by node. It mediates between the data (DialogueTree) and the presentation (DialogueUI).

using System;
using System.Collections;
using UnityEngine;

public class DialogueRunner : MonoBehaviour
{
    public static DialogueRunner Instance { get; private set; }

    [SerializeField] private DialogueUI dialogueUI;

    private DialogueTree _currentTree;
    private DialogueNode _currentNode;
    private bool _isRunning;

    public bool IsRunning => _isRunning;

    public event Action OnDialogueStarted;
    public event Action OnDialogueEnded;

    private void Awake()
    {
        Instance = this;
    }

    /// <summary>
    /// Start a conversation from the beginning of the given tree.
    /// </summary>
    public void StartDialogue(DialogueTree tree)
    {
        if (_isRunning) return;

        _currentTree = tree;
        _isRunning = true;

        OnDialogueStarted?.Invoke();
        dialogueUI.Show();

        ProcessNode(_currentTree.GetEntryNode());
    }

    private void ProcessNode(DialogueNode node)
    {
        if (node == null)
        {
            EndDialogue();
            return;
        }

        _currentNode = node;

        switch (node.nodeType)
        {
            case DialogueNodeType.Text:
                ProcessTextNode(node);
                break;
            case DialogueNodeType.Choice:
                ProcessChoiceNode(node);
                break;
            case DialogueNodeType.Condition:
                ProcessConditionNode(node);
                break;
            case DialogueNodeType.Event:
                ProcessEventNode(node);
                break;
        }
    }

    private void ProcessTextNode(DialogueNode node)
    {
        string displayText = GetLocalizedText(node.textKey, node.text);
        string displayName = GetLocalizedText(node.speakerKey, node.speakerName);

        dialogueUI.ShowText(displayName, displayText, node.speakerPortrait);
    }

    private void ProcessChoiceNode(DialogueNode node)
    {
        // Filter choices based on conditions
        var availableChoices = new System.Collections.Generic.List<DialogueChoice>();

        foreach (var choice in node.choices)
        {
            if (!string.IsNullOrEmpty(choice.requiredConditionKey))
            {
                bool conditionMet = GameStateManager.Instance.CheckCondition(choice.requiredConditionKey);
                if (!conditionMet && choice.hideIfUnavailable)
                    continue;
            }

            availableChoices.Add(choice);
        }

        dialogueUI.ShowChoices(availableChoices, OnChoiceSelected);
    }

    private void ProcessConditionNode(DialogueNode node)
    {
        bool result = GameStateManager.Instance.CheckCondition(node.conditionKey);
        string nextId = result ? node.trueNodeId : node.falseNodeId;
        ProcessNode(_currentTree.GetNode(nextId));
    }

    private void ProcessEventNode(DialogueNode node)
    {
        // Fire a game event (quest update, give item, play animation, etc.)
        GameEventManager.Instance.TriggerEvent(node.eventName, node.eventParameter);

        ProcessNode(_currentTree.GetNode(node.eventNextNodeId));
    }

    /// <summary>
    /// Called by UI when the player advances past a text node.
    /// </summary>
    public void AdvanceDialogue()
    {
        if (_currentNode == null || _currentNode.nodeType != DialogueNodeType.Text) return;

        // If typewriter is still running, complete it instantly
        if (dialogueUI.IsTyping)
        {
            dialogueUI.CompleteTypewriter();
            return;
        }

        // Move to next node
        string nextId = _currentNode.nextNodeId;
        if (string.IsNullOrEmpty(nextId))
        {
            EndDialogue();
        }
        else
        {
            ProcessNode(_currentTree.GetNode(nextId));
        }
    }

    private void OnChoiceSelected(int choiceIndex)
    {
        if (_currentNode == null || _currentNode.nodeType != DialogueNodeType.Choice) return;

        var choice = _currentNode.choices[choiceIndex];
        ProcessNode(_currentTree.GetNode(choice.nextNodeId));
    }

    public void EndDialogue()
    {
        _isRunning = false;
        _currentTree = null;
        _currentNode = null;

        dialogueUI.Hide();
        OnDialogueEnded?.Invoke();
    }

    private string GetLocalized
Compartilhar X LinkedIn