セーブとロード
メリークリスマス、Siv3D Advent Calendar 2013, 25 日目の記事です。
今日は BinaryWriter と BinaryReader を使って、簡単なゲームデータのセーブとロードに挑戦しましょう。
BinaryWriter はデータをテキストではなくバイト列としてファイルに書き込みます。
ファイルに得点をセーブするプログラムで TextWriter との違いを見てみましょう。
# include <Siv3D.hpp> void Main() { const int score = 1234567; TextWriter textWriter(L"save.txt"); textWriter.write(score); BinaryWriter binaryWriter(L"save.bin"); binaryWriter.write(score); }
save.txt
は 1234567 という文字列が保存された 10 バイトのテキストファイルになります。一方 save.bin
は int 型の 4 バイトのデータがそのまま保存され、テキストエディタで開くと変な文字列として解釈されます。
次に、今保存した得点をロードするプログラムで TextReader と BinaryReader の違いを見てみましょう。
# include <Siv3D.hpp> void Main() { TextReader textReader(L"save.txt"); String text; textReader.readLine(text); const int scoreA = Integer(text); BinaryReader binaryReader(L"save.bin"); int scoreB = 0; binaryReader.read(&scoreB, sizeof(scoreB)); Println(L"A: ", scoreA); Println(L"B: ", scoreB); WaitKey(); }
テキストファイルの場合、読み込んだ文字列を目的のデータ型に変換する必要があります。バイナリデータの場合はデータを変数に直接ロードできます。
セーブデータを人間が読める形式にする必要がないならば、速度やサイズで優れたバイナリ形式でセーブデータを扱いましょう。
構造体の読み書き
構造体を使うと、一度に多くのデータを読み書きできます。
# include <Siv3D.hpp> struct Result { int stage1Score; int stage2Score; double time; }; void Main() { Result result; result.stage1Score = 500; result.stage2Score = 400; result.time = 43.2; { BinaryWriter writer(L"result.bin"); writer.write(result); } // ここで writer のスコープが外れてファイルがクローズされる // writer.close() でもよい // クローズされたファイルでないと正常に開けない BinaryReader reader(L"result.bin"); Result loadData; reader.read(&loadData, sizeof(loadData)); Println(L"stage1Score: ", loadData.stage1Score); Println(L"stage2Score: ", loadData.stage2Score); Println(L"time: ", loadData.time); WaitKey(); }
開いたファイルは、読み書きが終了したらクローズしましょう。クローズしていないファイルは、別の BinaryReader や BinaryWriter で正常に開くことができません。
非 POD クラス型の読み書き
Point, Vec2, Color などは、ポインタとデータのサイズを指定して write できます。
# include <Siv3D.hpp> void Main() { const Point pos(10, 50); { BinaryWriter writer(L"pos.bin"); writer.write(&pos, sizeof(pos)); } BinaryReader reader(L"pos.bin"); Point loadData(0, 0); reader.read(&loadData, sizeof(loadData)); Println(L"pos: ", loadData); WaitKey(); }
String の読み書き
std::vector や String のように、メンバ変数にポインタを持つクラスを、単純に write / read してはいけません。write されるのはポインタだけで、実際のバッファのデータは write されないという問題が生じるためです。
したがって次のように工夫する必要があります。String を文字列の長さと、実際のデータに分けてセーブ・ロードするサンプルです。
# include <Siv3D.hpp> struct Monster { String name; int number; }; void Main() { Monster pikachu; pikachu.name = L"ピカチュウ"; pikachu.number = 25; { BinaryWriter writer(L"pikachu.bin"); writer.write(pikachu.name.length); writer.write(pikachu.name.data(), pikachu.name.length * sizeof(wchar_t)); writer.write(pikachu.number); } BinaryReader reader(L"pikachu.bin"); size_t length = 0; reader.read(&length, sizeof(length)); if (length > 6) { // 異常な値(誤ったファイルを読み込んで巨大な値になるのを防ぐ) return; } Monster loadData; loadData.name.resize(length); reader.read(&loadData.name[0], loadData.name.length * sizeof(wchar_t)); reader.read(&loadData.number, sizeof(loadData.number)); Println(L"Name: ", loadData.name); Println(L"Number: ", loadData.number); WaitKey(); }
保存するデータの内容によっては単純なバイナリではなく INI 形式や CSV 形式、場合によっては画像を使うこともあるでしょう。より高度なアプリケーションでは、ファイルの破損チェックや、データの改造を防ぐ暗号化も必要になります。
将来的にはそういったファイル保存のユーティリティや、String, std::vector などのデータの保存を簡単にする機能を Siv3D に追加する予定です。