I am working in Godot 4.0 and having issues with the TextureProgressBar. In order to recreate this issue, make a new project, create a new scene as a Node2D, and add the children Timer and CanvasLayer. Then give the CanvasLayer its own child, a TextureProgressBar. Add a gdscript file to the Node2D and connect the Timer's timeout() signal to it (I changed the name of the timer to StartTimer, the CanvasLayer to Loading Screen, and the TextureProgressBar to Loading Bar, which is why the func call name is different in the provided code). Then replace the code in the script file with this:
extends Node2D
func _ready():
get_node("Loading Screen/Loading Bar").position = Vector2(200, 360)
get_node("Loading Screen/Loading Bar").set_value(1)
func start_world_creation():
start_progress_bar()
func start_progress_bar():
for x in 100:
for y in 100:
for time_waster in range(20000):
time_waster += 300
get_node("Loading Screen/Loading Bar").value += 1
print(get_node("Loading Screen/Loading Bar").value)
func _process(delta):
pass
func _on_start_timer_timeout():
start_world_creation()
Finally, assign the images below to their respective places for the TextureProgressBar:
For some reason, the TextureProgressBar does not update until its value reaches 100. Why is this, and how can I fix it? According to the documentation, it should update with every integer increase in value, since the TextureProgressBar's step value is set to 1, and max_value is 100.
So the signal of the Timer
is emitted and this code will run before the next frame:
func _on_start_timer_timeout():
start_world_creation()
Which is calling this code which will finish before the next frame:
func start_world_creation():
start_progress_bar()
Which is calling this code which will finish before the next frame:
func start_progress_bar():
for x in 100:
for y in 100:
for time_waster in range(20000):
time_waster += 300
get_node("Loading Screen/Loading Bar").value += 1
print(get_node("Loading Screen/Loading Bar").value)
So when Godot finally gets around to the next frame, the value
of the TextureProgressBar
is at 100.0
, which is also the default max_value
, so you see it full.
Hopefully you can see the issue: The time_waster
et.al. is keeping the thread busy, so Godot cannot do other stuff. In this situation using set_deferred
or call_deferred
won't help you.
The easier solution is to await
a frame:
func start_progress_bar():
for x in 100:
for y in 100:
for time_waster in range(20000):
time_waster += 300
get_node("Loading Screen/Loading Bar").value += 1
print(get_node("Loading Screen/Loading Bar").value)
await get_tree().process_frame
When the execution reaches await
, Godot will save where it was, and continue after the specified signal (get_tree().process_frame
in this case) is emitted.
Another thing you could do is run the code in another Thread
. In this case you can use set_deferred
or call_deferred
to manipulate the UI.
If the time_waster
et.al. code is an stand-in for some asynchronous operation, you might also want to look for an appropriate signal (or make one) so you can await
until the asynchronous operation is completed.
WARNING: Using await
has drawbacks: It cannot be cancelled or disconnected.
Thus:
object
was freed, and the signal is emitted, you have a runtime error.If you run into that, refactor the code by connecting a Callable
continuation (which can be an anonymous method a.k.a. "lambda") to the signal... When the object is freed, Godot will disconnect it.
If you need to check if a Node
is about to be freed by queue_free
, you can check is_queued_for_deletion
. If you need to check if you have a reference to a Node
that was freed, use is_instance_valid
. If you need to do something before an Object
is freed, have it override _notification
and react to NOTIFICATION_PREDELETE
.