Untitled
Anonymous
plain_text
02/18/2026 2:25 AM
5.2 KB
9
Indexable
extends Control
signal choice_selected(choice_index: int)
@onready var box: Panel = $Box
@onready var portrait_rect: TextureRect = $Box/BoxCol/ContentRow/PortraitContainer/Portrait
@onready var name_label: Label = $Box/BoxCol/ContentRow/TextCol/NameLabel
@onready var body_text: RichTextLabel = $Box/BoxCol/ContentRow/TextCol/BodyText
@onready var choices_col: VBoxContainer = $Box/BoxCol/ContentRow/TextCol/Choices
@onready var hint_label: Label = $Box/BoxCol/HintLabel
var _is_open := false
var _is_typing := false
var _full_text := ""
var _char_index := 0
var _type_timer := 0.0
var _type_speed := 0.03 # seconds per char
var _has_choices := false
func _ready() -> void:
# Optional safety check
if name_label == null or body_text == null or choices_col == null or hint_label == null or portrait_rect == null:
push_error("DialogueUI: One or more UI node paths are wrong. Check DialogueUI tree.")
# Ensure RichTextLabel is set up for fixed-height scrolling
body_text.scroll_active = true
body_text.scroll_following = true
# Keep fixed height mode (Option B): do NOT enable fit content
# If you had it enabled in the inspector, disable it there.
# body_text.fit_content = false # (property name differs by version; set in inspector)
hide_dialogue()
func show_dialogue(speaker_name: String, text: String, portrait_tex: Texture2D, choices: Array) -> void:
_is_open = true
visible = true
box.visible = true
name_label.text = speaker_name
# Portrait (set texture + show/hide)
if portrait_tex != null:
portrait_rect.texture = portrait_tex
portrait_rect.visible = true
else:
portrait_rect.texture = null
portrait_rect.visible = false
# Typewriter init
_full_text = text
_char_index = 0
_type_timer = 0.0
_is_typing = true
body_text.text = ""
# Ensure scrolling is enabled every time we open
body_text.scroll_active = true
body_text.scroll_following = true
await _scroll_body_to_bottom()
# Choices
_clear_choices()
_has_choices = (choices.size() > 0)
if _has_choices:
_build_choices(choices)
# Hint
hint_label.visible = not _has_choices
hint_label.text = "E: continue" if not _has_choices else ""
func hide_dialogue() -> void:
_is_open = false
visible = false
func is_open() -> bool:
return _is_open
func is_typing() -> bool:
return _is_typing
func fast_reveal() -> void:
if not _is_typing:
return
_is_typing = false
body_text.text = _full_text
await _scroll_body_to_bottom()
func _process(delta: float) -> void:
if not _is_open:
return
if _is_typing:
var char_speed := _type_speed
if Input.is_action_pressed("fast_text"):
char_speed = 0.005
_type_timer += delta
var changed := false
while _type_timer >= char_speed and _char_index < _full_text.length():
_type_timer -= char_speed
_char_index += 1
body_text.text = _full_text.substr(0, _char_index)
changed = true
# Only scroll when the text actually changed (reduces jitter + work)
if changed:
await _scroll_body_to_bottom()
if _char_index >= _full_text.length():
_is_typing = false
await _scroll_body_to_bottom()
func _unhandled_input(event: InputEvent) -> void:
if not _is_open:
return
if event.is_action_pressed("interact"):
if _is_typing:
fast_reveal()
return
# If there are no choices, owner can listen for E and close/advance.
# We intentionally do nothing here.
func _clear_choices() -> void:
for c in choices_col.get_children():
c.queue_free()
func _build_choices(choices: Array) -> void:
# choices = [{ "label": "text" }, ...]
for i in range(choices.size()):
var b := Button.new()
b.text = str(choices[i].get("label", ""))
b.focus_mode = Control.FOCUS_ALL
b.pressed.connect(func(): emit_signal("choice_selected", i))
choices_col.add_child(b)
if choices_col.get_child_count() > 0:
choices_col.get_child(0).grab_focus()
func _scroll_body_to_bottom() -> void:
await get_tree().process_frame
if body_text.get_line_count() > 0:
body_text.scroll_to_line(body_text.get_line_count() - 1)
Editor is loading...
Leave a Comment