Godot Version
4.3
Question
I have a Player
scene which inherits from a Base_Character
scene
Inside my Base_character
scene, I have a progress bar to indicate HP and a related HP variable with a setter function
class_name Base_Character
extends CharacterBody2D
var base_stats: Dictionary = {"STR": 1, "INT": 1, "WIS": 1, "END": 1, "GUI": 1, "AGI": 1}
@export var stats: Dictionary = base_stats
@export var max_hp: float:
get():
return (base_stats['END'] * 10) + (base_stats['WIS'] * 3) + (lvl * 15)
@export var hp: float:
set(value):
hp = value
_update_hp_bar()
func _ready() -> void:
stats = base_stats
hp = max_hp
func _update_hp_bar():
$ProgressBar.value = (hp / max_hp) * 100
In my Player scene, however, I have another progress bar which functions exactly like the HP bar but instead is used to store SP
The player scene has something like this
extends Base_Character
class_name Player
# Player stats
@export var max_sp: float:
get():
return (stats['AGI'] + stats['END']) * 3
@export var sp: float:
set(value):
sp = value
print($ProgressBar2)
_update_sp_bar()
func _ready() -> void:
ally = 1
super._ready()
sp = max_sp
func _update_sp_bar():
$ProgressBar2.value = (sp/max_sp) * 100
My scene tree looks something like this:
Base_Character
Player
When I run the Base_Character scene it runs just fine with no issues. However, when I try to run the Player scene I get the following error:
E 0:00:00:0427 player.gd:13 @ @sp_setter(): Node not found: "ProgressBar2" (relative to "Base Character").
<C++ Error> Method/function failed. Returning: nullptr
<C++ Source> scene/main/node.cpp:1792 @ get_node()
<Stack Trace> player.gd:13 @ @sp_setter()
E 0:00:00:0427 player.gd:31 @ _update_sp_bar(): Node not found: "ProgressBar2" (relative to "Base Character").
<C++ Error> Method/function failed. Returning: nullptr
<C++ Source> scene/main/node.cpp:1792 @ get_node()
<Stack Trace> player.gd:31 @ _update_sp_bar()
player.gd:14 @ @sp_setter()
I donāt know why it seems to be trying to find the SP bar inside the Base_Character scene. Please any help in understanding the issue and solutions will be greatly appreciated
Thatās very odd. I just tried to replicate the issue in its simplest form and everything ran fine. I donāt see what is different about our structures, but maybe you will:
a.gd
class_name A
extends Node
func _ready() -> void:
print("a", get_children());
print($ASceneChild);
b.gd
class_name B
extends A
func _ready() -> void:
print("b", get_children());
print($BSceneChild);
super();
a_scene.tscn
- AScene (type A)
- ASceneChild (type Node2D, but irrelevant)
b_scene.tscn
- BScene (inherits ā¦aScene.tscn. Type B)
- BSceneChild (type Node2D, but irrelevant)
- ASceneChild (highlighted in yellow, inherited and un-mutable)
Running a_scene outputs:
a[ASceneChild:<Node2D#30333207902>]
ASceneChild:<Node2D#30333207902>
Running b_scene outputs:
b[BSceneChild:<Node2D#30836524383>, ASceneChild:<Node2D#30819747166>]
BSceneChild:<Node2D#30836524383>
a[BSceneChild:<Node2D#30836524383>, ASceneChild:<Node2D#30819747166>]
ASceneChild:<Node2D#30819747166>
@duckbanditdan thanks for your response. This seems to be an accurate simplification and representation of my initial code.
However, comparing my initial code and trying again, it seems that my code doesnāt even approach the _ready
function of āB_Sceneā before erroring out.
I may not have mentioned this earlier, but the
print($ProgressBar2)
line in the sp setter function displays <Object#null>
in the output console
From what I can observe the program seems to call _update_sp_bar()
before even getting to the ready function. This causes it to try to assign a value to a null object.
So my issue remains: Why does this same syntax work for HP in Base_Character
but fail for SP in Player
? And why does the error tell me the program is trying to look for the node in Base_Character
instead of Player
?
If you need any further information Iād be happy to provide.
I just tried commenting out the
$ProgressBar2.value = (sp/max_sp) * 100
line and it ran successfully (though as you can imagine this is not really a solution as I need to be able to actually update the progress bar)
However, this did allow me to observe something interesting. The script now reaches the _ready
function and get_children()
finally runs. This displays all the children including the $ProgressBar2
. And on trying to update the sp value the setter function now accurately prints the address of the $ProgressBar2
So these two print lines
@export var sp: float:
set(value):
sp = value
print($ProgressBar2)
_update_sp_bar()
func _ready() -> void:
print(get_children())
ally = 1
sp = max_sp
super()
and the print(get_children())
line from the Base_Character
_ready
function now display
<Object#null>
[ProgressBar2:<ProgressBar#29225911574>, Sprite2D:<Sprite2D#29108471046>, CollisionShape2D:<CollisionShape2D#29125248263>, ProgressBar:<ProgressBar#29142025480>, Pointer:<Sprite2D#29209134356>, Gear:<Node#29276243224>, Inventory:<ItemList#29326574873>]
ProgressBar2:<ProgressBar#29225911574>
[ProgressBar2:<ProgressBar#29225911574>, Sprite2D:<Sprite2D#29108471046>, CollisionShape2D:<CollisionShape2D#29125248263>, ProgressBar:<ProgressBar#29142025480>, Pointer:<Sprite2D#29209134356>, Gear:<Node#29276243224>, Inventory:<ItemList#29326574873>]
So I think the workaround I will implement going forward is to run a null check on $ProgressBar2
in _update_sp_bar
and pass during the initial setup. Something like this:
func _update_sp_bar():
if $ProgressBar2:
$ProgressBar2.value = (sp/max_sp) * 100
Though this works if anyone can explain the cause of the initial error it will be much appreciated. Especially why it worked for Base_Character
and not for Player
. And even more especially why it was looking for $ProgressBar2
in Base_Character
instead of Player
.
1 Like
Glad you found a workable solution.
I added these to my minimal reproduction:
# in A
@export var testa: int:
set(value):
testa = $ASceneChild.get_index();
# in B
@export var testb: int:
set(value):
testb = $BSceneChild.get_index();
a_scene still runs fine. b_scene runs into the same issue you have (or similar, "Cannot call method āget_indexā on a null value).
I then tried removing the @export annotation from testb, and b_scene could run again without issues.
It looks like derived scenes call setters on their @exports before _ready(), while base class scenes do not. Iāve no idea why this would be the case though.
It might be worth checking GitHub Ā· Where software is built to see if anything similar has been reported there. Or perhaps someone with a better understanding of the engines can come and tell us whatās going on Honestly, Iām more confused about why the base class doesnāt call the setter before _ready(), because the value of the export variable could well be needed before then.
1 Like
Alright then. Thanks for the help. I also slightly suspected that, though the why behind the behaviour still eludes me too. Glad to see itās not cause Iām just bad at least .
1 Like