
Godot Resource Data Patterns
- 236 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-resource-data-patterns for development tasks
About
godot-resource-data-patterns: A skill for development. This provides functionality for development workflows.
- godot-resource-data-patterns
Godot Resource Data Patterns by the numbers
- 236 all-time installs (skills.sh)
- +27 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,668 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-resource-data-patternsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 236 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-resource-data-patterns for development tasks
Files
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.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
# 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 statsUsage:
# 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 filteredCreate 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:
# 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] = []
@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 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].
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)
# character_stats_resource.gd
# Reactive data containers using signals
class_name CharacterStats extends Resource
# EXPERT NOTE: Resources can emit signals! Use this to update
# UI automatically when a stat value changes.
signal changed(property_name: String, new_val: Variant)
@export var level: int = 1:
set(val):
if level != val:
level = val
changed.emit("level", val)
emit_changed() # Native Resource signal
# custom_data_resource.gd
# Defining serialized data containers
class_name ItemData extends Resource
# EXPERT NOTE: Resources are pure data. Using class_name allows
# them to be instantiated in the Inspector as .tres files.
@export var name: String = "Unknown Item"
@export var icon: Texture2D
@export var base_value: int = 10
@export_multiline var description: String = ""
# Constructor with default values is REQUIRED for Inspector support
func _init(p_name: String = "Unknown", p_value: int = 10):
name = p_name
base_value = p_value
# skills/resource-data-patterns/code/data_factory_resource.gd
@tool
extends Resource
class_name DataFactoryResource
## Resource Data Expert Pattern
## Implements Static Factory methods and Inspector validation.
@export var item_id: String = "ITEM_000":
set(value):
item_id = value
# 1. Data Validation Scripts
# Expert logic: Enforce naming conventions in the inspector.
if not item_id.begins_with("ITEM_"):
push_warning("Item ID should start with 'ITEM_' for project consistency.")
@export var base_value: int = 10:
set(value):
base_value = clampi(value, 0, 9999) # Enforce non-negative
# 2. Static Factory Methods
# Professional pattern: Ensure consistent initialization project-wide.
static func create_item(id: String, val: int) -> DataFactoryResource:
var new_item = DataFactoryResource.new()
new_item.item_id = id
new_item.base_value = val
return new_item
# 3. Recursive Resource Flattening
# Pattern for deep cloning complex nested data.
func clone() -> DataFactoryResource:
var new_clone = self.duplicate(true)
# Perform additional deep-initialization if needed
return new_clone
## EXPERT NOTE:
## Use 'Custom Inspector Plugins': Create an 'EditorInspectorPlugin'
## to add 'Preview' buttons directly to this Resource in the inspector.
## NEVER modify shared Resources at runtime without calling '.duplicate()'.
## Modification of a shared .tres file in one scene will affect ALL
## other scenes using that same file, leading to 'Ghost Bugs'.
## Use 'ResourceSaver' to persist modified resources to binary .res
## files for 3-5x faster loading than .tres text format.
# dynamic_resource_generation.gd
# Creating and modifying Resources at runtime
extends Node
func create_procedural_loot():
var loot = ItemData.new("Magic Sword", 500)
loot.description = "Generated at: " + Time.get_datetime_string_from_system()
# We can now pass this around as a lightweight data object
_give_to_player(loot)
func _give_to_player(_item: ItemData):
pass
# flyweight_enemy_config.gd
# Using Resources to configure many entities efficiently
extends CharacterBody2D
# EXPERT NOTE: Instead of exporting 20 variables, export 1 Resource.
# This makes swapping "Normal" for "Elite" enemy stats instant.
@export var config: EnemyConfigResource
func _ready():
if config:
$Sprite3D.texture = config.skin
$HealthComponent.max_health = config.hp
# nested_resource_serialization.gd
# Building complex data hierarchies with Resources
class_name QuestData extends Resource
# EXPERT NOTE: Resources can contain other Resources.
# Godot handles the nested serialization automatically.
@export var title: String
@export var rewards: Array[ItemData]
@export var start_stats_requirement: CharacterStats
# resource_based_inventory.gd
# Managing item collections using Resource arrays
class_name Inventory extends Resource
@export var items: Array[ItemData] = []
func add_item(item: ItemData):
items.append(item)
emit_changed()
func remove_item(item: ItemData):
items.erase(item)
emit_changed()
# resource_flyweight_caching.gd
# Optimizing memory via shared Resource instances
extends Node
# EXPERT NOTE: Godot automatically uses the Flyweight pattern for
# Resources. Loading the same .tres file 100 times only uses
# the memory of one instance.
func spawn_item(path: String):
# This returns the cached reference if already loaded
var data = load(path) as ItemData
var item = Sprite2D.new()
item.texture = data.icon
add_child(item)
# resource_local_to_scene.gd
# Creating unique resource instances per node
extends Node
# EXPERT NOTE: If you modify a Resource's property at runtime,
# it affects EVERY node using it. Turn on "Local to Scene" or
# call duplicate() to prevent cross-contamination.
@export var stats: ItemData
func _ready():
# Create a unique copy so our changes don't affect other entities
stats = stats.duplicate()
stats.base_value += 5 # Safe modification
# skills/resource-data-patterns/scripts/resource_pool.gd
extends RefCounted
## Resource Pool Expert Pattern
## Object pooling for Resource instances to reduce allocation overhead.
class_name ResourcePool
var _pool: Array = []
var _resource_script: GDScript
var _max_size: int
func _init(resource_type: GDScript, pool_size: int = 10) -> void:
_resource_script = resource_type
_max_size = pool_size
_prewarm(pool_size)
func _prewarm(count: int) -> void:
for i in range(count):
_pool.append(_create_instance())
func _create_instance() -> Resource:
if _resource_script:
return _resource_script.new()
return null
func acquire() -> Resource:
if _pool.is_empty():
return _create_instance()
return _pool.pop_back()
func release(resource: Resource) -> void:
if _pool.size() < _max_size:
_pool.append(resource)
func clear() -> void:
_pool.clear()
## EXPERT USAGE:
## const DamageInfo := preload("res://data/damage_info.gd")
## var damage_pool := ResourcePool.new(DamageInfo, 50)
##
## func apply_damage():
## var damage := damage_pool.acquire()
## damage.amount = 10
## # use damage...
## damage_pool.release(damage)
# resource_preloading_strategy.gd
# Preventing frame drops by pre-loading data
extends Node
# EXPERT NOTE: Use a dictionary of preloaded Resources to
# avoid 'load()' calls during gameplay frame peaks.
var _vfx_cache: Dictionary = {
"hit": preload("res://vfx/hit.tres"),
"spark": preload("res://vfx/spark.tres")
}
func get_vfx(key: String) -> Resource:
return _vfx_cache.get(key)
# resource_save_system.gd
# Serializing game state into .tres files
extends Node
# EXPERT NOTE: ResourceSaver can save custom Resources to disk.
# This is a very clean way to implement a save system.
func save_player_stats(stats: CharacterStats, slot: int):
var path = "user://save_slot_%d.tres" % slot
var err = ResourceSaver.save(stats, path)
if err != OK:
push_error("Failed to save stats: %d" % err)
func load_player_stats(slot: int) -> CharacterStats:
var path = "user://save_slot_%d.tres" % slot
if FileAccess.file_exists(path):
return load(path) as CharacterStats
return CharacterStats.new()
# skills/resource-data-patterns/scripts/resource_validator.gd
@tool
extends EditorScript
## Resource Validator Expert Pattern
## Validates Resource files for missing exports and type safety.
func _run() -> void:
print("=== Resource Validator ===")
var issues: Array[Dictionary] = []
_scan_resources("res://", issues)
if issues.is_empty():
print("✓ All resources are properly configured!")
else:
_print_issues(issues)
func _scan_resources(path: String, issues: Array[Dictionary]) -> void:
var dir := DirAccess.open(path)
if not dir:
return
dir.list_dir_begin()
var file_name := dir.get_next()
while file_name != "":
var full_path := path + file_name
if dir.current_is_dir():
if not file_name.begins_with("."
) and file_name != "addons":
_scan_resources(full_path + "/", issues)
elif file_name.ends_with(".tres") or file_name.ends_with(".res"):
_validate_resource(full_path, issues)
file_name = dir.get_next()
func _validate_resource(resource_path: String, issues: Array[Dictionary]) -> void:
var resource := load(resource_path)
if not resource:
issues.append({
"file": resource_path,
"type": "load_failed",
"message": "Failed to load resource"
})
return
var script := resource.get_script()
if not script:
return # Built-in resource
var property_list := resource.get_property_list()
for prop in property_list:
# Check for unset required exports
if prop["usage"] & PROPERTY_USAGE_EDITOR:
var value = resource.get(prop["name"])
if value == null or (value is String and value.is_empty()):
issues.append({
"file": resource_path,
"type": "missing_export",
"property": prop["name"],
"message": "Property '%s' is not set" % prop["name"]
})
func _print_issues(issues: Array[Dictionary]) -> void:
print("\n❌ Found %d issues:" % issues.size())
for issue in issues:
print(" %s: %s" % [issue["file"], issue["message"]])
## EXPERT NOTE:
## Run before building to catch missing resource data.