fervor [>]CODING & CURIOSITY
FERVOR LEARNING SYSTEMTUTORIALS
← Godot

Godot / 8 MIN READ

Load Save Game 101

Load Save Game 101

From the original Fervor library. Examples may use older package versions.

In this tutorial we’ll create a simple save and load system in Godot using GDScript. We’ll use Godot’s built-in File API and JSON for serialization. This guide applies to Godot 3.x and can be easily adapted for Godot 4.x.

Below is an overview of what we’ll cover:

  1. Overview
  2. Project Setup and Autoload
  3. Creating the Save/Load Script
  4. Saving Data
  5. Loading Data
  6. Putting It All Together
  7. Testing Your System

Overview

A save system typically involves:

  • Collecting the game state (player stats, positions, inventory, etc.) in a dictionary.
  • Serializing that dictionary into a JSON string.
  • Storing that JSON string into a file on disk.
  • Reading the file, parsing the JSON back into a dictionary, and restoring the game state.

We’ll store the file in the user:// directory, which is a writable location provided by Godot.


Project Setup and Autoload

For convenience, many developers create a singleton (autoload) that handles saving and loading so it can be accessed from anywhere in your game.

  1. Create a new script:
    Create a new script (for example, SaveLoad.gd).

  2. Set it as an autoload:

    • Go to Project > Project Settings > Autoload.
    • Click Browse, select your SaveLoad.gd script.
    • Set a name (e.g., SaveLoad) and click Add.

Now, SaveLoad is accessible from any scene.


Creating the Save/Load Script

Open your SaveLoad.gd script and start by setting up the basic structure. For example:

# SaveLoad.gd
extends Node

# Define the file path for saving.
const SAVE_PATH := "user://savegame.json"

# A sample data structure that might represent your game state.
var game_state = {
    "player_position": Vector2(),
    "player_health": 100,
    "inventory": []
}

func _init():
    # You could initialize any required data here.
    pass

This dictionary is just an example. In your game, the data might come from your player node, enemies, and other systems.


Saving Data

We will create a function called save_game() that collects the game state, converts it to JSON, and writes it to a file.

func save_game():
    # Update game_state with current data.
    # For example, if you have a player node:
    # game_state["player_position"] = get_node("Player").position
    # game_state["player_health"] = get_node("Player").health
    # game_state["inventory"] = get_node("Inventory").items

    var file = File.new()
    var error = file.open(SAVE_PATH, File.WRITE)
    if error != OK:
        print("Error opening file for writing: ", error)
        return false

    # Convert dictionary to JSON string.
    var save_data = to_json(game_state)
    file.store_string(save_data)
    file.close()

    print("Game saved successfully!")
    return true

Explanation:

  • File.new() creates a new file object.
  • file.open() opens the file at the path defined by SAVE_PATH in write mode.
  • to_json(game_state) converts the dictionary to a JSON string.
  • file.store_string() writes the JSON string to the file.
  • Always close the file after writing.

Loading Data

Similarly, we create a function called load_game() that reads from the file, parses the JSON, and updates the game state.

func load_game():
    var file = File.new()
    # Check if the save file exists.
    if not file.file_exists(SAVE_PATH):
        print("No save file found!")
        return false

    var error = file.open(SAVE_PATH, File.READ)
    if error != OK:
        print("Error opening file for reading: ", error)
        return false

    var save_data = file.get_as_text()
    file.close()

    # Parse the JSON string back into a dictionary.
    var parsed = parse_json(save_data)
    if typeof(parsed) != TYPE_DICTIONARY:
        print("Error parsing save data!")
        return false

    # Update game_state with loaded data.
    game_state = parsed

    # Optionally, apply the loaded data to your game objects.
    # For example:
    # var player = get_node("Player")
    # player.position = game_state["player_position"]
    # player.health = game_state["player_health"]
    # get_node("Inventory").items = game_state["inventory"]

    print("Game loaded successfully!")
    return true

Explanation:

  • file.file_exists() checks if the save file exists.
  • file.get_as_text() reads the entire content of the file.
  • parse_json() converts the JSON string back into a dictionary.
  • Update the game state or propagate the data to your nodes as needed.

Putting It All Together

Here’s the complete SaveLoad.gd script combining both functions:

# SaveLoad.gd
extends Node

const SAVE_PATH := "user://savegame.json"

# Sample game state dictionary
var game_state = {
    "player_position": Vector2(),
    "player_health": 100,
    "inventory": []
}

func _init():
    # Initialization if needed.
    pass

func save_game():
    # Example: Update game_state with actual data from your game.
    # game_state["player_position"] = get_node("Player").position
    # game_state["player_health"] = get_node("Player").health
    # game_state["inventory"] = get_node("Inventory").items

    var file = File.new()
    var error = file.open(SAVE_PATH, File.WRITE)
    if error != OK:
        print("Error opening file for writing: ", error)
        return false

    var save_data = to_json(game_state)
    file.store_string(save_data)
    file.close()

    print("Game saved successfully!")
    return true

func load_game():
    var file = File.new()
    if not file.file_exists(SAVE_PATH):
        print("No save file found!")
        return false

    var error = file.open(SAVE_PATH, File.READ)
    if error != OK:
        print("Error opening file for reading: ", error)
        return false

    var save_data = file.get_as_text()
    file.close()

    var parsed = parse_json(save_data)
    if typeof(parsed) != TYPE_DICTIONARY:
        print("Error parsing save data!")
        return false

    game_state = parsed

    # Example: Apply loaded data to your game nodes.
    # var player = get_node("Player")
    # player.position = game_state["player_position"]
    # player.health = game_state["player_health"]
    # get_node("Inventory").items = game_state["inventory"]

    print("Game loaded successfully!")
    return true

Testing Your System

  1. Triggering Save and Load:
    In your game (for example, in your main scene or player script), you can call the save and load functions:

    # To save the game:
    SaveLoad.save_game()
    
    # To load the game:
    SaveLoad.load_game()
    
  2. Adjusting Game State:
    Make sure you update the game_state dictionary with the real values from your game objects before saving. Similarly, after loading, update your game objects with the values from game_state.

  3. Debugging:
    Check the output console for messages indicating success or errors. This will help you troubleshoot issues like file access permissions or JSON parsing errors.


Additional Tips

  • Versioning:
    If your game state structure changes over time, consider including a version number in your save data to handle migrations gracefully.

  • Encryption/Compression:
    For more advanced use cases, you might want to encrypt or compress your save files.

  • Multiple Save Slots:
    Instead of one fixed SAVE_PATH, you can allow multiple save slots by dynamically building the file path (e.g., "user://save_slot1.json").

  • Godot 4 Adjustments:
    If you’re using Godot 4, most of the File API and JSON methods remain similar. Just check the Godot 4 documentation for any updated method names or best practices.


By following this tutorial, you now have a basic save and load system set up in Godot. Customize the game state data and integrate it with your game’s logic to suit your project’s needs. Happy coding!

A solid save and load system opens up a lot of creative possibilities for your game. Beyond just preserving progress, you can use it to add layers of interactivity, storytelling, and even meta-game features. Here are some fun and cool ideas you can implement using your save/load system in Godot:


1. Multiple Save Slots & Branching Narratives

  • Multiple Playthroughs:
    Allow players to maintain different save slots. This way, they can explore various story branches or experiment with different gameplay styles without losing progress.

  • Branching Storylines:
    Store key decisions or flags in your save file to determine which narrative branch to follow when loading. This can lead to unique endings or unlock secret story paths based on past choices.


2. Quick Save / Quick Load and Time-Rewind Mechanics

  • Quick Save/Load Feature:
    Implement a feature that lets players quickly save and resume their game state with a button press. This is especially handy for games that are challenging or have time-sensitive mechanics.

  • Undo or Time-Rewind:
    By storing a history of game states (or snapshots), you can allow players to “rewind” their actions. Imagine a puzzle game where the player can step back a few moves to try a different strategy.


3. Persistent World States

  • Dynamic Environments:
    Use your save system to track changes in the game world. For example, if a player alters the environment (destroying an object or solving a puzzle), those changes persist across sessions, creating a living world that evolves with the player’s actions.

  • Unlockables and Achievements:
    Save player achievements, unlocked levels, or secret areas. This can be used to reward exploration and encourage players to discover hidden content.


4. Meta-Narrative and Easter Eggs

  • Developer Secrets:
    Hide secret messages or fun facts in your save data. For example, if a player edits their save file manually or discovers a “hidden” save slot, you could trigger a special in-game event or unlock a bonus mode.

  • Alternate Realities:
    Experiment with “corrupted” or alternate versions of your game state. A deliberately glitched save file might reveal a secret level or trigger unusual in-game behavior, adding an extra layer of mystery.


5. Customizable and Shareable Experiences

  • Player-Created Content:
    Allow players to modify or even share their save files. This can lead to community challenges where players design levels, characters, or scenarios that others can load and experience.

  • Leaderboard or Progress Sharing:
    Save high scores, best times, or other competitive stats. Players can then share their save files online or compare progress directly in the game.


6. Modding and Debugging Tools

  • Mod-Friendly Architecture:
    By designing your save data to be human-readable (like JSON), you make it easier for modders to tweak game variables. This can lead to an active community creating custom scenarios, challenges, or even entirely new game modes.

  • In-Game Debug Menus:
    Use the save system as a debugging tool. For instance, allow players (or yourself during development) to load specific game states that jump to different parts of the game world. This can be a fun way to explore all the hidden corners of your game.


Putting It All Together

Imagine a game where your decisions change not only the narrative but also the physical game world, and you have the power to rewind time to fix mistakes or explore “what if” scenarios. Your save/load system becomes more than just a background feature—it’s an integral part of the gameplay and storytelling, providing depth, replayability, and a personalized experience for each player.

Experiment with these ideas, mix and match them, and see how they can enhance the gameplay experience. The flexibility of a good save/load system in Godot means you’re only limited by your imagination. Happy coding!

Keep your curiosity going.Explore more Godot →
287 TUTORIALS · 22 TOPICSREADY