~2 min read

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.

Loading in the background during gameplay

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).

Web

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

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”

  1. Use the Resource Monitor in Debugger → Monitor → Objects: it shows live objects in memory. If the count only grows — a leak.
  2. 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.
  3. queue_free() instead of free() — frees at the end of the frame, safer for the current signals.

In the next chapter — building and optimization.