Godot : JSONを使わずセーブデータを保存・読込する
概要
Godotでセーブデータの読み書きを扱うコードのサンプルは、公式も含めJSON形式のものが多いです[1]。が、実は必ずしもセーブデータがJSON形式である必要はなく、JSON形式を採用しなければ(汎用性を犠牲に)処理を若干シンプルにすることができます。
この記事では、Dictionary
型のデータをJSON形式を使わずにバイナリデータとして保存するコードの紹介と、セーブデータの保存形式の選び方の検討をします[2][3]。
store_var()
関数を使ってみる
公式のドキュメントにも記載の通り、ファイルの読み書きに使われるFileクラスには、さまざまな型のデータを扱うための関数があります。いずれもstore_**()
やget_**()
という名称で、関数名の後半が型の名前になっているので、見ただけで役割が想像できるかと思います。
今回はFile.store_var()
関数を使ってみます。store_var()
は、配列や辞書、オブジェクトそのものに至るまで、任意の型をそのまま保存することができます。また、型変換などが不要なので、JSON形式を使った場合と比較して保存・読込の処理をシンプルにすることができます。
以下では、保存と読込に分けて、簡単なサンプルコードを紹介します。
保存処理
以下のコードでは、Dictionary
型のデータをそのままsavedata.txt
というファイルに保存します。
JSON形式で保存する場合には、JSON.print()
を使って、データを一回JSON形式に変換する必要がありますが、store_var()
で変数をそのまま保存するだけならデータの下処理は不要です。
# 保存するデータ
var dictionary = {"key_1" : "value_a", "key_2" : "value_b"}
var file = File.new()
# 保存先のファイルを開く / ファイルの有無はGodot側で確認してくれます。
# 指定したパスにファイルが無ければ自動で作成してくれます。
file.open("user://savedata.txt", File.WRITE)
# store_varで先程のdictionaryデータを保存
file.store_var(dictionary)
file.close()
読込処理
JSON形式で保存した場合と比較して、読み込みの処理もシンプルです。
保存したデータを読み込むにはFile.get_var()
関数を使います。読み込む際に型の推論もしてくれるので、型変換は不要です。保存したデータを読み込む際は、文字通りget_var()
だけを使えばOK[4]。
var file = File.new()
file.open("user://savedata.txt", File.READ)
var load_data = file.get_var()
file.close()
読み込んだデータを確認してみる
上記のコードでセーブデータを代入した変数load_data
には、本当に先ほど保存したデータが代入されているのでしょうか。確認してみましょう。
まずはtypeof()
関数で型の確認をします。以下の通り、Dictionary
型として代入されていることがわかります。
print("data type is : %s" % typeof(load_data))
# ↑「data type is : 18」と出力されます(18はDictionary型)
今度はload_data
の中身を出力してみましょう。こちらも以下の通り、保存したデータをそのまま読み込めていることがわかります。
print(load_data)
# ↑ 「{key_1 : "value_a", key_2 : "value_b"}」と出力されます
複数の変数を保存するには?
今回は単一のDictionary
型データをファイルに保存しました。しかし、Dictionary
型にまとめることが難しい変数など、複数の変数を1つのファイルにまとめたい場面もあるかと思います。一応ではありますが、上述の方法を繰り返すことで実装が可能です。
具体的にはFile.store_**()
関数を複数回実行すれば良いのですが、読み込み時が少し手間です。読み込みたいデータのありかを、File.seek()
関数を使って、バイト単位で指定する必要があるからです。
ResourceSaver
クラスが良さそう
複雑なデータは複数の変数を読み書きする場合はResourceSaver
とResourceLoader
クラスを使う方が良さそうです。ResourceSaver
クラスを使うと、tres
やres
などのGodotリソースファイルとしてデータを書き出すことができます。リソースファイルに格納されたデータの読み出しは簡単です。
Godotのリソースファイルについては以下の記事にまとめたので、チェックしてみてください。
結局、どうやってデータを保存するのがいいの?
個人的には、データの保存方法は以下のようにケースごとに設定するのがいいのかなと考えています。
-
変数は一つ、辞書型のデータで、必ずしもJSON形式である必要がない:
→ 今回の記事のようにDictionary
型でそのまま保存 -
辞書型のデータで、動作確認を含め外部のソフトなどで読み書きする:
→ JSON形式で保存 -
さまざまな形式の変数を含むデータを保存したい:
→ResourceSaver
クラスを使う
→ConfigFile
クラスを使う (情報提供:まくらけっとさん)
→File
クラスでも実装可能だがちょっと面倒 -
自動生成したマップなど、ゲーム内のリソースを保存したい:
→ResourceSaver
クラスを使う
他に良い方法があれば、ぜひコメントでご紹介ください。
[追記:2022/8/21]
まくらけっとさんより、ConfigFile
についてコメントをいただいたので、記事末尾のリストに追加いたしました。ありがとうございます!
-
サンプルコードにJSON形式のものが多い理由は、「JSONを使った方が効率がいい」というよりも、「汎用性が高いJSONもセーブデータに使える」ことを示すのが理由かと思います。JSON便利ですし、汎用性大事。 ↩︎
-
記事後半でも触れていますが、セーブデータを他のソフトで読み込んだり加工したりするのであれば、データの保存はJSON形式で行うのが良いと思います。また、特に開発段階ではセーブしたデータが意図通りのものになっているかを確認すると思いますが、そういった場合もJSON形式が便利です。
換言すれば、保存したデータを確認する必要もなく、外部ソフトで書き換えることもないのであれば、必ずしもJSON形式で保存する必要はありません。 ↩︎ -
ちなみにセーブデータの構造が複雑になってきた場合、JSON形式は人間にとって可読性の低さがデバッグの障害となります。そんな時はgronがおすすめです(日本語の紹介記事)。 ↩︎
-
ファイルを読み込む際には、ファイルの有無や、データが正しく読み込めているかの確認処理などもあるかと思いますが、この記事では割愛します。 ↩︎
Discussion
.tres
形式に近いですがConfigFile
を使うのもアリだと思います~Vector2
とかtexture
とかそのまま保存できるのでJSONと比べてコードが幾分シンプルになりそうです。
ちなみに暗号化もサポートされてたりします。
ConfigFile
…!以前.tres
を調べていた時に見つけて以来、すっかり忘れてました。ini
っぽい構文、そして.tres
と比較して自由に記述できそうなところが非常にいいですね。ありがとうございます!