godot-dialogue-system by thedivergentai/gd-agentic-skills
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-dialogue-system构建灵活、数据驱动的对话系统的专家指南。
使用资源的数据驱动对话树容器,用于模块化、分支叙事路径。
单行对话的序列化数据结构,包含说话者元数据和肖像。
具有分支逻辑和可编写脚本的可用性条件的交互式玩家选择定义。
用于遍历对话树和广播状态信号的集中式 AutoLoad 编排器。
反应式 UI 桥接器,将对话数据映射到视觉标签和动态选择按钮。
使用 Godot 内置 Tween 实现的精美“逐字符”文本显示效果。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
用于从对话节点触发外部游戏事件(例如开始任务)的桥接节点。
用于评估玩家状态或全局标志以切换对话选项的专家逻辑。
通过翻译键支持多语言对话文本的高级策略。
用于管理对话期间角色表情和入场动画的视觉控制器。
next_node_id 中的拼写错误会导致对话中途崩溃。使用 assert() 或中央 ID 注册表 [14]。DialogueManager [16]。get_node() 从 NPC 脚本中查找对话 UI —— 使用像 DialogueManager.start_dialogue(res) 这样的信号来维持解耦的架构。RichTextLabel 原生支持 BBCode 标签。使用 [b]、[i] 和 [url] 进行格式化。SaveSystem。OS.delay_msec()。使用 create_timer() 或 Tween 以保持流畅的 60fps 性能。DialogueNode 资源,或使用中央 PortraitDatabase。强制要求:在实现相应模式之前,请阅读适当的脚本。
基于图的对话,带有 BBCode 信号标签。解析文本中的 [trigger:event_id] 标签,触发信号,并加载外部 JSON 对话图。
数据驱动的对话引擎,具有分支、变量存储和条件选择功能。
# dialogue_line.gd
class_name DialogueLine
extends Resource
@export var speaker: String
@export_multiline var text: String
@export var portrait: Texture2D
@export var choices: Array[DialogueChoice] = []
@export var conditions: Array[String] = [] # Quest flags, etc.
@export var next_line_id: String = ""
# dialogue_choice.gd
class_name DialogueChoice
extends Resource
@export var choice_text: String
@export var next_line_id: String
@export var conditions: Array[String] = []
@export var effects: Array[String] = [] # Set flags, give items
# dialogue_manager.gd (AutoLoad)
extends Node
signal dialogue_started
signal dialogue_ended
signal line_displayed(line: DialogueLine)
signal choice_selected(choice: DialogueChoice)
var dialogues: Dictionary = {}
var flags: Dictionary = {}
func load_dialogue(path: String) -> void:
var data := load(path)
dialogues[path] = data
func start_dialogue(dialogue_id: String, start_line: String = "start") -> void:
dialogue_started.emit()
display_line(dialogue_id, start_line)
func display_line(dialogue_id: String, line_id: String) -> void:
var line: DialogueLine = dialogues[dialogue_id].lines[line_id]
# Check conditions
if not check_conditions(line.conditions):
# Skip to next
if line.next_line_id:
display_line(dialogue_id, line.next_line_id)
else:
end_dialogue()
return
line_displayed.emit(line)
# Auto-advance or wait for player
if line.choices.is_empty() and line.next_line_id:
# Wait for player to click
await get_tree().create_timer(0.1).timeout
elif line.choices.is_empty():
end_dialogue()
func select_choice(dialogue_id: String, choice: DialogueChoice) -> void:
choice_selected.emit(choice)
# Apply effects
for effect in choice.effects:
apply_effect(effect)
# Continue to next line
if choice.next_line_id:
display_line(dialogue_id, choice.next_line_id)
else:
end_dialogue()
func end_dialogue() -> void:
dialogue_ended.emit()
func check_conditions(conditions: Array[String]) -> bool:
for condition in conditions:
if not flags.get(condition, false):
return false
return true
func apply_effect(effect: String) -> void:
# Parse effect string, e.g., "set_flag:met_npc"
var parts := effect.split(":")
match parts[0]:
"set_flag":
flags[parts[1]] = true
"give_item":
# Integration with inventory
pass
# dialogue_ui.gd
extends Control
@onready var speaker_label := $Panel/Speaker
@onready var text_label := $Panel/Text
@onready var portrait := $Panel/Portrait
@onready var choices_container := $Panel/Choices
var current_dialogue: String
var current_line: DialogueLine
func _ready() -> void:
DialogueManager.line_displayed.connect(_on_line_displayed)
DialogueManager.dialogue_ended.connect(_on_dialogue_ended)
visible = false
func _on_line_displayed(line: DialogueLine) -> void:
visible = true
current_line = line
speaker_label.text = line.speaker
portrait.texture = line.portrait
# Typewriter effect
text_label.text = ""
for char in line.text:
text_label.text += char
await get_tree().create_timer(0.03).timeout
# Show choices
if line.choices.is_empty():
# Wait for input to continue
pass
else:
show_choices(line.choices)
func show_choices(choices: Array[DialogueChoice]) -> void:
# Clear existing
for child in choices_container.get_children():
child.queue_free()
# Add choice buttons
for choice in choices:
if not DialogueManager.check_conditions(choice.conditions):
continue
var button := Button.new()
button.text = choice.choice_text
button.pressed.connect(func(): _on_choice_selected(choice))
choices_container.add_child(button)
func _on_choice_selected(choice: DialogueChoice) -> void:
DialogueManager.select_choice(current_dialogue, choice)
func _on_dialogue_ended() -> void:
visible = false
# npc.gd
extends CharacterBody2D
@export var dialogue_path: String = "res://dialogues/npc_1.tres"
@export var start_line: String = "start"
func interact() -> void:
DialogueManager.start_dialogue(dialogue_path, start_line)
# dialogue_graph.gd
class_name DialogueGraph
extends Resource
@export var lines: Dictionary = {} # line_id → DialogueLine
func _init() -> void:
# Example structure
lines["start"] = create_line("Hero", "Hello!")
lines["response"] = create_line("NPC", "Greetings, traveler!")
func create_line(speaker: String, text: String) -> DialogueLine:
var line := DialogueLine.new()
line.speaker = speaker
line.text = text
return line
# Use Godot's built-in CSV import
# dialogue_en.csv:
# dialogue_id,speaker,text
# npc_1_start,Hero,"Hello!"
# npc_1_response,NPC,"Greetings!"
func get_localized_line(line_id: String) -> String:
return tr(line_id)
@onready var voice_player := $AudioStreamPlayer
func play_voice_line(line_id: String) -> void:
var audio := load("res://voice/" + line_id + ".mp3")
if audio:
voice_player.stream = audio
voice_player.play()
godot-signal-architecture、godot-save-load-systems、godot-ui-rich-text每周安装数
78
仓库
GitHub 星标数
59
首次出现
2026年2月10日
安全审计
安装于
opencode76
gemini-cli74
codex74
github-copilot73
kimi-cli72
amp72
Expert guidance for building flexible, data-driven dialogue systems.
Data-driven conversation tree container using Resources for modular, branching narrative paths.
Serialized data structure for a single line of dialogue, including speaker metadata and portraits.
Interactive player choice definition with branching logic and scriptable availability conditions.
Centralized AutoLoad orchestrator for traversing dialogue trees and broadcasting state signals.
Reactive UI bridge that maps dialogue data to visual labels and dynamic choice buttons.
Polished "Character-by-character" text reveal effect using Godot's built-in Tweens.
Bridge node for triggering external game events (e.g. starting a quest) from conversation nodes.
Expert logic for evaluating player stats or global flags to toggle dialogue choices.
Advanced strategy for supporting multi-language conversation text via translation keys.
Visual controller for managing character expressions and entry animations during dialogue.
next_node_id will crash the dialogue mid-convo. Use assert() or a central ID registry [14].DialogueManager [16].get_node() to find dialogue UI from the NPC script — Use signals like DialogueManager.start_dialogue(res) to maintain a decoupled architecture.RichTextLabel supports BBCode tags natively. Use , , and for formatting.MANDATORY : Read the appropriate script before implementing the corresponding pattern.
Graph-based dialogue with BBCode signal tags. Parses [trigger:event_id] tags from text, fires signals, and loads external JSON dialogue graphs.
Data-driven dialogue engine with branching, variable storage, and conditional choices.
# dialogue_line.gd
class_name DialogueLine
extends Resource
@export var speaker: String
@export_multiline var text: String
@export var portrait: Texture2D
@export var choices: Array[DialogueChoice] = []
@export var conditions: Array[String] = [] # Quest flags, etc.
@export var next_line_id: String = ""
# dialogue_choice.gd
class_name DialogueChoice
extends Resource
@export var choice_text: String
@export var next_line_id: String
@export var conditions: Array[String] = []
@export var effects: Array[String] = [] # Set flags, give items
# dialogue_manager.gd (AutoLoad)
extends Node
signal dialogue_started
signal dialogue_ended
signal line_displayed(line: DialogueLine)
signal choice_selected(choice: DialogueChoice)
var dialogues: Dictionary = {}
var flags: Dictionary = {}
func load_dialogue(path: String) -> void:
var data := load(path)
dialogues[path] = data
func start_dialogue(dialogue_id: String, start_line: String = "start") -> void:
dialogue_started.emit()
display_line(dialogue_id, start_line)
func display_line(dialogue_id: String, line_id: String) -> void:
var line: DialogueLine = dialogues[dialogue_id].lines[line_id]
# Check conditions
if not check_conditions(line.conditions):
# Skip to next
if line.next_line_id:
display_line(dialogue_id, line.next_line_id)
else:
end_dialogue()
return
line_displayed.emit(line)
# Auto-advance or wait for player
if line.choices.is_empty() and line.next_line_id:
# Wait for player to click
await get_tree().create_timer(0.1).timeout
elif line.choices.is_empty():
end_dialogue()
func select_choice(dialogue_id: String, choice: DialogueChoice) -> void:
choice_selected.emit(choice)
# Apply effects
for effect in choice.effects:
apply_effect(effect)
# Continue to next line
if choice.next_line_id:
display_line(dialogue_id, choice.next_line_id)
else:
end_dialogue()
func end_dialogue() -> void:
dialogue_ended.emit()
func check_conditions(conditions: Array[String]) -> bool:
for condition in conditions:
if not flags.get(condition, false):
return false
return true
func apply_effect(effect: String) -> void:
# Parse effect string, e.g., "set_flag:met_npc"
var parts := effect.split(":")
match parts[0]:
"set_flag":
flags[parts[1]] = true
"give_item":
# Integration with inventory
pass
# dialogue_ui.gd
extends Control
@onready var speaker_label := $Panel/Speaker
@onready var text_label := $Panel/Text
@onready var portrait := $Panel/Portrait
@onready var choices_container := $Panel/Choices
var current_dialogue: String
var current_line: DialogueLine
func _ready() -> void:
DialogueManager.line_displayed.connect(_on_line_displayed)
DialogueManager.dialogue_ended.connect(_on_dialogue_ended)
visible = false
func _on_line_displayed(line: DialogueLine) -> void:
visible = true
current_line = line
speaker_label.text = line.speaker
portrait.texture = line.portrait
# Typewriter effect
text_label.text = ""
for char in line.text:
text_label.text += char
await get_tree().create_timer(0.03).timeout
# Show choices
if line.choices.is_empty():
# Wait for input to continue
pass
else:
show_choices(line.choices)
func show_choices(choices: Array[DialogueChoice]) -> void:
# Clear existing
for child in choices_container.get_children():
child.queue_free()
# Add choice buttons
for choice in choices:
if not DialogueManager.check_conditions(choice.conditions):
continue
var button := Button.new()
button.text = choice.choice_text
button.pressed.connect(func(): _on_choice_selected(choice))
choices_container.add_child(button)
func _on_choice_selected(choice: DialogueChoice) -> void:
DialogueManager.select_choice(current_dialogue, choice)
func _on_dialogue_ended() -> void:
visible = false
# npc.gd
extends CharacterBody2D
@export var dialogue_path: String = "res://dialogues/npc_1.tres"
@export var start_line: String = "start"
func interact() -> void:
DialogueManager.start_dialogue(dialogue_path, start_line)
# dialogue_graph.gd
class_name DialogueGraph
extends Resource
@export var lines: Dictionary = {} # line_id → DialogueLine
func _init() -> void:
# Example structure
lines["start"] = create_line("Hero", "Hello!")
lines["response"] = create_line("NPC", "Greetings, traveler!")
func create_line(speaker: String, text: String) -> DialogueLine:
var line := DialogueLine.new()
line.speaker = speaker
line.text = text
return line
# Use Godot's built-in CSV import
# dialogue_en.csv:
# dialogue_id,speaker,text
# npc_1_start,Hero,"Hello!"
# npc_1_response,NPC,"Greetings!"
func get_localized_line(line_id: String) -> String:
return tr(line_id)
@onready var voice_player := $AudioStreamPlayer
func play_voice_line(line_id: String) -> void:
var audio := load("res://voice/" + line_id + ".mp3")
if audio:
voice_player.stream = audio
voice_player.play()
godot-signal-architecture, godot-save-load-systems, godot-ui-rich-textWeekly Installs
78
Repository
GitHub Stars
59
First Seen
Feb 10, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode76
gemini-cli74
codex74
github-copilot73
kimi-cli72
amp72
agent-browser 浏览器自动化工具 - Vercel Labs 命令行网页操作与测试
166,500 周安装
Base技能:Bankrbot/Clawdbot-Skill核心基础功能,GitHub星标984+的开发者工具
1 周安装
Bankr Signals - Base区块链交易信号验证与复制工具,提升交易绩效
1 周安装
Vue.js 官方文档与最佳实践指南 - 从入门到精通,涵盖响应式、TypeScript、组件等核心主题
1 周安装
SQLAlchemy 2.0 中文文档与参考指南 - Python ORM 数据库工具完整技能
1 周安装
Effect 错误管理指南:TypeScript 函数式编程中的预期错误与缺陷处理
2 周安装
Alpha Vantage API 集成指南:股票、外汇、加密货币、技术指标与金融数据
65 周安装
[b][i][url]SaveSystem.OS.delay_msec(). Use create_timer() or Tween to maintain smooth 60fps performance.DialogueNode resource in the inspector or use a central PortraitDatabase.