How to properly load a saved scene (skipping _ready method)?

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

Hi!

I’m trying to implement saving and loading the game state in an adventure game. I tried the approach from Saving games, but I realized there are too many variables to save: the position and rotation of the player, the position of every animated thing in the game (e.g. slowly closing door, rotating fan), the fact if each interactive item was activated already (e.g. pickable items cannot reappear if they were already picked up) and so on. I’ve found this comment on GitHub and it seems much more promising to just save the entire current scene of the game and then load it.

I managed to save the scene using something like this:

func _input(event):
	if Input.is_action_just_pressed("goat_save"):
		var current_scene = get_tree().get_current_scene()
		_set_owner(current_scene, current_scene)
		var scene = PackedScene.new()
		var e = scene.pack(current_scene)
		print(e)
		e = ResourceSaver.save("user://test.tscn", scene)
		print(e)


func _set_owner(node, root):
	if node != root:
		node.owner = root
	for child in node.get_children():
		_set_owner(child, root)

I’ve tested it on simple scenes and it seems to save the entire state (e.g. if a checkbox is checked), so this looks like a solution I’m looking for. I’ve saved the entire gameplay scene that way and the program didn’t crash. But there is a problem when I try to load it.

The code I’ve used looks more or less like this:

goat_save.goto_scene("user://test.tscn")

...

func goto_scene(path):
	call_deferred("_deferred_goto_scene", path)


func _deferred_goto_scene(path):
	get_tree().change_scene(path)

The results look like this:

First problem: the colors and lighting look horrible, the mouse doesn’t work. and many interface elements are doubled. I’m pretty sure that the original gameplay scene was removed by change_scene, so there shouldn’t be 2 scenes.

Second problem: the _ready function gets called. Many elements of the game are initialized using the _ready function, and it should not be called again when loading a saved scene. That will cause e.g. the initial animation to be played again, the picked up items to reappear etc.

What I’d like to achieve is to load the scene in the exact state as it was saved and attach it to the game. It can be done by replacing the current scene, but it can be also just a single node in the current scene (e.g. I can assume there is always a node called “Gameplay” and only this one will be saved and loaded). I think I’ve tried all combinations of change_scene, change_scene_to, ResourceLoader and a few other things, but I couldn’t find a proper solution. Can it be done in Godot Engine?

Cheers,
Paweł

:bust_in_silhouette: Reply From: ZeEndy

Its because its setting all the nodes including the nodes from scenes to the main node and in turn duplicates all of the nodes that exist in the sub scenes.

func _set_owner(node, root):
    if node != root:
        node.owner = root
    for child in node.get_children():
        _set_owner(child, root)

While I was messing around with a way to not set it if its a scene I found a solution

func _set_owner(node, root):
	if node != root:
		node.owner = root
	for child in node.get_children():
		if is_instanced_from_scene(child)==false:
			_set_owner(child, root)
		else:
			child.owner = root


func is_instanced_from_scene(p_node):
	if not p_node.filename.empty():
		return true
	return false

This should give the desired result of saving the entire scene without duping the additional nodes.
In addition I would like to point out that only the variables that are exported or are prebuilt with a node are saved, non of the variables that are set in the script are saved and they are wiped.
I know this old but I think this would be a great guide for anyone that’s also looking to save an entire scene
The saving system below works off an idea of a world node that controls everything and level nodes that save the the level. This can be changed to fit a different system.

extends Node


var saved = false


func _process(_delta):
	if saved == false:
		yield(get_tree().create_timer(0.1),"timeout")
		save_game()
		saved=true
	
	if Input.is_action_just_pressed("reload"):
		load_game()
	if Input.is_action_just_pressed("farlook"):
		save_game()
func save_game():
	var root_node=get_node("Level")
	var packed_scene = PackedScene.new()
	_set_owner(get_node("Level"), get_node("Level"))
	packed_scene.pack(root_node)
	ResourceSaver.save("res://saved_scene.tscn", packed_scene)


func load_game():
	remove_child(get_node("Level"))
	yield(get_tree().create_timer(0.01),"timeout")
	# Load the PackedScene resource
	var packed_scene = load("res://saved_scene.tscn")
	# Instance the scene
	var Level = packed_scene.instance()
	add_child(Level)


func _set_owner(node, root):
	if node != root:
		node.owner = root
	for child in node.get_children():
		if is_instanced_from_scene(child)==false:
			_set_owner(child, root)
		else:
			child.owner = root


func is_instanced_from_scene(p_node):
	if not p_node.filename.empty():
		return true
	return false

Thanks for the response! I no longer have that branch, but I’ll try to recreate it and test your solution :slight_smile: As I remember, this was an important step for game saves in GOAT, so solving this would help a lot!

Cheers,
Paweł

Miskatonic Studio | 2020-10-07 19:41

1 Like