Getting the size of a (child) control node, using control.size in _ready(), returns (0,0) even when waiting

Godot Version

V4.2.1

Question

I can’t get the size of a child control in _ready(), it always returns (0, 0). I’m expecting it to return (0, 90) in my usecase.
I’ve read previous topics about this issue. I’ve tried “call_deferred” and “await get_tree().process_frame” but they don’t work for my use case. I’ve written down what I’ve tried and their results.

A control.size() returns (0,0) when it hasn’t been set up yet.

# Part_info
@onready var item_picture_container_control = $VBoxContainer/MarginContainer2/Control

func _ready():
	print(item_picture_container_control.size) # Prints (0, 0)

My control has a few layers of nested controls.


The script I’ll be showing here is ran in part_info. I’m trying to get the size of the marked TextureRect.

I read that controls get a size after their parents are _ready().
I also read the parent’s _ready() only triggers after every child _ready() has triggered, which means waiting for the child control’s ._ready() is insufficient.
So I tried wrapping getting the size in a call_deferred inside _ready(), but it still returns 0,0.

# Part_info
@onready var item_picture_container_control = $VBoxContainer/MarginContainer2/Control

func _ready():
	print(item_picture_container_control.size) # Prints (0, 0)
	
	call_deferred("print_container_size") # Prints (0, 0)


I set a break point inside the call_deferred, and the child controls weren’t done setting up.

I then changed from call_deferred wrapping to just
await get_tree().process_frame
and now it works 33% of the time.
This suggests that it takes more than one frame for all control nodes to have set themselves up.

# Part_info
@onready var item_picture_container_control = $VBoxContainer/MarginContainer2/Control

func _ready():
	print(item_picture_container_control.size) # Prints (0, 0)
	
	call_deferred("print_container_size") # Prints (0, 0)
	
	await get_tree().process_frame
	print_container_size() # Prints (0, 0), sometimes prints (0, 90) which is expected

So I implemented a loop which runs every frame, and checks whether the required control’s size is no longer (0, 0):

# Part_info
@onready var item_picture_container_control = $VBoxContainer/MarginContainer2/Control

func _ready():
	await_to_be_sized(item_picture_container_control) # Consistently prints (0, 90)

func await_to_be_sized(control: Control):
	while control.size == Vector2.ZERO:
		await get_tree().process_frame
	print("Control size: %s" % [control.size])

This prints the expected values correctly, consistently, however it feels wrong and inefficient to do it this way.

What would be the best way to handle this?

Hi,
In your var definition, you point to the control node, not the textureRect. Can be the issue maybe.

1 Like

Copypasted that wrong into the post, sorry. But the issue also happends for that control :slight_smile:

I cannot help without knowing how each part of your UI layout is defined.
By creating a tree like yours in a project i don’t have any issue about the TextureRect size.
But if you can create a small project with the same issue or you don’t mind about sending your current project, i can take a look.

1 Like

I understand. The process of creating a minimum project has helped me solve the issue. Thanks! I’ll document my findings here.

TL;DR:

:white_check_mark: Do use await get_tree().process_frame in _draw() for correct, consistent results for control.size:

func _draw():
    await get_tree().process_frame
    print(control.size) # consistent 0, 90

:x: Do not use call_deferred()
:x: Do not use await get_tree().process_frame in _ready() even if it prints the correct values for you, since the results can be inconsistent or wrong depending on your UI complexity

Reference code

For reference, this is the (simplified) code in the control:

func _ready():
	check_control_size_timings()
	
func _draw():
	check_control_size_timings()

func check_control_size_timings():
	print_container_size()
	
	call_deferred("print_container_size")
	
	await get_tree().process_frame
	print_container_size()
	
	await_to_be_sized()
	
func print_container_size():
	# Expected output: (0, 90)
	print("Control size: %s" % [control_to_get_size_of.size])

func await_to_be_sized():
	while control_to_get_size_of.size == Vector2.ZERO:
		await get_tree().process_frame
	print_container_size()

Hierarchy

And this was my hierarchy:

-> Main_Scene
    -> UI  <- (Has script mentioned above)

        -> A bunch of control nodes here...
            -> A bunch of child control nodes here...

        -> A bunch of nested control nodes here...
            -> A bunch of nested control nodes here...
                -> ControlToGetSizeOf <- control.size

        -> A bunch of control nodes here...
            -> A bunch of child control nodes here...

Tests

When running the scene with the control.size script directly:

Expected value is (0, 90)

  1. Directly calling print(control.size) in:
    1.1. _ready(): (0, 0) :x:
    1.2. _draw(): (0,0) :x:
  2. Call_deferred print in:
    2.1. _ready(): (0, 0) :x:
    2.2. _draw(): (0, 0) :x:
  3. await get_tree().process_frame in:
    3.1. _ready(): (0, 90) :white_check_mark: Consistently
    3.2. _draw(): (0, 90) :white_check_mark: Consistently
  4. await_to_be_sized():
    4.1. _ready(): (0, 90) :white_check_mark: Consistently
    4.2. _draw(): (0, 90) :white_check_mark: Consistently

However, I’m not running that scene directly. It’s a child scene, and it should not be visible until a button is pressed. So I have two options:

Initiate the node by drag and dropping in the editor, and turning it invisible. It is turned visible by the press of a button. Here are the results:

  1. All _ready() methods return (0,0) :x:
  2. _draw():
    2.1. Direct print (control.size): (0, 0) :x:
    2.2. Call_deferred print: (0, 0) :x:
    2.3: await get_tree().process_frame and await_to_be_resized() both consistently return (0, 90), the expected value :white_check_mark:

Instantiating the scene manually is a no-go for my use-case.
Each scene represents one “card reward” in my game, and I want it the ‘rewards’ to vary between 1 to (many) cards at a time.
For completeness sake, I will spawn them in, in 2 ways:

  1. When the “Make visible” button is pressed, instead of making the invisible scene → visible, the ui node will instantiate a new scene instead when the button is pressed;
  2. the ui node will instantiate the required scenes before the button is pressed. The button press will turn the required scenes visible.

Here are the results.

Instantiate on button press:

  1. Directly calling print(control.size) in:
    1.1. _ready(): (0, 0) :x:
    1.2. _draw(): (0,0) :x:
  2. Call_deferred print in:
    2.1. _ready(): (0, 0) :x:
    2.2. _draw(): (0, 0) :x:
  3. await get_tree().process_frame in:
    3.1. _ready(): (0, 90) :exclamation: INCONSISTENTLY, can also print (0, 0)
    3.2. _draw(): (0, 90) :white_check_mark: Consistently
  4. await_to_be_sized():
    4.1. _ready(): (0, 90) :white_check_mark: Consistently
    4.2. _draw(): (0, 90) :white_check_mark: Consistently

Instantiate the scene(s) at the _ready() of the parent scene:

  1. Directly calling print(control.size) in:
    1.1. _ready(): (0, 0) :x:
    1.2. _draw(): (0,0) :x:
  2. Call_deferred print in:
    2.1. _ready(): (0, 0) :x:
    2.2. _draw(): (0, 0) :x:
  3. await get_tree().process_frame in:
    3.1. _ready(): (0, 0) :x:
    3.2. _draw(): (0, 90) :white_check_mark: Consistently
  4. await_to_be_sized():
    4.1. _ready(): (0, 90) :white_check_mark: Consistently
    4.2. _draw(): (0, 90) :white_check_mark: Consistently

In previous (forum) threads, call_deferred was recommended as a solution for this issue. However, in my tests, it returns the wrong value, so I cannot recommend using call_deferred for getting the control.size

Seeing as getting the size in _ready() can be wrong or inconsistent
:x:

func _ready():
    await get_tree().process_frame
    print(control.size) # 0,0 or inconsistent 0, 90

the proper solution is to get the size in _draw()
:white_check_mark:

func _draw():
    await get_tree().process_frame
    print(control.size) # consistent 0, 90

TL;DR:

:white_check_mark: Do use await get_tree().process_frame in _draw() for correct, consistent results for control.size:

func _draw():
    await get_tree().process_frame
    print(control.size) # consistent 0, 90

:x: Do not use call_deferred()
:x: Do not use await get_tree().process_frame in _ready() even if it prints the correct values for you, since the results can be inconsistent or wrong depending on your UI complexity

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.