文字の TextureRegion を取得して「大破」する

Siv3D Advent Calendar 2013, 8 日目の記事です。
今日は December 2013 で追加された機能の 1 つ、Font から指定した文字の TextureRegion を取得する機能を紹介します。

これまでの Font を使った文字描画では、文字単位での描画や変形をサポートしていませんでしたが、December 2013 で追加された Font::getTexture(wchar_t) メソッドを使うと、Font 内部で確保された Texture 上にある、指定した文字の TextureRegion にアクセスできます。

TextureRegion というのはあまり目にしないクラスですが、例えばよく使う以下のようなコードでも TextureRegion オブジェクトが作成されています。

# include <Siv3D.hpp>

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

	while (System::Update())
	{
		//texture(0, 0, 100, 100).draw() を分解

		const TextureRegion t = texture(0, 0, 100, 100);

		t.draw();
	}
}

このように、もとになる Texture があり、その一部分を切り抜いたものが TextureRegion です。

さっそく Font::getTexture(wchar_t) を使ったサンプルを見てみましょう。


# include <Siv3D.hpp>

void Main()
{
	const Font font(100);

	while (System::Update())
	{
		const TextureRegion t = font.getTexture(L'あ');

		t.mirror().draw();

		font.getTexture(L'い').rotate(0.6).draw(100, 200);

		font.getTexture(L'う').scale(1.0,0.5).draw(300, 150);

		font.getTexture(L'え').flip().draw(300, 300);
	}
}

文字ごとに自由な変形を適用できます。
ただし、元のフォントのサイズが小さい場合は拡大した際にドットが荒くなってしまうので注意してください。

さて、今回の機能を使って最近流行りのゲームの文句を真似てみました。
結果画像がこれです。

# include <Siv3D.hpp>

void Main()
{
	Window::ShowCursor(false);

	const Font font(72, L"MS 明朝");

	const Color color(255, 101, 93);

	Graphics::SetBackground({ 150, 200, 220 });

	std::vector<Vec2> pts;

	for (int i = 0; i < 36; ++i)
	{
		pts.push_back(Circular(90 + (i % 2) * 20, TwoPi / 36 * i));
	}

	const Polygon back(pts);

	while (System::Update())
	{
		const Vec2 center = Mouse::Pos();

		const Vec2 taiPos = center - Vec2(50, 50);

		const Vec2 haPos = center + Vec2(55, 20);

		back.draw(center);

		back.drawFrame(center, 8.0, color);

		for (int i = 0; i < 16; ++i)
		{
			const Vec2 offset = Circular(5, TwoPi / 16 * i);

			font.getTexture(L'大').rotate(0.3).drawAt(taiPos + offset, Palette::Black);
		}

		font.getTexture(L'大').rotate(0.3).drawAt(taiPos, color);

		for (int i = 0; i < 16; ++i)
		{
			const Vec2 offset = Circular(5, TwoPi / 16 * i);

			font.getTexture(L'破').scale(0.8).rotate(0.3).drawAt(haPos + offset, Palette::Black);
		}

		font.getTexture(L'破').scale(0.8).rotate(0.3).drawAt(haPos, color);
	}
}

スクリーンショットを保存する

Siv3D Advent Calendar 2013, 7 日目の記事です。
今日は Siv3D のスクリーンショットに関する機能を紹介します。


スクリーンショットを指定したファイル名で保存する

Graphics::SaveScreenshot(path) を呼ぶと、現在のフレームの描画が完了したときに、指定したファイル名でスクリーンショットが保存されます。使用できる拡張子は Image::save() と同様 bmp, png, jpg, jpeg, dds, gif, tif, tiff, tga, webp, jp2, ppm です。
第一引数のファイル名を省略した場合は Screenshot フォルダが作られ、その中にユニークなファイル名で保存されます。

# include <Siv3D.hpp>

void Main()
{
	while (System::Update())
	{
		if (Input::KeyS.clicked)
		{
			Graphics::SaveScreenshot(L"screenshot.png");
		}
	}
}

スクリーンショットを Image 形式で取得する

スクリーンショットをアプリケーションで使うときは、ファイルを介さずに直接 Image 形式で取得しましょう。直前のフレームで Graphics::RequestScreenCapture() を呼び、次のフレームで Graphics::ReceiveScreenCapture() から Image を受け取ります。

# include <Siv3D.hpp>

void Main()
{
	bool request = false;

	while (System::Update())
	{
		if (request)
		{
			request = false;

			const Image image = Graphics::ReceiveScreenCapture();
		}

		if (Input::KeyS.clicked)
		{
			Graphics::RequestScreenCapture();

			request = true;
		}
	}
}

Print Screen キーでスクリーンショットを保存する

Print Screen キーを押したときに自動でスクリーンショットを保存するよう設定できます。プレイ画像をいつでも好きな時に撮りたいときはこの機能を利用しましょう。

設定手順は engine フォルダにある config.ini の

bSaveScreenshot = false

bSaveScreenshot = true

に変更するだけです。
保存先は Graphics::SaveScreenshot() と同じく Screenshot フォルダです。

Siv3D で音楽プレイヤーを作る

Siv3D Advent Calendar 2013, 6 日目の記事です。
今日は Siv3D の GUI と Sound 機能を使って音楽プレイヤーを作ってみました。

このプログラムのソースコードと解説は Qiita | Siv3D で音楽プレイヤーを作る に掲載しています。GUI の使い方に慣れる良いチュートリアルになると思います。

開発中の機能 | 手書き文字認識

Siv3D Advent Calendar 2013, 5 日目の記事です。
今日は開発中の機能の 1 つ「手書き文字認識」を紹介します。

このように、書いている途中でも候補の文字を高速に取得できます。

判定には機械学習アルゴリズムを用いた文字認識エンジン Zinnia を使っています。
学習データを用意することで、オリジナルの図形や文字に対応させることもできます。

この機能は、早ければ次のアップデートで実験的機能として搭載する予定です。
学習データを作るツールも用意しようと思っています。

計算問題の答えをマウスやペンで入力させる脳トレのようなゲーム、紋章をマウスで描くと魔法が使える RPG ・・・, 使い道を想像するだけでも楽しいですね。

手拍子をカウントする

Siv3D Advent Calendar 2013, 4 日目の記事です。
今日はマイク入力を使って手拍子を認識するソフトを作ってみましょう。

まずは Play Siv3D!チュートリアルからマイク入力のサンプルを持ってきます。
チュートリアル | マイク入力 | 録音した波形の解析 | 振幅

# include <Siv3D.hpp>

void Main()
{
	const Font font(30);

	// 10 秒間, サンプリングレート 44100Hz で録音を開始する
	if (!Recorder::Start())
	{
		return;
	}

	while (System::Update())
	{
		const double max = Recorder::GetMaxAmplitude();
		const double ave = Recorder::GetAverageAmplitude();

		font.draw(Format(L"最大振幅: ", max));

		RectF(0, 50, max*Window::Width(), 30).draw();

		font.draw(Format(L"平均振幅: ", ave), 0, 100);

		RectF(0, 150, ave*Window::Width(), 30).draw();

		if (Recorder::IsEnd()) // 録音が終了
		{
			// 最初の位置から録音し直し
			Recorder::Restart();
		}
	}
}

録音中は
Recorder::GetMaxAmplitude(sec) で最新の sec 秒間の最大の振幅 [0.0, 1.0] を、
Recorder::GetAverageAmplitude(sec) で最新の sec 秒間の平均の振幅を取得できます。
sec はデフォルトでは 0.02 です。

さて、手拍子の波形は、ごく一瞬だけ大きな振動が現れるのが特徴です。

この特徴を利用して、毎フレーム最大の振幅を調べ、閾値を超えた瞬間を調べれば、それなりの精度で手拍子を認識できます。

実際に作ってみましょう。

# include <Siv3D.hpp>

void Main()
{
	const double threshold = 0.2;

	const Font font(30);

	const Point center = Window::Size() / 2;

	if (!Recorder::Start())
	{
		return;
	}

	bool clapping = false;

	int count = 0;

	int impact = 0;

	while (System::Update())
	{
		const double max = Recorder::GetMaxAmplitude();

		if (!clapping && max >= threshold)
		{
			++count;

			clapping = true;

			impact = 40;
		}

		if (max < threshold)
		{
			clapping = false;
		}

		font.draw(Format(L"最大振幅: ", max));

		RectF(0, 50, max*Window::Width(), 30).draw();

		impact = Max(impact - 4, 0);

		Circle(center, 80 - impact).draw(Color(Palette::Orange, 120 + impact*3));

		font.drawCenter(Format(count), center - Vec2(0, impact*0.3));

		if (Recorder::IsEnd())
		{
			Recorder::Restart();
		}
	}
}

ポケットをたたく(手拍子をする)たびにクッキーが増えていくゲームなんてどうでしょうか。

Circle の描画関数

Siv3D Advent Calendar 2013, 3 日目の記事です。
今日は Circle の描画関数を紹介します。

基本の円描画 draw(color)

# include <Siv3D.hpp>

void Main()
{
	Circle(30, 30, 20).draw(Palette::Yellow);

	Circle(100, 100, 50).draw(Palette::Skyblue);

	Circle(400, 400, 300).draw(Palette::Orange);

	WaitKey();
}

円の枠 drawFrame(innerThickness, outerThickness, color)

# include <Siv3D.hpp>

void Main()
{
	Circle(30, 30, 20).drawFrame(2.0, 0.0, Palette::Yellow);

	Circle(100, 100, 50).drawFrame(10.0, 0.0, Palette::Green);

	Circle(100, 100, 50).drawFrame(1.0, 10.0, Palette::Skyblue);

	Circle(400, 400, 300).drawFrame(250.0, 0.0, Palette::Orange);

	WaitKey();
}

丸いパイを切ったような形 drawPie(startAngle, angle, color)

# include <Siv3D.hpp>

void Main()
{
	Circle(30, 30, 20).drawPie(0.0, Radians(120), Palette::Yellow);

	Circle(100, 100, 50).drawPie(Radians(45), Pi, Palette::Skyblue);

	Circle(400, 400, 300).drawPie(0.0, -Radians(60), Palette::Orange);

	WaitKey();
}

アーチ型 drawArc(startAngle, angle, innerThickness, outerThickness, color)

# include <Siv3D.hpp>

void Main()
{
	Circle(30, 30, 20).drawArc(0.0, Radians(120), 5.0, 0.0, Palette::Yellow);

	Circle(100, 100, 50).drawArc(Radians(45), Pi, 10.0, 10.0, Palette::Skyblue);

	Circle(400, 400, 300).drawArc(0.0, -Radians(60), 200.0, 0.0, Palette::Orange);

	Circle(50, 50, 350).drawArc(HalfPi, HalfPi, 60.0, 0.0, { 255, 64 });

	WaitKey();
}

Siv3D の円の描画にはちょっとした工夫があって、半径が 32px 以下の円は、あらかじめ円がレンダリングされたスプライトを描画し、それより大きい円はその都度適当な分割数のポリゴンを生成しています。
draw() の画像をポリゴンで表示するとこうなります。

こうすることで、小さな円を大量に描画するシーンでは頂点数を節約し、大きな円は最適な品質で描くことができます。

INI ファイルからデータを読み込む

Siv3D Advent Calendar 2013, 2 日目の記事です。

今日は INI ファイルからデータを読み込む方法を紹介します。
INI ファイルは設定を記述するのに便利なテキストフォーマットです。

test1.ini

[AAA]
aaa = 50
bbb = 1.234
ccc = Hello

; コメント
[BBB]
aaa = (20, 50)
bbb = (255,128,0)
ccc = ((80,80),(120,120),(80,120))

このファイルを読み込んで、Siv3D のデータ型に変換しましょう。
読み込みの関数は INIReader::get(L"セクション.パラメータ", 失敗時の値) です。
INIReader::getOptionl は失敗時に none を返します。

# include <Siv3D.hpp>

void Main()
{
	const INIReader ini(L"test1.ini");

	const int AAAaaa = ini.get<int>(L"AAA.aaa", 0);

	const double AAAbbb = ini.get<double>(L"AAA.bbb", 0.0);

	const String AAAccc = ini.get<String>(L"AAA.ccc", L"");

	Println(AAAaaa, L' ', AAAbbb, L' ', AAAccc);

	if (const auto value = ini.getOptional<int>(L"AAA.ccc"))
		Println(value.get());
	else
		Println(L"読み込みに失敗");

	if (const auto value = ini.getOptional<int>(L"AAA.xxx"))
		Println(value.get());
	else
		Println(L"読み込みに失敗");

	Println(ini.get<int>(L"AAA.yyy", -123));

	Println(ini.get<Point>(L"BBB.aaa", { 0, 0 }));

	ini.get<Triangle>(L"BBB.ccc", { 0, 0, 0 }).draw(ini.get<Color>(L"BBB.bbb", { 0, 0, 0 }));

	WaitKey();
}

結果

INIReader で読み込んだ INI ファイルが実行中に変更されると、INIReader::hasChanged() が true を返します。INIReader::reload() すると INI ファイルをロードしなおします。
次のプログラムを動かして、実行中に INI ファイルを編集してみましょう。

test2.ini

; 長方形のプロパティ
[Rectangle]
rect = (20, 50, 200, 50)
color = (255,64,128)
frame = true
frameThickness = 5
frameColor = (255,255,128)

; 長方形内のテキストのプロパティ
[Content]
text = Hello
textColor = (255,255,255)
# include <Siv3D.hpp>

void Main()
{
	const Font font{ 20 };

	INIReader ini(L"test2.ini");

	Rect rect = ini.get<Rect>(L"Rectangle.rect", { 0, 0, 0, 0 });
	Color color = ini.get<Color>(L"Rectangle.color", { 0, 0, 0 });
	bool frame = ini.get<bool>(L"Rectangle.frame", false);
	int frameThickness = ini.get<int>(L"Rectangle.frameThickness", 1);
	Color frameColor = ini.get<Color>(L"Rectangle.frameColor", { 0, 0, 0 });
	String text = ini.get<String>(L"Content.text", L"");
	Color textColor = ini.get<Color>(L"Content.textColor", { 0, 0, 0 });

	while (System::Update())
	{
		rect.draw(color);

		if (frame)
		{
			rect.drawFrame(0, frameThickness, frameColor);
		}

		font.drawCenter(text, rect.center, textColor);

		if (ini.hasChanged())
		{
			ini.reload();

			rect = ini.get<Rect>(L"Rectangle.rect", { 0, 0, 0, 0 });
			color = ini.get<Color>(L"Rectangle.color", { 0, 0, 0 });
			frame = ini.get<bool>(L"Rectangle.frame", false);
			frameThickness = ini.get<int>(L"Rectangle.frameThickness", 1);
			frameColor = ini.get<Color>(L"Rectangle.frameColor", { 0, 0, 0 });
			text = ini.get<String>(L"Content.text", L"");
			textColor = ini.get<Color>(L"Content.textColor", { 0, 0, 0 });
		}
	}
}

結果の一例
INI ファイルを編集すると即座に変更が反映されます。

アプリ制作で微調整が必要な個所では INIReader が大いに役立ちます。