Resetting/Rerunning autoloaded script to generate new random variables - How??

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

I have a fruit-ninja style game that has a singleton/autoload script called Global.gd. This script randomly sets my “flying objects” to be used during the game. The target_objects and distractor_object are what the code generates, and if I quit and re-open the game, these will be randomly generated each time. I then use these in my main Game.gd script during the game. (Global.gd script below)

extends Node

var list = range(0,10)
var sample =[]
var distractor_object = []
var target_objects = []


var bowl = preload("res://Sprites/Objects/bowl.png")
var bread = preload("res://Sprites/Objects/bread.png")
var cheese_grater = preload("res://Sprites/Objects/cheese_grater.png")
var clock= preload("res://Sprites/Objects/clock.png")
var cup = preload("res://Sprites/Objects/cup.png")
var pot = preload("res://Sprites/Objects/pot.png")
var straws = preload("res://Sprites/Objects/straws.png")
var tissue_paper = preload("res://Sprites/Objects/tissue_paper.png")
var toilet_paper = preload("res://Sprites/Objects/toilet_paper.png")
var tooth_paste = preload("res://Sprites/Objects/tooth_paste.png")

var tex_ref_array = [bowl, bread, cheese_grater, clock, cup, pot, straws, tissue_paper,
 toilet_paper, tooth_paste]

func _ready():
	randomize()
	for i in range(4):
		var x = randi()%list.size()
		sample.append(list[x])
		list.remove(x)
	print("Array is " + str(sample))
	target_objects = sample.slice(0,2)
	print("Target objects are " + str(target_objects))
	distractor_object = sample[3]
	print("Distractor object is " + str(distractor_object))
	
func _reset_vars():
	queue_free()
	get_tree().reload_current_scene()

My problem is that I have now incorporated levels into my game. Therefore, when a player has reached a certain score, they advance to a more difficult level (the game is the exact same except the objects get smaller and occur with more frequency). So, I created a function in the global script above func _reset_vars(): that I thought would re-run the singleton script. However, it is not working in my game script.

In the game script, I have my process(): function that changes the level whenever a score is reached, and a _reset() function hat

func _process(delta):
	if targetObjectsPoints >= 5:
		level += 1 #increase level
		_reset() #reset game to rerun Global.gd script
		if level >= 7:
			end = true
			_save()
			get_tree().change_scene("res://Scenes/Restart.tscn")

func _reset():
    #reset points
	targetObjectsPoints = 0
	distractorObjectsPoints = 0
	missedObjectsPoints = 0

    #reload game scene
	get_tree().reload_current_scene()
	queue_free()

	#free Global script
	Global._reset_vars()

    #switch to scene that notifies player they have advanced to a new level
	get_tree().change_scene("res://Scenes/LevelSwitch.tscn")

However, if I do this I now get an error in my Game.gd script that the target object and distractor objects are nil meaning they’re no longer defined and the Global script did not re-run as intended.

Any advice here? Could it be that my Global script is higher in the scene tree than my Game script?

Thanks in advance!

:bust_in_silhouette: Reply From: yrtv

_ready() in singleton runs only once. Singleton has no instances, and can’t be instanced.

Make your generator a separate function and call it in _ready(), and where ever else you need it.

Hello thank you for your help. You and @Loopy have similar recommendations. I tried that and now my random number generator in my Global script is now printing the same set of numbers when it is “reset” and called again… More detail in my comment to @loopy

Do you know how I can get my random number generator to start working effectively again?

miss_bisque | 2021-02-18 01:10

:bust_in_silhouette: Reply From: Lopy

You generally do not want to queue_free() an autoloaded Node.

Instead, in Global.gd, you can just put all the code from _ready() into _reset_var() (whith some adjustments if needed), and call _reset_var() from _ready().

Autoloaded Nodes are siblings to the current scene’s main Node, the tree looks like this:

root
    Global1
    Global2
    Scene

You can see it in the tree dock to the left. Start the game, and you should see two tabs, “Local” and “Remote”. Remote represents the running tree, and updates with it. It can get values wrong sometimes, when in doubt, trust prints above this “Remote tree”.


Edit, not required to solve the issue

To clear a common misconception. Autoloads are _not_ Singletons, in that they do slightly less (no single instance system), and slightly more (added to the tree in a very special way). They are used in a very similar way, but differ.

Here is a Singleton implementation in GDScript:

class_name Single

const _instance := []

func _init():
    assert(_instance.size() == 0, "Manual instantiation")

static func get_instance():
    if _instance.empty():
        _instance.append(load("res://script path").new())
    return _instance[0]

How is it different from an Autoload?

  • Instantiation:
    When you tell Godot that a Script is an Autoload, you ask it to instantiate an instance of it for you, to put it as a direct child from the root Node named “root”, and that it should persist when changing scenes.
    A Singleton, however, should make sure that it can only be instantiated once, and you use a static function of the class to retrieve the instance. It will not necessarily be in the tree, and can even be a non-Node Object.

  • Access:
    Although subtle, this is an actual difference. When you set a script to be Autoloaded, you have the option (checked by default) to access the instantiated Node from anywhere, using a name commonly Uppercased, just like classes. However, it is not a class, but an instance of one.
    With Singletons, you use class_name (or load the script), to be able to access the class from anywhere, and from that call the get_instance() to get an object.

  • Resetting:
    It is generally more practical to reset your Autoload/Singleton with a function on the object, but you may also want to change the Object itself.
    With Autoloads, you can queue_delete the Node, but I do not know any way of setting a replacement without changing the SceneTree implementation.
    With Singletons, this is fairly easy. Using the implementation above, it would be: static func reset(): _instance.clear().
    This does however violate the “only ever one instance” rule.

Thank you for your help! I’m not getting an error anymore but my problem now is that the random number generator is generating the same numbers, even when I successfully call it from my Game script.

I changed my Global script to include a separate function called _get_random_objects(): (as you said) and that is now being called from _ready().

extends Node

var list = range(0,10)
var sample =[]
var distractor_object = []
var target_objects = []


var bowl = preload("res://Sprites/Objects/bowl.png")
var bread = preload("res://Sprites/Objects/bread.png")
var cheese_grater = preload("res://Sprites/Objects/cheese_grater.png")
var clock= preload("res://Sprites/Objects/clock.png")
var cup = preload("res://Sprites/Objects/cup.png")
var pot = preload("res://Sprites/Objects/pot.png")
var straws = preload("res://Sprites/Objects/straws.png")
var tissue_paper = preload("res://Sprites/Objects/tissue_paper.png")
var toilet_paper = preload("res://Sprites/Objects/toilet_paper.png")
var tooth_paste = preload("res://Sprites/Objects/tooth_paste.png")

var tex_ref_array = [bowl, bread, cheese_grater, clock, cup, pot, straws, tissue_paper,
 toilet_paper, tooth_paste]

func _ready():
	randomize()
	_get_random_objects()
	
func _get_random_objects():
	for i in range(4):
		var x = randi()%list.size()
		sample.append(list[x])
		list.remove(x)
	print("Array is " + str(sample))
	target_objects = sample.slice(0,2)
	print("Target objects are " + str(target_objects))
	distractor_object = sample[3]
	print("Distractor object is " + str(distractor_object))

func _reset_vars():
	var list = range(0,10)
	var sample =[]
	var distractor_object = []
	var target_objects = []
	get_tree().reload_current_scene()

meanwhile i also updated my Game script to call the Global._reset_vars() and Global._get_random_objects() functions inside my Game script

func _reset():
	targetObjectsPoints = 0
	distractorObjectsPoints = 0
	missedObjectsPoints = 0
	get_tree().reload_current_scene()
	queue_free()
	#free Global script
	Global._reset_vars()
	Global._get_random_objects()
	get_tree().change_scene("res://Scenes/LevelSwitch.tscn")

in the output for the first random number generator call (i.e. when the tree loads and runs the autoload script for the first time) my output (coming from the variable sample generated in my Global script), is, for example a set of 4 random numbers Array is [6,1,8,9]. However, when I call it again from the _reset() function in my Game script, I need those numbers to change as if I quit the game and started a whole new number generator in my Global script. Right now, I’m getting the same list [6,1,8,9].

Is there a tweak I need to make here?

miss_bisque | 2021-02-18 01:07

Try RandomNumberGenerator — Godot Engine (stable) documentation in English

GDScript build in functions may cache their result.

yrtv | 2021-02-18 09:54