simple thread to load a scene (solved!)

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

Hi ! I have 3 scenes (titlescreen, waitingroom & ingame) and I’ve been trying to smoothly load the ingame.tscn when clicking a button on the titlescreen.

Happycamper helped me with some lines of code (thx!), but I’m still not understanding how to deal with threads…

this is what I have in the titlescreen scene script :

  func _on_loadbutton_pressed():
    	get_node("/root/global").goto_scene("res://scenes/ingame.tscn",true)

and in the global.gd singleton :

extends Node 

onready var thread = Thread.new()
onready var current_scene =  get_node("/root/titlescreen")

func goto_scene(new_scene,loading):

	current_scene.queue_free()
	current_scene = new_scene    
	get_tree().get_root().call_deferred("add_child",current_scene)  

	if(loading):
		thread.start(self,"prep_scene")
		

func prep_scene(userdata):    

	var s = ResourceLoader.load("res://scenes/waitingroom.tscn")
	var scene = s.instance()

	goto_scene(scene,false)   
	thread.call_deferred('wait to finish')

All it does at the moment is clear the titlescreen, but there’s no waitingroom nor ingame scene appearing. I don’t know what to put instead of ‘wait to finish’ in the call_deferred, and don’t see where the code is checking the progression of the loading of the ingame scene…

Can someone please make it a bit clearer for me ? :confused:

:bust_in_silhouette: Reply From: ZacB

Why not get_tree().change_scene("your scene") ???

Doing so will block the main thread which results in not so user-friendly “freeze” if it takes more than a second to instance the loaded scene.

Xrayez | 2018-07-14 11:27

yes, Xrayez is right ! In my case, it takes about 13 seconds to load the ingame scene so the long freeze would look like a bug… not cool

nonomiyo | 2018-07-14 11:53

Guys I think you can load a scene async. Also you can preload the scene

ZacB | 2018-07-14 13:23

:bust_in_silhouette: Reply From: eons

change_scene_to allows you to use a loaded scene resource, which should be fast since the resources are already loaded.

Another way, add it to the tree and change the current_scene value.
It may not have much difference compared to change_scene_to if the scene is too big.

In that case, is the scene itself the one that will have to load parts on a thread and add one by one.

ok, so I tried this and it’s not working :

extends Node

onready var sceneToLoad = preload("res://scenes/ingame.tscn")

func _on_loadbutton_pressed():
	get_tree().change_scene_to(sceneToLoad)

It’s taking long to launch the project (godot’s logo screen for 13 seconds before showing the titlescreen). What I’d like is to launch the titlescreen fast, then preload the ingame scene without freezing the game so I can have a loading animation on the titlescreen while the ingame scene is being preloaded.

Did I do something wrong ? :confused:

nonomiyo | 2018-07-14 17:24

Loading it in the main thread is the problem here, the answer that details about the use of threads is what you want, I guess.

Also look for “background loading” on the documentation which explains a bit how it works.

eons | 2018-07-17 03:22

:bust_in_silhouette: Reply From: ICatRegister

Use ResourceLoader interactive mode and move it outside bg thread

thread.start(self,"prep_scene", ResourceLoader.load_interactive(path))

Change your “prep_scene” method like this :

func prep_scene(interactive_ldr):
 while (true):
	var err = interactive_ldr.poll();
	if(err == ERR_FILE_EOF):
		call_deferred("_on_load_level_done");
		return interactive_ldr.get_resource();

Add another func, wait thread and use loaded resource:

func _on_load_level_done():
 var level_res = thread.wait_to_finish()
 var scene = level_res.instance();
 add_child(scene);

it returned an error on the resourceloader line :

parser error : identifier not found : path
 0:00:01:0639 - Condition ' _debug_parse_err_line >= 0 ' is true. returned: 0

what do you mean “move it outside background thread” ? how do I do that ? Is it why it’s still not working ?

Thanks again !! --_–

nonomiyo | 2018-07-15 18:50

“path” must be a valid path to resource, in your case this is “res://scenes/waitingroom.tscn”

i mean ResourceLoader should be run from main thread. it’s already done in this line

thread.start(self,"prep_scene", ResourceLoader.load_interactive("res://scenes/waitingroom.tscn"))

ICatRegister | 2018-07-16 10:13

hmmm I tried that but it’s not working at all :
I still have the godot logo for 13 sec on launch, but when I click on “play”, it goes to the waitingroom scene and stays there.

just to be sure I did things right, this is what I have now in the autoload singleton’s script :

extends Node

onready var thread = Thread.new()
onready var current_scene =  get_node("/root/titlescreen")  

func goto_scene(new_scene,loading):

	current_scene.queue_free()
	current_scene = new_scene
	print("goto_scene OK")
	print("current scene =" + current_scene)
	print("new scene =" + new_scene)

	get_tree().get_root().call_deferred("add_child",current_scene)   

	if(loading):
		thread.start(self,"prep_scene", ResourceLoader.load_interactive("res://scenes/waitingroom.tscn"))		


func prep_scene(interactive_ldr):
	print("print prepscene")
	while (true):
		var err = interactive_ldr.poll();
		if(err == ERR_FILE_EOF):
			call_deferred("_on_load_level_done");
			return interactive_ldr.get_resource();
			
func _on_load_level_done():
	var level_res = thread.wait_to_finish()
	var scene = level_res.instance();
	add_child(scene);

Thanks for sticking with me on this problem ICatRegister, I really appreciate it !!
It feels like it’s something that should be super easy to code but it’s a damn puddle of mud…

nonomiyo | 2018-07-16 10:58

the code seems to be correct, except mess with order of scenes loading.
as i understand scene “ingame.tscn” is the largest, so you need:

  1. Show title scene
  2. Preload or load waitingroom.tscn, free title scene and show waitingroom.tscn

goto_scene(load(“res://scenes/waitingroom.tscn”).instance(), true);

  1. Load your largest scene in thread
thread.start(self, "prep_scene", ResourceLoader.load_interactive("res://scenes/ingame.tscn"))
  1. Free waitingroom .tscn and show ingame.tscn
    func _on_load_level_done():
      var level_res = thread.wait_to_finish()
      var scene = level_res.instance();
      goto_scene(scene, false);

ICatRegister | 2018-07-16 16:37

OK, it is working now !! :smiley:

argh, I was starting to lose hope here : it was working but it was taking about 40 sec (!) to run the ingame scene with this thread method

BUT I was using godot v3.0.2. Just updated to 3.0.5 and now it’s working perfectly fine :smiley: (weight out of my chest !)

Thank you so much mate, you’re a boss ! :wink:

nonomiyo | 2018-07-17 10:01

one laaaast question though :

get_tree().reload_current_scene()

this command, which is set in the ingame scene’s script, is not working anymore.
do you know what I could replace it with ?

nonomiyo | 2018-07-17 13:16