Install to Claude Code
npx -y skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-resource-data-patternsDescription
godot resource data patterns
SKILL.md
---
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."
---
## Godot 4.7 Baseline
- Expert patterns in this skill target **Godot 4.7+** (stable, 2026-06-18).
- Consult the [Godot 4.7 migration guide](https://docs.godotengine.org/en/4.7/tutorials/migrating/upgrading_to_godot_4.7.html) when upgrading projects from 4.6.
- **NEVER** assume 4.6 defaults (stretch mode, audio area_mask, RichTextLabel percent flags) without checking 4.7 migration notes.
# Resource & Data Patterns
Resource-based design, typed arrays, and serialization define reusable, inspector-friendly data structures.
## Available Scripts
### [custom_data_resource.gd](scripts/custom_data_resource.gd)
Pattern for defining serialized data containers (Items, Spells, Stats) for the Inspector.
### [resource_flyweight_caching.gd](scripts/resource_flyweight_caching.gd)
Expert example of the Flyweight pattern for memory-efficient resource sharing.
### [resource_local_to_scene.gd](scripts/resource_local_to_scene.gd)
Handling "Local to Scene" resources and `duplicate()` to prevent cross-contamination.
### [character_stats_resource.gd](scripts/character_stats_resource.gd)
Reactive data containers that emit signals when internal properties are modified.
### [resource_save_system.gd](scripts/resource_save_system.gd)
Pattern for serializing complex game state directly into `.tres` files on disk.
### [resource_based_inventory.gd](scripts/resource_based_inventory.gd)
Managing item collections and inventory logic using serialized Resource arrays.
### [flyweight_enemy_config.gd](scripts/flyweight_enemy_config.gd)
Using shared Resources to configure many entities efficiently (HP, Skins, Speed).
### [dynamic_resource_generation.gd](scripts/dynamic_resource_generation.gd)
Creating and modifying Resource instances programmatically at runtime (Loot, Procedural).
### [resource_preloading_strategy.gd](scripts/resource_preloading_strategy.gd)
Preventing frame drops by caching resources in a dictionary before gameplay starts.
### [nested_resource_serialization.gd](scripts/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 `.tres` file on disk for everyone [26].
- **NEVER use untyped arrays in Resources** — `@export var items: Array` allows logic errors. Always use `Array[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 `NodePath` or `UID` [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 = true` in 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
```gdscript
# 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:**
1. In Inspector: **Right-click → New Resource → ItemData**
2. Fill in properties, **Save** as `res://items/health_potion.tres`
### Pattern 2: Character Stats Resource
```gdscript
# 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:**
```gdscript
# 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)
```gdscript
# 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:**
1. Create `ItemDatabase` resource
2. Expand `items` array in Inspector
3. Add `ItemData` resources to array
4. Save as `res://data/item_database.tres`
**Usage:**
```gdscript
# 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:
```gdscript
# 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:**
```gdscript
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
```gdscript
# 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] = []
@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
```gdscript
# 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
```gdscript
# 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
```gdscript
# ✅ 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
```gdscript
# ✅ 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
```gdscript
# ✅ Good - typed array
@export var items: Array[ItemData] = []
# ❌ Bad - untyped array
@export var items: Array = []
```
## Saving/Loading Resources
```gdscript
# 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 Preloader
Avoid 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].
```gdscript
# 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 Registry
Building 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 Patterns
Mandatory 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)
