-
-
Notifications
You must be signed in to change notification settings - Fork 20.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unable to Force Update 2D UI Controls before Next Frame #20623
Comments
Try |
call_deferred doesn't solve the problem, it just pushes the problem ahead one frame |
Starting to get concerned, implementing hack after hack after hack trying to work with Controls, and now they are starting to diverge in behaviour across machines. I dug a little bit into the C++ code to try to find where the UI is updated. My thought is to expose a C++ call to GDScript that would force update the UI (minimum_sizes?) without rendering the next frame. And maybe this could be done via GDNative? Anyone weigh in on the feasibility or point to where in the C++ code the UI is calculated? |
Can you make a software demo of the issue? We can record the result and try to catch that frame or two on video to really demonstrate it. I agree that it's an issue too because even at 60fps I've noticed my text having to recenter and it looks very unclean when any label text is replaced and the words bounce around as they do. I've found my own hack to get around it, but I'd rather not mess with the code in my project and it's been a while since I've touched that code. I think a demo of the issue might really help all of us either way though as we'll have a minimal version of the issue to test with and find solutions for. |
How does one actually wait a frame to continue working? I have code that looks like this (for instance), how would I delay one frame given the code below...?
Since I am scaling and just waiting for the tween, I am not sure how I would know where to split this function (kapow_start and kapow_finish?). Just confusing... Also feels like it would make the code completely unwieldy to do something like this. In my case, I am showing a message onscreen that scales and fades out, but needs to be positioned exactly (and centered). Since this code cannot get the correct size, the label is always in the wrong spot. |
@juddrgledhill It's not possible AFAIK. I've tried many ways. Didn't look too deeply into your code. However what I've been doing recently is given up trying to make Godot update with accurate information, but rather, to always add in Controls with minimum sizes as parents of nodes that I need to animate their size. So for example if I want a dialog bubble to grow, I add an already-grown empty Control node as the parent for the dialog bubble to grow inside of (custom_minimum_size). That way there is no glitching. It isn't ideal because it's not exactly what I want but is better than glitchiness. Some hack like that might help you as well. @agameraaron I would like to make a sample project to demonstrate this, haven't had time, but it's on my bucket list. It's pretty simple though, just add a container/button to a VBox and get the size before and after the draw call. Unless you do something with the incorrect size, there won't be a glitch. So, moving the VBox before+after the draw call based on the incorrect size will cause it to glitch. |
Built a sample project. Which led me experiment with using "get_minimum_size" instead of "get_size". For static movement, it does seem that "get_minimum_size" correctly reports the size before next frame. So, that's a bit of good news! Not sure if it helps determining text box sizes, but that is another issue. Animated containers still pose a problem. Perhaps I'm just doing them wrong? Can we tween the minimum_size instead of rect_size? |
Since you specify the minimum size, that makes sense. The actual size is computed, which, I believe, is what you need that single frame for. I was asking online and someone suggested this modification to the approach. kapow_label.text = message
yield(get_tree(), "idle_frame") Set the text, then yield to an idle frame. Not sure if this is literally doing what I expect, but I could no longer see the issue physically. So I call that a "win". |
Good find! That does solve the first column issue of the glitch-2d-ui project attached above:
Very good progress. However the animated button is still glitchy, once a solution to that is found (whether existing or new) I think this issue could be closed. |
If you |
It'd be much better if there were a built-in way of forcing an update, instead of having to do hack workarounds. hide() and show() could work temporarily though but won't there be flicker doing that? |
I stumbled across the same thing. I'd like to create a dynamically layouting container in the sense, that i specifiy a maximum width (depending on the current screen size) and the container takes child elements, adds them in a horizontal list and the breaks to a new line as soon as my maximum width is reached. I found no way to do this, as i am missing an explicit layout() function. The initial size of the child elements or container is not correct, as it will be changed by layouting. |
I was able to mitigate this issue myself by using This forces a call to the Controls This amounts to the same thing as calling Hope this helps someone! |
That does not seem to change anything for me. I am trying to calculate how many lines will my cells use in a custom table I made with Vbox, Hbox and labels |
I suppose you could hide the control while it's being updated. It might be better than displaying a glitched version during one frame. |
For some reason if I do that it does not work.
While that works as intended by just removing the hide():
|
@Ramh5 Try setting the Control's modulate to If that still doesn't work, set the opacity to a very low value ( |
|
This is still an issue in 3.3.2. I want to position a panel based on the margins of a HBoxContainer while resizing both, but the update happens a frame too late. I can't seem to find Edit: I found it, but it didn't help. :P |
3.3.3 has the same problem. I have to hide the label, change the text (which of course we want to update the size) then Show() the label. |
Some out of context code that may help some people with solving this issue:
|
This is not an effective workaround; such invisible controls will still catch inputs and there's no way (at least in 3.x) to mark a control and all of its children as being inaccessible to inputs without recursively iterating over all of them. This is a really, really, really bad design pattern. In my case: I'm using the fully-transparent-modulation workaround in one of my games to make sure that a scroll container doesn't update a frame late when being opened, and I do indeed need to procedurally change the input flags of its children to keep it from causing problems. I need to add elements to a scroll container's child while they're all hidden and then also forcibly scroll the scroll container immediately after setting it to visible but before it renders. Some aspect of this does not work. I'm not convinced that it's a bug. The real problem is that there isn't a "ok, just forcibly update all the state you normally update when rendering" escape hatch on controls, not that there is such state to begin with. And I can't set it to visible-but-transparent, wait a frame, scroll it, then set it to non-transparent. It needs to all happen the exact frame that the corresponding input happens. If it doesn't, I'm probably going to get bugs where people can bring up the scroll field and also do something else that's incompatible in the same frame (like load a saved game), breaking the UI. Having to use complicated workarounds like this makes UIs more fragile and hard to reason about.
|
This issue is too old now, for old versions of Godot. If it still persists for someone, then reopen against a specific version |
I am confused, this issue is present in the currently live stable version of the engine (3.5.1). |
It's a fair question. Last comment is 1 year old, seems like enough workarounds have been found to prevent it from being a showstopper issue for anyone. Unlikely anyone is ever going to work on it for v3. I haven't tested it against 3.5 or 4. And I'm not sure if v4 has any features to force update. If not, maybe it could be made into a feature request for v4.x? |
If it's any help, my original use for this was in ignorance of the utility of Godot's container system. I have yet to find another use for it since then. |
Unlikely to be fixed is better than guaranteed to not be fixed in my eyes. |
This is still an issue in Godot 4.0.1 |
Well, it's not an issue, it's a design limitation. It can't be solved as a bug, as it needs a proposal that would explain how this would work in practice and how this needs to be implemented. Responses to this ticket are a good starting point, but without a qualified technical explanation, a change in core is unlikely to happen. |
Doesn't EDIT: |
The inherent problem with waiting for the next frame, though, is the potential visual glitch on that first frame. TBH tho I just haven't had a problem with this issue for over a year, workarounds are easy in my cases, as I could hide any problematic nodes until the next frame. |
what worked for me: "force redraw" by yield(idle_frame), but surround that with node.modulate.alpha=0 (then 1 after the yield/await) |
After infinite problems with Control sizing I also resorted to |
Same issue on
|
I confirmed |
(box as BoxContainer).notification(NOTIFICATION_SORT_CHILDREN)
(label as Label).update_minimum_size()
(label as Label).size.y = 0
print((label as Label).size) Remember, |
I will share my code. Hopefully, advance humanity. static func update_minimum_size_control_root(control: Control) :
var previous_parent: Node
var parent: Node = control
while parent is Control :
previous_parent = parent
parent = parent.get_parent()
update_minimum_size_sub_control(previous_parent)
static func update_minimum_size_sub_control(control: Control) :
if control is Container :
control.notification(Container.NOTIFICATION_SORT_CHILDREN)
control.update_minimum_size()
#control.size.y = 0
for child: Node in control.get_children() :
if child is Control :
update_minimum_size_sub_control(child)
control.update_minimum_size() |
NOTIFICATION_SORT_CHILDREN is defined in container.h, but the code that actually sorts the children is defined in individual containers, such as box_container.cpp or margin_container.cpp. Lines 56 to 59 in 15073af
godot/scene/gui/box_container.cpp Lines 292 to 294 in 15073af
godot/scene/gui/margin_container.cpp Lines 100 to 102 in 15073af
|
In Godot 3.5.3, yielding for a frame is sufficient about 80% of the time but sometimes two frames is necessary, so I'm using the following code to keep a label centered: var old_center_x := rect_position.x + rect_size.x * 0.5
yield(get_tree(), "idle_frame")
rect_position.x = old_center_x - rect_size.x * 0.5
yield(get_tree(), "idle_frame")
rect_position.x = old_center_x - rect_size.x * 0.5 |
Is there a reason you need to use code for this? Generally you should be able to center a label using built-in layout settings. |
Which built-in layout settings specifically? extends Control
func _ready() -> void:
var label := Label.new()
label.name = "Label"
label.anchor_bottom = 0.5
label.anchor_top = 0.5
label.anchor_left = 0.5
label.anchor_right = 0.5
add_child(label)
var timer := Timer.new()
timer.connect("timeout", self, "_on_Timer_timeout")
add_child(timer)
timer.start(1.0)
func _on_Timer_timeout() -> void:
$Label.text += "%" Here is a program which adds a label, centers it with anchors, and gradually makes it wider and wider. It does not stay centered as its text changes. How would you modify this program to keep the label centered, using built-in layout settings? The only approach I've come up with is similar to the post you replied to, manually calculating the rectangle's new bounds based on its contents and then adjusting its position. |
Try using |
They're on Godot 3.5.2. |
In Godot 3 it's called |
Godot version:
3.0.5
OS/device including version:
All
Issue description:
Other game engines have a way to force components to update after new data is set. Godot seems unable to do this.
For example: setting text in a wordwrapped Label and finding the new vertical size, adding children to VBoxContainer. Then, using that new size to position the control (for example, moving a VBoxContainer based on it's size).
If we have to wait for the next frame to find out what new sizes/etc our controls are, then we have one frame of visual glitchiness to contend with. And that can be multiplied for however many times you need to be changing controls. On some systems the framerate is so fast I can't see the glitches, but on most devices I've used they are very apparent. I've had to resort to hacks, like taking parts of the background, and pasting them again to hide the glitches, keeping an hidden clone and switching back and forth, but this shouldn't need to be done and eventually becomes cumbersome.
All of this can be fixed by have a means to force a cascading update.
The text was updated successfully, but these errors were encountered: