Custom Resources
Description
This skill covers using Godot 4's Resource class to create data-driven game configurations. Custom Resources replace hardcoded values with editor-friendly .tres files — the Godot equivalent of Unity's ScriptableObjects.
When To Use
- •Defining weapon stats, enemy data, item properties, or level configurations
- •Sharing data between scenes without autoloads
- •Creating data-driven systems (loot tables, dialogue, ability definitions)
- •Building editor-friendly configuration that designers can tweak without code
Prerequisites
- •Godot 4.3+ project
- •Understanding of
@exportand the Inspector - •GDScript fundamentals (class_name, type hints)
Instructions
1. Define a Custom Resource
gdscript
# resources/weapon_data.gd
class_name WeaponData
extends Resource
@export var name: String = ""
@export var damage: int = 10
@export var fire_rate: float = 0.5
@export var range_distance: float = 100.0
@export var projectile_scene: PackedScene
@export var fire_sound: AudioStream
@export var icon: Texture2D
@export_group("Upgrades")
@export var max_level: int = 5
@export var damage_per_level: int = 3
2. Create Instances in the Editor
- •Right-click in the FileSystem dock → New Resource.
- •Search for your class name (e.g.,
WeaponData). - •Fill in the properties in the Inspector.
- •Save as
.tres(text) or.res(binary) inresources/weapons/.
3. Reference in Scripts
gdscript
@export var weapon: WeaponData
func fire() -> void:
if weapon == null:
return
var projectile := weapon.projectile_scene.instantiate()
projectile.damage = weapon.damage
projectile.global_position = _muzzle.global_position
get_tree().current_scene.add_child(projectile)
_audio.stream = weapon.fire_sound
_audio.play()
4. Resource Collections
gdscript
# Inventory item class_name ItemData extends Resource @export var id: StringName = &"" @export var display_name: String = "" @export var description: String = "" @export var icon: Texture2D @export var max_stack: int = 99 @export var is_consumable: bool = false
gdscript
# Loot table using resources
class_name LootTable
extends Resource
@export var entries: Array[LootEntry] = []
func roll() -> ItemData:
var total_weight: float = 0.0
for entry in entries:
total_weight += entry.weight
var roll := randf() * total_weight
var current: float = 0.0
for entry in entries:
current += entry.weight
if roll <= current:
return entry.item
return entries[-1].item
gdscript
class_name LootEntry extends Resource @export var item: ItemData @export var weight: float = 1.0 @export_range(1, 99) var min_quantity: int = 1 @export_range(1, 99) var max_quantity: int = 1
5. Resource Events (Signal Bus Pattern)
gdscript
# Event channel as a Resource (shared reference)
class_name EventChannel
extends Resource
signal triggered(data: Variant)
func emit_event(data: Variant = null) -> void:
triggered.emit(data)
Assign the same .tres to both emitter and listener via @export:
gdscript
# Emitter
@export var on_score_changed: EventChannel
func add_score(points: int) -> void:
_score += points
on_score_changed.emit_event(_score)
# Listener (separate scene, no direct reference)
@export var on_score_changed: EventChannel
func _ready() -> void:
on_score_changed.triggered.connect(_on_score_changed)
Best Practices
- •Use Resources for any data that should be editable in the Inspector without code changes.
- •Store
.tresfiles in a dedicatedresources/folder, organized by type. - •Use
class_nameso resources appear in the "New Resource" dialog. - •Use
@export_group()and@export_subgroup()to organize Inspector properties. - •Use
@export_range(),@export_enum(),@export_multilinefor validation and UX. - •Resources are reference-counted — they can be safely shared between nodes.
Common Pitfalls
- •Mutating shared Resources at runtime. If two enemies share the same
WeaponData.tresand you modifydamage, both are affected. Useresource.duplicate()for per-instance data. - •Not registering
class_name. Withoutclass_name, the resource won't appear in the editor's resource picker. - •Storing node references in Resources. Resources persist across scenes — they can't hold node references. Use signals or
NodePathinstead. - •Using dictionaries instead of Resources. Resources are type-safe, editor-friendly, and serializable. Dictionaries are not.