Godotで独自のリソース / .tresを実装してみる
概説
Godot Docsのノードの使用をさけるべき場合といろいろな方法にもあるように、Godotにはさまざまな機能をもつノードとは別に、非常に軽量なクラスがあります[1]。
その中でもResourceクラスには他のクラスにはない特徴として、リソースファイルの読み書き機能が備わっています。この記事では、Resource
の機能を利用して作る カスタムリソース(独自のリソース) や .tres
リソースファイル とその便利さ、実装方法について紹介したいと思います。
この記事の内容をざっくりと
非常に雑な言い方をすると、Resource
派生クラスを用意さえすれば、任意の変数をまとめて格納できるカスタムリソースや、以下のような特徴を持つ独自の.tres
リソースファイルを利用することができます。
- JSONよりも可読性が高い
- さまざまな型の変数に対応している(Enumも使えます)
- 読み込み/書き出しが簡単
- Godotでもテキストエディタでも編集できる
- データの格納だけでなく、処理もできる
まずはResourceを知る
そもそも、Resource
とはどういう役割を持つクラスなのでしょうか。Godot Docsのリソースの説明が比較的わかりやすいので、引用してみます。
Node は、機能を提供します。例えばスプライトや3Dモデルの表示、物理演算、ユーザーインターフェースの配置などです。一方、Resourceはデータの格納に使います。それ自身はなにもしませんが、代わりにノードが、データの入ったリソースを使用します。
Godotが保存したり、ディスクから読み込んだりするものは、すべてリソースです。これは、シーン(.tscnや.scnファイル)や、画像、スクリプトなどが該当します。リソースの例は、Texture、Script、Mesh、Animation、AudioStream、Font、Translationなどです。
簡単に言えば複数のデータをまとめて格納したり、格納されたデータを外部ファイルとして読み書きできるクラスがResource
です。Resource
クラスがまとめたデータの塊が「リソース」、リソースを外部に保存したのが.tres
や.res
形式のリソースファイルです。
また、他のクラス同様にResource
クラスは継承することができます。派生クラスは、用途に応じた個別のリソースを管理することができます。これらのリソースは、それぞれ違った種類や数の変数を格納することができます[2]。
実際に同じResource
クラスを継承した、AtlasTexture
とDynamicFont
の2つのリソースを比較してみましょう。AtlasTexture
は内部にint
型で管理されるフラグや、Rect2
型で示された画像の切り出し範囲を格納しています。これは、Color
型やbool
型、int
型を格納するDynamicFont
とは明らかに違いますね。
ただ、どちらもさまざまな型の変数を一箇所にまとめることで、ノード間のデータのやり取りの利便性を高めている点が共通しています。
リソースファイル(.tres)の中身を見てみる
今度はリソースファイルの中身を見てみましょう。例として以下の画像のようなDynamicFont
型のリソースファイルSilver.tres
を保存し、テキストエディタで開くことにします[3]。
このリソースファイルを、テキストエディタで読み込んでみます
Silver.tres
をテキストエディタで開いてみると、以下のように表示されます。
[gd_resource type="DynamicFont" load_steps=2 format=2]
[ext_resource path="res://**/Silver.ttf" type="DynamicFontData" id=1]
[resource]
size = 17
outline_size = 1
outline_color = Color( 0.254902, 0.266667, 0.054902, 1 )
use_filter = true
extra_spacing_top = 2
extra_spacing_bottom = 1
extra_spacing_char = 3
extra_spacing_space = 4
font_data = ExtResource( 1 )
基本的に.tres
ファイルはさまざまな型の変数と、その値が書かれたテキストデータであることが分かります。iniやTOMLに似ていて読み易いですね。他にも以下のことが分かります。
- 1行めに
DynamicFont
型のリソースであることが宣言されている - 3行めにフォントデータである
Silver.ttf
のパスが記述されている - 変数は基本的に
[resource]
より下の行に記述されている -
Color
型の変数にも対応している
スクリプトでリソースファイル内の変数を読み込んでみる
.tres
ファイル内の変数は、スクリプトでも簡単に読み込むことができます。例えば、先ほどのSilver.tres
ファイルの8行めにあるoutline_color
を参照するには、以下のように記述します。
var resource = load("res://**/Silver.tres")
print(resource.outline_color)
# Color( 0.254902, 0.266667, 0.054902, 1 )と出力されます
きちんとColor
型の変数として扱われていますね。コードも非常に簡潔です。
自分で作った独自のリソースやリソースファイルを使いたい
ここまでResource
やその派生クラス、そしてリソースファイルについて見てきました。
さまざまなデータを格納できるリソース自体は便利そうですし、読み込みが簡単なリソースファイル.tres
も魅力的です。もしかしたら関わっているプロジェクトで「.tres
をJSONの代わりに使ってみたい」と思うかもしれません。
ですが、GodotはJSONファイルのようには.tres
ファイルを扱うことはできません。Godotで.tres
ファイルを読み書きするには、まずはリソースの仕様を定めた設計図(Resource
派生クラス)を作る必要があります。
自分でDynamicFont
のようなクラスを作ることで、はじめて独自のリソースやリソースファイルを扱えるようになるのです。
カスタムリソースを作る
幸いなことに、Resource
派生クラスを作って独自のリソースを実装するのは、普段GDscriptに触れているユーザーにとってさほど難しいことではありません。慣れてしまえば、ものの数分で実装することさえ可能です。
ここからは実際にResource
派生クラスとカスタムリソースを作ってみましょう。
Resource派生クラスを作る
以下にResource
派生クラスのサンプルコードを例示します。ゲームに登場するキャラクターのデータを格納するための必要最小限のクラスです[4]。
ご覧の通り、コードの中でいくつかの変数が宣言されており、それぞれにキャラクターの名前やダイアログデータが代入できます。CharacterData
型のリソースの誕生です。
# Recourceクラスを継承したCharacterDataクラスを宣言
extends Resource
class_name CharacterData
# キャラクターの属性やアビリティを扱うenumを宣言
enum CharacterClass {NULL, PLAYER, ENEMY, MOB,}
enum Ability {NULL, MAGIC, MELEE, RANGED, SNEAK}
# リソースで扱われる変数を全て宣言
export (String) var name:String
export (CharacterClass) var character_class:int
export (Vector2) var initial_position:Vector2
export (Array, Ability) var abilities:Array
export (Array, String) var dialog_data:Array
カスタムリソースに値を代入してみる
このCharacterData
型のリソースに、データを格納してみましょう。Godotのメニューから新規リソースを押下し、先ほど作ったCharacterData
を選択します。すると、馴染み深いUIでカスタムリソースの編集ができます。
CharacterData
型のカスタムリソースを作成し、編集している様子
スクリーンショットをご覧いただくと分かるかと思いますが、CharacterData
クラスで宣言された変数がきちんと表示されていますし、enum
を使った配列を使うことさえできます。もちろん、値を代入したリソースを.tres
ファイルとして保存することもできます。試しに武器屋のおじさん.tres
というファイル名で保存してみましょう。
保存したリソースファイルの中身を見てみる
保存した武器屋のおじさん.tres
ファイルを、テキストエディタで開いてみます。
enum
が数値で記述されていること、Vector2
は型が明示されていることなどがわかります。もちろん、先ほどGodotで編集・保存した内容と相違ありませんね。
[gd_resource type="Resource" load_steps=2 format=2]
[ext_resource path="res://**/character_data.gd" type="Script" id=1]
[resource]
script = ExtResource( 1 )
name = "武器屋のおじさん"
character_class = 3
abilities = [ 2 ]
dialog_data = [ "ここは武器屋だ", "何を買う?" ]
initial_position = Vector2( 320, 160 )
これで独自のリソース、そしてリソースファイルの実装と動作確認完了です。
カスタムリソースはデータの処理もできる
Godotのリソースが優れている点に、データの格納だけではなく、処理ができることが挙げられます。
先ほど例示したサンプルコードを少し変更し、name
変数からファイルパスを生成し出力するメソッド、get_texture_path()
を追加してみました。
extends Resource
class_name CharacterData
enum CharacterClass {NULL, PLAYER, ENEMY, MOB,}
enum Ability {NULL, MAGIC, MELEE, RANGED, SNEAK}
export (String) var name:String
export (CharacterClass) var character_class:int
export (Array, Ability) var abilities:Array
export (Array, String) var dialog_data:Array
export (Vector2) var initial_position:Vector2
# - - - - - - - - - ここから下が、新しく追加したコード - - - - - - - - -
# name変数に文字列を結合し、ファイルパスを返すメソッド
func get_texture_path() -> String:
return "res://file_path_to_texture/%s_texture.tres" % name
これでget_texture_path()
メソッドを使って、リソース内に存在しない値を呼び出すことができるようになりました。早速、先ほど保存した武器屋のおじさん.tres
で実験をしてみましょう。以下がサンプルコードです。
var resource = load("res://**/武器屋のおじさん.tres")
print(resource.get_texture_path())
# res://file_path_to_texture/武器屋のおじさん_texture.tres と出力されます
「武器屋のおじさん」のテクスチャのファイルパスが出力されたはずです。リソースから直接メソッドを呼び出せるので、コードがシンプルになるのも非常に良いですね。
カスタムリソースのススメ
このように、データの読み書きや処理などを行うことができるResource
派生クラスとカスタムリソース(独自のリソース)は非常に便利です。
ゲームに登場するキャラクターなど、同じ構造のデータを複数管理をする状況では大活躍しそうですね。逆に構造が定まってないデータや、専用のクラスを用意するまでもない一点もののデータの管理には、JSONなどの使用を検討してみても良いのかもしれません[5]。
他にもいろいろできます
この記事ではResouce
を表面をなぞる程度にしかその機能やメリットを紹介できていません。しかしResource
は他のクラス同様にsetter
やgetter
、そしてシグナルも扱えますし、他にも多くのメリットがあります[6]。
もしまだカスタムリソースを使ったことがないようであれば、これを機にぜひ触れてみてください。そして、もし素敵な使い方や活用方法を発見したら、フィードバックをいただければ幸いです。
-
全てのオブジェクトの継承元である最軽量な
Object
、自身への参照を追跡可能なReference
、そしてリソースファイルの読み書きができるResource
の3つが触れられています。 ↩︎ -
他の言語でいうところの構造体と似ていますが、ユーザーが
Resource
派生クラスをインスタンス化してデータを格納する必要はありません。 ↩︎ -
ここではPoppy Works謹製のSilverフォントを使っています。おすすめです! ↩︎
-
このコードでは、機能の説明のためにenumなどを使っていますが、もちろんそれらを削って、もっとシンプルなカスタムリソースを実装することもできます。 ↩︎
-
スプレッドシートのデータを手間をかけずに読み込みたい場合も、JSONやCSVをが有力な選択肢になりますね。 ↩︎
Discussion