How to Save and Load Godot Game Data

In our game, we may have the current score, level, and items collected that should be stored and reloaded if the player wants to quit the game and restart it later. Also, there is the situation of single player or multi-player gaming to consider.

So, we will have to consider saving data locally, to a server, or to a cloud-based web service.

In this tutorial we will save and load data to/from the local device in a most simple way to get started, and cover the other more advanced options in later tutorials. So let’s get started …

Global Game Data

All the Nodes/Scenes in our game should be able to access the game data concerning the current score etc. The way to facilitate this is with an Autoload script. This implements a singleton which is a single instance of a game object that cannot be duplicated like we would do with other game objects that share functionality, but have different property values such as position and damage level.

Our Autoload script makes available global variables to get/set or constants and functions that any node in our game will be able to use.

Let’s follow this up with a simple example of a game data Autoload script that stores the score and level for the player of the game.

Resource to store Player data

We may create a Resource which is a script without a companion Scene. It will contain export vars for every piece of data that we want to track. Note that it is neccesary for the variables to be export variables. In Godot 4: @export var var_name := ""

Example script (player.gd):

extends Resource

class_name Player

export var player_name = ""
export var score = 0
export var level = 1
export var high_score = {
		"value": 0,
		"date": ""
	}

We gave it a class name, so we will be able to instance new players in our other scripts in our game.

var player = Player.new()

Then, this player object will have the properties of the Player class.

So we may set the properties as needed.

var player = Player.new()

player.player_name = "Zippy"

print(player.high_score.value)

Saving using ResourceSaver

The ResourceSaver class lets the Godot Engine take care of correctly saving the data.

var result = ResourceSaver.save(FILE_NAME, player)
assert(result == OK)

To save as a text file we use the .tres file extension, and use the .res file extension to save in a more compact and non-human readable binary format. Also, save the file to: user://file_name.res (for example) to save it in the operating-system-specific user data folder for our Godot game.

The save method call returns a status code. We may use an assert statement to alert us to any error code during debugging and otherwise avoid a message about ignoring the return value.

An optional 3rd parameter may be added such as for the file compression (FLAG_COMPRESS) option.

Loading using ResourceLoader

We may use the ResourceLoader to load the player data after first checking if the file exists.

func load_data(file_name):
	if ResourceLoader.exists(file_name):
		var player = ResourceLoader.load(file_name)
		if player is Player: # Check that the data is valid
			return player

The return value of this function is either a Player object or null, so we need to program our script accordingly to deal with the various situations.

Using JSON

JSON is a common human-readable text format for representing data. But it is easy to make mistakes with it and get bugs. So it’s better to use Resource files.

We will use a Dictionary for our Player data that easily maps to a common data format called JSON for storing as text. Here is the GDScipt code for our data.gd Autoload script. This contains the Player data and functions to load and save the data:

extends Node

const FILE_NAME = "user://game-data.json"

var player = {
	"name": "Zippy",
	"score": 0,
	"level": 1,
	"high_score": {
		"value": 0,
		"date": ""
	}
}

func save():
	var file = File.new()
	file.open(FILE_NAME, File.WRITE)
	file.store_string(to_json(player))
	file.close()

func load():
	var file = File.new()
	if file.file_exists(FILE_NAME):
		file.open(FILE_NAME, File.READ)
		var data = parse_json(file.get_as_text())
		file.close()
		if typeof(data) == TYPE_DICTIONARY:
			player = data
		else:
			printerr("Corrupted data!")
	else:
		printerr("No saved data!")

After setting up the script as an Autoload in our Project settings, any node may call data.save() to save the Player data and data.load() to load the data. And the player data is accessed as data.player.score for example.

Note: the method names of save and load are very applicable to what we want to do here but load is already defined by the engine for loading resource files, and we have done a method override here so we can no longer use the original function in this script.

Encrypting JSON Data

In the above code for saving and loading game data we made no attempt to hide the details in the JSON data saved to disk. So the data file could be edited externally to say inflate the score or to add lives. To get around this, we could use the file.open_encrypted_with_pass method where we supply a password and the data is encrypted and hence incomprehensible to a casual hacker.

More solutions