- Resource & Data Patterns
- Resource-based design, typed arrays, and serialization define reusable, inspector-friendly data structures.
- Available Scripts
- data_factory_resource.gd
- Expert resource factory with type validation and batch instantiation.
- resource_pool.gd
- Object pooling for Resource instances - reduces allocation overhead in hot paths.
- resource_validator.gd
- Validates Resource files for missing exports and configuration issues.
- MANDATORY - For Data Systems
- Read data_factory_resource.gd before implementing item/stat databases. NEVER Do in Resource Design NEVER modify resource instances without duplicating — player.stats.health -= 10 on loaded resource? Modifies the .tres file on disk. MUST use .duplicate() first. NEVER use untyped arrays — @export var items: Array = [] accepts ANY type = runtime errors. Use Array[ItemData] for type safety + autocomplete. NEVER forget @export for inspector editing — Resource property without @export ? Invisible in Inspector. Use @export for editable properties. NEVER put logic in base Resource — Resource has no lifecycle ( _ready , _process ). Use extends RefCounted for runtime logic OR attach to Node. NEVER serialize Node references — @export var player_node: Node in Resource? Breaks on save/load. Store NodePath OR UID instead. NEVER use ResourceSaver.save() without error check — ResourceSaver.save(res, path) can fail (permissions, invalid path). MUST check return error code. Type Use Case Serializable Can Save to Disk Inspector Support Resource Data that needs saving/loading ✅ ✅ ✅ RefCounted Temporary runtime data ❌ ❌ ❌ Node Scene hierarchy entities ✅ (scene files) ✅ ✅ When to Use Resources Use Resources For: Item definitions (weapons, consumables, equipment) Character stats/progression systems Skill/ability data Configuration files Dialogue databases Enemy/NPC templates Use RefCounted For: Temporary calculations Runtime-only state machines Utility classes without data persistence Implementation Patterns Pattern 1: Custom Resource Class
item_data.gd
extends Resource class_name ItemData @ export var item_name : String = "" @ export var description : String = "" @ export_enum ( "Weapon" , "Consumable" , "Armor" ) var item_type : int = 0 @ export var icon : Texture2D @ export var value : int = 0 @ export var stackable : bool = false @ export var max_stack : int = 1 func use ( ) -> void : match item_type : 0 :
Weapon
print ( "Equipped weapon: " , item_name ) 1 :
Consumable
print ( "Consumed: " , item_name ) 2 :
Armor
print ( "Equipped armor: " , item_name ) Create Resource Instances: In Inspector: Right-click → New Resource → ItemData Fill in properties, Save as res://items/health_potion.tres Pattern 2: Character Stats Resource
character_stats.gd
extends Resource class_name CharacterStats @ export var max_health : int = 100 @ export var max_mana : int = 50 @ export var strength : int = 10 @ export var defense : int = 5 @ export var speed : float = 100.0 var current_health : int = max_health : set ( value ) : current_health = clampi ( value , 0 , max_health ) var current_mana : int = max_mana : set ( value ) : current_mana = clampi ( value , 0 , max_mana ) func take_damage ( amount : int ) -> int : var actual_damage := maxi ( amount - defense , 0 ) current_health -= actual_damage return actual_damage func heal ( amount : int ) -> void : current_health += amount func duplicate_stats ( ) -> CharacterStats : var stats := CharacterStats . new ( ) stats . max_health = max_health stats . max_mana = max_mana stats . strength = strength stats . defense = defense stats . speed = speed stats . current_health = current_health stats . current_mana = current_mana return stats Usage:
player.gd
extends CharacterBody2D @ export var stats : CharacterStats func _ready ( ) -> void : if stats :
Create runtime copy to avoid modifying the original resource
stats
stats . duplicate_stats ( ) Pattern 3: Database Pattern (Array of Resources)
item_database.gd
extends Resource class_name ItemDatabase @ export var items : Array [ ItemData ] = [ ] func get_item_by_name ( item_name : String ) -> ItemData : for item in items : if item . item_name == item_name : return item return null func get_items_by_type ( item_type : int ) -> Array [ ItemData ] : var filtered : Array [ ItemData ] = [ ] for item in items : if item . item_type == item_type : filtered . append ( item ) return filtered Create Database: Create ItemDatabase resource Expand items array in Inspector Add ItemData resources to array Save as res://data/item_database.tres Usage:
Global autoload
const ITEM_DB := preload ( "res://data/item_database.tres" ) func get_item ( name : String ) -> ItemData : return ITEM_DB . get_item_by_name ( name ) Pattern 4: Runtime-Only Data (RefCounted) For data that doesn't need persistence:
damage_calculation.gd
extends RefCounted class_name DamageCalculation var base_damage : int var critical_hit : bool var damage_type : String func calculate_final_damage ( target_defense : int ) -> int : var final_damage := base_damage - target_defense if critical_hit : final_damage *= 2 return maxi ( final_damage , 1 ) Usage: var calc := DamageCalculation . new ( ) calc . base_damage = 50 calc . critical_hit = randf ( )
0.8 calc . damage_type = "physical" var damage := calc . calculate_final_damage ( enemy . defense ) Advanced Patterns Pattern 5: Nested Resources
weapon_data.gd
extends ItemData class_name WeaponData @ export var damage : int = 10 @ export var attack_speed : float = 1.0 @ export var special_effects : Array [ StatusEffect ] = [ ]
status_effect.gd
extends Resource class_name StatusEffect @ export var effect_name : String @ export var duration : float @ export var damage_per_second : int Pattern 6: Resource Scripts with Signals
inventory.gd
extends Resource class_name Inventory signal item_added ( item : ItemData ) signal item_removed ( item : ItemData ) var items : Array [ ItemData ] = [ ] func add_item ( item : ItemData ) -> void : items . append ( item ) item_added . emit ( item ) func remove_item ( item : ItemData ) -> void : items . erase ( item ) item_removed . emit ( item ) Pattern 7: Resource Loading at Runtime
Load resource dynamically
var item : ItemData = load ( "res://items/sword.tres" )
Preload for better performance (compile-time)
const SWORD := preload ( "res://items/sword.tres" )
Load all resources in a directory
func load_all_items ( ) -> Array [ ItemData ] : var items : Array [ ItemData ] = [ ] var dir := DirAccess . open ( "res://items/" ) if dir : dir . list_dir_begin ( ) var file_name := dir . get_next ( ) while file_name != "" : if file_name . ends_with ( ".tres" ) : var item : ItemData = load ( "res://items/" + file_name ) items . append ( item ) file_name = dir . get_next ( ) return items Best Practices 1. Always Duplicate Resources in Runtime
✅ Good - create instance copy
@ export var stats : CharacterStats func _ready ( ) : stats = stats . duplicate ( )
Or custom duplicate method
❌ Bad - modifies the original resource file
@ export var stats : CharacterStats func _ready ( ) : stats . current_health -= 10
This changes the .tres file!
- Use @export for Inspector Editing
✅ Makes properties editable in Inspector
@ export var max_health : int = 100 @ export var icon : Texture2D @ export_range ( 0 , 100 ) var drop_chance : int = 50 3. Organize Resources by Category res://data/ items/ weapons/ sword.tres bow.tres consumables/ health_potion.tres characters/ player_stats.tres enemy_goblin.tres databases/ item_database.tres 4. Type Your Arrays
✅ Good - typed array
@ export var items : Array [ ItemData ] = [ ]
❌ Bad - untyped array
@ export var items : Array = [ ] Saving/Loading Resources
Save resource to disk
func save_inventory ( inventory : Inventory , path : String ) -> void : ResourceSaver . save ( inventory , path )
Load resource from disk
func load_inventory ( path : String ) -> Inventory : if ResourceLoader . exists ( path ) : return ResourceLoader . load ( path ) return null Reference Godot Docs: Resources Godot Docs: Data Preferences Related Master Skill: godot-master