Variable that already assigned became null for no reason during runtime

Godot Version

v4.5.1.stable.official [f62fdbde1]

Question

I have this script

@tool
class_name TileCardVisualStackerUi
extends Control

const EXTRA_MARGIN: Vector2 = Vector2(10, 10)

@export var card_stacker: CardStackContainer
@export var height: int = 64:
	set(v):
		height = max(v, 0)
		custom_minimum_size = Vector2(height / 2.0, height) + EXTRA_MARGIN
		print("Card Stacker is: ", card_stacker)
		if card_stacker != null:
			card_stacker.position = size / 2.0
			card_stacker.card_visual_scale = height / TileCardVisual.SPRITE_DIMENSION

see print(card_stacker). I used it do see if this object is null. This is a @tool script.

In the editor, as you can see, I already assigned this variable. And when I modify the height, card_stacker is not null.

But when I ran the game, card_stacker became null for no apparent reason. Why is it like that?
It’s weird because I did things like this before in this project and it’s fine. This time it’s not, but why?

It’s null during the height assignment. Next frame will be fine. If I assign card_stacker during runtime because it was null in editor, it would make sense. But card_stacker is already been set in the editor!! I have no script that manually set card_stacker to null.

I’m assuming, the exported variable is set to the child node?
Exported values are assigned during the initialization. That means, the child node doesn’t exist when the value is assigned.

2 Likes

When you run the script, your set routine is being called when the class is instantiated. It is possible that card_stacker has not been built when you call this routine. IIRC, Godot builds objects from the top of the tree down.

You can check this by setting the height in _ready instead of when the export variable is being defined.

I would also recommend making your reference to CardStackContainer a onready variable. Since you have it defined in the tree, I’m not sure you gain anything by defining it as an export variable.

1 Like

aaaah I see. Yes it is the child. Looks like there is a misunderstanding that makes me think it worked previously, which also confuses me.

This is the script I thought works:
This one also has card_visual as child.

@tool
class_name AnimaCardVisualUi
extends CenterContainer

@export var card_visual: AnimaCardVisual
@export var height: float = 64.0:
	set(v):
		height = v
		if card_visual != null:
			var target_size: Vector2 = Vector2(height * AnimaCardVisual.CARD_ASPECT_RATIO, height)
			custom_minimum_size = target_size
			size = custom_minimum_size
			var vis_scale: float = (
				height / float(card_visual.frame.texture.get_image().get_height())
			)
			card_visual.scale = Vector2(vis_scale, vis_scale)
			card_visual.position = target_size / 2.0

I tried to print card_visual here to see if it’s also null. But I noticed that setget on height was never called here.
Why is it not called here when it was called on my previous script?

There is no other place in which this height is modified anywhere in both scenes. This ‘height’ value is always for editor only.

I don’t think you misunderstood. I would expect that to work - when in the editor and running as a tool script. At that point all the nodes are defined.

As for your second bit of code, I suspect that the set routine is being called. You seem to be calling it in your second export statement. I would put a print statement before the if block to make sure.

It’s not called.

Setter functions don’t run for default initializers, only for exported values overwriting them. If the value isn’t changed in the inspector, the setter function won’t be called.

1 Like