From aae68a60d5db92abf2c0c74fd245b32659f032ce Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Fri, 12 Jun 2026 13:57:22 -0500 Subject: [PATCH] Fixed flicker --- ui/component/DialogueChoiceBox.gd | 16 ++++++++-- ui/component/DialogueTextbox.gd | 53 +++++++++++++++---------------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/ui/component/DialogueChoiceBox.gd b/ui/component/DialogueChoiceBox.gd index 092872f..2fab57a 100644 --- a/ui/component/DialogueChoiceBox.gd +++ b/ui/component/DialogueChoiceBox.gd @@ -32,6 +32,7 @@ func setup(responses:Array[DialogueResponse], entity:Entity) -> void: _list.add_child(label) size.x = MAX_WIDTH + modulate.a = 0.0 visible = true _updateSelection() @@ -79,9 +80,18 @@ func _updateWorldPosition() -> void: var camera:Camera3D = get_viewport().get_camera_3d() if camera == null: return + if size.y == 0: + return var worldPos:Vector3 = _entity.global_position + Vector3(0, 2.5, 0) + if camera.is_position_behind(worldPos): + modulate.a = 0.0 + return + modulate.a = 1.0 var screenPos:Vector2 = camera.unproject_position(worldPos) var viewportSize:Vector2 = get_viewport().get_visible_rect().size - position = screenPos - Vector2(size.x * 0.5, size.y) - position.x = clamp(position.x, 0.0, viewportSize.x - size.x) - position.y = clamp(position.y, 0.0, viewportSize.y - size.y) + position.x = clamp(screenPos.x - size.x * 0.5, 0.0, viewportSize.x - size.x) + var yAbove:float = screenPos.y - size.y + if yAbove >= 0.0: + position.y = yAbove + else: + position.y = clamp(screenPos.y + 10.0, 0.0, viewportSize.y - size.y) diff --git a/ui/component/DialogueTextbox.gd b/ui/component/DialogueTextbox.gd index d53e48c..a201604 100644 --- a/ui/component/DialogueTextbox.gd +++ b/ui/component/DialogueTextbox.gd @@ -22,7 +22,6 @@ var _linesPerPage:int = 0 var _revealTimer:float = 0.0 var _pauseTimer:float = 0.0 var _autoAdvanceTimer:float = 0.0 -var _layoutReady:bool = false var _isRevealing:bool = false var _isWaitingForInput:bool = false var _hasLetGoOfInteract:bool = true @@ -45,23 +44,41 @@ func setup(line:DialogueLine, entity:Entity, mode:AdvancementMode = AdvancementM _advancementMode = mode _speakerLabel.text = entity.displayName if entity else "" _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. + + # Set width explicitly before text so get_character_line() uses correct metrics. + # The Container layout pass hasn't run yet, so we derive inner width from the + # panel StyleBox margins rather than waiting for a deferred layout frame. + size.x = MAX_WIDTH + _bodyLabel.size.x = _bodyLabelWidth() _bodyLabel.text = line.text _bodyLabel.visible_characters = -1 _bodyLabel.scroll_to_line(0) + + # get_character_line() forces synchronous text shaping via _validate_line_caches(), + # so we can compute pre-wrapped text right now without any pre-pass frame. + _parsedText = _buildPreWrappedText(_bodyLabel.get_parsed_text()) + _bodyLabel.text = _parsedText + _bodyLabel.autowrap_mode = TextServer.AUTOWRAP_OFF + _bodyLabel.visible_characters = 0 + _startLine = 0 _linesPerPage = LINES_PER_PAGE _revealTimer = 0.0 _pauseTimer = 0.0 _autoAdvanceTimer = 0.0 - _layoutReady = false - _isRevealing = false + _isRevealing = true _isWaitingForInput = false _hasLetGoOfInteract = !Input.is_action_pressed("interact") - size.x = MAX_WIDTH - modulate.a = 0.0 + + _updateWorldPosition() visible = true + _revealNextChar() + +func _bodyLabelWidth() -> float: + var style:StyleBox = get_theme_stylebox("panel") + if style == null: + return MAX_WIDTH + return MAX_WIDTH - style.get_margin(SIDE_LEFT) - style.get_margin(SIDE_RIGHT) func _process(delta:float) -> void: if not visible: @@ -72,10 +89,6 @@ func _process(delta:float) -> void: if _isWaitingForInput: _advanceIndicator.modulate.a = 0.5 + 0.5 * sin(Time.get_ticks_msec() / 300.0) - if not _layoutReady: - _finishLayout() - return - if _isRevealing: _processReveal(delta) return @@ -87,20 +100,6 @@ func _process(delta:float) -> void: if _advancementMode == AdvancementMode.TIMED: _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 @@ -108,8 +107,6 @@ func _buildPreWrappedText(parsed:String) -> 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 @@ -147,7 +144,6 @@ func _revealNextChar() -> bool: _bodyLabel.visible_characters += 1 var idx:int = _bodyLabel.visible_characters - 1 - # Count newlines up to idx to get the current line — no Godot layout call needed. var charLine:int = _parsedText.left(idx + 1).count("\n") if charLine >= _startLine + _linesPerPage: _bodyLabel.visible_characters -= 1 @@ -215,6 +211,7 @@ func _advance() -> void: _revealTimer = 0.0 _pauseTimer = 0.0 _isRevealing = true + _revealNextChar() else: dismissed.emit() visible = false