godot-resource-data-patterns

📁 thedivergentai/gd-agentic-skills 📅 3 days ago
0
总安装量
3
周安装量
安装命令
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-resource-data-patterns

Agent 安装分布

opencode 3
gemini-cli 3
codex 3
claude-code 2
github-copilot 2

Skill 文档

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:

  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 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:

  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] = []

# 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!

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

Reference

Related