Resource & Data Patterns
Resource与数据模式
Resource-based design, typed arrays, and serialization define reusable, inspector-friendly data structures.
基于Resource的设计、类型化数组和序列化可构建可复用、便于在Inspector中编辑的数据结构。
custom_data_resource.gd
custom_data_resource.gd
Pattern for defining serialized data containers (Items, Spells, Stats) for the Inspector.
用于为Inspector定义序列化数据容器(物品、法术、属性)的模式。
resource_flyweight_caching.gd
resource_flyweight_caching.gd
Expert example of the Flyweight pattern for memory-efficient resource sharing.
resource_local_to_scene.gd
resource_local_to_scene.gd
Handling "Local to Scene" resources and
to prevent cross-contamination.
处理“Local to Scene”资源及使用
避免资源交叉污染的方法。
character_stats_resource.gd
character_stats_resource.gd
Reactive data containers that emit signals when internal properties are modified.
resource_save_system.gd
resource_save_system.gd
Pattern for serializing complex game state directly into
files on disk.
resource_based_inventory.gd
resource_based_inventory.gd
Managing item collections and inventory logic using serialized Resource arrays.
使用序列化Resource数组管理物品集合与物品栏逻辑的方案。
flyweight_enemy_config.gd
flyweight_enemy_config.gd
Using shared Resources to configure many entities efficiently (HP, Skins, Speed).
使用共享Resource高效配置大量实体(生命值、外观、速度)的实践。
dynamic_resource_generation.gd
dynamic_resource_generation.gd
Creating and modifying Resource instances programmatically at runtime (Loot, Procedural).
在运行时以编程方式创建和修改Resource实例(如战利品、 procedural生成)。
resource_preloading_strategy.gd
resource_preloading_strategy.gd
Preventing frame drops by caching resources in a dictionary before gameplay starts.
在游戏开始前将资源缓存到字典中,避免帧率下降的策略。
nested_resource_serialization.gd
nested_resource_serialization.gd
Building and saving complex data hierarchies using nested Resource properties.
使用嵌套Resource属性构建和保存复杂数据层级的方法。
NEVER Do in Resource Design
Resource设计中的禁忌
- NEVER modify resource instances directly — Without , changing a value (like HP) modifies the file on disk for everyone [26].
- NEVER use untyped arrays in Resources — allows logic errors. Always use 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 or [30].
- NEVER perform heavy calculations in Resource getters/setters — Resources should be data containers. Offload logic to Nodes or specialized RefCounted classes.
- NEVER skip 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 defaults — Resources created via 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 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 |
|---|
| Data that needs saving/loading | ✅ | ✅ | ✅ |
| Temporary runtime data | ❌ | ❌ | ❌ |
| Scene hierarchy entities | ✅ (scene files) | ✅ | ✅ |
- 切勿直接修改Resource实例 — 不使用的话,修改数值(如HP)会改变磁盘上的文件,影响所有使用该资源的地方 [26]。
- 切勿在Resource中使用非类型化数组 — 容易引发逻辑错误。始终使用保证类型安全 [27]。
- 切勿在Resource中存储Node引用 — 仅存在于特定SceneTree中的对象(如玩家/投射物)无法被序列化。应存储或 [30]。
- 切勿在Resource的getter/setter中执行大量计算 — Resource应仅作为数据容器。将逻辑处理转移到Node或专门的RefCounted类中。
- 切勿跳过的错误检查 — 保存操作可能因权限、磁盘空间或路径问题失败。务必检查返回码 [31]。
- 切勿将Resource用于高频运行时数据 — 如果某个值每秒变化60次(如速度),使用标准变量比Resource属性更快。
- 切勿创建循环Resource引用 — 如果A.tres引用B.tres且B.tres引用A.tres,引擎可能在加载时崩溃。
- 切勿忘记默认值 — 通过或在Inspector中创建的Resource需要在构造函数中设置默认值才能被编辑 [15]。
- 若实体需要唯一状态,切勿在它们之间共享Resource — 对于组件,在Inspector中设置
resource_local_to_scene = true
[26]。
- 切勿用存储超大规模数据集 — 若你有10000个物品,JSON或自定义二进制格式可能比单个Resource文件更高效。
| 类型 | 使用场景 | 可序列化 | 可保存到磁盘 | Inspector支持 |
|---|
| 需要保存/加载的数据 | ✅ | ✅ | ✅ |
| 临时运行时数据 | ❌ | ❌ | ❌ |
| 场景层级实体 | ✅(场景文件) | ✅ | ✅ |
When to Use Resources
何时使用Resource
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
Resource适用于:
- 物品定义(武器、消耗品、装备)
- 角色属性/成长系统
- 技能/能力数据
- 配置文件
- 对话数据库
- 敌人/NPC模板
RefCounted适用于:
Implementation Patterns
实现模式
Pattern 1: Custom Resource Class
模式1:自定义Resource类
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`
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)
**创建Resource实例:**
1. 在Inspector中:**右键 → New Resource → ItemData**
2. 填写属性,**保存**为`res://items/health_potion.tres`
Pattern 2: Character Stats Resource
模式2:角色属性Resource
character_stats.gd
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
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
extends CharacterBody2D
@export var stats: CharacterStats
func _ready() -> void:
if stats:
# Create runtime copy to avoid modifying the original resource
stats = stats.duplicate_stats()
extends CharacterBody2D
@export var stats: CharacterStats
func _ready() -> void:
if stats:
# 创建运行时副本,避免修改原始资源
stats = stats.duplicate_stats()
Pattern 3: Database Pattern (Array of Resources)
模式3:数据库模式(Resource数组)
item_database.gd
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
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
**创建数据库:**
1. 创建`ItemDatabase`资源
2. 在Inspector中展开`items`数组
3. 向数组中添加`ItemData`资源
4. 保存为`res://data/item_database.tres`
**使用示例:**
```gdscript
const ITEM_DB := preload("res://data/item_database.tres")
func get_item(name: String) -> ItemData:
return ITEM_DB.get_item_by_name(name)
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)
模式4:仅运行时数据(RefCounted)
For data that doesn't need persistence:
damage_calculation.gd
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)
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)
**使用示例:**
```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)
Pattern 5: Nested Resources
模式5:嵌套Resource
weapon_data.gd
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] = []
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
status_effect.gd
extends Resource
class_name StatusEffect
@export var effect_name: String
@export var duration: float
@export var damage_per_second: int
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
模式6:带信号的Resource脚本
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)
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
模式7:运行时加载Resource
Load resource dynamically
动态加载资源
var item: ItemData = load("res://items/sword.tres")
var item: ItemData = load("res://items/sword.tres")
Preload for better performance (compile-time)
预加载以提升性能(编译时加载)
const SWORD := preload("res://items/sword.tres")
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
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
1. Always Duplicate Resources in Runtime
1. 运行时始终复制Resource
✅ Good - create instance copy
✅ 良好实践 - 创建实例副本
@export var stats: CharacterStats
func _ready():
stats = stats.duplicate() # Or custom duplicate method
@export var stats: CharacterStats
func _ready():
stats = stats.duplicate() # 或自定义复制方法
❌ Bad - modifies the original resource file
❌ 不良实践 - 修改原始资源文件
@export var stats: CharacterStats
func _ready():
stats.current_health -= 10 # This changes the .tres file!
@export var stats: CharacterStats
func _ready():
stats.current_health -= 10 # 这会修改.tres文件!
2. Use for Inspector Editing
✅ Makes properties editable in Inspector
✅ 使属性可在Inspector中编辑
@export var max_health: int = 100
@export var icon: Texture2D
@export_range(0, 100) var drop_chance: int = 50
@export var max_health: int = 100
@export var icon: Texture2D
@export_range(0, 100) var drop_chance: int = 50
3. Organize Resources by Category
3. 按类别组织Resource
res://data/
items/
weapons/
sword.tres
bow.tres
consumables/
health_potion.tres
characters/
player_stats.tres
enemy_goblin.tres
databases/
item_database.tres
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
4. 为数组指定类型
✅ Good - typed array
✅ 良好实践 - 类型化数组
@export var items: Array[ItemData] = []
@export var items: Array[ItemData] = []
❌ Bad - untyped array
❌ 不良实践 - 非类型化数组
@export var items: Array = []
@export var items: Array = []
Saving/Loading Resources
保存/加载Resource
Save resource to disk
将资源保存到磁盘
func save_inventory(inventory: Inventory, path: String) -> void:
ResourceSaver.save(inventory, path)
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
func load_inventory(path: String) -> Inventory:
if ResourceLoader.exists(path):
return ResourceLoader.load(path)
return null
- Master Skill: godot-master