英文:
TextureProgressBar's progress_texture Doesn't Advance When Its value is Changed
问题
我在Godot 4.0中工作,遇到了TextureProgressBar的问题。为了重新创建这个问题,创建一个新项目,创建一个新场景作为Node2D,并添加子节点Timer和CanvasLayer。然后为CanvasLayer添加一个名为TextureProgressBar的子节点。在Node2D上添加一个gdscript文件,并将Timer的timeout()信号连接到它(我将计时器的名称更改为StartTimer,CanvasLayer更改为Loading Screen,TextureProgressBar更改为Loading Bar,这就是为什么在提供的代码中函数调用名称不同的原因)。然后将脚本文件中的代码替换为以下内容:
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 range(100):
for y in range(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()
最后,将以下图片分配给TextureProgressBar的相应位置:
由于某种原因,TextureProgressBar直到其值达到100才会更新。为什么会这样,我该如何修复它?根据文档,它应该在值每次增加1时更新,因为TextureProgressBar的步进值设置为1,最大值为100。
英文:
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.
答案1
得分: 1
所以 Timer
的信号被触发,这段代码将在下一帧运行:
func _on_start_timer_timeout():
start_world_creation()
调用这段代码,将在下一帧结束前完成:
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)
因此,当 Godot 最终进入下一帧时,TextureProgressBar
的 value
为 100.0
,这也是默认的 max_value
,所以你会看到它已满。
希望你能看到问题:time_waster
等正在让线程忙碌,所以 Godot 无法做其他事情。在这种情况下,使用 set_deferred
或 call_deferred
是无济于事的。
简单的解决方案是 await
一帧:
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
当执行到 await
时,Godot 会保存当前位置,并在指定信号(在这种情况下是 get_tree().process_frame
)触发后继续执行。
另一种方法是在另一个 Thread
中运行代码。在这种情况下,你可以使用 set_deferred
或 call_deferred
来操作 UI。
如果 time_waster
等的代码是某个异步操作的替身,你可能还想寻找适当的信号(或创建一个信号),以便在异步操作完成后使用 await
。
警告:使用 await
有缺点:它不能被取消或断开连接。
因此:
- 即使你知道信号永远不会被触发,你也无法让 Godot 忘记。
- 如果该
object
已被释放,并且信号被触发,你将会遇到运行时错误。
如果遇到这种情况,通过连接一个 Callable
的继续执行(可以是匿名方法,也叫做 "lambda")到信号上来重构代码... 当对象被释放时,Godot 会断开连接它。
如果你需要检查一个 Node
是否即将被 queue_free
销毁,你可以检查 is_queued_for_deletion
。如果你需要检查是否有对已释放的 Node
的引用,请使用 is_instance_valid
。如果你需要在释放 Object
前执行某些操作,请让它重写 _notification
并对 NOTIFICATION_PREDELETE
作出反应。
英文:
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:
- Even if you know the signal will never be emitted, you cannot make Godot forget.
- If the
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
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论