Variables and dictionaries don't work properly in singletons (autoloaded scripts)

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Athrosus

Hi, hope you’ve had a good day.

I’m new to Godot and a few days into my project I’ve noticed some strange behavior from the singleton (autoloaded script) I’ve made. For example:
In the Globlas.gd

export var life := 5
export var cooldown_time := 5

When I edit the value of life in the editor to 10 and print it:

func _ready():
	print(life)

I get 10, but then I reduce life by 1 in the same Globals.gd script and print the new life value:

func change_life(num):
	life += num
	print(life)

I get 4, as tho the game forgot that the value was changed (and yes I’ve made sure that there’s nowhere where I set life to 5 inbetween these two prints)

Also a similar thing happened to dictionaries:

onready var game_state = {
	menu = 1,
	play = 0
}

func _on_Start_button_up():
	game_state.menu=0
	game_state.play=1
	print(game_state.play)

print is 1 but then in another script where I check game_state.play it sais that it’s 0:

func _unhandled_input(event):
	print(Globals.game_state.play)
	if Globals.game_state.play == 1:
        ...

this prints 0, as tho nothing has changed even tho the print before showed that there was a change.

I think this might also be a problem when I try to reload the current scene, I have a dictionari in my Globals that contains the path of some nodes, in the debugger I see that dictionari being filled with new node id’s but then when we get to another script it’s as tho the dictionari is null. Basically for some reason the singleton didn’t update for the rest of the game, as tho it isn’t a singleton at all (and yes I did try re adding it to autoload)

Pleas help, this is extremely frustrating and I haven’t really seen anyone else with this problem

It sounds like your code is not being called in the order you expect it to, but we’d need more complete code example to see

beaverusiv | 2021-04-03 23:23

Thank you for the fast replay !

That’s the thing tho, there’s not much more to show.
The declaration of life, cooldown_time and the game_state dictionary is in the Globals GDscript.
The functions change_life, _ready(), and _on_Start_button_up are also in the Globals.gd .
The function _unhandled_input(event) is in another script that’s a part of the scene, and it’s being called all the time by stuff like mouse move so I can see that as soon as game_state.play=1 is set in the Globals function the print sais that game_state.play is 0.

What other context could I give, it’s a simple project.

Athrosus | 2021-04-04 07:16

okay, let’s start with life first, if you search your project there is ZERO reference to life other than what you’ve posted? Try printing life on _process() like so: make new var old_life up top and in _process() do if old_life != life: print("life changed", life) old_life=life Run it and see when it changes

beaverusiv | 2021-04-04 07:42

The rezult is :

life changed5
life changed10
life changed4

The life changed4 is when the change_life(num) function was called with -1, it was called from a RigidBody using it’s signal _on_PlayerRigidBody_body_entered(body)

And when I removed the value from export var life := 5 I got this:

life changed0
life changed10
You dead
life changed-1

But when I added Globals.life = 10 in the _ready() function of the RigidBody where I call Globals.change_life(-1) I got this:

life changed10
life changed10
life changed9

It’s starting to feel like the engin isn’t really treating Globals as a singleton even tho I can call it anywhere with Globals.etc

Athrosus | 2021-04-04 08:45

I put print("Life in xxxx",Globals.life) in a bunch of my methods, if I remove Globals.life = 10 in the _ready() function of the RigidBody I get this:

Life in Globals 0
Life in Globals 0
Life in Trail 0
Life in RidgidBody 0
Life in Camera2D 0
Life in misc_ui 0
Life in PowerUps 0

if I leave Globals.life = 10 in the _ready() function of the RigidBody I get this:
(the print is after the Globals.life = 10 in the ridgidbody)

Life in Globals 0
Life in Globals 0
Life in Trail 0
Life in RidgidBody 10
Life in Camera2D 10
Life in misc_ui 10
Life in PowerUps 10

Athrosus | 2021-04-04 09:06

set breakpoints and step throu your code line by line and inspect the values. Maybe you find something strange.


PS.:

It’s starting to feel like the engin isn’t really treating Globals as
a singleton even tho I can call it anywhere with Globals.etc

i used it many many times like this and i worked without a problem

whiteshampoo | 2021-04-04 09:47

It’s strange to me that onready and _ready() in the Globals.gd gets run through twice at the start of the app, is this suposed to happon ?

going step by step it runs through all the onready variables and the _ready function and when that’s finished the steps go through that again, that’s also why there’s two Life in Globals 0 in the prints in my last post.

Tho I should note, in the first pass life doesn’t get set with the value I put in the editor, but on the second pass it does.

Athrosus | 2021-04-04 11:01

Can you send the project-folder?

whiteshampoo | 2021-04-04 13:04

I would feel uncomftorable doing that, I can send the GDscripts in question tho, would that suffice. It’d mostly be the Globals.gd and the RidgedBody script. These are the only places that life is being edited anyway, and the RidgedBody script only calls a method that’s in Globals.gd anyway

Athrosus | 2021-04-04 13:20

you can also make a small example project, where your problem appears.
Its much much easier to help, if i have smothing to work with.

whiteshampoo | 2021-04-04 13:55

Here’s a Media fire link to an example project:
https://www.mediafire.com/file/kugw8t8ady89ciy/AutoLoadError.zip/file

All that’s there is a Globals.gd that’s autoloaded and a NewScript.gd that calls a function in the Globals when you press the left mouse button.
Globals has a life var that is set to 5 in the editor but you can see that NewScript thinks it’s set 0

When you run the project you’ll see text telling you which button to press to get which problem, there’s another get_tree().reload_current_scene() problem that I found in my project and it’s show here as well

Athrosus | 2021-04-04 15:02

:bust_in_silhouette: Reply From: whiteshampoo

You include your Globals.gd to a node in your Scene. Thats not how Singeltons/Autoloads work in Godot. You just have to include it in Autoload (ad .gd or as .tscn).
It then gets loaded parallel to your Scene. The tree then looks something like this

+root
|-Globals
|+Node2D
 |-Globals (<- this is wrong and causes the problems)
 |+NewScript
  |-Info

When you change the scene, Globals stays in the scene, so _ready() in Globals is not called again. so newscript_node in nodes_pleas becomes invalid/null, because it’s from the old scene, that no longer exists.

Hope you understand what i mean.
Just remove Globals from you scene and leave it in autoloads, then it will do what you want.

PS.: that means you cannot export variables to Globals in the Scene itself, but you can just use

Globals.life = 5

in the scenes _ready() method

PPS.: You can inspect this in the Scene-Tree under “remote” in the editor while running your game.

I think I understand, basically, I shouldn’t have a node in my scene that contains the autoloaded gdscript because it’ll be loaded automatically as a child of root.
This intern means that I can’t set any variables in the editor directly like I could normally with export.
Also this way of dealing with nodes is improper since the dictionary wont be updated after the scene has been reloaded so I need to deal with nodes in a better way.

Can you pleas tell me if I understand this correctly.

And thank you for the help and time <3 !

Athrosus | 2021-04-04 16:37

you understand it correctly.

You can autoload a complete scene and not only a script. you can then use exports on this scene, but you can only “set” the exports once.

whiteshampoo | 2021-04-04 16:44