Simple Save Systems in Godot with ConfigFiles

Posted by Gemma Ellison
./
July 11, 2025

Forget elaborate databases and complex serialization! If you’re building a smaller game in Godot and need a simple, robust way to save player progress, look no further than ConfigFiles. Many shy away from ConfigFiles, deeming them too basic, but they are perfect for beginners. Let’s dive into building a functional save system using them.

Creating the Save Data Dictionary

First, we need a dictionary to hold the data we want to save. This dictionary will act as a staging area before we write the data to our ConfigFile.

var save_data = {
    "player_name": "Hero",
    "player_level": 5,
    "player_health": 100,
    "items": ["sword", "potion", "shield"]
}

This is a simple example. Expand this dictionary to include any data relevant to your game’s save state: player position, inventory, quest progress – anything that needs to persist between sessions.

Remember to carefully consider the data types you’re saving. While ConfigFiles can handle a variety of types (integers, strings, booleans, arrays, dictionaries), complex objects might require more sophisticated serialization techniques.

Writing to the ConfigFile

Now for the magic: writing our save_data dictionary to a ConfigFile. Godot makes this surprisingly easy.

func save_game():
    var config = ConfigFile.new()
    for key in save_data:
        config.set_value("savegame", key, save_data[key])

    var save_path = "user://savegame.cfg" # User:// is platform-independent
    var error = config.save(save_path)

    if error != OK:
        printerr("Error saving game to: " + save_path)

Here’s a breakdown:

  • We create a new ConfigFile object.
  • We iterate through our save_data dictionary.
  • For each key-value pair, we use config.set_value() to write it to the file. The “savegame” here is the section name. This lets you organize your saves.
  • We define the save_path. user:// is the recommended way to store save files as it resolves to the appropriate user directory on each platform.
  • We call config.save() to write the data to disk. Always check for errors!

A common mistake is forgetting to specify the section name in set_value(). This will lead to your data not being saved correctly.

Loading from the ConfigFile

Loading the data is equally straightforward.

func load_game():
    var config = ConfigFile.new()
    var save_path = "user://savegame.cfg"
    var error = config.load(save_path)

    if error != OK:
        printerr("Error loading game from: " + save_path)
        return # Handle the error (e.g., start a new game)

    for key in save_data: # Loop through the keys in your default save data
        if config.has_section_key("savegame", key):
            save_data[key] = config.get_value("savegame", key)
        else:
            print("Key not found in save file:", key)

    # Now, update your game state with the loaded data
    player_name = save_data["player_name"]
    player_level = save_data["player_level"]
    player_health = save_data["player_health"]
    player_inventory = save_data["items"]

Key points:

  • We load the ConfigFile using config.load().
  • We iterate through our expected save_data keys. This prevents crashes if older save files are missing specific values.
  • We use config.has_section_key() to check if a key exists in the save file. This is crucial for handling save files from older versions of your game that might not contain all the data.
  • We retrieve the data using config.get_value().
  • We then update our game state (e.g., player stats, inventory) with the loaded data.

Handling Missing Save Files

What happens if the player runs the game for the first time and there’s no save file? Your game will crash if you don’t handle this gracefully. The load_game function above already includes a basic check for loading errors, but we need a robust solution.

The most reliable approach is to provide default values. If the save file is missing, initialize your game state with default values. This ensures a smooth player experience, even on the first run. Here’s how you can improve the error handling:

func load_game():
    var config = ConfigFile.new()
    var save_path = "user://savegame.cfg"
    var error = config.load(save_path)

    if error != OK:
        print("No save file found, using default values.")
        # Initialize save_data with default values:
        save_data["player_name"] = "Newbie"
        save_data["player_level"] = 1
        save_data["player_health"] = 50
        save_data["items"] = []
    else:
        for key in save_data:
            if config.has_section_key("savegame", key):
                save_data[key] = config.get_value("savegame", key)
            else:
                print("Key not found in save file:", key)

This version proactively handles the missing save file scenario by initializing save_data with default values.

Common Pitfalls and Best Practices

  • Data Validation: Always validate the data you load from the ConfigFile. A corrupted save file could contain invalid data that crashes your game. Check data types and ranges before applying them.

  • Save Frequency: Don’t save too frequently. Writing to disk is an expensive operation. Consider saving only at specific checkpoints or when the player performs a significant action (e.g., leveling up, completing a quest).

  • Backup Saves: Implement a system to create backup saves. If the main save file becomes corrupted, the player can revert to a previous backup.

  • Asynchronous Saving: For larger games, consider performing save operations asynchronously (in a separate thread) to avoid blocking the main game thread and causing performance hitches.

  • ConfigFile Limitations: ConfigFiles are ideal for simple data. For more complex data structures, explore alternatives like JSON or binary serialization. Godot also provides built-in Resource saving features which might be a better choice.

ConfigFiles are a great starting point for implementing save systems in Godot, especially for beginners. By understanding their strengths and limitations, and following the best practices outlined above, you can create a robust and reliable save system that enhances your players’ experience. Remember to always prioritize error handling and data validation to prevent unexpected crashes and data loss. Happy coding!