セーブとロード

メリークリスマス、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 に追加する予定です。

Circular 型

Siv3D Advent Calendar 2013, 24 日目の記事です。
今日は平面極座標を表現する Circular 型を紹介します。

Circular 型のメンバ r, theta はそれぞれ平面極座標の動径 r と偏角 θ に対応します。
極座標から直交座標への変換は、Siv3D では次の通り行われます。

Circular::operator Vec2() const
{
	return{ sin(theta)*r, -cos(theta)*r };
}

簡単なプログラムで Circular のはたらきを確かめてみましょう。

# include <Siv3D.hpp>

void Main()
{
	const Vec2 center = Window::Size() / 2;

	while (System::Update())
	{
		Circle(center, 5).draw(Palette::Red);

		for (int i = 0; i < 80; ++i)
		{
			const Vec2 pos = center + Circular(10 + i * 3, Radians(i * 20));

			Circle(pos, (i + 8)*0.2).draw(HSV(i*20));
		}
	}
}

Circular 型を使ったプログラムの例は サンプル | 時計 があります。

CodeIQ の Siv3D 問題 も Circular を使うと楽に実装することができます。

カーソルを変更する

Siv3D Advent Calendar 2013, 23 日目の記事です。
今日はカーソルの表示や画像を切り替える方法を紹介します。

カーソルの表示/非表示


システムカーソルの表示/非表示を切り替えるには Window::ShowCursor(bool) を使います。このプログラムでは、画面の右半分にいるときにカーソル表示をオフにします。

# include <Siv3D.hpp>

void Main()
{
	while (System::Update())
	{
		const bool show = Mouse::Pos().x < Window::Width() / 2;

		Window::ShowCursor(show);
	}
}

カーソルの画像を変更する

Window::SetCursorTexture() を使うと指定したテクスチャを、
Window::SetCursorTextureRegion() を使うと指定したテクスチャの一部領域をカーソル画像にすることができます。
それぞれの第二引数は、カーソル画像と実際のカーソルの中心位置とのオフセットです。
設定したカーソルは常に最前面に描画されます。

# include <Siv3D.hpp>

void Main()
{
	const Texture texture(L"Example/Windmill.png");

	Window::SetCursorTextureRegion(texture(100,100,50,50));

	GUI gui(GUISkin::Default());

	gui.addButton(L"OK", {L"OK"});

	while (System::Update())
	{
		
	}
}

進捗報告合宿

Siv3D Advent Calendar 2013, 22 日目の記事です。
今日は未踏の首藤 / 石黒 PM チーム合同の進捗報告合宿に来ています。

おもに 10 - 12 月の間に実装した機能や、現在の課題について発表、議論しました。
発表順が 2 番目だったので気楽に過ごせてます。


10 月の未踏ブースト合宿と違ってシングル部屋なのが嬉しい。

未踏 OB とのディスカッションで、Siv3D の今後の方向性に関わるアドバイスをいただきました。オープンソースマーケティングが来年の重要テーマになりそうです。

Siv3D Winter of Code 2013

Google Summer of Code を真似てみました。

Siv3D Advent Calendar 2013, 21 日目の記事です。
今日は Siv3D を使うクリエイターのみなさんに冬休みの宿題です。

今度リリースする Siv3D January 2014 の新機能に「正多角形や星形を作成する関数」というものがあります。六角形や星形のポリゴンをつくるとき、いちいち頂点ごとに座標を指定しなくてすむものです。

その関数を実装するという課題を CodeIQ に出題しました。
C++ と Siv3D に慣れていれば 30 分程度でできると思います。
CodeIQ | Siv3D でポリゴンを作ろう!

挑戦した方には、

  • コードのフィードバック
  • このブログに名前を掲載
  • ???

といった特典があります。
CodeIQ に登録して最初に解くのがこの問題だった方には、おまけがあります。

比較的簡単な問題なので CodeIQ 未経験の方にも優しいと思います。コードが公開されることはありません。時間も計測されないのでゆっくり考えられます。期限は今月 30 日までです。

Play Siv3D! の拡充

Siv3D Advent Calendar 2013, 20 日目の記事です。
今日は Siv3D の Web サイト整理のお話です。

これまで 5 年間にわたって Siv3D の開発記録や機能紹介を発信してきた Google Site の Siv3D ページ を縮小し、今後は Play Siv3D! に情報を集約することにしました。

1つのサイト内で Siv3D について知り、試し、学べるようにするのが目的です。
旧サイトは Play Siv3D!, 開発ブログ, Qiita などへのリンクをまとめるサイトになります。

今月末には Play Siv3D! に Siv3D Advent Calendar 25 日分の記事をマージし、ドキュメントをさらに充実させるほか、バグ報告フォームやアンケートページを用意して、ユーザーからの意見を開発に反映しやすくします。

それともう一つ、別の企画が進行中です。これは一両日中にアナウンスする予定です。December 2013 をインストールして待っていてください!

アイコンを変更する

Siv3D Advent Calendar 2013, 19 日目の記事です。
今日は December 2013 の新機能、アプリケーションのアイコンを変更する機能を紹介します。

アプリケーションのデフォルトのアイコンは、プロジェクトフォルダにある icon.ico を新しいアイコンに置き換えることで変更できます。

実行時にアイコンを変更する場合は Window::SetIcon(Image) を使います。アイコンに使える画像のサイズは 32x32 で、それ以外の場合は自動で拡大縮小されます。
クリックされた回数をアイコンに表示させてみましょう。

# include <Siv3D.hpp>

void Main()
{
	const Font font(18);
	
	Image image(64, 64, Palette::White);

	int count = 0;

	while (System::Update())
	{
		if (Input::MouseL.clicked)
		{
			++count;

			image.fill(Palette::White);

			Circle(32, 32, 28).write(image, Palette::Seagreen);

			font.writeCenter(image, Format(count), { 32, 32 }, Palette::White);

			Window::SetIcon(image);
		}
	}
}

数フレームごとにアイコンを変更すればアニメーションもできます。

Skype のアイコンのように、何らかの情報をアイコンで通知するといった使い方もできるでしょう。