- Edited
I have been seeing many questions about how to "properly" save things with Godot in the discord server, so I wanted to write a complete tutorial about how to save stuff, from easy to advanced solutions, for beginners and advanced users , hoping this help.
So, you want to save data? Maybe your player score? maybe a respawn point? or maybe a very complex data structure that involves very delicated and fine grained bytes? Godot got you covered.
The thing with Godot is that there's no only one solution, you had many solutions.
But before seeing these solutions, you must know what you want to save first.
I'll help you defining how and where you can use these saved methods, and you decide what to use, deal? Deal.
Define what you want to save first
Don't say "I'm going to use [Your Favorite Format Here] to save my things", this will probably over-complicate the process for you and your team. Decide what you really want to save first, and then say "That data is going to be saved using [Your Favorite Format Here]" if you want.
Why? Because is easier to define what tools do you need according your requirements than having the tools first and then meet the requirements.
For this example, let's assume that we want to save the player health
, player name
and player points
. An integer, an string and a real(float) value.
Decide what format do you want to use to save your data
This step may be tricky, specially for new users. You know what you want to save, but what kind of file are you going to use? Godot let you use whatever you want, so from all file formats that exist, what would be the "ideal" format?
A while ago I made a little flowchart to help you to decide what you need according what you want to save, here's an updated version:
But should I save nodes?
Most of the time you don't want to save a node, you want to save the data that node contains.
Save nodes when you want to save the node structure itself, with the current applied values
Where do I save my data files?
Save to user://
folder.
res://
folder will be read-only when you export the game.
You can read more about file paths in Godot on its official documentation page.
Using ConfigFile
ConfigFile
is a helper class that helps you writing INI (.ini) style format, which its structure looks like:
[section]
variable_name = <variable value>
This is widely used when you want to save configuration files, I've also seen the usage of this format on some mod loaders, where they define the assets and workflow of the mod.
With Godot, you only need to define a ConfigFile
object to save or load the data, and each data/section you need to save or load:
# Let's assume the PlayerNode is the node where we save the player data in game
var PlayerNode:Node
var save_path := "user://player_data.ini"
# To save data
func save() -> void:
var config_file := ConfigFile.new()
config_file.set_value("Player", "health", PlayerNode.health)
config_file.set_value("Player", "name", PlayerNode.name)
config_file.set_value("Player", "points", PlayerNode.points)
var error := config_file.save(save_path)
if error:
print("An error happened while saving data: ", error)
# To load data
func load() -> void:
var config_file := ConfigFile.new()
var error := config_file.load(save_path)
if error:
print("An error happened while loading data: ", error)
return
PlayerNode.health = config_file.get_value("Player", "health", 1)
PlayerNode.name = config_file.get_value("Player", "name", "UNDEFINED")
PlayerNode.points = config_file.get_value("Player", "points", 0.0)
The usage from Godot 3 and Godot 4 is practically the same with code. In Godot 3, you'll need to use an external text editor to see the saved data, Godot 4 let you see your ConfigFile in editor, through Script editor, loading it as plain text.
The main advantage of using ConfigFile is that Godot knows what is saved to a ConfigFile, so data types are preserved, even if is an Object
.
Using JSON
JSON stands for JavaScript Object Notation*, and is widely used for data transference between things that are not directly related but needs and easy and human readable way to transfer that data.
In a file, it may look like a GDScript dictionary:
{
"variable_name": <variable value>
}
There's no scenario apart from the data transference that I should recommend this format. Its usage really depends on you, and is ok if you don't use it for data transference.
# Let's assume the PlayerNode is the node where we save the player data in game
var PlayerNode:Node
var save_path := "user://player_data.json"
# To save data
func save() -> void:
var data := {
"name": PlayerNode.name,
"health": PlayerNode.health,
"points": PlayerNode.points
}
var json_string := JSON.stringify(data)
# We will need to open/create a new file for this data string
var file_access := FileAccess.open(save_path, FileAccess.WRITE)
if not file_access:
print("An error happened while saving data: ", FileAccess.get_open_error())
return
file_access.store_line(json_data)
file_access.close()
# To load data
func load() -> void:
if not FileAccess.file_exists(save_path):
return
var file_access := FileAccess.open(SAVE_PATH, FileAccess.READ)
var json_string := file_access.get_line()
file_access.close()
var json := JSON.new()
var error := json.parse(json_string)
if error:
print("JSON Parse Error: ", json.get_error_message(), " in ", json_string, " at line ", json.get_error_line())
return
# We saved a dictionary, lets assume is a dictionary
var data:Dictionary = json.data
PlayerNode.name = data.get("name", "UNDEFINED")
PlayerNode.health = data.get("health", 1)
PlayerNode.points = data.get("points", 0.0)
JSON has some limitations* which can be summarized on:
- It doesn't recognize what a real (float) or integer (int) value is, it saves a number.
- It can't save all data types, only arrays, dictionaries, numbers and strings.
Using FileAccess (File)
FileAcess (or just File in Godot 3) is a helper class to write and read files. No fancy stuff, you're dealing directly with the file itself.
Usually, you use FileAccess in complex scenarios, where you need total control about how the stuff in the file is read or written. Note that most of the FileAcess functions reads and write binary files, only string/text related functions writes and read from human readable text.
This may be useful when you want to write your own format.
# Let's assume the PlayerNode is the node where we save the player data in game
var PlayerNode:Node
var save_path := "user://player_data.dat" # <- custom format
func save(content):
var file = FileAccess.open(save_path, FileAccess.WRITE)
file.store_line(PlayerNode.name)
file.store_32(PlayerNode.health)
file.store_float(PlayerNode.points)
file.close()
func load():
var file = FileAccess.open(save_path, FileAccess.READ)
# Order matters. The same order you use to write, is the order you use to read
PlayerNode.name = file.get_line()
PlayerNode.health = file.get_32()
PlayerNode.points = file.get_float()
file.close()
If you try to read the generated file with a text editor you'll see this:
Your text editor tries to convert the binary data into unicode characters, resulting in that weird looking characters. The real data may look like this:
Using Resources
Resource is a class that you can serialize directly to a file using Godot ResourceSaver
and deserialize using ResourceLoader
. They were made for data, they live for data.
Majority (if not all) files you see in editor FileSystem
is a Resource
.
Resources are something that Godot engine can easily understand, and the way that it loads or save stuff can be modified/extended, so it gives you the same power as if you were writing it with other methods.
Additional to that, since is an object, this data can be shared between instances, it can hold methods, it can emit signals and their variables can define setters and getters.
Let's define one for our data:
@tool
extends Resource
class_name PlayerData
@export var name:String = "Default"
@export var health:int = 1
@export var points:float = 0.0
That's our data class. class_name
is optional, but would help us to use it in other scripts and makes it to appear in the resource creation list.
Using our current configuration, saving data would look like this:
# Let's assume the PlayerNode is the node where we save the player data in game
var PlayerNode:Node
var save_path := "user://player_data.tres" # <- tres is Text RESource
func save() -> void:
var data := PlayerData.new()
data.name = PlayerNode.name
data.health = PlayerNode.health
data.points = PlayerNode.points
var error := ResourceSaver.save(data, save_path)
if error:
print("An error happened while saving data: ", error)
func load() -> void:
var data:PlayerData = load(save_path)
PlayerNode.name = data.name
PlayerNode.health = data.health
PlayerNode.points = data.points
This works, but we miss the power of sharing the data between instances, what we can do is to update the data hold in the node:
# node script
@export data:PlayerData = PlayerData.new()
and now we can just do
func save() -> void:
ResourceSaver.save(PlayerNode.data, save_path)
func load() -> void:
PlayerNode.data = load(save_path)
This way you keep your data and your node code separated, so the data doesn't rely on the node and you can share/modify it and let other node handle it.
But I heard from someone that resources are insecure
Generally, you don't worry about security.
... there's no good reason to prevent malicious users from running arbitrary code, even if you did not provide an official way to do it. But there's also no good way. (xananax)
Yes, is true that Resources can execute arbitrary code, but this is true for all resources, not only yours. This is even true for the files you see in FileSystem, and all things loaded from FileAcess
that allows to bind complete objects (which includes the script). Is a more complex topic than just "I don't want to be hacked", a topic that we don't need to deal with.
Your resource files are not something that you'll share with the user, and if you'll do, you can write custom format parsers to read/write from file, so there's no problem at the end.