重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
godot-resource-data-patterns by thedivergentai/gd-agentic-skills
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-resource-data-patterns基于资源的设计、类型化数组和序列化定义了可复用的、便于检视器使用的数据结构。
为检视器定义序列化数据容器(物品、法术、状态)的模式。
享元模式的专家级示例,用于高效共享资源以节省内存。
处理“局部到场景”资源并使用 duplicate() 防止交叉污染。
反应式数据容器,当内部属性被修改时会发出信号。
将复杂游戏状态直接序列化到磁盘 .tres 文件的模式。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
使用序列化的资源数组管理物品集合和库存逻辑。
使用共享资源高效配置多个实体(生命值、皮肤、速度)。
在运行时通过编程方式创建和修改资源实例(战利品、程序化生成)。
通过在游戏开始前将资源缓存到字典中来防止帧率下降。
使用嵌套的资源属性构建和保存复杂的数据层次结构。
.duplicate(),更改一个值(如生命值)会修改磁盘上的 .tres 文件,影响所有使用者 [26]。@export var items: Array 可能导致逻辑错误。始终使用 Array[ResourceClass] 以确保类型安全 [27]。NodePath 或 UID [30]。ResourceSaver.save() 的错误检查 — 保存可能因权限、磁盘空间或路径问题而失败。始终检查返回码 [31]。_init 中的默认值 — 通过 new() 或在检视器中创建的资源需要在其构造函数中设置默认值才能被编辑 [15]。resource_local_to_scene = true [26]。.tres 用于海量数据集 — 如果你有 10,000 个物品,使用 JSON 或自定义二进制格式可能比单独的 .tres 文件更高效。| 类型 | 使用场景 | 可序列化 | 可保存到磁盘 | 检视器支持 |
|---|---|---|---|---|
Resource | 需要保存/加载的数据 | ✅ | ✅ | ✅ |
RefCounted | 临时运行时数据 | ❌ | ❌ | ❌ |
Node | 场景层次结构实体 | ✅(场景文件) | ✅ | ✅ |
为以下情况使用资源:
为以下情况使用 RefCounted:
# 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)
创建资源实例:
res://items/health_potion.tres# 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
用法:
# player.gd
extends CharacterBody2D
@export var stats: CharacterStats
func _ready() -> void:
if stats:
# 创建运行时副本以避免修改原始资源
stats = stats.duplicate_stats()
# 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
创建数据库:
ItemDatabase 资源items 数组ItemData 资源添加到数组中res://data/item_database.tres用法:
# 全局自动加载
const ITEM_DB := preload("res://data/item_database.tres")
func get_item(name: String) -> ItemData:
return ITEM_DB.get_item_by_name(name)
对于不需要持久化的数据:
# 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)
用法:
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)
# 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
# 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)
# 动态加载资源
var item: ItemData = load("res://items/sword.tres")
# 预加载以获得更好的性能(编译时)
const SWORD := preload("res://items/sword.tres")
# 加载目录中的所有资源
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
# ✅ 良好 - 创建实例副本
@export var stats: CharacterStats
func _ready():
stats = stats.duplicate() # 或自定义复制方法
# ❌ 糟糕 - 修改原始资源文件
@export var stats: CharacterStats
func _ready():
stats.current_health -= 10 # 这会改变 .tres 文件!
@export 以便在检视器中编辑# ✅ 使属性可在检视器中编辑
@export var max_health: int = 100
@export var icon: Texture2D
@export_range(0, 100) var drop_chance: int = 50
res://data/
items/
weapons/
sword.tres
bow.tres
consumables/
health_potion.tres
characters/
player_stats.tres
enemy_goblin.tres
databases/
item_database.tres
# ✅ 良好 - 类型化数组
@export var items: Array[ItemData] = []
# ❌ 糟糕 - 非类型化数组
@export var items: Array = []
# 将资源保存到磁盘
func save_inventory(inventory: Inventory, path: String) -> void:
ResourceSaver.save(inventory, path)
# 从磁盘加载资源
func load_inventory(path: String) -> Inventory:
if ResourceLoader.exists(path):
return ResourceLoader.load(path)
return null
每周安装数
65
代码仓库
GitHub 星标数
59
首次出现
2026年2月10日
安全审计
安装于
codex62
gemini-cli62
opencode62
github-copilot61
kimi-cli60
amp60
Resource-based design, typed arrays, and serialization define reusable, inspector-friendly data structures.
Pattern for defining serialized data containers (Items, Spells, Stats) for the Inspector.
Expert example of the Flyweight pattern for memory-efficient resource sharing.
Handling "Local to Scene" resources and duplicate() to prevent cross-contamination.
Reactive data containers that emit signals when internal properties are modified.
Pattern for serializing complex game state directly into .tres files on disk.
Managing item collections and inventory logic using serialized Resource arrays.
Using shared Resources to configure many entities efficiently (HP, Skins, Speed).
Creating and modifying Resource instances programmatically at runtime (Loot, Procedural).
Preventing frame drops by caching resources in a dictionary before gameplay starts.
Building and saving complex data hierarchies using nested Resource properties.
.duplicate(), changing a value (like HP) modifies the .tres file on disk for everyone [26].@export var items: Array allows logic errors. Always use Array[ResourceClass] for type safety [27].NodePath or UID [30].ResourceSaver.save() error checks — Saving can fail due to permissions, disk space, or path issues. Always check the return code [31].| 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) | ✅ | ✅ |
Use Resources For:
Use RefCounted For:
# 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:
res://items/health_potion.tres# 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()
# 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:
ItemDatabase resourceitems array in InspectorItemData resources to arrayres://data/item_database.tresUsage:
# 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)
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)
# 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
# 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)
# 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
# ✅ 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!
@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
res://data/
items/
weapons/
sword.tres
bow.tres
consumables/
health_potion.tres
characters/
player_stats.tres
enemy_goblin.tres
databases/
item_database.tres
# ✅ Good - typed array
@export var items: Array[ItemData] = []
# ❌ Bad - untyped array
@export var items: Array = []
# 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
Weekly Installs
65
Repository
GitHub Stars
59
First Seen
Feb 10, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex62
gemini-cli62
opencode62
github-copilot61
kimi-cli60
amp60
Perl 5.36+ 现代开发模式与最佳实践 | 构建健壮可维护应用程序指南
1,200 周安装
session-learner 智能代理技能 | AI会话学习与代码助手集成工具
120 周安装
Obsidian Agent Skill - 知识管理与笔记工具集成,提升AI助手工作效率
122 周安装
Turborepo 使用指南:JavaScript/TypeScript 单仓库构建系统与任务优化
101 周安装
Nuxt 4 服务器开发指南:基于 Nitro 的 API 路由、后端与全栈开发教程
105 周安装
Nuxt 4核心基础教程:项目设置、配置、路由与SEO优化指南
105 周安装
用户访谈脚本生成器 | 结构化定性研究工具 | 用户体验研究方法
169 周安装
_init defaults — Resources created via new() or in the Inspector need default values in their constructor to be editable [15].resource_local_to_scene = true in the Inspector for components [26]..tres for massive datasets — If you have 10,000 items, a JSON or custom binary format might be more efficient than individualized Resource files.