リソース
ノードとリソース
ここまでのチュートリアルではGodotの、エンジンのほとんどの機能が頼りにしている、Node(ノード)クラスについて主に紹介してきました。もうひとつ、同じくらい重要なデータ型はResource(リソース)です。
Node は、機能を提供します。例えばスプライトや3Dモデルの表示、物理演算、ユーザーインターフェースの配置などです。一方、Resourceはデータの格納に使います。それ自身はなにもしませんが、代わりにノードが、データの入ったリソースを使用します。
Godotが保存したり、ディスクから読み込んだりするものは、すべてリソースです。これは、シーン(.tscnや.scnファイル)や、画像、スクリプトなどが該当します。これらは Resource の例です:
エンジンがリソースをディスクから読み込む際に、読み込むのは一度きりです。もしメモリ上にすでにリソースのコピーがあれば、読み込む代わりに毎回そのコピーを返します。リソースはデータのみを含むので、重複させても意味はないのです。
ノードとリソースにかかわらず、すべてのオブジェクトは、自身のプロパティをエクスポートできます。プロパティには、String、整数(int)、Vector2など、さまざまな種類がありますが、これらすべての型はリソースになることができます。つまり、ノードとリソースはいずれも、他のリソースをプロパティとして持てるのです。
外部と内部の比較
リソースを保存する方法はふたつあります。それは:
シーンの 外部。個別のファイルとしてディスクに保存。
シーンの 内部。使用する*.tscnあるいは*.scnファイルの中に保存。
具体的には、Sprite2DノードのTexture2Dを以下に示します:
リソースのプレビューをクリックすると、リソースのプロパティを表示します。
Pathプロパティはリソースの元々の場所を表しています。この例の場合は、元は robi.png と名付けられたPNG画像です。このように、元がファイルであれば、外部リソースになります。Pathを消去するか、あるいはこの場所にない場合は、内部リソースになります。
リソースの内部と外部の切り替えは、シーンを保存するときに行われます。上記の例の場合、もしパス "res://robi.png" を消去してから保存すると、Godotは画像を .tscn シーンファイルの内部に保存します。
注釈
もし内部リソースを保存した場合でも、複数のシーンをインスタンス化したときは、エンジンがそのコピーを読み込むのは一度だけです。
コードからリソースを読み込む
コードにてリソースを読み込む方法は、ふたつあります。ひとつ目は load() 関数で、いつでも使えます:
func _ready():
# Godot loads the Resource when it reads this very line.
var imported_resource = load("res://robi.png")
$sprite.texture = imported_resource
public override void _Ready()
{
// Godot loads the Resource when it executes this line.
var texture = GD.Load<Texture>("res://Robi.png");
var sprite = GetNode<Sprite2D>("sprite");
sprite.Texture = texture;
}
リソースを preload することもできます。この関数は load とは違い、コンパイル時にファイルをディスクから読み込んでロードします。そのため、パスが変数の場合は preload は呼べません。パスは定数の文字列である必要があります。
func _ready():
# Godot loads the resource at compile-time
var imported_resource = preload("res://robi.png")
get_node("sprite").texture = imported_resource
// 'preload()' is unavailable in C Sharp.
シーンの読み込み
シーンもまたリソースですが、しかし注意点があります。シーンは、PackedScene型のリソースとしてディスクに保存されます。Resourceの内部にシーンが入れ込まれるのです。
シーンのインスタンスを得るには、PackedScene.instantiate()メソッドを使う必要があります。
func _on_shoot():
var bullet = preload("res://bullet.tscn").instantiate()
add_child(bullet)
private PackedScene _bulletScene = GD.Load<PackedScene>("res://Bullet.tscn");
private void OnShoot()
{
Node bullet = _bulletScene.Instantiate();
AddChild(bullet);
}
このメソッドは、シーンの階層どおりにノードを作成し、それらを設定してから、そのシーンのルートノードを返します。これは他のノードの子にすることができます。
この方法には、いくつかの利点があります。PackedScene.instantiate()メソッドは高速なので、新しい敵、弾丸、エフェクトなどを、ディスクから再び読み出すことなく作成できます。覚えていただきたいのは、ほとんどの場合、画像やメッシュなどは全て、シーンのインスタンス間で共有されます。
リソースの解放
リソース が使われなくなったときは、自動的に解放されます。ほとんどの場合、リソースはノードに格納されているので、ノードを解放した際には、それが持つリソースも同様に、他のノードに使用されていない限り、すべてエンジンによって解放されます。
独自のリソースを作成
Godotの他のオブジェクトと同様に、ユーザーはリソースをスクリプト化することもできます。リソーススクリプトは、オブジェクトプロパティと、シリアル化されたテキストまたはバイナリデータ (*.tres, *.res) との間で、自由に変換できる機能を継承します。また同時に、RefCounted型から参照カウントメモリ管理機能を継承します。
これには、JSON、CSV、カスタムTXTファイルなどの代替となるデータ構造を超える多くの明確な利点があります。これらのアセットは ユーザーがインポートするときに Dictionary (JSON) または File としてのみ解析できます。それに対してリソースはObject、RefCounted、およびResource機能の継承によって、際立った利便性があります。
定数を定義できるため、他のデータフィールドまたはオブジェクトの定数は必要ありません。
プロパティの設定(setter)と取得(getter )用のメソッドを含むメソッドを定義できます。これにより、基になるデータの抽象化とカプセル化が可能になります。(カプセル化する事によって)リソーススクリプトの構造を変更する必要が発生した場合でも、そのリソースを使用するゲームをその都度それに合わせて変更する必要が無くなります。
シグナルを定義できるため、リソースは管理するデータの変更に対する応答をトリガーできます。
プロパティが定義されているため、ユーザーは自分のデータが存在することを100%知っています。
リソースの自動シリアル化と逆シリアル化は、 Godot エンジンの組み込み機能です。ユーザーは、リソース ファイルのデータをインポート/エクスポートするためにカスタム ロジックを実装する必要はありません。
リソースはサブリソースを再帰的にシリアル化することもできるため、ユーザーはさらに高度なデータ構造を設計できます。
ユーザーは、リソースをバージョン管理に適したテキストファイル(*.tres)として保存できます。ゲームをエクスポートすると、Godotはリソースファイルをバイナリファイル(*.res)としてシリアル化し、読書きの速度と圧縮率を向上させます。
Godotエンジンのインスペクタは、すぐに使用できるリソースファイルをレンダリングおよび編集します。そのため、多くの場合、ユーザーはデータを視覚化または編集するためにカスタムロジックを自分で実装する必要はありません。リソースに関するこの作業を行うには、ファイルシステムドックでリソースファイルをダブルクリックするか、インスペクタのフォルダアイコンをクリックして、ダイアログでファイルを開きます。
基本リソースだけでなく、その他のリソースタイプも拡張できます。
Godotを使用すると、インスペクタでカスタムリソースを簡単に作成できます。
Create a new Resource object in the Inspector. This can even be a type that derives Resource, so long as your script is extending that type.
インスペクタで
scriptプロパティをスクリプトに設定します。
The Inspector will now display your Resource script's custom properties. If one edits those values and saves the resource, the Inspector serializes the custom properties too! To save a resource from the Inspector, click the save icon at the top of the Inspector, and select "Save" or "Save As...".
スクリプトの言語が スクリプトクラスをサポートしている場合は、この手順が合理化されます。スクリプトの名前を定義するだけで、その名前がインスペクタの作成ダイアログに追加されます。これにより、作成したリソースオブジェクトにスクリプトが自動的に追加されます。
いくつかの例を見てみましょう。 Resource を作成し、 bot_stats という名前を付けます。ファイルタブにファイル名 bot_stats.tres が表示されるはずです。スクリプトがないと意味がないので、データとロジックを追加しましょう。これに bot_stats.gd という名前のスクリプトを添付します (または、新しいスクリプトを作成してそこにドラッグします)。
注釈
To make the new resource class appear in the Create Resource GUI you need to provide a class name for GDScript, or use the [GlobalClass] attribute in C#.
@export var health: int
@export var sub_resource: Resource
@export var strings: PackedStringArray
# Make sure that every parameter has a default value.
# Otherwise, there will be problems with creating and editing
# your resource via the inspector.
func _init(p_health = 0, p_sub_resource = null, p_strings = []):
health = p_health
sub_resource = p_sub_resource
strings = p_strings
// BotStats.cs
using Godot;
namespace ExampleProject
{
[GlobalClass]
public partial class BotStats : Resource
{
[Export]
public int Health { get; set; }
[Export]
public Resource SubResource { get; set; }
[Export]
public string[] Strings { get; set; }
// Make sure you provide a parameterless constructor.
// In C#, a parameterless constructor is different from a
// constructor with all default values.
// Without a parameterless constructor, Godot will have problems
// creating and editing your resource via the inspector.
public BotStats() : this(0, null, null) {}
public BotStats(int health, Resource subResource, string[] strings)
{
Health = health;
SubResource = subResource;
Strings = strings ?? System.Array.Empty<string>();
}
}
}
ここで CharacterBody3D を作成し、 Bot という名前を付けて、次のスクリプトを追加します:
extends CharacterBody3D
@export var stats: Resource
func _ready():
# Uses an implicit, duck-typed interface for any 'health'-compatible resources.
if stats:
stats.health = 10
print(stats.health)
# Prints "10"
// Bot.cs
using Godot;
namespace ExampleProject
{
public partial class Bot : CharacterBody3D
{
[Export]
public Resource Stats;
public override void _Ready()
{
if (Stats is BotStats botStats)
{
GD.Print(botStats.Health); // Prints '10'.
}
}
}
}
次に bot という名前を付けた CharacterBody3D ノードを選択して bot_stats.tres リソースをインスペクタにドラッグ&ドロップします。 10 が出力されるはずです。明らかにこの設定はこれよりも高度な機能に使用できますが、すべてが「どのように」機能したかを本当に理解している限り、リソースに関連する他のすべてのことを理解する必要があります。
注釈
リソーススクリプトは、UnityのScriptableObjectsに似ています。インスペクタは、カスタムリソースの組み込みサポートを提供します。ですが、必要に応じて、ユーザーは独自のコントロールベースのツールスクリプトを設計し、それらを EditorPlugin と組み合わせて、データ用のカスタムビジュアライゼーションとエディタを作成することもできます。
Unreal EngineのDataTableとCurveTableも、リソーススクリプトを使用して簡単に再現できます。 DataTableはカスタム構造体にマッピングされた文字列であり、文字列をセカンダリカスタムリソーススクリプトにマッピングする辞書に似ています。
# 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 Godot;
[GlobalClass]
public partial class BotStatsTable : Resource
{
private Godot.Collections.Dictionary<string, BotStats> _stats = new Godot.Collections.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);
}
}
辞書の値をインライン化する代わりに、次のこともできます:
スプレッドシートから値のテーブルをインポートし、これらのキーと値のペアを生成します。
エディタ内でビジュアライゼーションをデザインし、これらの型のリソースを開いたときにインスペクターにビジュアライゼーションを追加するプラグインを作成します。
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(my_res, "res://my_res.tres")
using Godot;
public partial class MyNode : Node
{
[GlobalClass]
public partial 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, "res://MyRes.tres");
}
}