リソース

ノードとリソース

ここまでのチュートリアルではGodotの、エンジンのほとんどの機能が頼りにしている、Node(ノード)クラスについて主に紹介してきました。もうひとつ、同じくらい重要なデータ型はResource(リソース)です。

Node は、機能を提供します。例えばスプライトや3Dモデルの表示、物理演算、ユーザーインターフェースの配置などです。一方、Resourceデータの格納に使います。それ自身はなにもしませんが、代わりにノードが、データの入ったリソースを使用します。

Godotが保存したり、ディスクから読み込んだりするものは、すべてリソースです。これは、シーン(.tscnや.scnファイル)や、画像、スクリプトなどが該当します。リソースの例は、TextureScriptMeshAnimationAudioStreamFontTranslationなどです。

エンジンがリソースをディスクから読み込む際に、読み込むのは一度きりです。もしメモリ上にすでにリソースのコピーがあれば、読み込む代わりに毎回そのコピーを返します。リソースはデータのみを含むので、重複させても意味はないのです。

ノードとリソースにかかわらず、すべてのオブジェクトは、自身のプロパティをエクスポートできます。プロパティには、String、整数(int)、Vector2など、さまざまな種類がありますが、これらすべての型はリソースになることができます。つまり、ノードとリソースはいずれも、他のリソースをプロパティとして持てるのです。

../../_images/nodes_resources.png

外部と組み込みの比較

リソースを保存する方法はふたつあります。それは:

  1. シーンの外部。個別のファイルとしてディスクに保存。
  2. 内蔵。使用する*.tscnあるいは*.scnファイルの中に保存。

具体的には、スプライトノードの テクスチャ を以下に示します:

../../_images/spriteprop.png

リソースのプレビューをクリックすると、リソースのプロパティを表示して編集できるようになります。

../../_images/resourcerobi.png

Pathプロパティはリソースの元々の場所を表しています。この例の場合は、元は robi.png と名付けられたPNG画像です。このように、元がファイルであれば、外部リソースになります。Pathを消去するか、あるいはこの場所にない場合は、内部リソースになります。

リソースの内部と外部の切り替えは、シーンを保存するときに行われます。上記の例の場合、もしパス "res://robi.png" を消去してから保存すると、Godotは画像を .tscn シーンファイルの内部に保存します。

注釈

もし内部リソースを保存した場合でも、複数のシーンをインスタンス化したときは、エンジンがそのコピーを読み込むのは一度だけです。

コードからリソースを読み込む

コードにてリソースを読み込む方法は、ふたつあります。ひとつ目は load() 関数で、いつでも使えます:

func _ready():
        var res = load("res://robi.png") # Godot loads the Resource when it reads the line.
        get_node("sprite").texture = res
public override void _Ready()
{
    var texture = (Texture)GD.Load("res://robi.png"); // Godot loads the Resource when it reads the line.
    var sprite = (Sprite)GetNode("sprite");
    sprite.Texture = texture;
}

もうひとつは preload() です。この関数は``load()``とは違い、コンパイル時にファイルをディスクから読み込んでロードします。そのため、パスが変数の場合はpreloadは呼べません。パスは定数の文字列である必要があります。

func _ready():
        var res = preload("res://robi.png") # Godot loads the resource at compile-time
        get_node("sprite").texture = res
// 'preload()' is unavailable in C Sharp.

シーンの読み込み

シーンもまたリソースですが、しかし注意点があります。シーンは、PackedScene型のリソースとしてディスクに保存されます。リソースの内部にシーンが入れ込まれるのです。

シーンのインスタンスを得るには、PackedScene.instance() class_PackedScene_method_instance>メソッドを使う必要があります。

func _on_shoot():
        var bullet = preload("res://bullet.tscn").instance()
        add_child(bullet)
private PackedScene _bulletScene = (PackedScene)GD.Load("res://bullet.tscn");

public void OnShoot()
{
    Node bullet = _bulletScene.Instance();
    AddChild(bullet);
}

このメソッドは、シーンの階層どおりにノードを作成し、それらを設定してから、そのシーンのルートノードを返します。これは他のノードの子にすることができます。

この方法には、いくつかの利点があります。PackedScene.instance()メソッドは高速なので、新しい敵、弾丸、エフェクトなどを、ディスクから再び読み出すことなく作成できます。覚えていただきたいのは、ほとんどの場合、画像やメッシュなどは全て、シーンのインスタンス間で共有されます。

リソースの解放

リソース(Resource) が使われなくなったときは、自動的に解放されます。ほとんどの場合、リソースはノードに格納されているので、ノードを解放した際には、それが持つリソースも同様に、他のノードに使用されていない限り、すべてエンジンによって解放されます。

独自のリソースを作成

Godotの他のオブジェクトと同様に、ユーザーはリソースをスクリプト化することもできます。リソーススクリプトは、オブジェクトプロパティとシリアル化されたテキストまたはバイナリデータ(/.tres,/.res)の間を自由に変換する機能を継承します。また、参照タイプから参照カウントメモリ管理を継承します。

これには、JSON、CSV、カスタムTXTファイルなどの代替となるデータ構造を超える多くの明確な利点があります。これらのアセットは ユーザーがインポートするときに Dictionary (JSON) または File としてのみ解析できます。それに対してリソースはObjectReference、およびResource機能の継承によって、際立った利便性があります。

  • 定数を定義できるため、他のデータフィールドまたはオブジェクトの定数は必要ありません。
  • プロパティの設定(setter)と取得(getter )用のメソッドを含むメソッドを定義できます。これにより、基になるデータの抽象化とカプセル化が可能になります。(カプセル化する事によって)リソーススクリプトの構造を変更する必要が発生した場合でも、そのリソースを使用するゲームをその都度それに合わせて変更する必要が無くなります。
  • シグナルを定義できるため、リソースは管理するデータの変更に対する応答をトリガーできます。
  • プロパティが定義されているため、ユーザーは自分のデータが存在することを100%知っています。
  • リソースの自動シリアル化と逆シリアル化は、 Godot エンジンの組み込み機能です。ユーザーは、リソース ファイルのデータをインポート/エクスポートするためにカスタム ロジックを実装する必要はありません。
  • リソースはサブリソースを再帰的にシリアル化することもできるため、ユーザーはさらに高度なデータ構造を設計できます。
  • ユーザーは、リソースをバージョン管理に適したテキストファイル(*.tres)として保存できます。ゲームをエクスポートすると、Godotはリソースファイルをバイナリファイル(*.res)としてシリアル化し、読書きの速度と圧縮率を向上させます。
  • Godotエンジンのインスペクターは、すぐに使用できるリソースファイルをレンダリングおよび編集します。そのため、多くの場合、ユーザーはデータを視覚化または編集するためにカスタムロジックを自分で実装する必要はありません。リソースに関するこの作業を行うには、ファイルシステムドックでリソースファイルをダブルクリックするか、インスペクターのフォルダアイコンをクリックして、ダイアログでファイルを開きます。
  • 基本リソースだけでなく、その他のリソースタイプも拡張できます。

警告

リソースと辞書(ディクショナリ)はどちらも参照によって渡されますが、参照カウントされるのはリソースだけです。つまり、オブジェクト間で辞書が渡され、最初のオブジェクトが削除されると、辞書への他のすべてのオブジェクトの参照は無効になります。逆に、関連するオブジェクトが全て削除されるまで、リソースはメモリから解放されません。

extends Node

class MyObject:
    extends Object
    var dict = {}

func _ready():
    var obj1 = MyObject.new()
    var obj2 = MyObject.new()
    obj1.dict.greeting = "hello"
    obj2.dict = obj1.dict             # 'obj2.dict' now references 'obj1's Dictionary.
    obj1.free()                       # 'obj1' is freed and the Dictionary too!
    print(obj2.dict.greeting)         # Error! 'greeting' index accessed on null instance!

    # To avoid this, we must manually duplicate the Dictionary.
    obj1 = MyObject.new()
    obj1.dict.greeting = "hello"
    obj2.dict = obj1.dict.duplicate() # Now we are passing a copy, not a reference.
    obj1.free()                       # obj2's Dictionary still exists.
    print(obj2.dict.greeting)         # Prints 'hello'.

Godotを使用すると、インスペクタでカスタムリソースを簡単に作成できます。

  1. インスペクタでプレーンなリソースオブジェクトを作成します。これは、スクリプトがその型を拡張可能なら、新たなリソースを派生させる原型にもできます。
  2. インスペクタで script プロパティをスクリプトに設定します。

インスペクタにリソーススクリプトのカスタムプロパティが表示されます。これらの値を編集してリソースを保存すると、インスペクタはカスタム プロパティもシリアル化します。インスペクタからリソースを保存するには、インスペクタのツールメニュー(右上)をクリックし、「保存」または「名前を付けて保存」を選択します。

スクリプトの言語が script classesをサポートしている場合は、この手順が合理化されます。スクリプトの名前を定義するだけで、その名前がインスペクタの作成ダイアログに追加されます。これにより、作成したリソースオブジェクトにスクリプトが自動的に追加されます。

いくつかの例を見てみましょう。

# bot_stats.gd
extends Resource
export(int) var health
export(Resource) var sub_resource
export(Array, String) var strings

func _init(p_health = 0, p_sub_resource = null, p_strings = []):
    health = p_health
    sub_resource = p_sub_resource
    strings = p_strings

# bot.gd
extends KinematicBody

export(Resource) var stats

func _ready():
    # Uses an implicit, duck-typed interface for any 'health'-compatible resources.
    if stats:
        print(stats.health) # Prints '10'.
// BotStats.cs
using System;
using Godot;

namespace ExampleProject {
    public class BotStats : Resource
    {
        [Export]
        public int Health { get; set; }

        [Export]
        public Resource SubResource { get; set; }

        [Export]
        public String[] Strings { get; set; }

        public BotStats(int health = 0, Resource subResource = null, String[] strings = null)
        {
            Health = health;
            SubResource = subResource;
            Strings = strings ?? new String[0];
        }
    }
}

// Bot.cs
using System;
using Godot;

namespace ExampleProject {
    public class Bot : KinematicBody
    {
        [Export]
        public Resource Stats;

        public override void _Ready()
        {
            if (Stats != null && Stats is BotStats botStats) {
                GD.Print(botStats.Health); // Prints '10'.
            }
        }
    }
}

注釈

リソーススクリプトは、UnityのScriptableObjectsに似ています。インスペクタは、カスタムリソースの組み込みサポートを提供します。ですが、必要に応じて、ユーザーは独自のコントロールベースのツールスクリプトを設計し、それらを EditorPlugin と組み合わせて、データ用のカスタムビジュアライゼーションとエディタを作成することもできます。

Unreal Engine 4のDataTablesとCurveTablesも、リソーススクリプトを使用して簡単に再現できます。 DataTablesは、カスタム構造体にマッピングされた文字列であり、文字列をセカンダリカスタムリソーススクリプトにマッピングする辞書に似ています。

# bot_stats_table.gd
extends Resource

const BotStats = preload("bot_stats.gd")

var data = {
    "GodotBot": BotStats.new(10), # Creates instance with 10 health.
    "DifferentBot": BotStats.new(20) # A different one with 20 health.
}

func _init():
    print(data)
using System;
using Godot;

public class BotStatsTable : Resource
{
    private Godot.Dictionary<String, BotStats> _stats = new Godot.Dictionary<String, BotStats>();

    public BotStatsTable()
    {
        _stats["GodotBot"] = new BotStats(10); // Creates instance with 10 health.
        _stats["DifferentBot"] = new BotStats(20); // A different one with 20 health.
        GD.Print(_stats);
    }
}

辞書の値を単にインライン化する代わりに...

  1. スプレッドシートから値の表をインポートして、これらのキーと値のペアを生成する、または...
  2. エディタ内でビジュアライゼーションを設計し、これらのタイプのリソースを開いたときにインスペクタに追加する単純なプラグインを作成します。

CurveTablesも同じですが、float値の配列または Curve/Curve2D リソースオブジェクトにマッピングされます。

警告

リソース ファイル (*.tres/*.res) は、使用するスクリプトのパスをファイルに格納します。リソース ファイルが読み込まれると、そのスクリプトをフェッチして型の拡張機能として読み込みます。スクリプトでサブクラス、つまりスクリプトの内部クラス (GDScript で class キーワードを使用するなど) を割り当てようとしても、それは機能しません。Godot は、スクリプト サブクラスのカスタム プロパティを正しくシリアル化しません。

以下の例では、Godotは Node スクリプトを読み込み、それが Resource を拡張していないことを確認して、互換性がないタイプなので、スクリプトがリソースオブジェクトのロードに失敗したと判断します。

extends Node

class MyResource:
    extends Resource
    export var value = 5

func _ready():
    var my_res = MyResource.new()

    # This will NOT serialize the 'value' property.
    ResourceSaver.save("res://my_res.tres", my_res)
using Godot;

public class MyNode : Node
{
    public class MyResource : Resource
    {
        [Export]
        public int Value { get; set; } = 5;
    }

    public override void _Ready()
    {
        var res = new MyResource();

        // This will NOT serialize the 'Value' property.
        ResourceSaver.Save("res://MyRes.tres", res);
    }
}