Godot Saver Loader

introduction

In Godot, You have to decide what kind of data to save and how to replicate the scenes when you load the game data. In the official tutorial, it used persistent group in order to know what nodes needed to save data. However, when you load the data, those nodes might not exist so you couldn’t find them when you iterate through the nodes inside the group!

my solution

Inspired by multiplayerSpawner, I realized that I could create a Node that was specifically used for loading game data and replicating the scene. This way, I can do get_tree().get_nodes_in_group("loader"), get all loaders, and feed them the save data.

For example, I was currently writing a Supermarket game and I needed to save each shelves’ locations. For saving, it’s pretty easy, just add Shelf nodes to Saver group and call them when the player saves the game. The save format was in JSON and its type was Array[Dictionary]. Thus, the save data would look like this: [{"type":"shelf","x":-2.5,"y":0.1,"z":-1.5},{"type":"shelf","x":-2.5,"y":0.1,"z":-0.5}].

When loading the game, I feed each Dictionary to all loaders. The loaders would check type and see if it’s for them. If yes, it would spawn the node accordingly and return true. It would return false otherwise, like this:

extends Node
class_name ShelfLoader

const SHELF = preload("res://scenes/game/furniture/shelf/shelf.tscn")

@export var add_to_path : Node

func load(v:Dictionary) -> bool:
	if v["type"] != "shelf":
		return false

	var n := SHELF.instantiate()
	n.position = Vector3(v["x"], v["y"], v["z"])
	add_to_path.add_child(n)

	return true

On the main node (the one who reads the data from the file and feed them into loaders), it would iterate through all Dictionary and feed them to each loader until all loaders was tried or a loader said that it was for them (by returning true), like this:

const SAVE_PATH = "user://game"

func _ready() -> void:
    if FileAccess.file_exists(SAVE_PATH):
		var file := FileAccess.open(SAVE_PATH, FileAccess.READ)
		var s := file.get_line()
		var d : Array = JSON.parse_string(s)
		load_all(d)

func load_all(d:Array) -> void:
	for i:Dictionary in d:
		for node in get_tree().get_nodes_in_group("loader"):
			if node.load(i):
				break

Of course, this isn’t very efficient. But my games were all small games and this pattern served me really well so far!

Written on October 23, 2024