Resource loading — preload, load, threaded
How Godot loads assets — synchronously, asynchronously, with progress.
Godot has no separate system like Unity Addressables — the built-in mechanism covers most scenarios through a few APIs.
Three ways to load
1. preload — static, at parse time
const ENEMY_SCENE := preload("res://scenes/enemy.tscn")
const FIRE_SOUND := preload("res://audio/fire.ogg")
func spawn() -> void:
var enemy = ENEMY_SCENE.instantiate()
add_child(enemy)
preload is a GDScript compiler directive. The resource is loaded when the script itself is
loaded (once, then cached). It’s the fastest way at instantiation time.
Pro: zero stutters at runtime. Con: the asset is always in memory as long as the script lives. Not suitable for heavy geometry that isn’t always needed.
2. load — dynamic, synchronous
func load_level(name: String) -> void:
var path = "res://levels/%s.tscn" % name
var scene = load(path) as PackedScene
if scene == null:
push_error("Failed to load %s" % path)
return
get_tree().change_scene_to_packed(scene)
load blocks the thread until it finishes loading. Suitable for lightweight resources at moments
where the pause isn’t noticeable.
3. ResourceLoader.load_threaded_request — asynchronous
For heavy scenes/models:
var loading_path: String
func start_load(scene_path: String) -> void:
loading_path = scene_path
ResourceLoader.load_threaded_request(scene_path)
func _process(_delta: float) -> void:
if loading_path.is_empty():
return
var progress := []
var status = ResourceLoader.load_threaded_get_status(loading_path, progress)
match status:
ResourceLoader.THREAD_LOAD_IN_PROGRESS:
$LoadingBar.value = progress[0] * 100.0 # 0..1
ResourceLoader.THREAD_LOAD_LOADED:
var resource = ResourceLoader.load_threaded_get(loading_path)
_on_loaded(resource)
loading_path = ""
ResourceLoader.THREAD_LOAD_FAILED:
push_error("Load failed: ", loading_path)
loading_path = ""
func _on_loaded(scene: PackedScene) -> void:
get_tree().change_scene_to_packed(scene)
progress is an array where progress[0] is a float from 0..1 of the progress. You can build a real
loading screen.
This is the analog of Unity Addressables’ LoadAssetAsync: while the player goes through the level,
load the next one in the background. Once it reaches 100% — an instant transition.
Type hinting
load and load_threaded_get return a Resource. To get a typed object, use as:
var scene := load(path) as PackedScene
var texture := load(tex_path) as Texture2D
var data := load(cfg_path) as WeaponData
If the cast fails, the result is null.
PCK packs
Godot lets you pack assets into PCK files (.pck — Pack file) and load them at runtime.
Used for DLC, hot updates, and mod support:
# Load a PCK into the current project
if not ProjectSettings.load_resource_pack("res://dlc/level_pack_2.pck"):
push_error("Failed to load DLC pack")
else:
# Now the assets from the pack are available by their paths
var dlc_scene = load("res://dlc_levels/extra_01.tscn")
A PCK is created via Project → Export in “Export PCK/Zip” mode (without the engine binary).
Code splitting: import('./heavy-module') is async — the browser downloads the chunk over the
network. load_threaded_request is the same conceptually, but for assets.
Unity Addressables ↔ Godot PCK + ResourceLoader. Godot doesn’t have such a convenient remote-CDN integration out of the box, but the basic mechanisms are there.
Caching
load called twice on the same path returns the same Resource (due to the refcounted nature of
Resource):
var a = load("res://item.tres")
var b = load("res://item.tres")
print(a == b) # true — it's the same object
If you want an independent copy (for example, to modify it without side effects):
var clone = a.duplicate()
For a deep clone — duplicate(true) (recursively duplicates nested resources).
resource_local_to_scene
A Resource property that makes each scene clone the resource on load. Useful for materials that must be unique on each instance:
var mat = StandardMaterial3D.new()
mat.resource_local_to_scene = true
# Now when the scene is instanced, each one gets its own copy
Freeing memory
A Resource is removed automatically by reference count. If your singleton references or global arrays hold assets, null them out explicitly:
my_cache.clear() # array with references
preloaded_scene = null
After the last reference is gone, Godot will clean up the memory on the next frame.
What to do when something “won’t unload”
- Use the Resource Monitor in Debugger → Monitor → Objects: it shows live objects in memory. If the count only grows — a leak.
- Remote Scene Tree — lets you inspect the running scene from the editor. If a node “should have been removed but is still hanging around,” you’ll see it.
queue_free()instead offree()— frees at the end of the frame, safer for the current signals.
In the next chapter — building and optimization.