Dialogen
NPC dialoog systeem en interactie flows.
Dialog Box
┌─────────────────────────────────────────────────────────────────┐
│ ┌───────┐ │
│ │ │ Oma Wilma │
│ │ 👵 │ ───────────────────────────────────────────────── │
│ │ │ "Welkom terug, lief kind! Heb je al ontbijt gehad? │
│ └───────┘ Ik heb verse pannenkoeken gemaakt!" │
│ │
│ [Ja, lekker!] [Nee, bedankt] [Vertel me meer] │
└─────────────────────────────────────────────────────────────────┘
Scene Structuur
DialogBox (Control)
├── Background (NinePatchRect)
├── Portrait (TextureRect)
├── ContentContainer (VBoxContainer)
│ ├── SpeakerName (Label)
│ ├── Separator (HSeparator)
│ └── DialogText (RichTextLabel)
├── ResponseContainer (VBoxContainer)
│ └── [DialogResponse buttons]
└── ContinueIndicator (TextureRect)
Dialog Data Format
JSON Structuur
{
"dialog_id": "oma_wilma_greeting",
"speaker": "Oma Wilma",
"portrait": "npcs/oma_wilma.png",
"nodes": [
{
"id": "start",
"text": "Welkom terug, lief kind! Heb je al ontbijt gehad?",
"responses": [
{
"text": "Ja, lekker!",
"next": "happy_response"
},
{
"text": "Nee, bedankt",
"next": "decline_response"
},
{
"text": "Vertel me meer",
"next": "more_info",
"condition": "quest_active:pancake_quest"
}
]
},
{
"id": "happy_response",
"text": "Fijn om te horen! Geniet van je dag!",
"responses": [],
"actions": [
{"type": "add_friendship", "amount": 5}
]
}
]
}
Dialog Manager
# dialog_manager.gd
class_name DialogManager extends Node
signal dialog_started(dialog_id: String)
signal dialog_ended(dialog_id: String)
signal response_selected(response_index: int)
var _current_dialog: Dictionary = {}
var _current_node: Dictionary = {}
var _dialog_box: DialogBox
func start_dialog(dialog_id: String) -> void:
_current_dialog = _load_dialog(dialog_id)
if _current_dialog.is_empty():
push_error("Dialog not found: " + dialog_id)
return
_dialog_box = _create_dialog_box()
_show_node("start")
dialog_started.emit(dialog_id)
func _show_node(node_id: String) -> void:
_current_node = _find_node(node_id)
if _current_node.is_empty():
_end_dialog()
return
_dialog_box.set_speaker(_current_dialog.speaker, _current_dialog.portrait)
_dialog_box.set_text(_current_node.text)
# Filter responses by conditions
var valid_responses = _filter_responses(_current_node.responses)
_dialog_box.set_responses(valid_responses)
func _on_response_selected(index: int) -> void:
var response = _current_node.responses[index]
# Execute any actions
if _current_node.has("actions"):
_execute_actions(_current_node.actions)
# Go to next node
if response.has("next"):
_show_node(response.next)
else:
_end_dialog()
func _end_dialog() -> void:
_dialog_box.queue_free()
dialog_ended.emit(_current_dialog.dialog_id)
Typewriter Effect
# dialog_box.gd
var _full_text: String = ""
var _visible_chars: int = 0
var _typing_speed: float = 0.03
var _is_typing: bool = false
func set_text(text: String) -> void:
_full_text = text
_visible_chars = 0
_is_typing = true
dialog_text.text = ""
dialog_text.visible_characters = 0
func _process(delta: float) -> void:
if _is_typing:
_visible_chars += delta / _typing_speed
dialog_text.visible_characters = int(_visible_chars)
if _visible_chars >= _full_text.length():
_is_typing = false
_show_responses()
func skip_typing() -> void:
if _is_typing:
_is_typing = false
dialog_text.visible_characters = -1 # Show all
_show_responses()
Response Buttons
func set_responses(responses: Array) -> void:
# Clear existing
for child in response_container.get_children():
child.queue_free()
if responses.is_empty():
# No responses = click to continue
continue_indicator.visible = true
return
continue_indicator.visible = false
for i in range(responses.size()):
var response = responses[i]
var button = Button.new()
button.text = response.text
button.pressed.connect(_on_response_pressed.bind(i))
response_container.add_child(button)
func _on_response_pressed(index: int) -> void:
response_selected.emit(index)
Conditional Responses
func _filter_responses(responses: Array) -> Array:
var valid = []
for response in responses:
if _check_condition(response):
valid.append(response)
return valid
func _check_condition(response: Dictionary) -> bool:
if not response.has("condition"):
return true
var condition = response.condition
var parts = condition.split(":")
match parts[0]:
"quest_active":
return quest_manager.is_quest_active(parts[1])
"quest_completed":
return quest_manager.is_quest_completed(parts[1])
"has_item":
return inventory_manager.has_item(parts[1])
"friendship_level":
return npc_manager.get_friendship(current_npc) >= int(parts[1])
_:
return true
Dialog Actions
func _execute_actions(actions: Array) -> void:
for action in actions:
match action.type:
"add_friendship":
npc_manager.add_friendship(current_npc, action.amount)
"give_item":
inventory_manager.add_item(action.item_id, action.quantity)
"start_quest":
quest_manager.start_quest(action.quest_id)
"complete_quest":
quest_manager.complete_quest(action.quest_id)
"teleport":
player.teleport_to(action.scene, Vector2(action.x, action.y))
"play_sound":
audio_manager.play_sound(action.sound_id)
Quest Dialog Integration
# Automatisch quest-gerelateerde dialogen tonen
func get_npc_dialog(npc_id: String) -> String:
# Check for quest dialogs first
var quest_dialog = _get_quest_dialog(npc_id)
if not quest_dialog.is_empty():
return quest_dialog
# Check friendship level dialogs
var friendship = npc_manager.get_friendship(npc_id)
var friendship_dialog = _get_friendship_dialog(npc_id, friendship)
if not friendship_dialog.is_empty():
return friendship_dialog
# Default greeting
return _get_default_dialog(npc_id)
func _get_quest_dialog(npc_id: String) -> String:
for quest in quest_manager.get_active_quests():
var def = quest_manager.get_quest_definition(quest.quest_id)
if def.giver_npc == npc_id:
if quest.status == "active":
return def.dialog_id + "_progress"
elif _can_complete(quest):
return def.dialog_id + "_complete"
return ""
Localization
# Dialog teksten laden uit localization files
func _load_dialog(dialog_id: String) -> Dictionary:
var path = "res://data/dialogs/%s.json" % dialog_id
if not FileAccess.file_exists(path):
return {}
var file = FileAccess.open(path, FileAccess.READ)
var json = JSON.new()
json.parse(file.get_as_text())
var dialog = json.data
# Apply translations
dialog = _apply_translations(dialog)
return dialog
func _apply_translations(dialog: Dictionary) -> Dictionary:
# Replace text with translated versions
for node in dialog.nodes:
node.text = tr(node.text)
for response in node.responses:
response.text = tr(response.text)
return dialog