mcpField
R
resource-pattern

resource-pattern

github.com/jame581開発889181 回インストール4 日前に更新
リポジトリ

resource-pattern について

Godot 4.3以降の開発者向けデータ設計スキルです。Resourceで編集・再利用できる設定やゲームデータを扱う方法と、共有状態やインスタンスごとの複製を解説します。

主な機能

  1. 01カスタムResourceとInspector編集
  2. 02データ駆動型のゲーム設定
  3. 03Resourceコレクションと読み込み
  4. 04共有リソースとインスタンス複製の管理
  5. 05Resourceの保存とシリアライズ

活用シーン

  1. 01アイテム、敵、能力のデータ定義
  2. 02Inspectorでの設定編集
  3. 03シーン間でのデータリソース再利用
  4. 04型安全なゲームデータの保存

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jame581/godotprompter --skill resource-pattern

インストール後、Claude Code で/resource-patternを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
collections.md
configuration-pattern.md
editor-integration.md
saving-resources.md
sharing-vs-unique.md

詳細

Godot 4.3+ のリソースパターン

リソースは、Godot に組み込まれているデータコンテナです。シーンツリーの外部に存在する設定、アイテム定義、キャラクターのステータスなどのデータに使用します。すべての例は、非推奨 API を使用せず Godot 4.3+ を対象としています。

関連スキル: リソースベースのアイテム定義については inventory-system、リソースのシリアライズについては save-load、データ駆動型のコンポーネント設定については component-system、このパターンを基盤とするリソースベースのアビリティ定義については ability-system を参照してください。


1. リソースとは

Resource は、参照カウント方式のデータオブジェクトです。次の特徴があります。

  • .tres(テキスト)または .res(バイナリ)ファイルとしてディスクに保存されます
  • Godot のインスペクターで直接編集できます
  • 一度だけ読み込まれ、デフォルトでは共有されます — 同じパスを読み込むすべてのノードが、同じメモリ上のオブジェクトを取得します
  • 他のリソースや PackedScene の中にネストできます
  • シーン変更後も、何か(オートロードや static var など)が参照している間だけ存続します — RefCounted であるため、最後の参照がなくなると解放されます

リソースはデフォルトで共有されるため、読み取り専用データ(アイテム定義、オーディオ設定、アビリティの設計図など)に最適です。インスタンスごとに変更可能な状態を持たせるには、duplicate() を呼び出す(または resource_local_to_scene を設定する)必要があります。セクション 8 を参照してください。


2. リソースを使用する場面

用途リソースの例代替手段
アイテム定義名前、アイコン、価値を持つ ItemDataDictionary(型安全性が失われます)
敵の設定体力、速度、ダメージを持つ EnemyStatsNode のエクスポート変数(再利用できません)
キャラクターのステータス基本値を持つ CharacterStatsオートロード(グローバル状態となり、テストが困難です)
アビリティ定義クールダウン、コスト、効果を持つ AbilityDataハードコードされた定数
レベルメタデータ音楽、制限時間、目標を持つ LevelConfigJSON(エディターとの連携がありません)
オーディオ/ビジュアルのテーマカラーパレット、フォントを持つ UIThemeDataTheme リソース(同じ考え方で、組み込みのものです)
会話ツリー次の行を参照する DialogueLineJSON(型チェックがありません)

インスペクターでの編集+型付きデータ+シーン間での共有が必要な場合は、カスタムリソースを使用してください。


3. 基本的なカスタムリソース

GDScript
# item_data.gd
class_name ItemData
extends Resource

enum ItemType { WEAPON, ARMOUR, CONSUMABLE, QUEST }

@export var name:        String   = ""
@export var description: String   = ""
@export var icon:        Texture2D
@export var value:       int      = 0
@export var item_type:   ItemType = ItemType.CONSUMABLE

エディターでインスタンスを作成します。FileSystem パネルを右クリック → New Resource → ItemData を選択します。インスペクターのフィールドに入力し、res://data/items/health_potion.tres として保存します。

実行時に読み込みます。

var potion: ItemData = load("res://data/items/health_potion.tres")
print(potion.name)        # "Health Potion"
print(potion.value)       # 50
C#
// ItemData.cs
using Godot;

[GlobalClass]
public partial class ItemData : Resource
{
    public enum ItemType { Weapon, Armour, Consumable, Quest }

    [Export] public string   Name        { get; set; } = "";
    [Export] public string   Description { get; set; } = "";
    [Export] public Texture2D Icon       { get; set; }
    [Export] public int      Value       { get; set; } = 0;
    [Export] public ItemType Type        { get; set; } = ItemType.Consumable;
}

エディターがクラスを認識し、New Resource に表示するには、C# では [GlobalClass] が必要です。

var potion = GD.Load<ItemData>("res://data/items/health_potion.tres");
GD.Print(potion.Name);   // "Health Potion"
GD.Print(potion.Value);  // 50

4. エディターとの連携

class_name と @icon を使用すると、カスタムリソースをインスペクターで第一級の要素として扱えます(セッターをエディターで実行する必要がある場合のみ @tool を追加します)。リソースピッカーに表示され、右クリックして「New Resource」から作成でき、カスタムアイコンも表示されます。@export_group と @export_subgroup でプロパティを整理できます。

class_name、@icon、@export_group を使った GDScript + C# の完全なパターンについては、references/editor-integration.md を参照してください。


5. 設定としてのリソース

最も効果を発揮する用途は、データ駆動型のゲームコンテンツです。戦利品テーブル、敵のステータス、アビリティ定義、アイテムカタログをすべてカスタムリソースにできます。デザイナーはインスペクターで .tres ファイルを調整し、プログラマーはローダーを組み込みます。JSON の緩いスキーマや、文字列に依存したパースを避けられます。

LootTable + DropEntry の具体例(GDScript + C#)については、references/configuration-pattern.md を参照してください。


6. リソースのコレクション

@export var entries: Array[Entry] = [] を使うと、型付き配列をインスペクターに表示できます。複数のリソースファイルをドラッグ&ドロップできます。起動時に読み込むセットには ResourcePreloader を使用します。実行時にアセットフォルダーを検索するには、DirAccess を使って走査します。

型付き配列のエクスポート(v1.6.0 の C# 対応を維持)、ResourcePreloader の設定、ディレクトリー走査によるローダーパターンについては、references/collections.md を参照してください。


7. リソースとノード

項目リソースノード
目的データの保存と設定動作、レンダリング、物理演算、入力
シーンツリーツリーには含まれませんシーンツリー内に存在します
ライフサイクルフックツリーのコールバックはありません(_init、_setup_local_to_scene)_ready、_process、_physics_process など
共有デフォルトで共有されます(同じパスなら同じオブジェクト)各インスタンスは独立しています
シリアライズ.tres / .res として保存され、インスペクターで編集できます.tscn 内に保存されます
シグナル対応しています対応しています
用途アイテムデータ、ステータス、設定、アビリティの設計図プレイヤー、敵、UI ウィジェット、カメラ
避ける用途フレームごとの更新やシーンのクエリが必要なもの実行時に変化しない静的データ

目安: 動作がなく、シーンツリー内に存在する必要もない場合は、リソースにします。移動、レンダリング、入力の受け取り、フレームごとのロジック実行が必要な場合は、ノードにします。


8. 共有と固有

デフォルトでは、リソースは参照によって共有されます。res://items/sword.tres を参照する2つのシーンは、同じインスタンスを参照します。一方を変更すると、もう一方にも反映されます。インスタンスごとの状態には、.duplicate()(浅い複製)または .duplicate(true)(深い複製。バージョン固有の制限についてはリファレンスを参照)を使用してください。

パターンの全容については、references/sharing-vs-unique.md を参照してください(v1.6.0 の C# との同等性を維持しています)。


9. カスタムリソースの保存

ResourceSaver.save(resource, path) は .tres(テキスト)または .res(バイナリ)ファイルに書き込みます。スキーマがリソースサブクラスとしてコード内にあるセーブデータには、強い型付けを使いたい場合、手作業で作成した JSON の代わりに使用してください。

保存と読み込みのパターンの全容(GDScript + C#)とセキュリティ上の注意(埋め込み GDScript が実行されるため、信頼できないソースから .tres を読み込まないでください)については、references/saving-resources.md を参照してください。


10. アンチパターン

duplicate() を使わない変更可能な共有リソース — 意図しない状態の共有
# BAD — all enemies share the same EnemyStats object.
# Damaging one enemy damages all of them.
class_name Enemy
extends CharacterBody2D

@export var stats: EnemyStats  # loaded from .tres, shared

func take_damage(amount: int) -> void:
    stats.health -= amount  # mutates the shared Resource!
# GOOD — each enemy owns its own copy.
func _ready() -> void:
    stats = stats.duplicate()  # now safe to mutate
リソース内のゲームロジック
# BAD — Resources have no scene tree access, no _process, no signals from nodes.
class_name EnemyStats
extends Resource

func update_health_regen(delta: float) -> void:
    # Can't call get_tree(), can't read Input, can't access nodes.
    # This logic belongs in a Node.
    health = min(health + regen_rate * delta, max_health)
# GOOD — keep logic in Nodes, data in Resources.
# enemy.gd
func _process(delta: float) -> void:
    _current_health = minf(_current_health + stats.regen_rate * delta, stats.max_health)
巨大なモノリシックリソース
# BAD — one Resource holds everything; impossible to reuse parts.
class_name GameConfig
extends Resource

@export var player_health: int
@export var player_speed: float
@export var enemy_goblin_health: int
@export var enemy_goblin_speed: float
@export var enemy_troll_health: int
# ... 200 more properties
# GOOD — small focused Resources, composed together.
class_name PlayerConfig
extends Resource

@export var health: int   = 100
@export var speed:  float = 200.0
class_name EnemyConfig
extends Resource

@export var health: int   = 50
@export var speed:  float = 80.0
// ❌ Anti-pattern: mutating a shared Resource — accidental shared state
// All enemies share the same EnemyStats object loaded from the .tres file.
// Damaging one enemy damages all of them.
[GlobalClass]
public partial class Enemy : CharacterBody2D
{
    [Export] public EnemyStats Stats;  // loaded from .tres, shared by default

    public void TakeDamage(int amount)
    {
        Stats.Health -= amount;  // mutates the shared Resource — affects every Enemy!
    }
}

// ✅ Correct: duplicate before mutating so each instance owns its own copy.
public partial class EnemyGood : CharacterBody2D
{
    [Export] public EnemyStats Stats;

    public override void _Ready()
    {
        Stats = (EnemyStats)Stats.Duplicate();  // now safe to mutate independently
    }

    public void TakeDamage(int amount)
    {
        Stats.Health -= amount;  // only affects this instance
    }
}

// ❌ Anti-pattern: game logic inside a Resource
// Resources have no scene-tree access, no _Process, and cannot call GetTree() or read Input.
[GlobalClass]
public partial class EnemyStatsBad : Resource
{
    [Export] public float Health    { get; set; }
    [Export] public float MaxHealth { get; set; }
    [Export] public float RegenRate { get; set; }

    // This logic belongs in a Node, not a Resource.
    public void UpdateHealthRegen(double delta)
    {
        // Cannot call GetTree(), cannot access nodes, cannot read Input.
        Health = Mathf.Min(Health + RegenRate * (float)delta, MaxHealth);
    }
}

// ✅ Correct: keep logic in Nodes, data in Resources.
public partial class EnemyCorrect : CharacterBody2D
{
    [Export] public EnemyStats Stats;
    private float _currentHealth;

    public override void _Ready()
    {
        Stats = (EnemyStats)Stats.Duplicate();
        _currentHealth = Stats.MaxHealth;
    }

    public override void _Process(double delta)
    {
        _currentHealth = Mathf.Min(_currentHealth + Stats.RegenRate * (float)delta, Stats.MaxHealth);
    }
}

// ❌ Anti-pattern: giant monolithic Resource
// One Resource holds everything; impossible to reuse individual pieces.
[GlobalClass]
public partial class GameConfigBad : Resource
{
    [Export] public int   PlayerHealth      { get; set; }
    [Export] public float PlayerSpeed       { get; set; }
    [Export] public int   EnemyGoblinHealth { get; set; }
    [Export] public float EnemyGoblinSpeed  { get; set; }
    [Export] public int   EnemyTrollHealth  { get; set; }
    // ... 200 more properties
}

// ✅ Correct: small focused Resources, composed together.
[GlobalClass]
public partial class PlayerConfig : Resource
{
    [Export] public int   Health { get; set; } = 100;
    [Export] public float Speed  { get; set; } = 200.0f;
}

[GlobalClass]
public partial class EnemyConfig : Resource
{
    [Export] public int   Health { get; set; } = 50;
    [Export] public float Speed  { get; set; } = 80.0f;
}

11. チェックリスト

  • リソースのサブクラスに class_name を指定し、エディターの 新規リソース から見つけられるようにする
  • C# クラスに [GlobalClass] 属性を付ける
  • インスペクターで編集可能なフィールドすべてに @export / [Export] を使用する
  • デザイナーが調整可能な範囲を設ける数値フィールドに @export_range を使用する
  • 多数のフィールドを持つリソースでは、インスペクターのレイアウトを整理するために @export_group と @export_category を使用する
  • インスタンスごとに変更するリソースを _ready() で duplicate() する
  • 読み取り専用の共有リソース(定義、設計図)は複製しない
  • ゲームロジック(フレームごとの更新、シーンのクエリ)はリソースではなくノードに置く
  • 大規模なデータセットを、単一の責務に特化したリソースに分割する
  • 開発中は .tres を使用し、リリース用の本番データには .res の使用を検討する
  • ResourceSaver.save() の戻り値を確認し、エラーを push_error() で報告する
  • 信頼できない外部ソースから .tres / .res ファイルを読み込まない

コメント