Fixed flicker
This commit is contained in:
@@ -32,6 +32,7 @@ func setup(responses:Array[DialogueResponse], entity:Entity) -> void:
|
|||||||
_list.add_child(label)
|
_list.add_child(label)
|
||||||
|
|
||||||
size.x = MAX_WIDTH
|
size.x = MAX_WIDTH
|
||||||
|
modulate.a = 0.0
|
||||||
visible = true
|
visible = true
|
||||||
_updateSelection()
|
_updateSelection()
|
||||||
|
|
||||||
@@ -79,9 +80,18 @@ func _updateWorldPosition() -> void:
|
|||||||
var camera:Camera3D = get_viewport().get_camera_3d()
|
var camera:Camera3D = get_viewport().get_camera_3d()
|
||||||
if camera == null:
|
if camera == null:
|
||||||
return
|
return
|
||||||
|
if size.y == 0:
|
||||||
|
return
|
||||||
var worldPos:Vector3 = _entity.global_position + Vector3(0, 2.5, 0)
|
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 screenPos:Vector2 = camera.unproject_position(worldPos)
|
||||||
var viewportSize:Vector2 = get_viewport().get_visible_rect().size
|
var viewportSize:Vector2 = get_viewport().get_visible_rect().size
|
||||||
position = screenPos - Vector2(size.x * 0.5, size.y)
|
position.x = clamp(screenPos.x - size.x * 0.5, 0.0, viewportSize.x - size.x)
|
||||||
position.x = clamp(position.x, 0.0, viewportSize.x - size.x)
|
var yAbove:float = screenPos.y - size.y
|
||||||
position.y = clamp(position.y, 0.0, viewportSize.y - size.y)
|
if yAbove >= 0.0:
|
||||||
|
position.y = yAbove
|
||||||
|
else:
|
||||||
|
position.y = clamp(screenPos.y + 10.0, 0.0, viewportSize.y - size.y)
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ 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
|
||||||
@@ -45,23 +44,41 @@ 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.
|
# 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.text = line.text
|
||||||
_bodyLabel.visible_characters = -1
|
_bodyLabel.visible_characters = -1
|
||||||
_bodyLabel.scroll_to_line(0)
|
_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
|
_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 = true
|
||||||
_isRevealing = false
|
|
||||||
_isWaitingForInput = false
|
_isWaitingForInput = false
|
||||||
_hasLetGoOfInteract = !Input.is_action_pressed("interact")
|
_hasLetGoOfInteract = !Input.is_action_pressed("interact")
|
||||||
size.x = MAX_WIDTH
|
|
||||||
modulate.a = 0.0
|
_updateWorldPosition()
|
||||||
visible = true
|
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:
|
func _process(delta:float) -> void:
|
||||||
if not visible:
|
if not visible:
|
||||||
@@ -72,10 +89,6 @@ 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
|
||||||
@@ -87,20 +100,6 @@ 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:
|
func _buildPreWrappedText(parsed:String) -> String:
|
||||||
if parsed.is_empty():
|
if parsed.is_empty():
|
||||||
return parsed
|
return parsed
|
||||||
@@ -108,8 +107,6 @@ func _buildPreWrappedText(parsed:String) -> String:
|
|||||||
var prevLine:int = 0
|
var prevLine:int = 0
|
||||||
for i in range(len(parsed)):
|
for i in range(len(parsed)):
|
||||||
var ch:String = parsed[i]
|
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":
|
if ch == "\n":
|
||||||
result += "\n"
|
result += "\n"
|
||||||
prevLine += 1
|
prevLine += 1
|
||||||
@@ -147,7 +144,6 @@ 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
|
||||||
|
|
||||||
# Count newlines up to idx to get the current line — no Godot layout call needed.
|
|
||||||
var charLine:int = _parsedText.left(idx + 1).count("\n")
|
var charLine:int = _parsedText.left(idx + 1).count("\n")
|
||||||
if charLine >= _startLine + _linesPerPage:
|
if charLine >= _startLine + _linesPerPage:
|
||||||
_bodyLabel.visible_characters -= 1
|
_bodyLabel.visible_characters -= 1
|
||||||
@@ -215,6 +211,7 @@ func _advance() -> void:
|
|||||||
_revealTimer = 0.0
|
_revealTimer = 0.0
|
||||||
_pauseTimer = 0.0
|
_pauseTimer = 0.0
|
||||||
_isRevealing = true
|
_isRevealing = true
|
||||||
|
_revealNextChar()
|
||||||
else:
|
else:
|
||||||
dismissed.emit()
|
dismissed.emit()
|
||||||
visible = false
|
visible = false
|
||||||
|
|||||||
Reference in New Issue
Block a user