【Godot】Creating and Using Custom Resources - Data-Driven Design in Godot Engine

Created: 2025-12-08Last updated: 2025-12-16

Practical methods for data-driven design using Godot Engine's custom resources. Covers performance optimization, common mistakes, and best practices.

Why Custom Resources Matter: The First Step to Data-Driven Design

When developing games in Godot Engine, managing in-game data is an unavoidable challenge. Character stats, item definitions, enemy AI settings—much data determines game behavior. Writing this data directly into nodes or scenes makes data reuse difficult, and as project scale grows, management becomes complex and changes become difficult.

This is where Custom Resources come in. Custom resources extend Godot's powerful Resource system to create and save custom data types. Similar to Unity's ScriptableObject, they're key to separating data from game logic and achieving Data-Driven Design.


Basic Concepts of Custom Resources

In Godot, a resource is a data container that can be saved to disk. Scenes, scripts, textures, audio—most non-node data in projects is treated as resources. Custom resources are defined by creating scripts that inherit from the Resource class.

1. Defining a Custom Resource

Custom resources are created as GDScript files inheriting from the Resource class.

# res://resources/character_data.gd
# Register as global type with class_name
class_name CharacterData extends Resource

# Define properties shown/saved in inspector with @export
@export var character_name: String = "New Character"
@export var max_health: int = 100
@export var attack_power: float = 15.0

# Using Enum for job makes it selectable in inspector
enum Job { WARRIOR, MAGE, ROGUE }
@export var job: Job = Job.WARRIOR

@export var skills: Array[String] = []

# You can also implement methods for logic tied to data
func get_description() -> String:
    # Get name string from enum value with find_key() (more explicit)
    return "Name: %s, HP: %d, ATK: %.1f, Job: %s" % [character_name, max_health, attack_power, Job.find_key(job)]

The class_name keyword registers this script as a global type named CharacterData.

2. Creating and Using Resource Files

After saving the script, right-click in the FileSystem dock and select "New" -> "Resource". Type CharacterData in the search box, select your custom resource type, and save with a name like player_data.tres.

To use this resource in-game, export a variable in a node's script.

# player.gd
extends CharacterBody2D

# Can directly specify CharacterData as type
@export var data: CharacterData

func _ready():
    if data:
        print("Player info loaded.")
        print(data.get_description())
    else:
        print("Player data not set.")

Practical Example: Building an Item Database

One of the most powerful uses of custom resources is building databases for in-game items and entities.

Defining Item Resources

# res://resources/item_data.gd
class_name ItemData extends Resource

@export var item_id: int = 0
@export var item_name: String = "Unknown Item"
@export_multiline var description: String = "A mysterious item."
@export var icon_texture: Texture2D
@export var stackable: bool = true
@export var max_stack: int = 99

Creating Derived Resources

Specific item types (e.g., weapons, potions) may need additional unique properties. In that case, create custom resources that inherit from ItemData.

# res://resources/weapon_data.gd
class_name WeaponData extends ItemData

@export var attack_bonus: float = 5.0
@export var weapon_type: String = "Sword"
@export var two_handed: bool = false

Using in Game

extends Node

@export var equipped_weapon: WeaponData

func _ready():
    if equipped_weapon:
        print("Equipped weapon: %s" % equipped_weapon.item_name)
        print("Attack bonus: %s" % equipped_weapon.attack_bonus)

func load_item_data(path: String) -> ItemData:
    var item_resource = ResourceLoader.load(path)
    if item_resource is ItemData:
        return item_resource
    return null

Common Mistakes and Best Practices

Common MistakeBest Practice
Directly modifying shared resourcesWhen modifying data at runtime, create a copy with resource.duplicate() and modify that instance. This prevents affecting shared data used elsewhere.
Initializing properties in _init()Custom resource properties should have default values set at declaration like @export var health: int = 100. Initialization in _init() gets overwritten by inspector values and won't work as intended.
Combining massive data into one resourcePacking large amounts of data (like an item encyclopedia) into one resource increases load time and memory usage. Consider creating separate resource files per data item and dynamically loading only what's needed with ResourceLoader.load().
Hardcoding file paths as stringsExport as resource type like @export var item: Resource and configure from inspector for a structure resilient to path changes.
Can't detect resource changesWhen you want to notify other objects that data in a resource changed, define signals within the resource.

Performance and Comparison with Alternative Patterns

Performance Considerations

  • .tres vs .res: .tres is text format, human-readable and suitable for version control. .res is binary format, faster to load and smaller file size, but content can't be directly edited. Using .tres during development and converting to .res for release is common practice.
  • Lazy Loading: Loading all resources at game start can lengthen startup time. Using ResourceLoader.load_threaded_request() for asynchronous loading is important for maintaining game responsiveness.

Comparison with Alternative Patterns

Data Management MethodProsConsBest For
Custom ResourcesStrong editor integration. Type-safe. High reusability.Depends on Godot engine.Managing structured game data like characters, items, skills.
JSON FilesEasy human read/write. Language and engine agnostic.No type information. Requires separate parsing.Config files, data exchange with external tools.
CSV FilesEasy editing in spreadsheet software.Not suited for complex data structures.Large amounts of simple records (e.g., dialogue collections).
SQLite DatabaseEfficient querying/manipulation of large data.Setup somewhat complex. Requires addon.Thousands of items or user data.

Benefits of Custom Resources and Data-Driven Design

BenefitDescriptionRelation to Data-Driven Design
Data SeparationGame logic (GDScript) and data (.tres files) are completely separated, improving code readability and maintainability.Data becomes independent from code, making changes easier.
ReusabilityOnce created, resource files can be shared and reused across multiple scenes and nodes.Data definitions are consolidated in one place.
Editor Integration@export properties appear in Godot's inspector, allowing designers and planners to intuitively edit data without programming knowledge.Entire development team can easily access data.
InstantiationUnlike nodes, resources don't belong to the scene tree. Using Resource.duplicate() makes it easy to create data copies.Foundation for dynamically generating/modifying data at runtime.

Summary

Godot Engine's custom resources aren't just a data storage mechanism—they're a powerful tool for evolving your project's design philosophy to data-driven.

Beginners should start by trying custom resources for basic character stats or simple item definitions. This dramatically improves game data management and builds a scalable design foundation capable of handling larger, more complex projects.