Talo logoDocsBlogGitHub
Back to blog

How we built a flexible game save system for Godot

5 min read
How we built a flexible game save system for Godot

Building a robust save system for your game is both challenging and daunting. You need to handle data persistence, manage multiple save files, potentially sync between online and offline states and ensure everything loads correctly.

We created Talo's save system to simplify saving and loading in Godot, while ensuring it works seamlessly across different game genres. In this post, we'll explore how it works and how you can integrate it into your games.

We've also built a small demo game for our Godot plugin highlighting how to save and load scenes in any Godot game.

TL;DR

Talo provides a complete save system for Godot games that handles both online and offline saves with automatic syncing. Using the TaloLoadable node, you can quickly implement persistent game saves without worrying about the underlying infrastructure.

How do Talo saves work?

At its core, Talo's save system is built around the concept of "loadable" nodes. Any node in your scene that extends the TaloLoadable class can have its state saved and loaded. This approach gives you granular control over exactly what gets saved while keeping the implementation simple.

Here's a basic example of how you might implement a saveable player character:

var stars := 0
var spawn_level := "starting_zone"
var spawn_point := Vector2.ZERO

func register_fields():
    register_field("stars", stars)
    register_field("spawn_point", spawn_point)
    register_field("spawn_level", spawn_level)

func on_loaded(data: Dictionary):
    stars = data.get("stars", 0)
    spawn_point = data.get("spawn_point", Vector2.ZERO)
    spawn_level = data.get("spawn_level", "starting_zone")

When a save is created, Talo automatically serialises all registered fields into a structured save file. These saves are stored both locally and in the cloud (you can also self-host Talo), ensuring your players never lose progress even if they're offline.

The architecture behind saves

Every save file in Talo is a collection of "saved objects" - one for each loadable node in your scene. Each saved object contains:

  • A unique identifier
  • The node path in your scene
  • The serialised data for that node

This structure allows Talo to accurately reconstruct your game state when loading a save. Here's what a typical saved object looks like:

{
    id: "uuid-uuid-uuid-uuid",
    name: "/root/MyScene/Player",
    data: [
        {
            key: "stars",
            type: "2",
            value: "5"
        },
        {
            key: "spawn_point",
            type: "5",
            value: "Vector2(225, 166)"
        }
    ]
}

Saved objects must have a unique id: this is used to match up saved objects with the corresponding loadable. The name in a saved object refers to the NodePath of the loadable in the scene.

The most interesting part is the data which contains the fields we register in the loadable (explained below) as well as the original type of the data. Notice that all values are serialised into strings. This is so that when the data is loaded, it can be easily converted back into its original type.

Data hydration

When saves are created, all the loadable nodes in your scene will be serialised into saved objects. Similarly, when a save is loaded, each saved object is paired up with a matching loadable. Once paired up, the loadable is hydrated with the latest data from the saved object. Hydrating the loadable calls the loadable's on_loaded() function with the data containing each value converted back to its original type.

Registering fields

In order to save and load data, we need to tell Talo what we want to save. Here's an example of what that would look like for the player saved object above:

var stars := 0
var spawn_level := "starting_zone"
var spawn_point := Vector2.ZERO

...

func register_fields():
	register_field("stars", stars)
	register_field("spawn_point", spawn_point)
	register_field("spawn_level", spawn_level)

As you can see, each field needs to be registered with the register_field() function. The register_fields() function is called just before data is serialised to ensure we have the latest data.

Loading data

As described above, when a saved object is paired up with a loadable, the loadable is hydrated with the data from the saved object. This calls the on_loaded() function of the loadable, where you can set the properties of the node:

func on_loaded(data: Dictionary):
	stars = data.get("stars", 0)

	spawn_point = data.get("spawn_point", Vector2.ZERO)
	character_body.position = spawn_point

	spawn_level = data.get("spawn_level", "starting_zone")
	# check if we need to change the scene

Putting it all together, here's how a save file would be loaded into a scene in your Godot game:

Flowchart showing how save files are loaded

Advanced features

Handling destroyed objects

Sometimes you'll need to handle objects that have been destroyed between saves. Talo makes this simple with the handle_destroyed() function:

func on_loaded(data: Dictionary) -> void:
    if handle_destroyed(data):
        return
        
    # load data as normal

This function checks if the saved object was previously marked as destroyed (typically using queue_free()). If it was, the function will return true and the object will also be automatically removed from the scene using queue_free().

Automatic syncing

One of the most powerful features of Talo's save system is its automatic handling of online/offline states. When a player goes offline:

  • Local saves are created in the user data directory and changes are tracked locally
  • When their connection is restored, saves are automatically synced
  • Conflicts are resolved by using the most recently updated save

This means you don't need to write any additional code to handle different network states - it just works.

Debuggable save files

Observability is always super important but when saves get too big it can be hard to inspect the JSON data. We built a custom node graph to visualise saved objects and their data. You can see this in the Talo dashboard under a player's profile.

Visualising save data in the Talo dashboard

Getting started with Talo saves

Ready to implement saves in your game? Here are the steps you need to get started:

  1. Install the Talo Godot plugin
  2. Extend the TaloLoadable node in scenes you want to save
  3. Register the fields you want to persist
  4. Implement the on_loaded() function to handle loading save data
  5. Listen for the Talo.saves.save_loading_completed signal to know when the save has finished loading

Make sure to also look at the saves docs for more details on creating, updating, loading and deleting saves.

Check out our saves feature page for more details and frequently asked questions about what's possible with Talo saves.

Support and community

We're constantly improving our save system based on developer feedback. If you have questions or need help implementing saves in your game, join our Discord community where our team and other developers are always happy to help.

Our goal is to make game development easier and more accessible for indie developers and small teams of all skill levels. By using Talo's save system, you can focus on building your game's unique features while we handle the complexity of data persistence. Talo is completely open source too so you can contribute to the saves system and help us improve it for everyone.


TudorWritten by Tudor

Build your game faster with Talo

Don't reinvent the wheel. Integrate leaderboards, stats, event tracking and more in minutes.

Using Talo, you can view and manage your players directly from the dashboard. It's free!

Get started

More from the Talo Blog

Changelog: game stat history, channel management and new socket events
3 min read

Changelog: game stat history, channel management and new socket events

Learn more about Talo's March 2025 updates: game stat history APIs, channel management improvements and new socket events. Find out how to update game channels from the dashboard.

How to track global and player stats in your Godot game
3 min read

How to track global and player stats in your Godot game

Learn how to leverage Talo's powerful Stats API to track player progression, create competitive mechanics and analyse player behaviour in your Godot game.

How to create a custom Unity package - and why you shouldn't
5 min read

How to create a custom Unity package - and why you shouldn't

Thinking of creating a custom Unity package? Learn the process, common pitfalls, and why dependency management is a major challenge.

Changelog: leaderboard refreshes and player presence
2 min read

Changelog: leaderboard refreshes and player presence

This month we've added support for daily leaderboard refreshes and a new API for player presence tracking.