Play Siv3D!

ゲームとメディアアートのための C++ ライブラリ「Siv3D」

Boost. 勉強会 Siv3D デモ

Boost. 勉強会 Siv3D デモ

#1 Hello, Siv3D

# include <Siv3D.hpp>

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

	while (System::Update())
	{
		font(L"ようこそ、Siv3D の世界へ!").draw();

		Circle(Mouse::Pos(), 50).draw({ 255, 0, 0, 127 });
	}
}

#2 ブロック崩し

# include <Siv3D.hpp>

void Main()
{
	const Point blockSize(40, 20);
	const double speed = 8.0;
	Rect wall(60, 10);
	Circle ball(320, 400, 8);
	Vec2 ballSpeed(0, -speed);

	Array<Rect> blocks;
	for (auto p : step({ Window::Width() / blockSize.x , 5}))
	{
		blocks.emplace_back((p*blockSize).moveBy(0, 60), blockSize);
	}

	while (System::Update())
	{
		ball.moveBy(ballSpeed);
		wall.setCenter(Mouse::Pos().x, 420);

		for (auto it = blocks.begin(); it != blocks.end(); ++it)
		{
			if (it->intersects(ball))
			{
				(it->bottom.intersects(ball) || it->top.intersects(ball) ? ballSpeed.y : ballSpeed.x) *= -1;

				blocks.erase(it);
				break;
			}
		}

		for (auto const& block : blocks)
		{
			block.stretched(-1).draw(HSV(block.y - 40));
		}

		if (ball.y<0 && ballSpeed.y<0)
		{
			ballSpeed.y *= -1;
		}

		if ((ball.x < 0 && ballSpeed.x < 0) || (Window::Width() < ball.x && ballSpeed.x > 0))
		{
			ballSpeed.x *= -1;
		}

		if (ballSpeed.y>0 && wall.intersects(ball))
		{
			ballSpeed = Vec2((ball.x - wall.center.x) / 8, -ballSpeed.y).normalized()*speed;
		}

		ball.draw();
		wall.draw();
	}
}

#3 マイクで録音した音の FFT

# include <Siv3D.hpp>

void Main()
{
	Window::Resize(1024, 320);

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

	while (System::Update())
	{
		if (Recorder::IsEnded())
		{
			Recorder::Restart();
		}

		const auto fft = FFT::Analyze(Recorder::GetWave(), Recorder::GetPosSample());

		for (auto i : step(1024))
		{
			RectF(i, Window::Height(), 1, -Pow(fft.buffer[i], 0.5f) * 500).draw(HSV(i));
		}
	}
}

#4 タイムストレッチとピッチシフト

# include <Siv3D.hpp>

void Main()
{
	auto sound = Dialog::OpenDynamicSound();
	sound.play();

	while (System::Update())
	{
		Line(0, 240, 640, 240).drawArrow(4, { 20, 20 });
		Line(320, 480, 320, 0).drawArrow(4, { 20, 20 });

		const auto pos = Mouse::Pos();
		Circle(pos, 20).draw(Palette::Orange);

		const double tempo = Exp2((pos.x - 320) / 240.0);
		const double pitch = -(pos.y - 240) / 80.0;

		sound.changeTempo(tempo);
		sound.changePitchSemitones(pitch);

		PutText(PyFmt, L"tempo: {}\npitch: {}", tempo, pitch).from(20, 20);
	}
}

#5 標準 GUI

# include <Siv3D.hpp>

void Main()
{
	Graphics::SetBackground(Color(160, 200, 100));

	const Texture texture(Image(L"Example/Windmill.png").scale(0.3));

	GUI gui(GUIStyle::Default);
	gui.setTitle(L"タイトル");

	// テキスト
	gui.add(GUIText::Create(L"テキストと画像"));

	// 画像
	gui.addln(GUITexture::Create(texture));

	// 水平線
	gui.add(L"hr", GUIHorizontalLine::Create(1));
	gui.horizontalLine(L"hr").style.color = Color(127);

	// ボタン
	gui.add(L"bt1", GUIButton::Create(L"OK"));
	gui.add(L"bt2", GUIButton::Create(L"キャンセル"));
	gui.add(L"bt3", GUIButton::Create(L"OK", false));
	gui.addln(L"bt4", GUIButton::Create(L"キャンセル", false));

	// トグルスイッチ
	gui.add(L"ts1", GUIToggleSwitch::Create(L"OFF", L"ON", false));
	gui.add(L"ts2", GUIToggleSwitch::Create(L"OFF", L"ON", true));
	gui.add(L"ts3", GUIToggleSwitch::Create(L"OFF", L"ON", false, false));
	gui.addln(L"ts4", GUIToggleSwitch::Create(L"OFF", L"ON", true, false));

	// スライダー
	gui.add(L"sl1", GUISlider::Create(0.0, 100.0, 30.0, 200));
	gui.addln(L"sl2", GUISlider::Create(0.0, 100.0, 30.0, 200, false));

	// チェックボックス
	gui.add(L"cb1", GUICheckBox::Create({ L"Red", L"Green", L"Blue" }, { 1u, 2u }, true));
	gui.add(L"cb2", GUICheckBox::Create({ L"Red", L"Green", L"Blue" }, { 1u, 2u }, false));

	// ラジオボタン
	gui.add(L"rb1", GUIRadioButton::Create({ L"Red", L"Green", L"Blue" }, none, true));
	gui.add(L"rb2", GUIRadioButton::Create({ L"Red", L"Green", L"Blue" }, 1u, true));
	gui.addln(L"rb3", GUIRadioButton::Create({ L"Red", L"Green", L"Blue" }, 2u, false));

	// テキストフィールド
	gui.add(L"tf1", GUITextField::Create(none));
	gui.addln(L"tf2", GUITextField::Create(none, false));

	// テキストエリア
	gui.add(L"ta1", GUITextArea::Create(2, 10));
	gui.addln(L"ta2", GUITextArea::Create(2, 10, none, false));

	while (System::Update())
	{
		if (gui.button(L"bt1").pushed)
		{
			System::Exit();
		}
	}
}

#6 ツイート投稿

# include <Siv3D.hpp>

void Main()
{
	Graphics::SetBackground(Color(160, 200, 100));

	// Consumer Key (API Key)
	const String API_key = L"XXXXXXXXXXXXXXXXXXXXXXXXXXXX";

	// Consumer Secret (API Secret)
	const String API_secret = L"XXXXXXXXXXXXXXXXXXXXXXXXXXXX";

	TwitterClient twitter(API_key, API_secret);
	twitter.openTokenPage();

	GUI guiAuth(GUIStyle::Default);
	guiAuth.setTitle(L"PIN の入力");
	guiAuth.addln(L"PIN", GUITextField::Create(7));
	guiAuth.add(L"auth", GUIButton::Create(L"認証"));
	guiAuth.setPos(40, 40);

	GUI guiTweet(GUIStyle::Default);
	guiTweet.setTitle(L"ツイート");
	guiTweet.addln(L"text", GUITextArea::Create(6, 24, 140, false));
	guiTweet.add(L"tweet", GUIButton::Create(L"ツイート"));
	guiTweet.setPos(40, 200);

	while (System::Update())
	{
		guiAuth.button(L"auth").enabled = (guiAuth.textField(L"PIN").text.length == 7);
		guiTweet.button(L"tweet").enabled = !guiTweet.textArea(L"text").text.isEmpty;

		if (guiAuth.button(L"auth").pushed && twitter.verifyPIN(guiAuth.textField(L"PIN").text))
		{
			guiAuth.release();
			guiTweet.textArea(L"text").enabled = true;
		}

		if (guiTweet.button(L"tweet").pushed)
		{
			twitter.tweet(guiTweet.textArea(L"text").text);

			guiTweet.textArea(L"text").setText(L"");
		}
	}
}

#7 Image to Polygon

# include <Siv3D.hpp>

void Main()
{
	double max_distance = 4.0;

	GUI gui(GUIStyle::Default);
	gui.addln(L"max_distance", GUISlider::Create(0.5, 40.0, max_distance, 300));
	gui.addln(L"vertices", GUIText::Create(L""));
	gui.addln(L"original", GUIText::Create(L""));
	gui.setPos(280, 20);

	const Image image(L"Example/siv3d-kun.png");

	const Polygon polygon = Imaging::FindExternalContour(image, true);

	Polygon simplifiedPolygon = polygon.simplified(gui.slider(L"max_distance").value);

	const Texture texture(image);

	const Circle circle(350, 250, 15);

	while (System::Update())
	{
		if (gui.slider(L"max_distance").hasChanged)
		{
			max_distance = gui.slider(L"max_distance").value;

			simplifiedPolygon = polygon.simplified(max_distance);
		}

		texture.draw(Alpha(60));

		gui.text(L"vertices").text = Format(simplifiedPolygon.num_vertices, L" 頂点\tmax_distance : ", max_distance);

		gui.text(L"original").text = Format(L"オリジナル : ", polygon.num_vertices, L" 頂点");

		simplifiedPolygon.drawWireframe(1.0, Palette::Yellowgreen);

		const Polygon movedPolygon = simplifiedPolygon.movedBy(Mouse::Pos() - simplifiedPolygon.boundingRect.center);

		const Color color = Alpha(movedPolygon.intersects(circle) ? 180 : 40);

		circle.draw(Palette::Red);

		movedPolygon.draw(color);
	}
}

#8 AR マーカー

# include <Siv3D.hpp>

void Main()
{
	if (!Webcam::Start())
	{
		return;
	}

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

	DynamicTexture texture;

	Array<ARMarker> markers;

	const Font font(30, Typeface::Heavy);

	while (System::Update())
	{
		if (Webcam::HasNewFrame())
		{
			Webcam::GetFrame(texture);
		}

		if (AR::HasNewFrame())
		{
			AR::GetFrame(markers);
		}

		if (texture)
		{
			texture.draw();
		}

		for (const auto& marker : markers)
		{
			marker.quad.drawFrame(5, Palette::Red);

			font(marker.id).drawCenter(marker.screenPos, Palette::Red);
		}
	}
}

#おまけ 10 行でお絵かきソフト

# include <Siv3D.hpp>

void Main()
{
	Image image(Window::Size(), Palette::White);

	DynamicTexture texture(image);

	while (System::Update())
	{
		if (Input::MouseL.pressed)
		{
			const Point pos = Input::MouseL.clicked ? Mouse::Pos() : Mouse::PreviousPos();

			Line(pos, Mouse::Pos()).overwrite(image, 8, Palette::Orange);

			texture.fill(image);
		}

		texture.draw();
	}
}

#おまけ 12 種類の方法で遊べるブロック崩し

# include <Siv3D.hpp>
# include <Siv3DAddon/LeapMotion.hpp>

enum class InputType {
	Mouse, Keyboard, Leap, XInput, Clap, Acceleration, Arduino, Kinect, Pentab, Touch, AR, FFT
};

Array<Rect> GetWalls(InputType type, double& rotation) {
	const int W = Window::Width(), H = Window::Height();
	static Serial arduino(7);
	static int x = 180;
	Array<Rect> walls;

	rotation *= (type == InputType::AR);

	if (type == InputType::Mouse)
	{
		walls.push_back(Rect(200, 20).setCenter(Mouse::Pos().x, H - 100));
	}
	else if (type == InputType::Keyboard)
	{
		x += -24 * Input::KeyLeft.pressed + 24 * Input::KeyRight.pressed;
		walls.push_back(Rect(200, 20).setCenter(x, H - 100));
	}
	else if (type == InputType::Leap)
	{
		for (const auto& pointable : LeapMotion::Hands())
			walls.emplace_back(int(W / 2 + pointable.pos.x * 8), H - 100, 200, 20);
	}
	else if (type == InputType::XInput)
	{
		x += (XInput(0).leftThumbX / 1500) * (abs(XInput(0).leftThumbX) > 7000);
		walls.push_back(Rect(200, 20).setCenter(x, H - 100));
	}
	else if (type == InputType::Clap)
	{
		if (x == 10 && Recorder::GetMaxAmplitude() >= 0.2)
			x = W + 200;
		walls.push_back(Rect(x = Max(10, x - 40), 20).setCenter(W / 2, H - 100));
	}
	else if (type == InputType::Acceleration)
	{
		walls.push_back(Rect(300, 20).setCenter(W / 2 + int(KinectV1::GetAcceleration().x * (W + 200)), H - 100));
	}
	else if (type == InputType::Arduino)
	{
		Array<uint8> data;
		if (arduino.available() && arduino.readBytes(data))
			x = data[0] / 256.0 * W;
		walls.emplace_back(x, H - 100, 200, 20);
	}
	else if (type == InputType::Kinect)
	{
		std::array<Optional<KinectV1Body>, 2> bodies;
		if (KinectV1::GetBodyFrame(bodies))
			bodies[0].then([&](KinectV1Body b){ x = (b.joints[V1JointType::Head].depthSpacePos.x - 320) * 6; });
		walls.push_back(Rect(400, 20).setCenter(x, H - 100));
	}
	else if (type == InputType::Pentab)
	{
		walls.push_back(Rect(200 + Pentablet::Pressure() * 300, 20).setCenter(Mouse::Pos().x, H - 100));
		rotation = Radians(Pentablet::DegreeY());
	}
	else if (type == InputType::Touch)
	{
		for (const auto& touch : Input::GetTouches())
			walls.emplace_back(Rect(200, 40).setCenter(touch.pos));
	}
	else if (type == InputType::AR)
	{
		Array<ARMarker> markers;
		if (AR::HasNewFrame() && AR::GetFrame(markers) && !markers.empty()) {
			x = static_cast<int>((1.0 - markers[0].screenPos.x / 640.0) *W*1.2 - W*0.2);
			rotation = -markers[0].rotation2D;
		}
		walls.emplace_back(x, H - 100, 250, 30);
	}
	else if (type == InputType::FFT)
	{
		const auto fft = FFT::Analyze(Recorder::GetWave(), Recorder::GetPosSample());
		const auto it = std::max_element(&fft.buffer[100], &fft.buffer[360]);
		if (*it > 0.004)
			x = std::distance(&fft.buffer[100], it) * 6;
		walls.push_back(Rect(400, 20).setCenter(x, H - 100));
	}

	x = Clamp(x, 0, W);
	return walls;
}

Array<Rect> InitBlocks() {
	Array<Rect> rects;
	const Size size(80, 40);
	const int w = Window::Width() / size.x;
	for (int i : step(5*w))
		rects.emplace_back(i % w * size.x, 60 + i / w  * size.y, size);
	return rects;
}

void Main() {
	Window::Resize(1600, 1000);
	Graphics::SetBackground(Palette::White);
	LeapMotion::RegisterAddon();
	Recorder::Start();
	Webcam::Start(0);
	AR::Start();
	KinectV1::Start(KinectV1DataType::DefaultSeated);

	const Sound sound{ Waving::Synthesize(0.1, [](double t){return Fraction(t * 880)*0.5 - 0.25; }) };
	const double speed = 12.0;
	double rotation = 0.0, tone = 0;
	Circle ball(700, 400, 16);
	Vec2 ballVelocity(2.0, -speed);
	Array<Vec3> effects;
	Array<Rect> blocks = InitBlocks();
	GUI gui(GUIStyle::Default);
	gui.add(L"device", GUIRadioButton::Create({ L"マウス", L"キーボード", L"LeapMotion", L"Xbox 360",
		L"手拍子", L"加速度センサ", L"Arduino", L"Kinect", L"ペンタブ", L"マルチタッチ", L"AR マーカー", L"口笛" }, 0u));

	while (System::Update()) {
		if (Input::KeyR.clicked)
			blocks = InitBlocks();

		if (Recorder::IsEnded())
			Recorder::Restart();

		for (auto it = blocks.begin(); it != blocks.end(); ++it) {
			if (it->intersects(ball)) {
				if (it->bottom.intersects(ball) || it->top.intersects(ball))
					ballVelocity.y *= -1;
				else if (it->left.intersects(ball) || it->right.intersects(ball))
					ballVelocity.x *= -1;

				effects.emplace_back(Vec2(it->center), 0.0);
				blocks.erase(it);
				sound.playMulti(1, 1, Exp2(tone++ / 12.0));
				break;
			}
		}

		if ((ball.center.y<0 && ballVelocity.y<0) || (ball.y > Window::Height()))
			ballVelocity.y *= -1;

		if ((ball.center.x<0 && ballVelocity.x<0) || (Window::Width()<ball.center.x && ballVelocity.x>0))
			ballVelocity.x *= -1;

		for (auto& effect : effects) {
			const double value = Exp(-(effect.z += 0.01) * 5.0);
			Circle(effect.xy, 400.0 * (1.0 - value)).drawFrame(value * 50, 0.0, ColorF(1.0, 0.8, 0.8));
		}

		for (const auto& block : blocks)
			block.draw(HSV((block.y - 40) * 1.2, 0.8, 1.0).toColorF(0.9));

		for (const auto& w : GetWalls(InputType(gui.radioButton(L"device").checkedItem.value()), rotation)) {
			const auto wall = RoundRect(Rect(-w.size / 2, w.size), 6).asPolygon().rotated(rotation).moveBy(w.center);
			wall.draw(Palette::Skyblue);

			if (ballVelocity.y>0 && wall.intersects(ball)) {
				ballVelocity = Vec2(Clamp((ball.center.x - w.center.x) / 8, -10., 10.)
					+ Random(-2., 2.), -ballVelocity.y).normalized()*speed;
				sound.playMulti(1, 1, 0.5);
			}
		}

		Erase_if(effects, [](const Vec3& effect){ return effect.z > 1.0; });
		tone = ball.y > Window::Height() * 0.6 ? 0 : tone;
		ball.moveBy(ballVelocity).draw(Palette::Skyblue);
		gui.style.visible = Input::KeySpace.pressed;
	}
}