Skip to content

1. はじめての Siv3D

この章では Siv3D プログラミングの雰囲気を体験します。

1.1 最初のサンプルコード

Siv3D プロジェクトを作成すると、最初に次のようなサンプルコードが用意されています。
このサンプルコードをいきなり全部理解する必要はありません。
まずは動かして体験しましょう。
サンプルコードを実行すると、次のようなプログラムが実行されます。

用意されているサンプルコードを表示する
# include <Siv3D.hpp>

void Main()
{
	// 背景の色を設定する | Set the background color
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// 画像ファイルからテクスチャを作成する | Create a texture from an image file
	const Texture texture{ U"example/windmill.png" };

	// 絵文字からテクスチャを作成する | Create a texture from an emoji
	const Texture emoji{ U"🦖"_emoji };

	// 太文字のフォントを作成する | Create a bold font with MSDF method
	const Font font{ FontMethod::MSDF, 48, Typeface::Bold };

	// テキストに含まれる絵文字のためのフォントを作成し、font に追加する | Create a font for emojis in text and add it to font as a fallback
	const Font emojiFont{ 48, Typeface::ColorEmoji };
	font.addFallback(emojiFont);

	// ボタンを押した回数 | Number of button presses
	int32 count = 0;

	// チェックボックスの状態 | Checkbox state
	bool checked = false;

	// プレイヤーの移動スピード | Player's movement speed
	double speed = 200.0;

	// プレイヤーの X 座標 | Player's X position
	double playerPosX = 400;

	// プレイヤーが右を向いているか | Whether player is facing right
	bool isPlayerFacingRight = true;

	while (System::Update())
	{
		// テクスチャを描く | Draw the texture
		texture.draw(20, 20);

		// テキストを描く | Draw text
		font(U"Hello, Siv3D!🎮").draw(64, Vec2{ 20, 340 }, ColorF{ 0.2, 0.4, 0.8 });

		// 指定した範囲内にテキストを描く | Draw text within a specified area
		font(U"Siv3D (シブスリーディー) は、ゲームやアプリを楽しく簡単な C++ コードで開発できるフレームワークです。")
			.draw(18, Rect{ 20, 430, 480, 200 }, Palette::Black);

		// 長方形を描く | Draw a rectangle
		Rect{ 540, 20, 80, 80 }.draw();

		// 角丸長方形を描く | Draw a rounded rectangle
		RoundRect{ 680, 20, 80, 200, 20 }.draw(ColorF{ 0.0, 0.4, 0.6 });

		// 円を描く | Draw a circle
		Circle{ 580, 180, 40 }.draw(Palette::Seagreen);

		// 矢印を描く | Draw an arrow
		Line{ 540, 330, 760, 260 }.drawArrow(8, SizeF{ 20, 20 }, ColorF{ 0.4 });

		// 半透明の円を描く | Draw a semi-transparent circle
		Circle{ Cursor::Pos(), 40 }.draw(ColorF{ 1.0, 0.0, 0.0, 0.5 });

		// ボタン | Button
		if (SimpleGUI::Button(U"count: {}"_fmt(count), Vec2{ 520, 370 }, 120, (checked == false)))
		{
			// カウントを増やす | Increase the count
			++count;
		}

		// チェックボックス | Checkbox
		SimpleGUI::CheckBox(checked, U"Lock \U000F033E", Vec2{ 660, 370 }, 120);

		// スライダー | Slider
		SimpleGUI::Slider(U"speed: {:.1f}"_fmt(speed), speed, 100, 400, Vec2{ 520, 420 }, 140, 120);

		// 左キーが押されていたら | If left key is pressed
		if (KeyLeft.pressed())
		{
			// プレイヤーが左に移動する | Player moves left
			playerPosX = Max((playerPosX - speed * Scene::DeltaTime()), 60.0);
			isPlayerFacingRight = false;
		}

		// 右キーが押されていたら | If right key is pressed
		if (KeyRight.pressed())
		{
			// プレイヤーが右に移動する | Player moves right
			playerPosX = Min((playerPosX + speed * Scene::DeltaTime()), 740.0);
			isPlayerFacingRight = true;
		}

		// プレイヤーを描く | Draw the player
		emoji.scaled(0.75).mirrored(isPlayerFacingRight).drawAt(playerPosX, 540);
	}
}

Visual Studio や Xcode では、新しいプログラムをビルドするときに、古いプログラムが実行中のままだとビルドが失敗します。実行中のプログラムを終了するには、ウィンドウを閉じるか、エスケープキーを押します。

1.2 プログラムをカスタマイズする

Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

はシーンの背景色の設定で、(R, G, B) = (0.6, 0.8, 0.7) です。数字を 0.0~1.0 の範囲で変更して、背景色を変えてみましょう。

const Texture emoji{ U"🦖"_emoji };

は絵文字データをロードしています。🐈 を 🐕 や 🐧, 🍔 に変えてみましょう(絵文字の前後に余計な空白を付けないよう気を付けてください)。Windows では [Windows] + [.] ショートカットキーで絵文字入力メニューが登場します。「いぬ」と日本語で入力して変換することで犬の絵文字を得ることもできます。

texture.draw(20, 20);

は画像ファイル windmill.png から作成したテクスチャを、画面上の位置 (x, y) = (20, 20) を指定して描画します。数字を変えて、描かれる位置を変更してみましょう。

font(U"Hello, Siv3D!🎮").draw(64, Vec2{ 20, 340 }, ColorF{ 0.2, 0.4, 0.8 });

はテキストを画面に描画します。メッセージを書き換えてみましょう。
64 は文字の大きさを指定しています。小さくしたり大きくしたりしてみましょう。

Circle{ Cursor::Pos(), 40 }.draw(ColorF{ 1.0, 0.0, 0.0, 0.5 });

は円をマウスカーソルの位置、半径 40, (R, G, B, 不透明度) = (1.0, 0.0, 0.0, 0.5) で描きます。円の半径や色、不透明度を変更してみましょう。

1.3 遊べるサンプル

Siv3D の様々な機能を体験できるおすすめサンプルを紹介します。

この Web サイトのサンプルコードは、コードエリアの右上にある「Copy to Clipboard」アイコンを押すと、コードをクリップボードにコピー できます。これまで書いていたサンプルコードを新しいコードで上書きして実行してみましょう。

どのサンプルも発展的な Siv3D の機能を使っているので、コードの意味を理解するのは難しいかもしれませんが、今はまだ気にしなくて大丈夫です。

1 | ブロックくずし

マウスでパドルを移動させてブロックを全部消しましょう。
ただし、クリアしても何も起こりません。

ブロックくずしのサンプルコードを表示する
# include <Siv3D.hpp>

////////////////////////////////
//
//	定数
//
////////////////////////////////

// 画面のサイズ (800x600)
constexpr Size SceneSize = Scene::DefaultSceneSize;

// 1 つのブロックのサイズ
constexpr Size BrickSize{ 40, 20 };

// 横に何個のブロックが並ぶか
constexpr int32 BrickCountX = (SceneSize.x / BrickSize.x);

// 縦に何個のブロックが並ぶか
constexpr int32 BrickCountY = 5;

// ブロックを並べ始める座標
constexpr Point BrickStartPosition{ 0, 60 };

// ボールの初期位置(ピクセル)
constexpr Vec2 BallInitialPos{ (SceneSize.x * 0.5), (SceneSize.y * 0.75) };

// ボールの速さ(ピクセル / 秒)
constexpr double BallSpeedPerSec = 480.0;

// パドルの Y 座標
constexpr int32 PaddleY = 500;

// パドルのサイズ
constexpr Size PaddleSize{ 60, 10 };

////////////////////////////////
//
//	初期状態を作る関数
//
////////////////////////////////

// ブロック(brick: レンガ)の初期配列を作る関数
Array<Rect> MakeBricks()
{
	// ブロックの配列(1 つのブロックを Rect で表現)
	Array<Rect> bricks;

	for (int32 y = 0; y < BrickCountY; ++y)
	{
		for (int32 x = 0; x < BrickCountX; ++x)
		{
			// ブロックの左上の X 座標
			const int32 posX = (x * BrickSize.x + BrickStartPosition.x);

			// ブロックの左上の Y 座標
			const int32 posY = (y * BrickSize.y + BrickStartPosition.y);

			// Rect を追加
			bricks << Rect{ posX, posY, BrickSize };
		}
	}

	return bricks;
}

// 初期のボールを作成する関数
Circle MakeBall()
{
	return{ BallInitialPos, 8 };
}

// 初期のボールの速度を作成する関数
Vec2 MakeBallVelocity()
{
	return{ 0, -BallSpeedPerSec };
}

////////////////////////////////
//
//	Main
//
////////////////////////////////

void Main()
{
	// ブロックの配列
	Array<Rect> bricks = MakeBricks();

	// ボール(中心座標と半径)
	Circle ball = MakeBall();

	// ボールの速度
	Vec2 ballVelocity = MakeBallVelocity();

	while (System::Update())
	{
		////////////////////////////////
		//
		//	状態の更新
		//
		////////////////////////////////

		// パドル
		const Rect paddle{ Arg::center(Cursor::Pos().x, PaddleY), PaddleSize };

		// ボールを移動
		ball.moveBy(ballVelocity * Scene::DeltaTime());

		// ブロックを順にチェック
		for (auto it = bricks.begin(); it != bricks.end(); ++it)
		{
			// ブロックとボールが交差していたら
			if (it->intersects(ball))
			{
				// ブロックの上辺、または底辺と交差していたら
				if (it->bottom().intersects(ball)
					|| it->top().intersects(ball))
				{
					// ボールの速度の Y 成分を反転
					ballVelocity.y *= -1;
				}
				else
				{
					// ボールの速度の X 成分を反転
					ballVelocity.x *= -1;
				}

				// ブロックを配列から削除(イテレータは無効になるので注意)
				bricks.erase(it);

				// これ以上チェックしない
				break;
			}
		}

		// 天井にぶつかったら
		if ((ball.y < 0) && (ballVelocity.y < 0))
		{
			// ボールの速度の Y 成分を反転
			ballVelocity.y *= -1;
		}

		// 左右の壁にぶつかったら
		if (((ball.x < 0) && (ballVelocity.x < 0))
			|| ((SceneSize.x < ball.x) && (0 < ballVelocity.x)))
		{
			// ボールの速度の X 成分を反転
			ballVelocity.x *= -1;
		}

		// パドルにあたったら
		if ((0 < ballVelocity.y) && paddle.intersects(ball))
		{
			// パドルの中心からの距離に応じてはね返る方向(速度ベクトル)を変える
			ballVelocity = Vec2{ (ball.x - paddle.center().x) * 10, -ballVelocity.y }
			.setLength(BallSpeedPerSec); // ボールの速さが BallSpeedPerSec になるよう、ベクトルの長さを調整
		}

		// 画面の底を越えたら(ゲームオーバーになったら)
		if (SceneSize.y <= ball.y)
		{
			// ブロックの配列をリセット
			bricks = MakeBricks();

			// ボールをリセット
			ball = MakeBall();

			// ボールの速度をリセット
			ballVelocity = MakeBallVelocity();
		}

		////////////////////////////////
		//
		//	描画
		//
		////////////////////////////////

		// マウスカーソルを非表示にする
		Cursor::RequestStyle(CursorStyle::Hidden);

		// すべてのブロックを描画する
		for (const auto& brick : bricks)
		{
			brick.stretched(-1) // 1 px 縮ませることで境界線をわかりやすくする
				.draw(HSV{ brick.y - 40 }); // Y 座標に応じて色を変える
		}

		// ボールを描く
		ball.draw();

		// パドルを描く
		paddle.rounded(3) // 角を少し丸くする
			.draw();
	}
}

2 | 万華鏡ペイント

万華鏡のような模様を描けます。右クリックすると、書いたものをリセットします。

万華鏡ペイントのサンプルコードを表示する
# include <Siv3D.hpp>

void Main()
{
	// キャンバスのサイズ
	constexpr Size CanvasSize{ 600, 600 };

	// ウィンドウをキャンバスのサイズに
	Window::Resize(CanvasSize);

	// 分割数
	constexpr int32 N = 12;

	// 背景色
	constexpr Color BackgroundColor{ 20, 40, 60 };

	// 書き込み用の画像
	Image image{ CanvasSize, BackgroundColor };

	// 画像を表示するための動的テクスチャ
	DynamicTexture texture{ image };

	while (System::Update())
	{
		if (MouseL.pressed())
		{
			// 画面の中心が (0, 0) になるようにマウスカーソルの座標を移動
			const Vec2 begin = (MouseL.down() ? Cursor::PosF() : Cursor::PreviousPosF()) - Scene::Center();
			const Vec2 end = (Cursor::PosF() - Scene::Center());

			for (auto i : step(N))
			{
				// 円座標に変換
				std::array<Circular, 2> cs = { begin, end };

				for (auto& c : cs)
				{
					// 角度をずらす
					c.theta = IsEven(i) ? (-c.theta - 2_pi / N * (i - 1)) : (c.theta + 2_pi / N * i);
				}

				// ずらした位置をもとに、画像に線を書き込む
				Line{ cs[0], cs[1] }.moveBy(Scene::Center())
					.overwrite(image, 2, HSV{ (Scene::Time() * 60), 0.5, 1.0 });
			}

			// 書き込んだ画像でテクスチャを更新
			texture.fillIfNotBusy(image);
		}
		else if (MouseR.down()) // 右クリックされたら
		{
			// 画像を塗りつぶす
			image.fill(BackgroundColor);

			// 塗りつぶした画像でテクスチャを更新
			texture.fill(image);
		}

		// テクスチャを描く
		texture.draw();
	}
}

3 | QR コード生成

テキストボックスに入力したテキストを QR コードに変換します。
スマートフォンのカメラで読み取ってみましょう。

QR コード生成のサンプルコードを表示する
# include <Siv3D.hpp>

void Main()
{
	Window::Resize(1280, 720);

	// テキストボックスの中身
	TextEditState textEdit{ U"abc" };

	String previous;

	// QR コードを書き込む動的テクスチャ
	DynamicTexture texture;

	while (System::Update())
	{
		// テキスト入力
		SimpleGUI::TextBox(textEdit, Vec2{ 20,20 }, 1240);

		// テキストの更新があれば QR コードを再作成
		if (const String current = textEdit.text;
			current != previous)
		{
			// 入力したテキストを QR コードに変換
			if (const auto qr = QR::EncodeText(current))
			{
				// 枠を付けて拡大した画像で動的テクスチャを更新
				texture.fill(QR::MakeImage(qr).scaled(500, 500, InterpolationAlgorithm::Nearest));
			}

			previous = current;
		}

		texture.drawAt(640, 400);
	}
}

4 | 物理演算ワールド

四角や丸を描くと物体が生成されて物理演算をします。
マウスホイールや右クリックで視点を移動できます。

物理演算ワールドのサンプルコードを表示する
# include <Siv3D.hpp>

void Main()
{
	// ウィンドウを 1280x720 にリサイズ
	Window::Resize(1280, 720);

	// 2D 物理演算のシミュレーションステップ(秒)
	constexpr double StepSec = (1.0 / 200.0);

	// 2D 物理演算のシミュレーション蓄積時間(秒)
	double accumulatorSec = 0.0;

	// 2D 物理演算のワールド
	P2World world;

	// [_] 地面
	const P2Body ground = world.createLine(P2Static, Vec2{ 0, 0 }, Line{ -600, 0, 600, 0 });

	// 物体
	Array<P2Body> bodies;

	// 2D カメラ
	Camera2D camera{ Vec2{ 0, -300 } };

	LineString points;

	while (System::Update())
	{
		for (accumulatorSec += Scene::DeltaTime(); StepSec <= accumulatorSec; accumulatorSec -= StepSec)
		{
			// 2D 物理演算のワールドを更新
			world.update(StepSec);
		}

		// 地面より下に落ちた物体は削除
		bodies.remove_if([](const P2Body& b) { return (200 < b.getPos().y); });

		// 2D カメラの更新
		camera.update();
		{
			// 2D カメラから Transformer2D を作成
			const auto t = camera.createTransformer();

			// 左クリックもしくはクリックしたままの移動が発生したら
			if (MouseL.down() ||
				(MouseL.pressed() && (not Cursor::DeltaF().isZero())))
			{
				points << Cursor::PosF();
			}
			else if (MouseL.up())
			{
				points = points.simplified(2.0);

				if (const Polygon polygon = Polygon::CorrectOne(points))
				{
					const Vec2 pos = polygon.centroid();

					bodies << world.createPolygon(P2Dynamic, pos, polygon.movedBy(-pos));
				}

				points.clear();
			}

			// すべてのボディを描画
			for (const auto& body : bodies)
			{
				body.draw(HSV{ body.id() * 10.0 });
			}

			// 地面を描画
			ground.draw(Palette::Skyblue);

			points.draw(3);
		}

		// 2D カメラの操作を描画
		camera.draw(Palette::Orange);
	}
}

5 | kd-tree

kd-木は近くにあるユニットを高速に検索できるデータ構造です。
シミュレーションゲームなどで役に立ちます。

kd-tree のサンプルコードを表示する
# include <Siv3D.hpp>

struct Unit
{
	Circle circle;

	ColorF color;

	void draw() const
	{
		circle.draw(color);
	}
};

// Unit を KDTree で扱えるようにするためのアダプタ
struct UnitAdapter : KDTreeAdapter<Array<Unit>, Vec2>
{
	static const element_type* GetPointer(const point_type& point)
	{
		return point.getPointer();
	}

	static element_type GetElement(const dataset_type& dataset, size_t index, size_t dim)
	{
		return dataset[index].circle.center.elem(dim);
	}
};

void Main()
{
	// 200 個の Unit を生成
	Array<Unit> units(200);

	for (auto& unit : units)
	{
		unit.circle = Circle{ RandomVec2(Scene::Rect()), 4 };
		unit.color = RandomColorF();
	}

	// kd-tree を構築
	KDTree<UnitAdapter> kdTree{ units };

	// radius search する際の探索距離
	constexpr double SearchDistance = 80.0;

	while (System::Update())
	{
		const Vec2 cursorPos = Cursor::PosF();

		Circle{ cursorPos, SearchDistance }.draw(ColorF{ 1.0, 0.2 });

		// SearchDistance 以内の距離にある Unit のインデックスを取得
		for (auto index : kdTree.radiusSearch(cursorPos, SearchDistance))
		{
			Line{ cursorPos, units[index].circle.center }.draw(4);
		}

		// ユニットを描画
		for (const auto& unit : units)
		{
			unit.draw();
		}
	}
}

6 | 音楽プレーヤー

パソコンに保存されている音楽ファイルを再生して、スペクトラムも表示します。 パソコンに再生できる音楽ファイルが無い場合、サンプル用の音楽ファイルが App/example/test.mp3 にあります。フリーの BGM 素材 (MP3) をダウンロードして試すこともできます。

音楽プレーヤーのサンプルコードを表示する
# include <Siv3D.hpp>

void Main()
{
	// 音楽
	Audio audio;

	// FFT の結果
	FFTResult fft;

	// 再生位置の変更の有無
	bool seeking = false;

	while (System::Update())
	{
		ClearPrint();

		// 再生・演奏時間
		const String time = FormatTime(SecondsF{ audio.posSec() }, U"M:ss")
			+ U" / " + FormatTime(SecondsF{ audio.lengthSec() }, U"M:ss");

		// プログレスバーの進み具合
		double progress = (static_cast<double>(audio.posSample()) / audio.samples());

		if (audio.isPlaying())
		{
			// FFT 解析
			FFT::Analyze(fft, audio);

			// 結果を可視化
			for (auto i : step(Min(Scene::Width(), static_cast<int32>(fft.buffer.size()))))
			{
				const double size = (Pow(fft.buffer[i], 0.6f) * 1000);
				RectF{ Arg::bottomLeft(i, 480), 1, size }.draw(HSV{ 240.0 - i });
			}

			// 周波数表示
			Rect{ Cursor::Pos().x, 0, 1, 480 }.draw();
			Print << U"{:.2f} Hz"_fmt(Cursor::Pos().x * fft.resolution);
		}

		// フォルダから音楽ファイルを開く
		if (SimpleGUI::Button(U"Open", Vec2{ 40, 500 }, 120))
		{
			// 現在再生中のオーディオを 0.5 秒かけてフェードアウトさせて停止
			audio.stop(0.5s);

			// ファイルダイアログからオーディオを開く
			audio = Dialog::OpenAudio();

			// オーディオを再生
			audio.play();
		}

		// 再生
		if (SimpleGUI::Button(U"\U000F040A Play", Vec2{ 170, 500 }, 120, audio && (not audio.isPlaying())))
		{
			audio.play(0.2s);
		}

		// 一時停止
		if (SimpleGUI::Button(U"\U000F03E4 Pause", Vec2{ 300, 500 }, 120, audio.isPlaying()))
		{
			audio.pause(0.2s);
		}

		// スライダー
		if (SimpleGUI::Slider(time, progress, Vec2{ 40, 540 }, 120, 590, (not audio.isEmpty())))
		{
			audio.pause(0.05s);

			while (audio.isPlaying()) // 再生が止まるまで待機
			{
				System::Sleep(0.01s);
			}

			// 再生位置を変更
			audio.seekSamples(static_cast<size_t>(audio.samples() * progress));

			// ノイズを避けるため、スライダーから手を離すまで再生は再開しない
			seeking = true;
		}
		else if (seeking && MouseL.up())
		{
			// 再生を再開
			audio.play(0.05s);
			seeking = false;
		}
	}

	// 終了時に再生中の場合、音量をフェードアウト
	if (audio.isPlaying())
	{
		audio.fadeVolume(0.0, 0.3s);
		System::Sleep(0.3s);
	}
}

7 | ナビメッシュ

制御点をもとに道路を作り、始点から終点までの最短経路を求めます。

ナビメッシュのサンプルコードを表示する
# include <Siv3D.hpp>

void Main()
{
	Window::Resize(1280, 720);

	Scene::SetBackground(ColorF{ 0.8, 0.9, 0.8 });

	// 制御点
	Array<Vec2> points;

	// 道路用ポリゴン
	Polygon polygon;

	// 経路
	LineString path;

	// ナビメッシュ
	NavMesh navMesh;

	while (System::Update())
	{
		// 左クリックされたら
		if (MouseL.down())
		{
			// 制御点を追加
			points << Cursor::Pos();

			// スプライン曲線を作り丸く太らせて道路を作る
			polygon = Spline2D{ points }.calculateRoundBuffer(24, 8, 12);

			// ポリゴンからナビメッシュを構築(エージェントの半径 20 ピクセル)
			navMesh.build(polygon, { .agentRadius = 20.0 });

			// 制御点の先頭から終点までの経路を計算
			path = navMesh.query(points.front(), points.back());
		}

		// 道路を描画
		polygon.draw(ColorF{ 1.0 }).drawFrame(2, ColorF{ 0.7 });

		// 経路があれば
		if (path)
		{
			// 経路を描画
			path.draw(8, ColorF{ 0.1, 0.5, 0.9 });

			// スタート地点に円を描画
			path.front().asCircle(12).draw(ColorF{ 1.0, 0.3, 0.0 });

			// ゴール地点に円を描画
			path.back().asCircle(12).draw(ColorF{ 1.0, 0.3, 0.0 });
		}
	}
}

8 | ライフゲーム エディタ

ライフゲームを実行するプログラムです。
ライフゲームとは: ライフゲーム (Wikipedia)

ライフゲーム エディタのサンプルコードを表示する
# include <Siv3D.hpp>

// 1 セルが 1 バイトになるよう、ビットフィールドを使用
struct Cell
{
	bool previous : 1 = 0;
	bool current : 1 = 0;
};

// フィールドをランダムなセル値で埋める関数
void RandomFill(Grid<Cell>& grid)
{
	grid.fill(Cell{});

	// 境界のセルを除いて更新
	for (auto y : Range(1, grid.height() - 2))
	{
		for (auto x : Range(1, grid.width() - 2))
		{
			grid[y][x] = Cell{ 0, RandomBool(0.5) };
		}
	}
}

// フィールドの状態を更新する関数
void Update(Grid<Cell>& grid)
{
	for (auto& cell : grid)
	{
		cell.previous = cell.current;
	}

	// 境界のセルを除いて更新
	for (auto y : Range(1, grid.height() - 2))
	{
		for (auto x : Range(1, grid.width() - 2))
		{
			const int32 c = grid[y][x].previous;

			int32 n = 0;
			n += grid[y - 1][x - 1].previous;
			n += grid[y - 1][x].previous;
			n += grid[y - 1][x + 1].previous;
			n += grid[y][x - 1].previous;
			n += grid[y][x + 1].previous;
			n += grid[y + 1][x - 1].previous;
			n += grid[y + 1][x].previous;
			n += grid[y + 1][x + 1].previous;

			// セルの状態の更新
			grid[y][x].current = (c == 0 && n == 3) || (c == 1 && (n == 2 || n == 3));
		}
	}
}

// フィールドの状態を画像化する関数
void CopyToImage(const Grid<Cell>& grid, Image& image)
{
	for (auto y : step(image.height()))
	{
		for (auto x : step(image.width()))
		{
			image[y][x] = grid[y + 1][x + 1].current
				? Color{ 0, 255, 0 } : Palette::Black;
		}
	}
}

void Main()
{
	// フィールドのセルの数(横)
	constexpr int32 Width = 60;

	// フィールドのセルの数(縦)
	constexpr int32 Height = 60;

	// 計算をしない境界部分も含めたサイズで二次元配列を確保
	Grid<Cell> grid((Width + 2), (Height + 2), Cell{ 0,0 });

	// フィールドの状態を可視化するための画像
	Image image{ Width, Height, Palette::Black };

	// 動的テクスチャ
	DynamicTexture texture{ image };

	Stopwatch stopwatch{ StartImmediately::Yes };

	// 自動再生
	bool autoStep = false;

	// 更新頻度
	double speed = 0.5;

	// グリッドの表示
	bool showGrid = true;

	// 画像の更新の必要があるか
	bool updated = false;

	while (System::Update())
	{
		// フィールドをランダムな値で埋めるボタン
		if (SimpleGUI::ButtonAt(U"Random", Vec2{ 700, 40 }, 170))
		{
			RandomFill(grid);
			updated = true;
		}

		// フィールドのセルをすべてゼロにするボタン
		if (SimpleGUI::ButtonAt(U"Clear", Vec2{ 700, 80 }, 170))
		{
			grid.fill({ 0, 0 });
			updated = true;
		}

		// 一時停止 / 再生ボタン
		if (SimpleGUI::ButtonAt(autoStep ? U"Pause" : U"Run ▶", Vec2{ 700, 160 }, 170))
		{
			autoStep = !autoStep;
		}

		// 更新頻度変更スライダー
		SimpleGUI::SliderAt(U"Speed", speed, 1.0, 0.1, Vec2{ 700, 200 }, 70, 100);

		// 1 ステップ進めるボタン、または更新タイミングの確認
		if (SimpleGUI::ButtonAt(U"Step", Vec2{ 700, 240 }, 170)
			|| (autoStep && stopwatch.sF() >= (speed * speed)))
		{
			Update(grid);
			updated = true;
			stopwatch.restart();
		}

		// グリッド表示の有無を指定するチェックボックス
		SimpleGUI::CheckBoxAt(showGrid, U"Grid", Vec2{ 700, 320 }, 170);

		// フィールド上でのセルの編集
		if (Rect{ 0, 0, 599 }.mouseOver())
		{
			const Point target = (Cursor::Pos() / 10 + Point{ 1, 1 });

			if (MouseL.pressed())
			{
				grid[target].current = true;
				updated = true;
			}
			else if (MouseR.pressed())
			{
				grid[target].current = false;
				updated = true;
			}
		}

		// 画像の更新
		if (updated)
		{
			CopyToImage(grid, image);
			texture.fill(image);
			updated = false;
		}

		// 画像をフィルタなしで拡大して表示
		{
			const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
			texture.scaled(10).draw();
		}

		// グリッドの表示
		if (showGrid)
		{
			for (auto i : step(61))
			{
				Rect{ 0, i * 10, 600, 1 }.draw(ColorF{ 0.4 });
				Rect{ i * 10, 0, 1, 600 }.draw(ColorF{ 0.4 });
			}
		}

		// 盤面上ではマウスカーソルの代わりに選択セルを強調表示
		if (Rect{ 0, 0, 599 }.mouseOver())
		{
			Cursor::RequestStyle(CursorStyle::Hidden);
			Rect{ Cursor::Pos() / 10 * 10, 10 }.draw(Palette::Orange);
		}
	}
}

9 | 模写アプリ

真っ白な画像からスタートして、ランダムな色の円を重ねていくことで、目標の画像に近づけていくプログラムです。パソコンに適当な画像ファイルが無い場合、サンプル用の画像ファイルが App/example/ フォルダにあります。

ランダムな色の円で目的の絵を作るサンプルコードを表示する
# include <Siv3D.hpp>

// 2 つの画像の距離を計算する関数
double Diff(const Image& a, const Image& b)
{
	const Color* pA = a.data();
	const Color* pB = b.data();
	const Color* const pAEnd = (pA + a.num_pixels());
	double d = 0.0;

	// すべてのピクセルに対して
	while (pA != pAEnd)
	{
		d += AbsDiff(pA->r, pB->r) + AbsDiff(pA->g, pB->g) + AbsDiff(pA->b, pB->b);
		++pA;
		++pB;
	}

	return d;
}

void Main()
{
	// 目標とする画像をファイルダイアログで選択、シーンのサイズにフィットするようリサイズ
	const Image target = Dialog::OpenImage().fit(Scene::Size());

	// 現在の画像
	Image image{ target.size(), Palette::White };

	// 直前の画像
	Image old = image;

	// 現在の画像を表示するための動的テクスチャ
	DynamicTexture texture{ image };

	// 目標との距離
	double d1 = Diff(target, image);

	while (System::Update())
	{
		for (int32 i = 0; i < 100; ++i)
		{
			// ランダムな座標
			const Point pos = RandomPoint(Rect{ image.size() });

			// ランダムな色
			const ColorF color{ Random(), Random(), Random(), Random() };

			// ランダムな半径
			const int32 size = Random(1, 10);

			// 円を現在の画像に書き込む
			Circle{ pos, size }.paint(image, color);

			// 目標との距離を計算
			const double d2 = Diff(target, image);

			if (d2 < d1) // 目標に近づいていたら採用
			{
				d1 = d2;
				old = image;
			}
			else // 近づいていなかったら元に戻す
			{
				image = old;
			}
		}

		// 動的テクスチャを更新
		texture.fill(image);

		// テクスチャを画面の中心に描画
		texture.drawAt(Scene::Center());

		// 保存ボタン
		if (SimpleGUI::Button(U"Save", Vec2{ 660, 550 }))
		{
			// 現在の画像をファイルダイアログ経由で保存
			image.saveWithDialog();
		}
	}
}

10 | マイクで入力した音の周波数解析

マイクで入力した音声波形のスペクトラムをリアルタイムで表示します。

マイクで入力した音の周波数解析のサンプルコードを表示する
# include <Siv3D.hpp>

void Main()
{
	// マイクをセットアップ(ただちに録音をスタート)
	Microphone mic{ StartImmediately::Yes };

	if (not mic)
	{
		// マイクを利用できない場合、終了
		throw Error{ U"Microphone not available" };
	}

	FFTResult fft;

	while (System::Update())
	{
		// FFT の結果を取得
		mic.fft(fft);

		// 結果を可視化
		for (auto i : step(800))
		{
			const double size = (Pow(fft.buffer[i], 0.6f) * 1200);
			RectF{ Arg::bottomLeft(i, 600), 1, size }.draw(HSV{ 240 - i });
		}

		// 周波数表示
		Rect{ Cursor::Pos().x, 0, 1, Scene::Height() }.draw();

		ClearPrint();
		Print << U"{} Hz"_fmt(Cursor::Pos().x * fft.resolution);
	}
}

11 | ピアノ

キーボードを使ってピアノを演奏できるプログラムです。
コードを書き換えて楽器の音を変更できます。

ピアノのサンプルコードを表示する
# include <Siv3D.hpp>

void Main()
{
	// 白鍵の大きさ
	constexpr Size KeySize{ 55, 400 };

	// 楽器の種類
	constexpr GMInstrument Instrument = GMInstrument::Shamisen;

	// ウインドウをリサイズ
	Window::Resize((12 * KeySize.x), KeySize.y);

	// 鍵盤の数
	constexpr int32 NumKeys = 20;

	// 音を作成
	std::array<Audio, NumKeys> sounds;
	for (auto i : step(NumKeys))
	{
		sounds[i] = Audio{ Instrument, static_cast<uint8>(PianoKey::A3 + i), 0.5s };
	}

	// 対応するキー
	constexpr std::array<Input, NumKeys> Keys =
	{
		KeyTab, Key1, KeyQ,
		KeyW, Key3, KeyE, Key4, KeyR, KeyT, Key6, KeyY, Key7, KeyU, Key8, KeyI,
		KeyO, Key0, KeyP, KeyMinus, KeyEnter,
	};

	// 描画位置計算用のオフセット値
	constexpr std::array<int32, NumKeys> KeyPositions =
	{
		0, 1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22
	};

	while (System::Update())
	{
		// キーが押されたら対応する音を再生
		for (auto i : step(NumKeys))
		{
			if (Keys[i].down())
			{
				sounds[i].playOneShot(0.5);
			}
		}

		// 白鍵を描画
		for (auto i : step(NumKeys))
		{
			// オフセット値が偶数
			if (IsEven(KeyPositions[i]))
			{
				RectF{ (KeyPositions[i] / 2 * KeySize.x), 0, KeySize }
				.stretched(-1).draw(Keys[i].pressed() ? Palette::Pink : Palette::White);
			}
		}

		// 黒鍵を描画
		for (auto i : step(NumKeys))
		{
			// オフセット値が奇数
			if (IsOdd(KeyPositions[i]))
			{
				RectF{ (KeySize.x * 0.68 + KeyPositions[i] / 2 * KeySize.x), 0, (KeySize.x * 0.58), (KeySize.y * 0.62) }
				.draw(Keys[i].pressed() ? Palette::Pink : Color(24));
			}
		}
	}
}

12 | 3D 描画

3D 描画も扱えます。

3D 描画のサンプルコードを表示する
# include <Siv3D.hpp>

void Main()
{
	// ウインドウとシーンを 1280x720 にリサイズ
	Window::Resize(1280, 720);

	// 背景色 (リニアレンダリング用なので removeSRGBCurve() で sRGB カーブを除去)
	const ColorF backgroundColor = ColorF{ 0.4, 0.6, 0.8 }.removeSRGBCurve();

	// UV チェック用テクスチャ (ミップマップ使用。リニアレンダリング時に正しく扱われるよう、sRGB テクスチャであると明示)
	const Texture uvChecker{ U"example/texture/uv.png", TextureDesc::MippedSRGB };

	// 3D シーンを描く、マルチサンプリング対応レンダーテクスチャ
	// リニア色空間のレンダリング用に TextureFormat::R8G8B8A8_Unorm_SRGB
	// 奥行きの比較のための深度バッファも使うので HasDepth::Yes
	// マルチサンプル・レンダーテクスチャなので、描画内容を使う前に resolve() が必要
	const MSRenderTexture renderTexture{ Scene::Size(), TextureFormat::R8G8B8A8_Unorm_SRGB, HasDepth::Yes };

	// 3D シーンのデバッグ用カメラ
	// 縦方向の視野角 30°, カメラの位置 (10, 16, -32)
	// 前後移動: [W][S], 左右移動: [A][D], 上下移動: [E][X], 注視点移動: アローキー, 加速: [Shift][Ctrl]
	DebugCamera3D camera{ renderTexture.size(), 30_deg, Vec3{ 10, 16, -32 } };

	while (System::Update())
	{
		// デバッグカメラの更新 (カメラの移動スピード: 2.0)
		camera.update(2.0);

		// 3D シーンにカメラを設定
		Graphics3D::SetCameraTransform(camera);

		// 3D 描画
		{
			// renderTexture を背景色で塗りつぶし、
			// renderTexture を 3D 描画のレンダーターゲットに
			const ScopedRenderTarget3D target{ renderTexture.clear(backgroundColor) };

			// 床を描画
			Plane{ 64 }.draw(uvChecker);

			// ボックスを描画
			Box{ -8,2,0,4 }.draw(ColorF{ 0.8, 0.6, 0.4 }.removeSRGBCurve());

			// 球を描画
			Sphere{ 0,2,0,2 }.draw(ColorF{ 0.4, 0.8, 0.6 }.removeSRGBCurve());

			// 円柱を描画
			Cylinder{ 8, 2, 0, 2, 4 }.draw(ColorF{ 0.6, 0.4, 0.8 }.removeSRGBCurve());
		}

		// 3D シーンを 2D シーンに描画
		{
			// renderTexture を resolve する前に 3D 描画を実行する
			Graphics3D::Flush();

			// マルチサンプル・テクスチャのリゾルブ
			renderTexture.resolve();

			// リニアレンダリングされた renderTexture をシーンに転送
			Shader::LinearToScreen(renderTexture);
		}
	}
}