Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Cheat Sheets
  3. GDScript
  4. GDScript Resources Cheat Sheet
GDScriptCheat Sheet

GDScript Resources Cheat Sheet

Quick-reference for Godot 4 resources and data-driven design. Each section includes copy-ready snippets for item stats, level configs, and asset management.

On this page
  1. 1Resource Basics
  2. 2load() vs preload()
  3. 3Custom Resource Classes
  4. 4@export with Resources
  5. 5PackedScene as Resource
  6. 6Threaded Loading
  7. 7Saving Resources
  8. 8Shared vs Unique Resources
  9. 9Common Resource Patterns
  10. 10Common Resource Pitfalls
Resource Basicsload() vs preload()Custom Resource Classes@export with ResourcesPackedScene as ResourceThreaded LoadingSaving ResourcesShared vs Unique ResourcesCommon Resource PatternsCommon Resource Pitfalls

Resource Basics

Resources are data containers that can be saved to disk and shared between nodes. Textures, audio, scenes, and custom data all extend Resource.

Built-in resource types
# Everything in Godot is either a Node (in the tree) or a Resource (data)
# Common resources:
var tex: Texture2D       # images, sprites
var sound: AudioStream   # audio files
var scene: PackedScene   # .tscn files
var font: Font           # font files
var material: Material   # shaders, materials
Resources are reference-counted
# Resources use RefCounted — automatically freed when no references remain
var tex := load("res://icon.svg")
# tex is freed when nothing references it anymore

load() vs preload()

preload() loads at parse time (compile time). load() loads at call time (runtime). Use preload for known assets, load for dynamic paths.

preload — fast, compile-time
# Loaded when the script is parsed — no runtime delay
const SwordScene := preload("res://scenes/weapons/sword.tscn")
const HitSound := preload("res://audio/sfx/hit.ogg")
const EnemySprite := preload("res://sprites/goblin.png")
load — dynamic, runtime
# Loaded when this line executes — path can be a variable
var weapon_type := "axe"
var scene: PackedScene = load("res://scenes/weapons/%s.tscn" % weapon_type)

# Load from user data
var save := load("user://saves/slot1.tres")
When to use each
# preload: known at write-time, small/medium assets
const BulletScene := preload("res://scenes/bullet.tscn")

# load: path computed at runtime, large assets, user content
func load_level(level_name: String) -> PackedScene:
    return load("res://levels/%s.tscn" % level_name)

preload() cannot use variables — the path must be a string literal. load() accepts any expression.

Custom Resource Classes

Create your own Resource subclasses for game data — item stats, enemy configs, ability definitions.

Define a custom resource (item_data.gd)
class_name ItemData
extends Resource

@export var name: String
@export var icon: Texture2D
@export var damage: int
@export var rarity: int  # 0=common, 1=rare, 2=epic
@export_multiline var description: String
Create instances in the Inspector
# 1. Create item_data.gd with class_name ItemData
# 2. Right-click in FileSystem > New Resource > ItemData
# 3. Fill in fields in the Inspector
# 4. Save as .tres file (e.g. res://data/items/iron_sword.tres)
Use in other scripts
# weapon.gd
@export var item_data: ItemData

func get_damage() -> int:
    return item_data.damage

func get_display_name() -> String:
    return item_data.name

@export with Resources

Expose resource slots in the Inspector so designers can assign data without touching code.

Export single resource
@export var weapon_stats: ItemData
@export var hit_sound: AudioStream
@export var death_effect: PackedScene
Export array of resources
@export var inventory: Array[ItemData] = []
@export var loot_table: Array[ItemData] = []

func drop_random_loot() -> ItemData:
    return loot_table.pick_random()
Export with type hints in the Inspector
# The Inspector shows a dropdown filtered to this type
@export var character_class: CharacterClassData
@export var skill_tree: SkillTreeData

# Nested resources
@export var equipment: EquipmentSlots  # Another custom Resource

PackedScene as Resource

PackedScene is just a Resource that holds a node tree. instantiate() creates live nodes from it.

Store scenes for spawning
@export var enemy_scenes: Array[PackedScene] = []

func spawn_random_enemy(pos: Vector2) -> void:
    var scene := enemy_scenes.pick_random()
    var enemy := scene.instantiate()
    enemy.global_position = pos
    add_child(enemy)
Scene factory pattern
# data/wave_config.gd
class_name WaveConfig
extends Resource

@export var enemy_scene: PackedScene
@export var count: int
@export var spawn_delay: float

# spawner.gd
@export var waves: Array[WaveConfig] = []

func run_wave(config: WaveConfig) -> void:
    for i in range(config.count):
        var enemy := config.enemy_scene.instantiate()
        add_child(enemy)
        await get_tree().create_timer(config.spawn_delay).timeout

Threaded Loading

Load large assets in the background to avoid frame drops. Essential for level transitions.

Request and poll threaded load
var next_scene_path := "res://levels/world_2.tscn"

func start_loading() -> void:
    ResourceLoader.load_threaded_request(next_scene_path)

func _process(delta: float) -> void:
    var status := ResourceLoader.load_threaded_get_status(next_scene_path)
    match status:
        ResourceLoader.THREAD_LOAD_IN_PROGRESS:
            # Update loading bar
            var progress: Array = []
            ResourceLoader.load_threaded_get_status(next_scene_path, progress)
            $LoadingBar.value = progress[0] * 100
        ResourceLoader.THREAD_LOAD_LOADED:
            var scene := ResourceLoader.load_threaded_get(next_scene_path)
            get_tree().change_scene_to_packed(scene)
Simple async load helper
func load_scene_async(path: String) -> PackedScene:
    ResourceLoader.load_threaded_request(path)
    while true:
        var status := ResourceLoader.load_threaded_get_status(path)
        if status == ResourceLoader.THREAD_LOAD_LOADED:
            return ResourceLoader.load_threaded_get(path)
        await get_tree().process_frame

Saving Resources

Use ResourceSaver to write resource data to disk. Perfect for save files and user-generated content.

Save a custom resource
# save_data.gd
class_name SaveData
extends Resource

@export var player_name: String
@export var level: int
@export var inventory: Array[String] = []
@export var position: Vector2

# game_manager.gd
func save_game() -> void:
    var data := SaveData.new()
    data.player_name = player.name
    data.level = current_level
    data.inventory = player.get_inventory_names()
    data.position = player.global_position
    ResourceSaver.save(data, "user://saves/save_01.tres")
Load a saved resource
func load_game() -> void:
    if ResourceLoader.exists("user://saves/save_01.tres"):
        var data: SaveData = load("user://saves/save_01.tres")
        player.name = data.player_name
        current_level = data.level
        player.global_position = data.position
.tres vs .res file format
# .tres = text format (human-readable, good for version control)
ResourceSaver.save(data, "res://data/config.tres")

# .res = binary format (smaller, faster to load)
ResourceSaver.save(data, "res://data/config.res")

Use .tres during development (readable diffs). Switch to .res for release builds if size matters.

Shared vs Unique Resources

Resources are shared by default. Multiple nodes referencing the same .tres edit the SAME data. Use duplicate() or make_unique() to avoid this.

Shared resource bug
# Two enemies reference the same ItemData .tres
# Modifying one affects both!
@export var stats: EnemyStats

func buff() -> void:
    stats.damage += 10  # BUG: ALL enemies using this stats get buffed
Fix with duplicate()
func _ready() -> void:
    # Create a unique copy so changes are per-instance
    stats = stats.duplicate()

func buff() -> void:
    stats.damage += 10  # Only this enemy is buffed
local_to_scene for auto-unique in Inspector
# In the Inspector, check "Local to Scene" on a sub-resource
# This makes each scene instance get its own copy automatically

# Or set it in code:
@export var stats: EnemyStats

func _ready() -> void:
    stats.resource_local_to_scene = true

duplicate() is explicit and clear. local_to_scene is convenient for Inspector-assigned resources.

Common Resource Patterns

Data-driven design patterns using custom Resources.

Ability definition resource
class_name AbilityData
extends Resource

@export var name: String
@export var icon: Texture2D
@export var cooldown: float
@export var mana_cost: int
@export var damage: int
@export var effect_scene: PackedScene

# ability_system.gd
@export var abilities: Array[AbilityData] = []

func use_ability(index: int) -> void:
    var ability := abilities[index]
    if mana >= ability.mana_cost:
        mana -= ability.mana_cost
        spawn_effect(ability.effect_scene)
Dialogue resource
class_name DialogueLine
extends Resource

@export var speaker: String
@export var text: String
@export var portrait: Texture2D
@export var choices: Array[String] = []

class_name DialogueSequence
extends Resource

@export var lines: Array[DialogueLine] = []
Loot table resource
class_name LootEntry
extends Resource

@export var item: ItemData
@export_range(0, 100) var drop_chance: float

class_name LootTable
extends Resource

@export var entries: Array[LootEntry] = []

func roll() -> ItemData:
    for entry in entries:
        if randf() * 100.0 < entry.drop_chance:
            return entry.item
    return null

Common Resource Pitfalls

Quick fixes for the most frequent resource bugs in Godot 4.

preload with variable path
# BUG: preload requires a string literal
# const scene = preload(path_variable)  # ERROR

# FIX: use load() for dynamic paths
var scene = load(path_variable)
Modifying shared resource at runtime
# BUG: all enemies share the same stats resource
# Changing one changes them all

# FIX: duplicate in _ready
func _ready() -> void:
    stats = stats.duplicate(true)  # true = deep duplicate
Loading from user:// before it exists
# BUG: load() returns null if file doesn't exist
var data = load("user://saves/save.tres")
data.level  # null access crash!

# FIX: check existence first
if ResourceLoader.exists("user://saves/save.tres"):
    var data = load("user://saves/save.tres")
else:
    create_new_save()

Always check ResourceLoader.exists() before loading from user:// — the file may not exist on first run.

Learn GDScript in Depth
GDScript Scene Instancing Practice →GDScript Exports Practice →
Warm-up1 / 2

Can you write this from memory?

Define `UpgradeData` as a custom `Resource` class for upgrade config (include both `extends` and `class_name`).

See Also
Node Operations →Arrays & Dictionaries →

Start Practicing GDScript

Free daily exercises with spaced repetition. No credit card required.

← Back to GDScript Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.