When I first learned about resources
and that they are loaded statically into memory, I was blown away. Here was a simple concept that I feel is pretty foundational to Godot, that I hear almost nobody talk about. Not only was this a game changer in terms of my Godot journey, but it was also the impetus for me to create this whole blog in order to spread the word about the beautiful features of Godot that are hidden in plain view.
What is a “resource”?
First things first, when we’re talking about Godot, what exactly is a “resource” and what does it do? According to Godot’s documentation:
Anything Godot saves or loads from disk is a resource. Be it a scene (a.tscn
or an.scn
file), an image, a script…
But for our purposes, this definition is way too broad. I’m not concerned with scenes, fonts, images, textures, scripts, meshes, animations, audio streams, or translations. In this blog post, we’re going to focus specifically on custom scripted resources. If you are familiar with Unity game development, many people compare resource scripts in Godot to Unity’s ScriptableObject
.
A custom scripted resource is a resource that uses a custom definition as defined by an attached script. These are often used as the native Godot alternative to using other types of data storage like JSON or CSV, and resources come with some pretty advantageous benefits, too. Because they have scripts attached, you can define constants, methods, signals, and property setters/getters within them. They have concretely defined properties which can be statically typed. Godot resources have built-in serialization and deserialization, so you don’t have to do any extra work to save the data to a file or read it back. Not only that, but you can either use .tres
for human-readable text files or .res
for more performant binary file storage.
The less obvious superpower about Godot resources is that they’re loaded statically from memory. That is to say, no matter how many times you load a resource, it’s only ever loaded into memory once, all subsequent loads just point to the exact same instance of that resource in memory. For your average resource, this isn’t super special besides improving performance. Where this really shines, is when it comes to the previously mentioned custom scripted resources. By sharing the same resource in memory, you can access shared data in various places in code, without wiring up complex signals to reference a specific node. The main benefit that custom resources have over using an autoload script (sometimes referred to as a singleton or a global script), is that they are only loaded into memory while a reference to them exists in memory. If you unload all references to a resource, it will be freed from memory. Whereas autoload scripts are loaded once the program starts and never unloaded until the program quits. That being said, it is important to keep in mind that resources are freed from memory once all references to them are unloaded. That is, you can’t expect them to persist data between scenes, unless you constantly keep them loaded and never have a scene that doesn’t have the resource loaded (unless, of course, you explicitly save their data to the file system).
There are, however, some downsides to using resources. The most prominent one is its security. Given that resources can be bound to scripts, they have the ability to run inline GDScript or even a separate attached file when loaded into memory. As cool as this can be, it does open you up to script injection. This, in my opinion, is a pretty big deal. As a rule of thumb, you need to be very cautious, particularly when using Godot resources to save and load external data. Now I’ll not be alarmist, the risks here are mostly benign. The idea that someone might open up their own save file on their own computer in order to inject malicious code into a program running on their own machine doesn’t really present a public security risk. That being said, if you intend your game to be competitive, do know that script injection is a viable way to introduce cheats and hacks. The absolute most malicious outcome is if someone shares a compromised save file online and tricks someone else into downloading it and running it, they could essentially run any code they want on the target’s computer. There are things you can do to try and safely load external resources, like using this plugin. That being said, a save and load system is a different topic, for another time. If you’re curious in the meantime, this video by Godotneers does a spectacular job of covering various different save formats and strategies for creating a saving and loading system. That sounds cool and all, but if it has security concerns, when can you use resources?
Using custom resources
Imagine you have a character that has some stats. Now imagine you need to access those stats in several different scripts attached to several different nodes all strewn about your scene tree. How would you go about handling that? You could create an autoloaded script that stores that data. But what if you didn’t want it to always be loaded? What if you wanted more control over the lifecycle of that data? This is where Godot resources can come into play.
Godot resources demo
Let’s walk through a brief demonstration to highlight a use case for custom resources. Below is a simple game made using custom resources where you play a character collecting coins.
Custom Resources Demonstration
Controls
- ←/→ – Move Left/Right
- Space – Jump
- Esc – Open Pause Menu
- Del – Decrement the Reference Counter On Player Inventory
The mechanics aren’t that important, the real focus of this project is the use of a custom resource. The coin counter in the upper left hand corner is representing the data held by the custom resource. Given this is a simple example, I only added a basic coin count to the resource, but it could easily contain far more properties to manage.
class_name PlayerInventory extends Resource
@export var coin_count: int:
set(value):
coin_count = value
changed.emit()
There are a few important things to point out about this script. If you are planning on saving the data in the resource to disk, you need to make sure to include the @export
annotation because only @export
ed properties will be saved if you use the built-in ResourceSaver. The Resource class has a built-in .changed()
signal that you can leverage for yourself with custom getters and setters. While I might save setters and getters for its own post, I can briefly cover them here.
Getters and setters, briefly
You can have variable properties that call a function every time you set their value (setter) or retrieve their value (getter). Keep in mind that if you define a setter, you need to specify if you’re going to have the variable set to the incoming value (like is done in line 5 of player_inventory.gd
above). There are many cases where you might want to change the default behavior of setters and getters, but all you need to know for now is that it’s possible and it’s very useful for emitting a signal anytime a variable property is changed.

If you start by creating the script first and you name the script with class_name
, then when you go to create the resource, you can just search by the class name you assigned and it will automatically attach your script to the resource. The resource can be created by right-clicking in the FileSystem dock where you want the resource to be and then selecting Create New -> Resource
and then naming the .tres
file.

The main motivation for implementing the “player inventory” through a custom resource pattern is for distributed access. The “player inventory” is accessed by the world.gd
, hud.gd
, and coin.gd
scripts. While not impossible with direct references, it’s not really ideal, especially since the Coin
node is added dynamically from a script. While this can absolutely be achieved with an autoloaded script, this is another method to achieve a similar outcome and, in some cases, can even be more performant. We’ll get to the performance implications in a little bit, but first we should cover how to load the custom resource.
Loading resources via @export
The first method for loading is via making use of an exported variable. With this method, you can select the .tres
you previously created in the inspector pane.
@export var player_inventory: PlayerInventory
Once you save your script, the inspector will present the option to select the appropriate resource file to attach.
There are a few downsides to this solution. Firstly, this method only works for internal resources that get packaged inside the application when you export your project. This method of loading resources cannot be used to load external resources saved to the end user’s file system. I don’t consider this a major flaw since we already covered the security concerns of using resources for external data storage. The other major downside is that, by default, the resource is loaded for the lifetime of the node (unless you unload the resource manually in code).

While this isn’t inherently bad, it certainly can have an impact on performance, as well as limit how the resource can be used. But more on that later. The biggest benefit of this solution is that it allows you to reuse the script and point to different resources each time, if needed.
Loading resources via ResourceLoader
The second way to load a resource is by making use of the built-in ResourceLoader
class that comes packaged with Godot. The major benefit of this solution is in loading the resource programmatically, you have complete control over the lifecycle of the resource reference.
var player_inventory: PlayerInventory = ResourceLoader.load("res://data/player_inventory.tres")
This solution also works to load external data from the user’s file system, but for the same reasons listed before, this brings with it security concerns. To load an internal resource, make sure the path begins with res://
, otherwise Godot may try to load a file from the filesystem.
NOTE:
Using load(str)
is effectively the same as using ResourceLoader.load(str)
, it’s just shorthand.
Resource lifecycle
Alright, time to finally discuss all this “lifecycle of the resource reference” nonsense I keep talking about. One of the biggest advantages (my personal opinion, obviously) of using custom resources over an autoload script is that you control how long that data is stored in memory. Autoloaded scripts, in general, remain in memory for as long as your program is running, which can introduce problems if there’s a significant amount of data stored in your script.
Given that Resource
extends RefCounted
, resources are able to track references to the resource and are able to know if all references have been discarded. This essentially means that once you’re done referencing a resource, it gets freed from memory. To try and experiment with this feature and how it might work, I first connected the Del key with code to decrement the reference counter.
func _input(event: InputEvent) -> void:
if Input.is_action_just_pressed("ui_text_delete"):
player_inventory.unreference()
Using the unreference()
method leads to pretty unexpected behaviors and, unsurprisingly, its use isn’t recommended. If you delete all references (so that the counter in the upper corner displays 0 references) and then try to collect another coin, the code throws errors if you’re debugging and reloading the scene leads to an integer overflow error. Pressing Del so there are negative references leads to even wilder behaviors. But again, the documentation doesn’t recommend using unreference()
unless “you really know what you are doing”.
Given that wasn’t a great way to demonstrate how resource lifecycle works, I sought to build a different solution. If you hit the Esc key, you’ll open up a pause menu, from which you can either reload the scene while keeping the resource referenced or you can reload the scene while discarding all references to the resource. Both of these options lead to a “loading screen” for a brief period, and then reload the gameplay scene. The only difference is that one loading scene will load the resource as a variable, just to keep it referenced, while the other does not.
class_name BaseLoadingScene extends Control
# Loading scene only exists to either unload resources or keep resources loaded
var anim_timer: float = 0.0
func _process(delta: float) -> void:
anim_timer += delta
if anim_timer > 0.33:
anim_timer = 0.0
$Label.text += "."
func _on_timer_timeout() -> void:
get_tree().change_scene_to_file("res://scenes/world.tscn")
extends BaseLoadingScene
var player_inventory: PlayerInventory = ResourceLoader.load("res://data/player_inventory.tres")
As a result, if you load while preserving references, the coin count persists between scenes. Whereas, if you don’t preserve references, then the coin count resets to 0. The only exception to this is that if you mess with the reference count, weird things start to happen.
Conclusion
Ultimately, custom resources seem like an incredibly useful tool to have. To be clear, I am not advocating, nor do I think, that custom resources should replace autoloads in every case. I think that it’s largely a personal preference, but custom resources can net you some performance gains, in some situations. For example, if you were to have a data structure that represents something like Boss health and stats, it might be beneficial to store that in a resource. They aren’t always needed, only when you’re fighting a boss, and can remain unloaded from memory the rest of the time. All the while, still remaining accessible globally without doing any convoluted node tree traversal.
Plenty to think about here, I’m guilty of defaulting to Singleton use for data that isn’t constantly required. Keep up the good work 🙂
Your writing is a masterclass in subtlety — each word chosen with care, and every sentence a work of art.