godot-resource-data-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Resource & Data Patterns

Resource与数据模式

Resource-based design, typed arrays, and serialization define reusable, inspector-friendly data structures.
基于Resource的设计、类型化数组和序列化可构建可复用、便于在Inspector中编辑的数据结构。

Available Scripts

可用脚本

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
duplicate()
to prevent cross-contamination.
处理“Local to Scene”资源及使用
duplicate()
避免资源交叉污染的方法。

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
.tres
files on disk.
将复杂游戏状态直接序列化为磁盘上
.tres
文件的模式。

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
    .duplicate()
    , changing a value (like HP) modifies the
    .tres
    file on disk for everyone [26].
  • NEVER use untyped arrays in Resources
    @export var items: Array
    allows logic errors. Always use
    Array[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
    NodePath
    or
    UID
    [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 = true
    in 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.

TypeUse CaseSerializableCan Save to DiskInspector Support
Resource
Data that needs saving/loading
RefCounted
Temporary runtime data
Node
Scene hierarchy entities✅ (scene files)
  • 切勿直接修改Resource实例 — 不使用
    .duplicate()
    的话,修改数值(如HP)会改变磁盘上的
    .tres
    文件,影响所有使用该资源的地方 [26]。
  • 切勿在Resource中使用非类型化数组
    @export var items: Array
    容易引发逻辑错误。始终使用
    Array[ResourceClass]
    保证类型安全 [27]。
  • 切勿在Resource中存储Node引用 — 仅存在于特定SceneTree中的对象(如玩家/投射物)无法被序列化。应存储
    NodePath
    UID
    [30]。
  • 切勿在Resource的getter/setter中执行大量计算 — Resource应仅作为数据容器。将逻辑处理转移到Node或专门的RefCounted类中。
  • 切勿跳过
    ResourceSaver.save()
    的错误检查
    — 保存操作可能因权限、磁盘空间或路径问题失败。务必检查返回码 [31]。
  • 切勿将Resource用于高频运行时数据 — 如果某个值每秒变化60次(如速度),使用标准变量比Resource属性更快。
  • 切勿创建循环Resource引用 — 如果A.tres引用B.tres且B.tres引用A.tres,引擎可能在加载时崩溃。
  • 切勿忘记
    _init
    默认值
    — 通过
    new()
    或在Inspector中创建的Resource需要在构造函数中设置默认值才能被编辑 [15]。
  • 若实体需要唯一状态,切勿在它们之间共享Resource — 对于组件,在Inspector中设置
    resource_local_to_scene = true
    [26]。
  • 切勿用
    .tres
    存储超大规模数据集
    — 若你有10000个物品,JSON或自定义二进制格式可能比单个Resource文件更高效。

类型使用场景可序列化可保存到磁盘Inspector支持
Resource
需要保存/加载的数据
RefCounted
临时运行时数据
Node
场景层级实体✅(场景文件)

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类

gdscript
undefined
gdscript
undefined

item_data.gd

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

gdscript
undefined
gdscript
undefined

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

**Usage:**
```gdscript
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

**使用示例:**
```gdscript

player.gd

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()
undefined
extends CharacterBody2D
@export var stats: CharacterStats
func _ready() -> void: if stats: # 创建运行时副本,避免修改原始资源 stats = stats.duplicate_stats()
undefined

Pattern 3: Database Pattern (Array of Resources)

模式3:数据库模式(Resource数组)

gdscript
undefined
gdscript
undefined

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

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)
undefined
const ITEM_DB := preload("res://data/item_database.tres")
func get_item(name: String) -> ItemData: return ITEM_DB.get_item_by_name(name)
undefined

Pattern 4: Runtime-Only Data (RefCounted)

模式4:仅运行时数据(RefCounted)

For data that doesn't need persistence:
gdscript
undefined
适用于无需持久化的数据:
gdscript
undefined

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)

Advanced Patterns

进阶模式

Pattern 5: Nested Resources

模式5:嵌套Resource

gdscript
undefined
gdscript
undefined

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
undefined
extends Resource class_name StatusEffect
@export var effect_name: String @export var duration: float @export var damage_per_second: int
undefined

Pattern 6: Resource Scripts with Signals

模式6:带信号的Resource脚本

gdscript
undefined
gdscript
undefined

inventory.gd

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)
undefined
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)
undefined

Pattern 7: Resource Loading at Runtime

模式7:运行时加载Resource

gdscript
undefined
gdscript
undefined

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

Best Practices

最佳实践

1. Always Duplicate Resources in Runtime

1. 运行时始终复制Resource

gdscript
undefined
gdscript
undefined

✅ 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!
undefined
@export var stats: CharacterStats func _ready(): stats.current_health -= 10 # 这会修改.tres文件!
undefined

2. Use
@export
for Inspector Editing

2. 使用
@export
支持Inspector编辑

gdscript
undefined
gdscript
undefined

✅ 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
undefined
@export var max_health: int = 100 @export var icon: Texture2D @export_range(0, 100) var drop_chance: int = 50
undefined

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. 为数组指定类型

gdscript
undefined
gdscript
undefined

✅ Good - typed array

✅ 良好实践 - 类型化数组

@export var items: Array[ItemData] = []
@export var items: Array[ItemData] = []

❌ Bad - untyped array

❌ 不良实践 - 非类型化数组

@export var items: Array = []
undefined
@export var items: Array = []
undefined

Saving/Loading Resources

保存/加载Resource

gdscript
undefined
gdscript
undefined

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
undefined
func load_inventory(path: String) -> Inventory: if ResourceLoader.exists(path): return ResourceLoader.load(path) return null
undefined

Reference

参考资料

Related

相关技能

  • Master Skill: godot-master
  • 大师技能:godot-master