SupaLidlGame/addons/dialogue_manager/example_balloon/example_balloon.gd

213 lines
6.8 KiB
GDScript

extends CanvasLayer
@onready var balloon: ColorRect = $Balloon
@onready var margin: MarginContainer = $Balloon/Margin
@onready var character_label: RichTextLabel = $Balloon/Margin/VBox/CharacterLabel
@onready var dialogue_label := $Balloon/Margin/VBox/DialogueLabel
@onready var responses_menu: VBoxContainer = $Balloon/Margin/VBox/Responses
@onready var response_template: RichTextLabel = %ResponseTemplate
## The dialogue resource
var resource: DialogueResource
## Temporary game states
var temporary_game_states: Array = []
## See if we are waiting for the player
var is_waiting_for_input: bool = false
## See if we are running a long mutation and should hide the balloon
var will_hide_balloon: bool = false
## The current line
var dialogue_line: DialogueLine:
set(next_dialogue_line):
is_waiting_for_input = false
if not next_dialogue_line:
queue_free()
return
# Remove any previous responses
for child in responses_menu.get_children():
responses_menu.remove_child(child)
child.queue_free()
dialogue_line = next_dialogue_line
character_label.visible = not dialogue_line.character.is_empty()
character_label.text = tr(dialogue_line.character, "dialogue")
dialogue_label.modulate.a = 0
dialogue_label.custom_minimum_size.x = dialogue_label.get_parent().size.x - 1
dialogue_label.dialogue_line = dialogue_line
# Show any responses we have
responses_menu.modulate.a = 0
if dialogue_line.responses.size() > 0:
for response in dialogue_line.responses:
# Duplicate the template so we can grab the fonts, sizing, etc
var item: RichTextLabel = response_template.duplicate(0)
item.name = "Response%d" % responses_menu.get_child_count()
if not response.is_allowed:
item.name = String(item.name) + "Disallowed"
item.modulate.a = 0.4
item.text = response.text
item.show()
responses_menu.add_child(item)
# Show our balloon
balloon.show()
will_hide_balloon = false
dialogue_label.modulate.a = 1
if not dialogue_line.text.is_empty():
dialogue_label.type_out()
await dialogue_label.finished_typing
# Wait for input
if dialogue_line.responses.size() > 0:
responses_menu.modulate.a = 1
configure_menu()
elif dialogue_line.time != null:
var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float()
await get_tree().create_timer(time).timeout
next(dialogue_line.next_id)
else:
is_waiting_for_input = true
balloon.focus_mode = Control.FOCUS_ALL
balloon.grab_focus()
get:
return dialogue_line
func _ready() -> void:
response_template.hide()
balloon.hide()
balloon.custom_minimum_size.x = balloon.get_viewport_rect().size.x
Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated)
func _unhandled_input(_event: InputEvent) -> void:
# Only the balloon is allowed to handle input while it's showing
get_viewport().set_input_as_handled()
## Start some dialogue
func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void:
temporary_game_states = extra_game_states
is_waiting_for_input = false
resource = dialogue_resource
self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states)
## Go to the next line
func next(next_id: String) -> void:
self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states)
### Helpers
# Set up keyboard movement and signals for the response menu
func configure_menu() -> void:
balloon.focus_mode = Control.FOCUS_NONE
var items = get_responses()
for i in items.size():
var item: Control = items[i]
item.focus_mode = Control.FOCUS_ALL
item.focus_neighbor_left = item.get_path()
item.focus_neighbor_right = item.get_path()
if i == 0:
item.focus_neighbor_top = item.get_path()
item.focus_previous = item.get_path()
else:
item.focus_neighbor_top = items[i - 1].get_path()
item.focus_previous = items[i - 1].get_path()
if i == items.size() - 1:
item.focus_neighbor_bottom = item.get_path()
item.focus_next = item.get_path()
else:
item.focus_neighbor_bottom = items[i + 1].get_path()
item.focus_next = items[i + 1].get_path()
item.mouse_entered.connect(_on_response_mouse_entered.bind(item))
item.gui_input.connect(_on_response_gui_input.bind(item))
items[0].grab_focus()
# Get a list of enabled items
func get_responses() -> Array:
var items: Array = []
for child in responses_menu.get_children():
if "Disallowed" in child.name: continue
items.append(child)
return items
func handle_resize() -> void:
if not is_instance_valid(margin):
call_deferred("handle_resize")
return
balloon.custom_minimum_size.y = margin.size.y
# Force a resize on only the height
balloon.size.y = 0
var viewport_size = balloon.get_viewport_rect().size
balloon.global_position = Vector2((viewport_size.x - balloon.size.x) * 0.5, viewport_size.y - balloon.size.y)
### Signals
func _on_mutated(_mutation: Dictionary) -> void:
is_waiting_for_input = false
will_hide_balloon = true
get_tree().create_timer(0.1).timeout.connect(func():
if will_hide_balloon:
will_hide_balloon = false
balloon.hide()
)
func _on_response_mouse_entered(item: Control) -> void:
if "Disallowed" in item.name: return
item.grab_focus()
func _on_response_gui_input(event: InputEvent, item: Control) -> void:
if "Disallowed" in item.name: return
if event is InputEventMouseButton and event.is_pressed() and event.button_index == 1:
next(dialogue_line.responses[item.get_index()].next_id)
elif event.is_action_pressed("ui_accept") and item in get_responses():
next(dialogue_line.responses[item.get_index()].next_id)
func _on_balloon_gui_input(event: InputEvent) -> void:
if not is_waiting_for_input: return
if dialogue_line.responses.size() > 0: return
# When there are no response options the balloon itself is the clickable thing
get_viewport().set_input_as_handled()
if event is InputEventMouseButton and event.is_pressed() and event.button_index == 1:
next(dialogue_line.next_id)
elif event.is_action_pressed("ui_accept") and get_viewport().gui_get_focus_owner() == balloon:
next(dialogue_line.next_id)
func _on_margin_resized() -> void:
handle_resize()