Skill v1.0.1
currentAutomated scan100/10014 files
version: "1.0.1" name: godot-resource-data-patterns description: "Expert blueprint for data-oriented design using Resource/RefCounted classes (item databases, character stats, reusable data structures). Covers typed arrays, serialization, nested resources, and resource caching. Use when implementing data systems OR inventory/stats/dialogue databases. Keywords Resource, RefCounted, ItemData, CharacterStats, database, serialization, @export, typed arrays."
Resource & Data Patterns
Resource-based design, typed arrays, and serialization define reusable, inspector-friendly data structures.
Available Scripts
custom_data_resource.gd
Pattern for defining serialized data containers (Items, Spells, Stats) for the Inspector.
resource_flyweight_caching.gd
Expert example of the Flyweight pattern for memory-efficient resource sharing.
resource_local_to_scene.gd
Handling "Local to Scene" resources and duplicate() to prevent cross-contamination.
character_stats_resource.gd
Reactive data containers that emit signals when internal properties are modified.
resource_save_system.gd
Pattern for serializing complex game state directly into .tres files on disk.
resource_based_inventory.gd
Managing item collections and inventory logic using serialized Resource arrays.
flyweight_enemy_config.gd
Using shared Resources to configure many entities efficiently (HP, Skins, Speed).
dynamic_resource_generation.gd
Creating and modifying Resource instances programmatically at runtime (Loot, Procedural).
resource_preloading_strategy.gd
Preventing frame drops by caching resources in a dictionary before gameplay starts.
nested_resource_serialization.gd
Building and saving complex data hierarchies using nested Resource properties.
NEVER Do in Resource Design
- NEVER modify resource instances directly — Without
.duplicate(), changing a value (like HP) modifies the.tresfile on disk for everyone [26]. - NEVER use untyped arrays in Resources —
@export var items: Arrayallows logic errors. Always useArray[ResourceClass]for type safety [27]. - NEVER store Node references in Resources — Objects that only exist in a specific SceneTree (like Players/Projectiles) cannot be serialized. Store
NodePathorUID[30]. - NEVER perform heavy calculations in Resource getters/setters — Resources should be data containers. Offload logic to Nodes or specialized RefCounted classes.
- NEVER skip `ResourceSaver.save()` error checks — Saving can fail due to permissions, disk space, or path issues. Always check the return code [31].
- NEVER use Resources for high-frequency runtime data — If a value changes 60 times a second (like velocity), a standard variable is faster than a Resource property.
- NEVER allow circular Resource references — If A.tres references B.tres and B.tres references A.tres, the engine may crash on load.
- NEVER forget the `_init` defaults — Resources created via
new()or in the Inspector need default values in their constructor to be editable [15]. - NEVER share a Resource between entities if they need unique state — Use
resource_local_to_scene = truein the Inspector for components [26]. - NEVER use `.tres` for massive datasets — If you have 10,000 items, a JSON or custom binary format might be more efficient than individualized Resource files.
| 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.gdextends Resourceclass_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 = 1func use() -> void:match item_type:0: # Weaponprint("Equipped weapon: ", item_name)1: # Consumableprint("Consumed: ", item_name)2: # Armorprint("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.gdextends Resourceclass_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.0var 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_damagereturn actual_damagefunc heal(amount: int) -> void:current_health += amountfunc duplicate_stats() -> CharacterStats:var stats := CharacterStats.new()stats.max_health = max_healthstats.max_mana = max_manastats.strength = strengthstats.defense = defensestats.speed = speedstats.current_health = current_healthstats.current_mana = current_manareturn stats
Usage:
# player.gdextends CharacterBody2D@export var stats: CharacterStatsfunc _ready() -> void:if stats:# Create runtime copy to avoid modifying the original resourcestats = stats.duplicate_stats()
Pattern 3: Database Pattern (Array of Resources)
# item_database.gdextends Resourceclass_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 itemreturn nullfunc 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
ItemDatabaseresource - Expand
itemsarray in Inspector - Add
ItemDataresources to array - Save as
res://data/item_database.tres
Usage:
# Global autoloadconst 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.gdextends RefCountedclass_name DamageCalculationvar base_damage: intvar critical_hit: boolvar damage_type: Stringfunc calculate_final_damage(target_defense: int) -> int:var final_damage := base_damage - target_defenseif critical_hit:final_damage *= 2return maxi(final_damage, 1)
Usage:
var calc := DamageCalculation.new()calc.base_damage = 50calc.critical_hit = randf() > 0.8calc.damage_type = "physical"var damage := calc.calculate_final_damage(enemy.defense)
Advanced Patterns
Pattern 5: Nested Resources
# weapon_data.gdextends ItemDataclass_name WeaponData@export var damage: int = 10@export var attack_speed: float = 1.0@export var special_effects: Array[StatusEffect] = []@export var effect_name: String@export var duration: float@export var damage_per_second: int### Pattern 5b: Binary Serialization (.res vs .tres)For production builds, use the binary `.res` format. It is faster to save and load and provides better compression than the human-readable `.tres` format [7, 8].- **Recursion**: Godot's `ResourceSaver` automatically serializes nested sub-resources recursively. Saving the parent saves the entire tree [7].- **Cache Mode**: When loading, use `ResourceLoader.CACHE_MODE_REPLACE` to force a fresh reload from disk, bypassing the internal cache if data has changed [5, 6].### 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
✅ Best - Use Local to Scene
Set resource_local_to_scene = true in the Inspector (or script).
This ensures each scene instance gets its own unique copy of the resource [10].
func _setup_local_to_scene(): # Use this virtual method for unique per-instance initialization [14]. print("Unique resource instance ready!")
### 2. 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
## Expert Data Patterns### 1. O(1) Resource PreloaderAvoid disk I/O hitches during gameplay by caching mission-critical assets into a Dictionary during a loading phase. This enables instant O(1) retrieval for spawning [3, 17].
Global Asset Cache (Autoload)
var _cache: Dictionary = {}
func cache_asset(path: String): # Use threaded loading for background processing ResourceLoader.load_threaded_request(path) # ... poll status ... var asset = ResourceLoader.load_threaded_get(path) _cache[path.get_file().get_basename()] = asset
func get_asset(name: StringName) -> Resource: return _cache.get(name)
### 2. Recursive Serialization RegistryBuilding complex databases using nested Resources. The root resource (e.g. `QuestDatabase`) saves all child `QuestData` and `ObjectiveData` resources in a single `.res` file [7].### 3. Local-to-Scene Component PatternsMandatory for components like `HealthComponent` or `AIConfig` that share a base `.tres` but must track unique runtime values. Setting `resource_local_to_scene = true` prevents the "Damaging one damages all" bug [10].## Reference- [Godot Docs: Resources](https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html)- [Godot Docs: Data Preferences](https://docs.godotengine.org/en/stable/tutorials/best_practices/data_preferences.html)### Related- Master Skill: [godot-master](../godot-master/SKILL.md)