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 Mistake | Best Practice |
|---|---|
| Directly modifying shared resources | When 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 resource | Packing 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 strings | Export as resource type like @export var item: Resource and configure from inspector for a structure resilient to path changes. |
| Can't detect resource changes | When 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
.tresvs.res:.tresis text format, human-readable and suitable for version control..resis binary format, faster to load and smaller file size, but content can't be directly edited. Using.tresduring development and converting to.resfor 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 Method | Pros | Cons | Best For |
|---|---|---|---|
| Custom Resources | Strong editor integration. Type-safe. High reusability. | Depends on Godot engine. | Managing structured game data like characters, items, skills. |
| JSON Files | Easy human read/write. Language and engine agnostic. | No type information. Requires separate parsing. | Config files, data exchange with external tools. |
| CSV Files | Easy editing in spreadsheet software. | Not suited for complex data structures. | Large amounts of simple records (e.g., dialogue collections). |
| SQLite Database | Efficient querying/manipulation of large data. | Setup somewhat complex. Requires addon. | Thousands of items or user data. |
Benefits of Custom Resources and Data-Driven Design
| Benefit | Description | Relation to Data-Driven Design |
|---|---|---|
| Data Separation | Game logic (GDScript) and data (.tres files) are completely separated, improving code readability and maintainability. | Data becomes independent from code, making changes easier. |
| Reusability | Once 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. |
| Instantiation | Unlike 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.