A brief look at custom resources in Godot 4
In Godot, a Resource is anything Godot saves to, or loads from, the disk, such as textures, audio files, and even scripts. Typically acting as data containers, resources allow for optimized access to these files by caching them while in use so that if multiple objects need to use the same resource, it is only loaded into memory once. Whether you know it or not, if you’re using Godot then you’re making use of its built-in resources, but you can also create your own custom resources that can do a lot more, which is something I’ve been doing in all of my games for several years due to just how nice they are to work with. Let’s take a look at what they are, how to use them, and some gotchas you might run into.
Creating custom resources
As mentioned, resources primarily act as data containers that are enhanced by Godot. With custom resources, you can define exported properties you can edit right in the inspector, run custom code, and even nest other resources within them. In my tactics game Unto Deepest Depths, for instance, the player’s progress is tracked via a custom resource, with fields for things such as how far the player has gotten in a run, how much XP they’ve earned, and what units they have, which are, in fact, defined by their own custom resources defining what kind of unit they are, their stats, and more.
Creating your own resource is as simple as creating a script, giving it a class_name, and extending from the Resource class. As an example, I’ll make a small resource representing the stats of a unit in our game.
class_name UnitStats
extends Resource
You can also inherit from other resource types if you want, including other custom resources.
class_name ExtraUnitStats
extends UnitStats
Next, you’ll probably want to add some properties that can be customized in the inspector by exporting them the same way you would on a node.
class_name UnitStats
extends Resource
@export var health: int = 100
@export var mana: int
From here, if you have another node or resource that exports your custom resource type, you can create an instance of it right in the inspector, or create an instance of the resource in your game’s project files. Either way, you can open up the resource, edit it in the inspector, and then use it like you would any other resource.
# Example node making use of the unit stats resource type
extends Node
@export var stats: UnitStats
func _ready() -> void:
print(stats.health)
You can also load/preload the resource if you want to do so directly via code.
extends Node
var stats: UnitStats = preload('path_or_uid.tres')
func _ready() -> void:
print(stats.health)
Like other code in your game, you can also add constants, functions, signals, and more to your resources if you want to encapsulate some additional data or functionality, letting you really make your custom resources a powerful tool for managing data in your game.
class_name UnitStats
extends Resource
signal health_updated(new_amount)
const BASE_DEFENSE: int = 10
@export var health: int = 100
@export var mana: int
func take_damage(amount: int) -> void:
health -= maxi(amount - BASE_DEFENSE, 0)
health_updated.emit(health)
From there, you’re ready to start using custom resources as you see fit, but there are a few more things you may want to know…
Gotchas
No scene tree
The most important thing to know is that resources do not have access to the scene tree, meaning you cannot directly access time-stepped callbacks, such as process or physics_process, can’t use the input callbacks, such as unhandled_input, and there’s no _ready function. If you require access to these, you’ll need to write a custom function and then call it from an object that does have access to the scene tree.
class_name MyCustomResource
extends Resource
func on_process(delta: float) -> void:
# Do stuff
class_name SomeNode
extends Node
@export var custom_resource: MyCustomResource
func _process(delta: float) -> void:
custom_resource.on_process(delta)
Uniqueness of Resources
Another important thing that may trip you up when using custom resources is the memory optimization I mentioned before. To avoid loading the same resource into memory multiple times, Godot loads it once and then shares that reference with everything that accesses it. On the one hand, this is a good thing as it means you can reference your custom resource throughout your application and know that you’re working with the canonical source of truth. On the other hand, if you want multiple instances of the same type of resource that don’t share data, you have to make sure you’re explicitly using a unique copy of it. With our example UnitStats resource, for instance, there will need to be an instance of it for each unit in our game to make sure they don’t all share the same pool of data.
If you know ahead of time where you’ll need a unique resource, you can just make an instance of that resource type as appropriate. At runtime, though, you’ll need to either instantiate and initialize the resource yourself, or duplicate a source resource that you want to make a copy of.
var new_stats = UnitStats.new()
new_stats.health = 200
var duplicated_stats = new_stats.duplicate()
# Will print 200
print(duplicated_stats.health)
duplicated_stats.health = 300
# Will print 200
print(new_stats.health)
# Will print 300
print(duplicated_stats.health)
Reference counting
If you take a look at the inheritance tree of the Resource class, you’ll notice that it inherits from the RefCounted class, which I’ve talked about previously. What’s important to note for resources is that if they aren’t being used, they can be unloaded from memory, meaning runtime changes that haven’t been saved will be lost. If you’re using resources as static data containers, then this isn’t a concern as the data will be unchanged if you need to load it again later. But if you’re making changes to the data at runtime, you either need a system for saving and loading the data, or will need to make sure the resource is always being referenced by something so that it isn’t unloaded.
Saving and loading data
Another feature of resources is the ability to save and load them to and from the disk with GDScript using the ResourceSaver and ResourceLoader singletons. For Unto Deepest Depths, for example, I built a custom plugin to help me view and edit unit data that made use of these singletons.
ResourceSaver.save(unit_data, save_path)
var data = ResourceLoader.load(save_path)
But there is one very important thing to know about saving and loading resources, and that is that, as of this writing, loading resources external to your codebase is not safe. As we’ve seen, Resources can contain code, and this includes code that can run when a resource is being loaded by the engine, allowing for arbitrary code execution to occur. For resources internal to the game, this isn’t an issue, but this does mean that you shouldn’t use the save/load feature for things external to your source code, such as saving player progress, sharing content add-ons, and the like, since a malicious user could share a “save file” that contains more than just the player’s progress. Instead, you should follow the official recommendations for handling save data.
Closeout
And that’s a quick look at using custom resources in Godot. They’re a great choice for data containers that support some additional functionality due to their ability to run code, and something I reach for often when I want to store things like custom configurations, settings for objects in my games, and the like.