Improve scrolling

This commit is contained in:
2026-06-12 12:13:18 -05:00
parent 96141e48a2
commit 45540c31b2
+59 -12
View File
@@ -22,6 +22,7 @@ var _linesPerPage:int = 0
var _revealTimer:float = 0.0 var _revealTimer:float = 0.0
var _pauseTimer:float = 0.0 var _pauseTimer:float = 0.0
var _autoAdvanceTimer:float = 0.0 var _autoAdvanceTimer:float = 0.0
var _layoutReady:bool = false
var _isRevealing:bool = false var _isRevealing:bool = false
var _isWaitingForInput:bool = false var _isWaitingForInput:bool = false
var _hasLetGoOfInteract:bool = true var _hasLetGoOfInteract:bool = true
@@ -44,20 +45,23 @@ func setup(line:DialogueLine, entity:Entity, mode:AdvancementMode = AdvancementM
_advancementMode = mode _advancementMode = mode
_speakerLabel.text = entity.displayName if entity else "" _speakerLabel.text = entity.displayName if entity else ""
_speakerLabel.visible = _speakerLabel.text != "" _speakerLabel.visible = _speakerLabel.text != ""
# Show all characters so Godot shapes the full text and computes line-break
# positions before reveal starts. The box is transparent this frame.
_bodyLabel.text = line.text _bodyLabel.text = line.text
_bodyLabel.visible_characters = 0 _bodyLabel.visible_characters = -1
_bodyLabel.scroll_to_line(0) _bodyLabel.scroll_to_line(0)
_startLine = 0 _startLine = 0
_linesPerPage = LINES_PER_PAGE _linesPerPage = LINES_PER_PAGE
_revealTimer = 0.0 _revealTimer = 0.0
_pauseTimer = 0.0 _pauseTimer = 0.0
_autoAdvanceTimer = 0.0 _autoAdvanceTimer = 0.0
_layoutReady = false
_isRevealing = false
_isWaitingForInput = false _isWaitingForInput = false
_hasLetGoOfInteract = !Input.is_action_pressed("interact") _hasLetGoOfInteract = !Input.is_action_pressed("interact")
size.x = MAX_WIDTH size.x = MAX_WIDTH
modulate.a = 0.0
visible = true visible = true
_parsedText = _bodyLabel.get_parsed_text()
_isRevealing = true
func _process(delta:float) -> void: func _process(delta:float) -> void:
if not visible: if not visible:
@@ -68,6 +72,10 @@ func _process(delta:float) -> void:
if _isWaitingForInput: if _isWaitingForInput:
_advanceIndicator.modulate.a = 0.5 + 0.5 * sin(Time.get_ticks_msec() / 300.0) _advanceIndicator.modulate.a = 0.5 + 0.5 * sin(Time.get_ticks_msec() / 300.0)
if not _layoutReady:
_finishLayout()
return
if _isRevealing: if _isRevealing:
_processReveal(delta) _processReveal(delta)
return return
@@ -79,6 +87,40 @@ func _process(delta:float) -> void:
if _advancementMode == AdvancementMode.TIMED: if _advancementMode == AdvancementMode.TIMED:
_processAutoAdvance(delta) _processAutoAdvance(delta)
# Called on the first _process frame after setup(). By this point Godot has
# shaped the full text, so get_character_line() returns stable positions.
# We bake explicit \n characters into the text at every wrap boundary, then
# disable autowrap so the layout never changes during reveal.
func _finishLayout() -> void:
var raw:String = _bodyLabel.get_parsed_text()
_parsedText = _buildPreWrappedText(raw)
_bodyLabel.text = _parsedText
_bodyLabel.autowrap_mode = TextServer.AUTOWRAP_OFF
_bodyLabel.visible_characters = 0
modulate.a = 1.0
_layoutReady = true
_isRevealing = true
func _buildPreWrappedText(parsed:String) -> String:
if parsed.is_empty():
return parsed
var result:String = ""
var prevLine:int = 0
for i in range(len(parsed)):
var ch:String = parsed[i]
# Source-level newlines advance the expected line counter directly;
# skip the charLine check so we never double-insert.
if ch == "\n":
result += "\n"
prevLine += 1
continue
var charLine:int = _bodyLabel.get_character_line(i)
if charLine > prevLine:
result += "\n"
prevLine = charLine
result += ch
return result
func _processReveal(delta:float) -> void: func _processReveal(delta:float) -> void:
if _pauseTimer > 0.0: if _pauseTimer > 0.0:
var speedMult:float = SPEEDUP_MULTIPLIER if Input.is_action_pressed("interact") else 1.0 var speedMult:float = SPEEDUP_MULTIPLIER if Input.is_action_pressed("interact") else 1.0
@@ -102,16 +144,20 @@ func _revealNextChar() -> bool:
_bodyLabel.visible_characters += 1 _bodyLabel.visible_characters += 1
var idx:int = _bodyLabel.visible_characters - 1 var idx:int = _bodyLabel.visible_characters - 1
# Stop if this character has wrapped onto the next page. # Count newlines up to idx to get the current line — no Godot layout call needed.
if idx > 0: var charLine:int = _parsedText.left(idx + 1).count("\n")
var charLine:int = _bodyLabel.get_character_line(idx) if charLine >= _startLine + _linesPerPage:
if charLine >= _startLine + _linesPerPage: _bodyLabel.visible_characters -= 1
_bodyLabel.visible_characters -= 1 _onPageFull()
_onPageFull() return false
return false
if idx < len(_parsedText): if idx < len(_parsedText):
_pauseTimer = _getPauseForChar(idx) var isLastVisible:bool = idx >= len(_parsedText) - 1
if not isLastVisible:
var nextCharLine:int = _parsedText.left(idx + 2).count("\n")
isLastVisible = nextCharLine >= _startLine + _linesPerPage
if not isLastVisible:
_pauseTimer = _getPauseForChar(idx)
return true return true
@@ -157,7 +203,8 @@ func _processAutoAdvance(delta:float) -> void:
func _advance() -> void: func _advance() -> void:
_advanceIndicator.visible = false _advanceIndicator.visible = false
_advanceIndicator.modulate.a = 1.0 _advanceIndicator.modulate.a = 1.0
var hasMorePages:bool = _startLine + _linesPerPage < _bodyLabel.get_line_count() var totalLines:int = _parsedText.count("\n") + 1
var hasMorePages:bool = _startLine + _linesPerPage < totalLines
if hasMorePages: if hasMorePages:
_startLine += _linesPerPage _startLine += _linesPerPage
_bodyLabel.scroll_to_line(_startLine) _bodyLabel.scroll_to_line(_startLine)