Skip to content

Getting Started

Daniel de Oliveira edited this page Feb 28, 2025 · 2 revisions

First project with Locker

Welcome to the Getting Started guide for the Locker Framework. Here, you will learn how to use the basic functionality of saving and loading data using this framework in a Godot project.

Tutorial

Initial structure

The Locker plugin was developed in a way that makes it easy to import it to existing projects without requiring significant modifications in their structure.

With that in mind, for this tutorial, let's assume we have an existing project in which we want to be able to save and load the data of a player character. For that, we have to decide what data specifically we want to persist. In this example, let's say we want to store the player's position, health, modulate color and nickname.

Now, let's imagine how the player's scene tree is organized: for this example, the Player is a node that extends the CharacterBody2D and is the root of its scene. This node has a Sprite child which extends the Sprite2D. This Sprite stores the player's texture and its modulate color.

Screenshot of the scene tree
Screenshot of the Player scene tree

Now for the health and nickname, let's say they are kept inside a Stats resource, which is defined as below:

class_name Stats
extends Resource

@export var nickname: String = ""

@export var health: float = 0.0

An instance of that Stats is stored by the Player node in a stats property, using the script below:

class_name Player
extends CharacterBody2D

const SPEED: float = 256.0

@export var stats: Stats

func _process(_delta: float) -> void:
	velocity = Input.get_vector("left", "right", "up", "down") * SPEED
	
	move_and_slide()

Using the LokStorageAccessor

The main way of accessing the data saved with this plugin is implemented in a new Node, the LokStorageAccessor, so that it can simply be added to scenes that need to modify or access that data, without requiring other big modifications to the structure of the scene.

Let's add this node to the Player's scene:

Screenshot of the scene tree with the LokStorageAccessor
Screenshot of the Player scene tree with the LokStorageAccessor

When we add a LokStorageAccessor to the scene, a few warnings will appear. Those warnings tell us that we need to set a valid id and version to our new LokStorageAccessor.

That id refers to a property of the LokStorageAccessor class, which we can see when we click on its node, and the version refers to a LokStorageAccessorVersion which we must set in the versions Array.

Screenshot of the LokStorageAccessor's properties
Screenshot of the LokStorageAccessor's properties

For the id, we should set it to a unique String that we won't use for any other LokStorageAccessor in the game, so that we avoid mixing data from different LokStorageAccessors when we save the game.

In this project, just setting it to "player" should be okay, since we don't have any other Players in the game.

Before procceding to the version issue, let's also set the dependency_paths property with NodePaths to important nodes that our LokStorageAccessor will need in order to save and load data. In our case, it's important that it knows the Player node and the Sprite node, since it will need to access the Player's global_position, health and nickname, and also the Sprite's modulate color.

After defining that, the LokStorageAccessor's properties should look like the following:

Screenshot of the LokStorageAccessor properties with the dependency_paths set
Screenshot of the LokStorageAccessor's properties with dependency_paths

Using the LokStorageAccessorVersion

Now, to address the version warning, we must set a LokStorageAccessorVersion in the versions property of the LokStorageAccessor. The LokStorageAccessorVersion is a Resource that describes how to gather the data of a LokStorageAccessor, so that it can be saved, and how to use that data when it is loaded.

That definition should be implemented via the override of the _retrieve_data and _consume_data methods of the LokStorageAccessorVersions. This means we will have to write a script that extends that class in order to override those methods.

The PlayerAccessorV1 script

To start that script, we need to define its class_name and its superclass. Since this is the first version of this LokStorageAccessor, I recommend setting its name to something like PlayerAccessorV1.

class_name PlayerAccessorV1
extends LokStorageAccessorVersion

After that, we can start setting up the _retrieve_data method. This method receives a dependencies Dictionary and should return another Dictionary with the data that is supposed to be saved.

It's through that deps parameter (short for dependencies) that the dependency_paths we set before in the LokStorageAccessor are passed, so that we can access them and recover their data to save.

Despite having set those dependency_paths as NodePaths, in the deps parameter they already come as Nodes, so that we can reference them directly.

func _retrieve_data(deps: Dictionary) -> Dictionary:
	var player := deps.get(&"player") as Player
	var sprite := deps.get(&"sprite") as Sprite2D
	
	var data: Dictionary = {}
	
	var nickname: Variant = player.get_indexed(^"stats:nickname")
	var health: Variant = player.get_indexed(^"stats:health")
	
	data["position"] = var_to_str(player.global_position)
	
	if nickname != null:
		data["nickname"] = nickname
	if health != null:
		data["health"] = health
	
	data["color"] = var_to_str(sprite.modulate)
	
	return data

In the above script, we get a reference to the Player and its Sprite through the deps parameter.

After that, we define a data Dictionary that will store the data that should be saved and get the values of the player.stats.nickname and the player.stats.health.

Finally, we save the player.global_position, nickname, health and sprite.modulate in the data Dictionary and return it to be saved.

Note that we just set the nickname and health if they exist, since they might be null.

It's also important to note that when saving complex values like Vector2 and Color, we're using the var_to_str built-in Godot method, in order to make sure they are parsed to a text friendly format. This is necessary if we're using the LokJSONAccessStrategy or the LokEncryptedAccessStrategy, which we are, by default.


Now, in the inverse path, we have to define the _consume_data method, which serves to specify how the game should handle the data loaded.

Similarly to the _retrieve_data method, the _consume_data also receives a deps parameter with those same dependencies. Different from the _retrieve_data, though, that's only the second parameter of this method, with its first one being a res Dictionary (short for result). Also different from its saving counterpart, this method should not return anything.

The res parameter brings with it two keys: the "status" and the "data". The "status" key stores an Error code value describing how went the loading. If it's Error.OK, it means everything went fine. The "data" key is what we're interested in right now, since that's where a Dictionary with the loaded data itself is located.

func _consume_data(res: Dictionary, deps: Dictionary) -> void:
	var player := deps.get(&"player") as Player
	var sprite := deps.get(&"sprite") as Sprite2D
	
	var data: Dictionary = res.get("data")
	
	var position: Variant = data.get("position")
	var nickname: Variant = data.get("nickname")
	var health: Variant = data.get("health")
	var color: Variant = data.get("color")
	
	if position != null:
		player.global_position = str_to_var(position)
	if nickname != null:
		player.set_indexed(^"stats:nickname", nickname)
	if health != null:
		player.set_indexed(^"stats:health", health)
	if color != null:
		sprite.modulate = str_to_var(color)

In the script above, we start by getting references to the Player and its Sprite through the deps parameter. Following that, we get the loaded data from the res parameter.

After that, we procced to set the given data to their respective spots: the "position" goes to the player.global_position, the "nickname" goes to the player.stats.nickname, the "health" goes to the player.stats.health and the the "color" goes to the sprite.modulate. Each one is only set if it exists, though, that's why we check for their nullity.

It should be noted that the keys of the loaded properties should be the same keys used when saving ("position", "nickname", "health" and "color").

Also, it's important to note that, just like in the saving process, we need a way to convert our data from String back to variables if we're using the LokJSONAccessStrategy or LokEncryptedAccessStrategy. That's why we use the str_to_var method in the variables that we saved before using var_to_str.

Back to the LokStorageAccessor

Now that we finished our LokStorageAccessorVersion script, we should be able to set a new instance of it in the LokStorageAccessor versions property. When we do that, the LokStorageAccessor's warnings should disappear and its properties should look like the following.

Screenshot of the LokStorageAccessor properties with a version set
Screenshot of the LokStorageAccessor's properties with a version set

With this, we're all set to start saving and loading data with the Locker Plugin. All we need to do is call the save_data and load_data methods of the LokStorageAccessor when we wish to execute those actions.

To test that, we can, for example, create a SaveButton and a LoadButton in the scene and connect their pressed signals to the methods mentioned.

After doing that, our scene tree should have a structure similar to the following:

Screenshot of the scene tree with the save and load buttons
Screenshot of the scene tree with the save and load buttons

Like mentioned before, the SaveButton and LoadButton nodes have to be connected, respectively, to thesave_data and load_datamethods of the StorageAccessor, similarly as it's shown below:

Screenshot of the connection of the SaveButton
Screenshot of the connection of the SaveButton

In order to be able to see the effects of the saving and loading operations with the color, nickname, and health properties, we'll need to have some way of setting them in the game. For that, I'm using use some of the Godot's built-in Control nodes that make that task easy with their signals.

I'm going to leave that part out of this tutorial since it doesn't really concern the Locker Plugin, but if you want to see how I implemented that, you can look in the repository.

When done with that, we should be able to successfully see the saving and loading in action.

The LokGlobalStorageManager and LokSceneStorageManager

At this point, we are now able to save and load our game using one LokStorageAccessor node. That's not the most common way of realizing those operations using the Locker framework, though.

Using the save_data and load_data methods directly from the LokStorageAccessor is fine if we only want to save and load THAT accessor's data. That's useful in multiplayer games, for example, when we want to load only the data of a player that just joined the game.

In most cases, though, we'll want to save and load the entire game, which we should do using the save_data and load_data methods of the LokGlobalStorageManager autoload. This autoload is automatically created when the Locker plugin is active.

Using the methods from that autoload, the data from all LokStorageAccessors currently active in the game can be involved in the operations.

Since the LokGlobalStorageManager is an autoload, we can't connect the SaveButton and LoadButton's signals to it directly through the editor, though. In order to be able to do that, we can use the LokSceneStorageManager, which works like an interface of access to the LokGlobalStorageManager.

To use this node, we have to add it to the scene tree, which will make it look something like the following:

Screenshot of the scene tree with the LokSceneStorageManager
Screenshot of the scene tree with the LokSceneStorageManager

After adding it, we should change the SaveButton and LoadButton's signals to connect to the save_data and load_data methods of that new LokSceneStorageManager node.

Outro

When done with that, our basic saving and loading system should be working! 😀 Congratulations for setting up your first project with the Locker Framework!

Now, when clicking on the SaveButton, the position, nickname, health and color of the player should be saved in the storage and when clicking on the LoadButton, that data should be recovered and sent back to the player.

If you want to download an example project with the implementation of this tutorial, you can get it from this repository.