Godot 4.2 Stable
The game I’m working on is heavily menu-based, and I’m trying to find a way to get the scroll container to scroll to the bottom of its contents via script whenever a new menu pops up. Looking at the other answers on here talks about using functions that don’t appear to exist anymore. Anything associated with .value and max_value don’t work, although max value shows up as an integer when printed. What exactly am I missing?
Modify it via ScrollContainer.scroll_vertical
instead of changing the ScrollBar
would be fine.
I’m afraid this doesn’t work either. I tried multiple ways of using ScrollContainer, including set_deferred and across multiple scripts. I either get error messages or nothing happens.
Strangely, when I attach the function to a button it works perfectly, but I can’t seem to get it to automatically go. I’ve even tried to use signals to relay the command to a custom script attached to the container, but nothing seems to do the trick.
Update: I just added a print to the signal function and it appears to be calling correctly. It’s almost as if something is overwriting the scroll value after the function is called. Considering I have no other functions attached to the scroll value, any ideas on possible culprits?
well um, just set it to 9999 or really get the max scroll value and set_deffered the scroll_vertical of the scrollcontainer to that value
how to get the max scroll value?
the_scrollcontainer.get_v_scroll_bar().max_value
how to set the scroll_vertical to max value?
the_scrollcontainer.set_deferred("scroll_vertical", the_scrollcontainer.get_v_scroll_bar().max_value)
As I said, I tried set_deferred already. My function is calling properly, but something else appears to be overwriting the value after the function sets it.
I’ve come across a workaround by using a timer set for .01 seconds into runtime.
have you tried tweening the value to that?
1 Like
This solution works as well, and without the need for an extra node. Much appreciated
You just remind me of the scene tree timer:
await get_tree().create_timer(.01).timeout
the_scrollcontainer.set_deferred("scroll_vertical", the_scrollcontainer.get_v_scroll_bar().max_value)
or
get_tree().create_timer(.01).timeout.connect(
func () -> void:
the_scrollcontainer.set_deferred("scroll_vertical", the_scrollcontainer.get_v_scroll_bar().max_value)
)
Just put this in your ScrollContainer. Assuming you are adding some labels to the VBoxContainer when a button is pressed.
extends ScrollContainer
var start_text=100
func _enter_tree():
set_deferred("scroll_vertical", 10000)
func _on_button_pressed():
var label=Label.new()
label.horizontal_alignment=HORIZONTAL_ALIGNMENT_CENTER
start_text += 10
label.text = str(start_text)
$VBoxContainer.add_child(label)
call_deferred("update_scroll")
pass # Replace with function body.
func update_scroll():
set_deferred("scroll_vertical", 10000)
I’m losing my mind. I’m using 4.3, using ChatGPT, I cannot get the scrollbar to go to the end.
I’m using
await get_tree().create_timer(1).timeout
scroll_container.set_deferred("scroll_vertical", 10000)
does not work. why is this so hard.
Here’s the function I used in my script to solve this problem. I’m taking a LineEdit
’s submitted signal, making a new Label
(of a custom extended class, CommandEntryLabel
), assigning the submitted text to it, and appending it into a VBoxContainer
within a ScrollContainer
.
My best guess about what’s going on is that you tell it to scroll to what the maximum value is, but then a split second later it updates what that max value ACTUALLY is, which makes it appear like it didn’t really scroll to the bottom.
I hope this helps!
func _on_command_entry_text_submitted(command_text: String) -> void:
var new_entry: CommandEntryLabel = CommandEntryLabel.new()
new_entry.text = command_text
%HistoryEntries.add_child(new_entry)
%CommandEntry.text = ''
# Waiting a split second after adding the text allows the program to catch up and properly
# scroll all the way to the bottom. It's frustrating that I have to even do this hack,
# but... here we are.
get_tree().create_timer(.01).timeout.connect(
func () -> void:
%CommandHistory.scroll_vertical = %CommandHistory.get_v_scroll_bar().max_value
)
1 Like
texts_container.add_child(message_scene)
await to_signal(RenderingServer.singleton, RenderingServer.FRAME_POST_DRAW)
texts_container.get_parent().get_parent().scroll_vertical = 10000
This is how I did it. Waiting for the next frame works well
Another option is to do two call_deferred – in other words, wait two process frames.
func post_message(msg: String):
# ... post your message
call_deferred("scroll_chat_to_bottom_1")
func scroll_chat_to_bottom_1():
call_deferred("scroll_chat_to_bottom_2")
func scroll_chat_to_bottom_2():
scroll_container.scroll_vertical = scroll_container.get_v_scroll_bar().max_value
This is also really low-cost, as it’s just a normal (efficient) call_deferred, twice, rather than spawning a timer or awaiting a signal.