コンテンツにスキップ

2D 描画のサンプル

1. 複雑な図形や絵文字に影を付ける

コード
# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
	const Texture emoji{ U"🐈"_emoji };

	// 影用のレンダーテクスチャ
	const RenderTexture shadowTexture{ Scene::Size(), ColorF{ 1.0, 0.0 } };
	const RenderTexture blur4{ shadowTexture.size() / 4 };
	const RenderTexture internal4{ shadowTexture.size() / 4 };

	while (System::Update())
	{
		const double angle = (Scene::Time() * 10_deg);

		// 影の形状を描く
		{
			const ScopedRenderTarget2D target{ shadowTexture.clear(ColorF{ 1.0, 0.0 }) };

			// RGB 値は無視して、描画された最大のアルファ値を保持するブレンドステートを適用する
			const ScopedRenderStates2D blend{ BlendState::MaxAlpha };

			// 影を右下方向に落とすため、描画位置をずらす
			const Transformer2D transform{ Mat3x2::Translate(2, 2) };

			Shape2D::Hexagon(100, Vec2{ 200, 200 }).draw();
			Shape2D::Star(120, Vec2{ 400, 400 }, angle).draw();
			Shape2D::RectBalloon(Rect{ 500, 103, 200, 100 }, Vec2{ 480, 240 }).drawFrame(10);
			emoji.rotated(angle).drawAt(600, 500);
		}

		// shadowTexture をダウンサンプリング + ガウスぼかし
		{
			Shader::Downsample(shadowTexture, blur4);
			Shader::GaussianBlur(blur4, internal4, blur4);
		}

		// ぼかした影を描く
		blur4.resized(Scene::Size()).draw(ColorF{ 0.0, 0.5 });

		// 通常の形状を描く
		if (not MouseL.pressed())
		{
			Shape2D::Hexagon(100, Vec2{ 200, 200 }).draw();
			Shape2D::Star(120, Vec2{ 400, 400 }, angle).draw(Palette::Yellow);
			Shape2D::RectBalloon(Rect{ 500, 100, 200, 100 }, Vec2{ 480, 240 })
				.drawFrame(10, Palette::Seagreen);
			emoji.rotated(angle).drawAt(600, 500);
		}
	}
}

2. 紙から切り抜いたような描画

コード
# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 1.0, 0.9, 0.7 });

	constexpr Vec2 pos{ 220, 60 };

	const Image image{ U"example/siv3d-kun.png" };

	const Texture texture{ image };

	// 画像の輪郭から Polygon を作成する
	const Polygon polygon = image.alphaToPolygon(160, AllowHoles::No);

	// 凸包を計算する
	const Polygon convexHull = polygon.computeConvexHull();

	// Polygon を太らせる
	const Polygon largeConvex = convexHull.calculateBuffer(20);

	// 影用のテクスチャ
	const RenderTexture shadowTexture{ Scene::Size(), ColorF{ 1.0, 0.0 } };
	const RenderTexture gaussianA4{ shadowTexture.size() / 4 };
	const RenderTexture gaussianB4{ shadowTexture.size() / 4 };

	while (System::Update())
	{
		// 影の形状を描く
		{
			const ScopedRenderTarget2D target{ shadowTexture.clear(ColorF{ 1.0, 0.0 }) };
			const ScopedRenderStates2D blend{ BlendState::MaxAlpha };
			const Transformer2D transform{ Mat3x2::Translate(6, 6) };
			largeConvex.draw(pos);
		}

		// shadowTexture をダウンサンプリング + ガウスぼかし
		{
			Shader::Downsample(shadowTexture, gaussianA4);
			Shader::GaussianBlur(gaussianA4, gaussianB4, gaussianA4);
		}

		// ぼかした影を描く
		gaussianA4.resized(Scene::Size()).draw(ColorF{ 0.0, 0.5 });

		largeConvex.draw(pos, ColorF{ 0.96, 0.98, 1.0 });

		texture.draw(pos);
	}
}

3. 付箋

コード
# include <Siv3D.hpp>

void DrawStickyNote(const RectF& rect, const ColorF& noteColor)
{
	// 少しだけ回転させて影を描く
	{
		const Transformer2D t{ Mat3x2::Rotate(2_deg, rect.pos) };

		rect.stretched(-2, 1, 1, -4).drawShadow(Vec2{ 0, 0 }, 12, 0, ColorF{ 0.0, 0.4 });
	}

	rect.draw(noteColor);
}

void Main()
{
	Scene::SetBackground(ColorF{ 1.0, 0.98, 0.96 });

	const Font font{ FontMethod::MSDF, 48, Typeface::Bold };

	while (System::Update())
	{
		for (auto i : step(10))
		{
			const RectF rect{ (80 + i / 5 * 280), (40 + i % 5 * 90), 230, 70 };

			DrawStickyNote(rect, HSV{ (i * 36), 0.46, 1.0 });

			font(U"Text").draw(36, rect.pos.movedBy(20, 10), ColorF{ 0.1, 0.95 });
		}
	}
}

4. テクスチャの反射

コード
# include <Siv3D.hpp>

void Main()
{
	const Array<Texture> textures =
	{
		Texture{ U"💹"_emoji },
		Texture{ U"📅"_emoji },
		Texture{ U"🏡"_emoji },
	};

	constexpr Size ImageSize = Emoji::ImageSize;

	while (System::Update())
	{
		Rect{ 0, 300, 800, 300 }.draw(ColorF{ 0.2, 0.3, 0.4 });

		for (auto&& [i, texture] : Indexed(textures))
		{
			const Vec2 pos{ (140 + i * 200), 220 };

			texture.draw(pos);

			// 反射するテクスチャ
			texture(0, (ImageSize.y / 2), ImageSize.x, (ImageSize.y / 2))
				.flipped()
				.draw(pos.x, (pos.y + ImageSize.y),
					Arg::top(1.0, 0.8), Arg::bottom(1.0, 0.0));
		}
	}
}

5. 2D ライトブルーム

コード
# include <Siv3D.hpp>

void DrawScene(const Texture& emoji)
{
	Circle{ 680, 40, 20 }.draw();
	Rect{ Arg::center(680, 110), 30 }.draw();
	Triangle{ 680, 180, 40 }.draw();

	Circle{ 740, 40, 20 }.draw(HSV{ 0 });
	Rect{ Arg::center(740, 110), 30 }.draw(HSV{ 120 });
	Triangle{ 740, 180, 40 }.draw(HSV{ 240 });

	Circle{ 50, 200, 300 }.drawFrame(4);
	Circle{ 550, 450, 200 }.drawFrame(4);

	for (auto i : step(12))
	{
		const double angle = (i * 30_deg + Scene::Time() * 5_deg);
		const Vec2 pos = OffsetCircular{ Scene::Center(), 200, angle };
		Circle{ pos, 8 }.draw(HSV{ i * 30 });
	}

	emoji.drawAt(400, 300);
}

void Main()
{
	const Size sceneSize{ 800, 600 };
	const Texture emoji{ U"🐈"_emoji };

	// ブルーム用のテクスチャ
	const RenderTexture blur1{ sceneSize };
	const RenderTexture internal1{ sceneSize };
	const RenderTexture blur4{ sceneSize / 4 };
	const RenderTexture internal4{ sceneSize / 4 };
	const RenderTexture blur8{ sceneSize / 8 };
	const RenderTexture internal8{ sceneSize / 8 };

	// 3 種類のぼかしテクスチャの寄与度
	double a1 = 0.0, a4 = 0.0, a8 = 0.0;

	while (System::Update())
	{
		// 通常のシーン描画
		{
			DrawScene(emoji);
		}

		// ブルーム用テクスチャを用意する
		{
			// シーンを描く
			{
				// ブルーム用テクスチャをレンダーターゲットにする
				const ScopedRenderTarget2D target{ blur1.clear(ColorF{ 0.0 }) };

				// シーンを描く
				DrawScene(emoji);
			} // blur1 のレンダーターゲットが解除される

			// (1) blur1: 1x blur
			Shader::GaussianBlur(blur1, internal1, blur1);

			// (2) blur4: 1x blur + 1/4 scale + 1x blur 
			Shader::Downsample(blur1, blur4);
			Shader::GaussianBlur(blur4, internal4, blur4);

			// (3) blur8: 1x blur + 1/4 scale + 1x blur + 1/2 scale + 1x blur
			Shader::Downsample(blur4, blur8);
			Shader::GaussianBlur(blur8, internal8, blur8);
		}

		{
			const ScopedRenderStates2D blend{ BlendState::Additive };

			if (a1)
			{
				blur1.resized(sceneSize).draw(ColorF{ a1 });
			}

			if (a4)
			{
				blur4.resized(sceneSize).draw(ColorF{ a4 });
			}

			if (a8)
			{
				blur8.resized(sceneSize).draw(ColorF{ a8 });
			}
		}

		SimpleGUI::Slider(U"a1: {:.1f}"_fmt(a1), a1, 0.0, 4.0, Vec2{ 40, 40 });
		SimpleGUI::Slider(U"a4: {:.1f}"_fmt(a4), a4, 0.0, 4.0, Vec2{ 40, 80 });
		SimpleGUI::Slider(U"a8: {:.1f}"_fmt(a8), a8, 0.0, 4.0, Vec2{ 40, 120 });

		if (SimpleGUI::Button(U"0, 0, 0", Vec2{ 40, 160 }))
		{
			a1 = a4 = a8 = 0.0;
		}

		if (SimpleGUI::Button(U"0, 0, 1", Vec2{ 40, 200 }))
		{
			a1 = a4 = 0.0;
			a8 = 1.0;
		}

		if (SimpleGUI::Button(U"0, 1, 1", Vec2{ 40, 240 }))
		{
			a1 = 0.0;
			a8 = a4 = 1.0;
		}

		if (SimpleGUI::Button(U"1, 1, 1", Vec2{ 40, 280 }))
		{
			a1 = a4 = a8 = 1.0;
		}
	}
}

6. パターンブラシ

コード
pattern_brush.hlsl
//
//	Textures
//
Texture2D g_texture0 : register(t0);
Texture2D g_texture1 : register(t1);
SamplerState g_sampler0 : register(s0);
SamplerState g_sampler1 : register(s1);

namespace s3d
{
	//
	//	VS Output / PS Input
	//
	struct PSInput
	{
		float4 position	: SV_POSITION;
		float4 color	: COLOR0;
		float2 uv		: TEXCOORD0;
	};
}

//
//	Constant Buffer
//
cbuffer PSConstants2D : register(b0)
{
	float4 g_colorAdd;
	float4 g_sdfParam;
	float4 g_sdfOutlineColor;
	float4 g_sdfShadowColor;
	float4 g_internal;
}

// 定数バッファ (PS_1)
cbuffer PatternBrush : register(b1)
{
	float2 g_uvScale;
}

//
//	Functions
//
float4 PS(s3d::PSInput input) : SV_TARGET
{
	const float alpha = g_texture0.Sample(g_sampler0, input.uv).r;

	float4 texColor = g_texture1.Sample(g_sampler1, input.uv * g_uvScale);

	texColor.a = alpha;

	return ((texColor * input.color) + g_colorAdd);
}
pattern_brush.frag
# version 410

//
//	Textures
//
uniform sampler2D Texture0;
uniform sampler2D Texture1;

//
//	PSInput
//
layout(location = 0) in vec4 Color;
layout(location = 1) in vec2 UV;

//
//	PSOutput
//
layout(location = 0) out vec4 FragColor;

//
//	Constant Buffer
//
layout(std140) uniform PSConstants2D
{
	vec4 g_colorAdd;
	vec4 g_sdfParam;
	vec4 g_sdfOutlineColor;
	vec4 g_sdfShadowColor;
	vec4 g_internal;
};

// PS_1
layout(std140) uniform PatternBrush
{
	vec2 g_uvScale;
};

//
//	Functions
//
void main()
{
	float alpha = texture(Texture0, UV).r;

	vec4 texColor = texture(Texture1, UV * g_uvScale);

	texColor.a = alpha;

	FragColor = (texColor * Color) + g_colorAdd;
}
# include <Siv3D.hpp>

// パターン画像を作る
Image CreatePattern()
{
	Image image{ 16, 16, Palette::White };
	Circle{ 0, 4, 2 }.paint(image, Palette::Black);
	Circle{ 8, 4, 2 }.paint(image, Palette::Black);
	Circle{ 16, 4, 2 }.paint(image, Palette::Black);
	Circle{ 4, 12, 2 }.paint(image, Palette::Black);
	Circle{ 12, 12, 2 }.paint(image, Palette::Black);
	return image;
}

// 定数バッファ (PS_1)
struct PatternBrush
{
	// パターンの UV のスケール
	Float2 uvScale;
};

void Main()
{
	constexpr Size sceneSize{ 600, 600 };

	// パターン画像のテクスチャ
	const Texture patternTexture{ CreatePattern(), TextureDesc::Mipped };

	// パターンブラシ用のピクセルシェーダ
	const PixelShader ps = HLSL{ U"pattern_brush.hlsl", U"PS" }
		| GLSL{ U"pattern_brush.frag", { { U"PSConstants2D", 0 }, { U"PatternBrush", 1 } } };

	if (not ps)
	{
		throw Error{ U"Failed to load a shader file" };
	}

	// 定数バッファ
	ConstantBuffer<PatternBrush> cb;
	cb->uvScale = (Float2{ sceneSize } / patternTexture.size());

	// ペンで書き込むレンダーテクスチャ
	MSRenderTexture renderTexture{ sceneSize, Palette::Black };

	// ペンの太さ
	constexpr double Thickness = 20;

	while (System::Update())
	{
		if (MouseL.pressed())
		{
			{
				const ScopedRenderTarget2D target{ renderTexture };

				if (MouseL.down())
				{
					Circle{ Cursor::PosF(), (Thickness * 0.5) }.draw();
				}
				else if (MouseL.pressed() && (not Cursor::Delta().isZero()))
				{
					Line{ Cursor::PreviousPosF(), Cursor::PosF() }
						.draw(LineStyle::RoundCap, Thickness);
				}
			}

			Graphics2D::Flush();
			renderTexture.resolve();
		}

		Rect{ sceneSize }.draw();
		{
			// パターン画像を PS テクスチャスロット 1 にセット 
			Graphics2D::SetPSTexture(1, patternTexture);
			Graphics2D::SetPSConstantBuffer(1, cb);

			// パターンをくり返しマッピングできるようにする
			{
				const ScopedRenderStates2D sampler{ {ShaderStage::Pixel, 1, SamplerState::RepeatLinear} };
				const ScopedCustomShader2D shader{ ps };
				renderTexture.draw();
			}
		}

		// パターン画像を右上に表示
		patternTexture.draw(620, 20);
	}
}

7. 2D 描画における射影変換

コード
# include <Siv3D.hpp>

struct Homography
{
	Float4 m1;
	Float4 m2;
	Float4 m3;
};

// チェッカーパターンの Image を作る
Image MakeCheckerPattern()
{
	Image image{ 1280, 720 , Palette::White };
	for (auto p : step(image.size() / Size{ 40, 40 }))
	{
		if (IsEven(p.x + p.y))
		{
			Rect{ p * 40, 40 }.overwrite(image, Color{ 40 });
		}
	}
	return image;
}

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.8, 0.9, 1.0 });

	const Texture texture{ U"example/bay.jpg", TextureDesc::Mipped };
	const Texture checker{ MakeCheckerPattern(), TextureDesc::Mipped };

	constexpr double circleR = 12.0;
	const VertexShader vs = HLSL{ U"example/shader/hlsl/homography.hlsl", U"VS" }
		| GLSL{ U"example/shader/glsl/homography.vert", { { U"VSConstants2D", 0 }, { U"VSHomography", 1} } };
	const PixelShader ps = HLSL{ U"example/shader/hlsl/homography.hlsl", U"PS" }
		| GLSL{ U"example/shader/glsl/homography.frag", { { U"PSConstants2D", 0 }, { U"PSHomography", 1} } };

	if ((not vs) || (not ps))
	{
		throw Error{ U"Failed to load shader files" };
	}

	ConstantBuffer<Homography> vsHomography;
	ConstantBuffer<Homography> psHomography;

	Quad quad{ Vec2{100, 300}, Vec2{500, 300}, Vec2{500, 600}, Vec2{100, 600} };
	Optional<size_t> grabbedIndex;

	bool homography = true;

	while (System::Update())
	{
		SimpleGUI::CheckBox(homography, U"Homography", Vec2{ 40, 40 });

		if (homography)
		{
			const ScopedCustomShader2D shader{ vs, ps };
			const ScopedRenderStates2D sampler{ SamplerState::ClampAniso };

			{
				const Mat3x3 mat = Mat3x3::Homography(quad.movedBy(580, 0));
				vsHomography = { Float4{ mat._11_12_13, 0 }, Float4{ mat._21_22_23, 0 }, Float4{ mat._31_32_33, 0 } };
				Graphics2D::SetVSConstantBuffer(1, vsHomography);

				const Mat3x3 inv = mat.inverse();
				psHomography = { Float4{ inv._11_12_13, 0 }, Float4{ inv._21_22_23, 0 }, Float4{ inv._31_32_33, 0 } };
				Graphics2D::SetPSConstantBuffer(1, psHomography);

				// 1x1 の Rect に貼り付けて描くと、適切にホモグラフィ変換される
				Rect{ 1 }(checker).draw();
			}

			{
				const Mat3x3 mat = Mat3x3::Homography(quad);
				vsHomography = { Float4{ mat._11_12_13, 0 }, Float4{ mat._21_22_23, 0 }, Float4{ mat._31_32_33, 0 } };
				Graphics2D::SetVSConstantBuffer(1, vsHomography);

				const Mat3x3 inv = mat.inverse();
				psHomography = { Float4{ inv._11_12_13, 0 }, Float4{ inv._21_22_23, 0 }, Float4{ inv._31_32_33, 0 } };
				Graphics2D::SetPSConstantBuffer(1, psHomography);

				// 1x1 の Rect に貼り付けて描くと、適切にホモグラフィ変換される
				Rect{ 1 }(texture).draw();
			}
		}
		else
		{
			quad.movedBy(580, 0)(checker).draw();
			quad(texture).draw();
		}

		if (grabbedIndex)
		{
			if (not MouseL.pressed())
			{
				grabbedIndex.reset();
			}
			else
			{
				quad.p(*grabbedIndex).moveBy(Cursor::DeltaF());
			}
		}
		else
		{
			for (auto i : step(4))
			{
				const Circle circle = quad.p(i).asCircle(circleR);

				if (circle.mouseOver())
				{
					Cursor::RequestStyle(CursorStyle::Hand);
				}

				if (circle.leftClicked())
				{
					grabbedIndex = i;
					break;
				}
			}
		}

		for (auto i : step(4))
		{
			quad.p(i).asCircle(circleR).draw(ColorF{ 1.0, 0.3, 0.3, 0.5 });
		}
	}
}

8. ドット絵をアニメーションさせる

コード
# include <Siv3D.hpp>

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

	const Texture texture{ U"example/spritesheet/siv3d-kun-16.png" };
	constexpr int32 patterns[4] = { 1, 2, 1, 0 };

	while (System::Update())
	{
		const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };

		const uint64 t = Time::GetMillisec();
		const int32 x = ((t / 2000 % 2) * 3);
		const int32 y = (t / 4000 % 4);
		const int32 n = (t / 250 % 4);

		Rect{ ((patterns[n] + x) * 20 * 4), (y * 28 * 4), (20 * 4), (28 * 4) }
		.draw(ColorF{ 0.3, 0.9, 0.8 });

		texture.scaled(4).draw();

		Rect{ 520, 60, (20 * 8 + 80), (28 * 8 + 80) }
			.draw(ColorF{ 0.5, 0.9, 0.5 });

		texture((patterns[n] + x) * 20, (y * 28), 20, 28)
			.scaled(8).draw(560, 100);
	}
}

9. 集中線の描画

コード
# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.98, 0.96, 0.94 });
	const Texture texture{ U"🦀"_emoji };

	Ellipse target{ 400, 300, 180, 120 };
	Rect outer = Scene::Rect();
	double minThickness = 3.0, maxThickness = 10.0;
	double lineCount = 150;
	double offsetRange = 60.0;

	SaturatedLinework<Ellipse> linework{ target, outer };
	linework
		.setThickness(minThickness, maxThickness)
		.setLineCount(static_cast<size_t>(lineCount))
		.setOffsetRange(offsetRange);

	while (System::Update())
	{
		if (MouseR.down())
		{
			target.setCenter(Cursor::Pos());
			linework.setTargetShape(target);
		}

		texture.scaled(1.6).drawAt(target.center);

		linework.draw(ColorF{ 0.1 });

		if (SimpleGUI::Slider(U"lineCount", lineCount, 0.0, 400.0, Vec2{ 20, 20 }, 150))
		{
			linework.setLineCount(static_cast<size_t>(lineCount));
		}

		if (SimpleGUI::Slider(U"offsetRange", offsetRange, 0.0, 100.0, Vec2{ 20, 60 }, 150))
		{
			linework.setOffsetRange(offsetRange);
		}
	}
}

10. PerlinNoise の生成

コード
# include <Siv3D.hpp>

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	Image image1{ 512, 512, Palette::White };
	Image image2{ 512, 512, Palette::White };
	DynamicTexture texture1{ image1 };
	DynamicTexture texture2{ image2 };

	PerlinNoise noise;
	size_t oct = 5;
	double persistence = 0.5;
	const Array<String> options = Range(1, 6).map(Format);

	while (System::Update())
	{
		SimpleGUI::Headline(U"octaves", Vec2{ 40, 540 });
		SimpleGUI::HorizontalRadioButtons(oct, options, Vec2{ 40, 580 });

		SimpleGUI::Headline(U"persistence", Vec2{ 400, 540 });
		SimpleGUI::Slider(U"{:.2f}"_fmt(persistence), persistence, Vec2{ 400, 580 }, 60, 120);

		if (SimpleGUI::Button(U"Generate", Vec2{ 620, 580 }))
		{
			noise.reseed(RandomUint64());
			const int32 octaves = static_cast<int32>(oct + 1);

			for (auto p : step(image1.size()))
			{
				image1[p] = ColorF{ noise.normalizedOctave2D0_1((p / 128.0), octaves, persistence) };
			}
			for (auto p : step(image2.size()))
			{
				image2[p] = ColorF{ noise.octave2D0_1((p / 128.0), octaves, persistence) };
			}
			texture1.fill(image1);
			texture2.fill(image2);
		}

		texture1.draw();
		texture2.draw(512, 0);
	}
}

11. キーフレームアニメーション

コード
# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
	const Texture texture1{ U"🐥"_emoji };
	const Texture texture2{ U"🐢"_emoji };

	SimpleAnimation a1;
	a1.setLoop(12s)
		.set(U"r", { 0.5s, 0 }, { 1.5s, 1 }, EaseOutBounce)
		.set(U"g", { 1s, 0 }, { 2s, 1 }, EaseOutBounce)
		.set(U"b", { 1.5s, 0 }, { 2.5s, 1 }, EaseOutBounce)
		.set(U"angle", { 3s, 0_deg }, { 8.5s, 720_deg }, EaseOutBounce)
		.set(U"size", { 0s, 0 }, { 0.5s, 320 }, EaseOutExpo)
		.set(U"size", { 9s, 320 }, { 9.5s, 0 }, EaseOutExpo)
		.start();

	SimpleAnimation a2;
	a2.setLoop(6s)
		.set(U"x", { 1s, 150 }, { 3s, 650 }, EaseInOutExpo)
		.set(U"y", { 0s, 350 }, { 1s, 150 }, EaseOutBack)
		.set(U"y", { 3s, 150 }, { 4s, 350 }, EaseInQuad)
		.set(U"t", { 0s, 0 }, { 4s, 12_pi }, EaseInOutQuad)
		.set(U"a", { 5s, 1 }, { 6s, 0 }, EaseOutCubic)
		.start();

	SimpleAnimation a3;
	a3.setLoop(6s)
		.set(U"x", { 0s, 100 }, { 3s, 700 }, EaseInOutQuad)
		.set(U"x", { 3s, 700 }, { 6s, 100 }, EaseInOutQuad)
		.set(U"mirrored", { 0s, 1 }, { 3s, 1 })
		.set(U"mirrored", { 3s, 0 }, { 6s, 0 })
		.start();

	while (System::Update())
	{
		Triangle{ Scene::Center(), a1[U"size"], a1[U"angle"] }
		.draw(ColorF{ a1[U"r"], 0, 0 }, ColorF{ 0, a1[U"g"], 0 }, ColorF{ 0, 0, a1[U"b"] });

		texture1
			.drawAt(a2[U"x"], a2[U"y"] + Math::Sin(a2[U"t"]) * 20.0, ColorF{ 1, a2[U"a"] });

		texture2
			.mirrored(a3[U"mirrored"])
			.drawAt(a3[U"x"], 500);
	}
}

12. 液体風の HP バー

コード
# include <Siv3D.hpp>

class LiquidBar
{
public:

	LiquidBar() = default;

	explicit LiquidBar(const Rect& rect)
		: m_rect{ rect } {}

	void update(double targetHP)
	{
		m_targetHP = targetHP;
		m_liquidHP = Math::SmoothDamp(m_liquidHP, targetHP, m_liquidHPVelocity, LiquidSmoothTime);

		if (m_solidHP < targetHP)
		{
			m_solidHP = targetHP;
		}
		else
		{
			m_solidHP = Math::SmoothDamp(m_solidHP, targetHP, m_solidHPVelocity, SolidSmoothTime, MaxSolidBarSpeed);
		}
	}

	void draw(const ColorF& liquidColorFront, const ColorF& liquidColorBack, const ColorF& solidColor) const
	{
		// バーの背景を描く
		m_rect.draw(ColorF{ 0.2, 0.15, 0.25 });

		// バーの枠を描く
		m_rect.drawFrame(2, 0);

		const Point basePos = m_rect.pos.movedBy(FrameThickness, FrameThickness);
		const int32 height = (m_rect.h - (FrameThickness * 2));
		const double width = (m_rect.w - (FrameThickness * 2));

		const double solidWidth = Min(Max((width * m_solidHP) + (height * 0.5 * 0.3), 0.0), width);
		const double liquidWidth = (width * m_liquidHP);

		// 固体バーを描く
		{
			const RectF solidBar{ basePos, solidWidth, height };
			const double alpha = ((0.005 < AbsDiff(m_targetHP, m_solidHP)) ? 1.0 : (AbsDiff(m_targetHP, m_solidHP) / 0.005));
			solidBar.draw(ColorF{ solidColor, alpha });
		}

		// 液体バーを描く
		{
			const double t = Scene::Time();
			const double offsetScale = ((m_liquidHP < 0.05) ? (m_liquidHP / 0.05) : (0.98 < m_liquidHP) ? 0.0 : 1.0);

			// 背景の液体バーを描く
			for (int32 i = 0; i < height; ++i)
			{
				const Vec2 pos = basePos.movedBy(0, i);
				const double waveOffset = (i * 0.3)
					+ (Math::Sin(i * 17_deg + t * 800_deg) * 0.8)
					+ (Math::Sin(i * 11_deg + t * 700_deg) * 1.2)
					+ (Math::Sin(i * 7_deg + t * 550_deg) * 1.6);
				const RectF rect{ pos, Clamp((liquidWidth + waveOffset * offsetScale), 0.0, width), 1 };

				const double distance = Clamp(1.0 - i / (height - 1.0) + 0.7, 0.0, 1.0);
				HSV hsv{ liquidColorBack };
				hsv.v *= Math::Pow(distance, 2.0);
				rect.draw(hsv);
			}

			// 前景の液体バーを描く
			for (int32 i = 0; i < height; ++i)
			{
				const Vec2 pos = basePos.movedBy(0, i);
				const double waveOffset = (i * 0.3)
					+ (Math::Sin(i * 17_deg - t * 800_deg) * 0.8)
					+ (Math::Sin(i * 11_deg - t * 700_deg) * 1.2)
					+ (Math::Sin(i * 7_deg - t * 550_deg) * 1.6);
				const RectF rect{ pos, Clamp((liquidWidth + waveOffset * offsetScale), 0.0, width), 1 };

				const double distance = Clamp(1.0 - i / (height - 1.0) + 0.7, 0.0, 1.0);
				HSV hsv{ liquidColorFront };
				hsv.v *= Math::Pow(distance, 2.0);
				rect.draw(hsv);
			}
		}
	}

private:

	// 液体バーが減少するときの平滑化時間(小さいと早く目標に到達)
	static constexpr double LiquidSmoothTime = 0.03;

	// 固体バーが減少するときの平滑化時間(小さいと早く目標に到達)
	static constexpr double SolidSmoothTime = 0.5;

	// 固体バーが減少するときの最大の速さ
	static constexpr double MaxSolidBarSpeed = 0.25;

	static constexpr int32 FrameThickness = 2;

	Rect m_rect = Rect::Empty();

	double m_targetHP = 1.0;
	double m_liquidHP = 1.0;
	double m_solidHP = 1.0;
	double m_liquidHPVelocity = 0.0;
	double m_solidHPVelocity = 0.0;
};

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

	LiquidBar bar1{ Rect{ 100, 100, 200, 30 } };
	LiquidBar bar2{ Rect{ 100, 200, 300, 50 } };

	double hp = 1.0;

	while (System::Update())
	{
		bar1.update(hp);
		bar2.update(hp);

		bar1.draw(ColorF{ 0.9, 0.1, 0.1 }, ColorF{ 0.7, 0.05, 0.05 }, ColorF{ 0.9, 0.5, 0.1 });
		bar2.draw(ColorF{ 0.9, 0.1, 0.1 }, ColorF{ 0.7, 0.05, 0.05 }, ColorF{ 0.9, 0.5, 0.1 });

		SimpleGUI::Slider(U"{:.2f}"_fmt(hp), hp, Vec2{ 440, 100 }, 60, 160);
	}
}

13. パーティクルシステム

コード
# include <Siv3D.hpp>

// Siv3D-kun の形の Polygon を作成
Polygon CreatePolygon()
{
	return Image{ U"example/siv3d-kun.png" }
	.alphaToPolygonCentered().simplified(1).scaled(1.3);
}

void Main()
{
	Window::Resize(1280, 720);
	const Texture textureSiv3D{ U"example/siv3d-kun.png" };
	const std::array<BlendState, 4> blends = {
		BlendState::Default2D, BlendState::Additive, BlendState::Opaque, BlendState::Subtractive
	};

	// パーティクル用のテクスチャ
	const Texture texture{ U"example/particle.png", TextureDesc::Mipped };

	// デバッグモードにするか
	bool debugMode = false;

	// 背景を白にするか
	bool whiteBackground = false;

	// Siv3D-kun を表示するか
	bool drawTexture = false;

	Vec2 position{ 300, 340 };
	Vec2 force{ 0.0, 0.0 };
	HSV startColor = ColorF{ 1.0 };
	size_t blendIndex = 1;
	size_t emitterIndex = 1;

	CircleEmitter2D circleEmitter;
	ArcEmitter2D arcEmitter;
	RectEmitter2D rectEmitter;
	PolygonEmitter2D polygonEmitter{ CreatePolygon() };
	ParticleSystem2DParameters parameters;

	ParticleSystem2D particleSystem{ position, force };
	particleSystem.setEmitter(arcEmitter);
	particleSystem.setTexture(texture);
	particleSystem.setParameters(parameters);
	particleSystem.prewarm();

	while (System::Update())
	{
		// パーティクルの個数を表示する
		ClearPrint();
		Print << U"{} particles"_fmt(particleSystem.num_particles());

		// パーティクルを更新する
		particleSystem.update();

		if (debugMode)
		{
			// デバッグモードでパーティクルを描画する
			particleSystem.drawDebug();
		}
		else
		{
			// パーティクルを描画する
			particleSystem.draw();
		}

		if (emitterIndex == 3 && drawTexture)
		{
			// Siv3D-kun を表示する
			textureSiv3D.scaled(1.3).drawAt(position);
		}

		if (MouseR.pressed())
		{
			// パーティクルの発生位置を移動させる
			particleSystem.setPosition(position = Cursor::Pos());
		}

		SimpleGUI::CheckBox(debugMode, U"Debug", Vec2{ 80, 660 }, 140);
		SimpleGUI::CheckBox(whiteBackground, U"White", Vec2{ 240, 660 }, 140);
		SimpleGUI::CheckBox(drawTexture, U"Texture", Vec2{ 400, 660 }, 140, (emitterIndex == 3));
		Scene::SetBackground(whiteBackground ? Color{ 250, 252, 255 } : Palette::DefaultBackground);

		const int32 x0 = 560, x1 = 900;
		{
			SimpleGUI::Slider(U"Rate", parameters.rate, 1.0, 500.0, Vec2{ x0, 20 }, 120, 200);
			SimpleGUI::Slider(U"Max", parameters.maxParticles, 50.0, 2500.0, Vec2{ x0, 60 }, 120, 200);
			SimpleGUI::Slider(U"LifeTime", parameters.startLifeTime, 0.0, 5.0, Vec2{ x0, 100 }, 120, 200);
			SimpleGUI::Slider(U"Speed", parameters.startSpeed, 0.0, 320.0, Vec2{ x0, 140 }, 120, 200);
			SimpleGUI::Slider(U"Color H", startColor.h, 0.0, 360.0, Vec2{ x0, 180 }, 120, 200);
			SimpleGUI::Slider(U"Color S", startColor.s, 0.0, 1.0, Vec2{ x0, 220 }, 120, 200);
			SimpleGUI::Slider(U"Color V", startColor.v, 0.0, 1.0, Vec2{ x0, 260 }, 120, 200);
			SimpleGUI::Slider(U"Color A", startColor.a, 0.0, 1.0, Vec2{ x0, 300 }, 120, 200);
			SimpleGUI::Slider(U"Size", parameters.startSize, 0.0, 150.0, Vec2{ x0, 340 }, 120, 200);
			SimpleGUI::Slider(U"Rotation", parameters.startRotationDeg, -180, 180, Vec2{ x0, 380 }, 120, 200);
			SimpleGUI::Slider(U"AngularVel", parameters.startAngularVelocityDeg, -720, 720, Vec2{ x0, 420 }, 120, 200);
			SimpleGUI::Slider(U"Force X", force.x, -320.0, 320.0, Vec2{ x0, 460 }, 120, 200);
			SimpleGUI::Slider(U"Force Y", force.y, -320, 320.0, Vec2{ x0, 500 }, 120, 200);
			SimpleGUI::RadioButtons(blendIndex, { U"Default", U"Additive", U"Opaque", U"Subtractive" }, Vec2{ x0, 540 }, 320);

			parameters.blendState = blends[blendIndex];
			parameters.startColor = startColor;
			particleSystem.setParameters(parameters);
			particleSystem.setForce(force);
		}

		{
			bool changed = false;
			changed |= SimpleGUI::RadioButtons(emitterIndex, { U"Circle", U"Arc", U"Rect", U"Polygon" }, Vec2{ x1, 20 }, 360);

			if (emitterIndex == 0)
			{
				changed |= SimpleGUI::Slider(U"Source Radius", circleEmitter.sourceRadius, 0.0, 40.0, Vec2{ x1, 180 }, 160, 200);
				changed |= SimpleGUI::Slider(U"R", circleEmitter.r, 0.0, 320.0, Vec2{ x1, 220 }, 160, 200);
				changed |= SimpleGUI::CheckBox(circleEmitter.randomDirection, U"Random Direction", Vec2{ x1, 260 }, 360);
				changed |= SimpleGUI::CheckBox(circleEmitter.fromShell, U"From Shell", Vec2{ x1, 300 }, 300);

				if (changed) // setEmitter は重い処理なので、変更があった時だけ
				{
					particleSystem.setEmitter(circleEmitter);
				}
			}
			else if (emitterIndex == 1)
			{
				changed |= SimpleGUI::Slider(U"Source Radius", arcEmitter.sourceRadius, 0.0, 40.0, Vec2{ x1, 180 }, 160, 200);
				changed |= SimpleGUI::Slider(U"R", arcEmitter.r, 0.0, 320.0, Vec2{ x1, 220 }, 160, 200);
				changed |= SimpleGUI::Slider(U"Direction", arcEmitter.direction, -180, 180, Vec2{ x1, 260 }, 160, 200);
				changed |= SimpleGUI::Slider(U"Angle", arcEmitter.angle, 0.0, 360, Vec2{ x1, 300 }, 160, 200);
				changed |= SimpleGUI::CheckBox(arcEmitter.randomDirection, U"Random Direction", Vec2{ x1, 340 }, 360);
				changed |= SimpleGUI::CheckBox(arcEmitter.fromShell, U"From Shell", Vec2{ x1, 380 }, 360);

				if (changed) // setEmitter は重い処理なので、変更があった時だけ
				{
					particleSystem.setEmitter(arcEmitter);
				}
			}
			else if (emitterIndex == 2)
			{
				changed |= SimpleGUI::Slider(U"Source Radius", rectEmitter.sourceRadius, 0.0, 40.0, Vec2{ x1, 180 }, 160, 200);
				changed |= SimpleGUI::Slider(U"Width", rectEmitter.width, 0, 720, Vec2{ x1, 220 }, 160, 200);
				changed |= SimpleGUI::Slider(U"Height", rectEmitter.height, 0, 720, Vec2{ x1, 260 }, 160, 200);
				changed |= SimpleGUI::CheckBox(rectEmitter.randomDirection, U"Random Direction", Vec2{ x1, 300 }, 360);
				changed |= SimpleGUI::CheckBox(rectEmitter.fromShell, U"From Shell", Vec2{ x1, 340 }, 360);

				if (changed) // setEmitter は重い処理なので、変更があった時だけ
				{
					particleSystem.setEmitter(rectEmitter);
				}
			}
			else if (emitterIndex == 3)
			{
				if (changed) // setEmitter は重い処理なので、変更があった時だけ
				{
					particleSystem.setEmitter(polygonEmitter);
				}
			}
		}
	}
}

14. ゲームへのパーティクルシステム組み込み

コード
# include <Siv3D.hpp>

// ボール用のパーティクルクラス
class BallParticleSystem
{
public:

	BallParticleSystem(const Vec2& pos, const Vec2& force)
		: m_system{ pos, force }
		, m_texture{ U"example/particle.png", TextureDesc::Mipped }
	{
		m_system.setTexture(m_texture);

		// パラメータ
		ParticleSystem2DParameters parameters;
		parameters.rate = 300.0;
		parameters.startSpeed = 50.0;
		parameters.startColor = HSV{ 10.0, 0.8, 0.25 };
		parameters.startSize = 60.0;
		parameters.blendState = BlendState::Additive;
		m_system.setParameters(parameters);

		// エミッター
		CircleEmitter2D circleEmitter;
		circleEmitter.r = 12.0;
		m_system.setEmitter(circleEmitter);

		m_system.prewarm();
	}

	void setPos(const Vec2& pos)
	{
		m_system.setPosition(pos);
	}

	void update()
	{
		m_system.update();
	}

	void draw() const
	{
		m_system.draw();
	}

private:

	// 2D パーティクルシステム
	ParticleSystem2D m_system;

	// パーティクル用のテクスチャ
	Texture m_texture;
};


# include <Siv3D.hpp>

void Main()
{
	// 1 つのブロックのサイズ | Size of a single block
	constexpr Size BrickSize{ 40, 20 };

	// ボールの速さ(ピクセル / 秒) | Ball speed (pixels / second)
	constexpr double BallSpeedPerSec = 480.0;

	// ボールの速度 | Ball velocity
	Vec2 ballVelocity{ 0, -BallSpeedPerSec };

	// ボール | Ball
	Circle ball{ 400, 400, 8 };

	// ブロックの配列 | Array of bricks
	Array<Rect> bricks;

	for (int32 y = 0; y < 5; ++y)
	{
		for (int32 x = 0; x < (Scene::Width() / BrickSize.x); ++x)
		{
			bricks << Rect{ (x* BrickSize.x), (60 + y * BrickSize.y), BrickSize };
		}
	}

	// ボール用のパーティクルクラス
	BallParticleSystem particleSystem{ ball.center, Vec2{ 0.0, -120 } };

	while (System::Update())
	{
		// パドル | Paddle
		const Rect paddle{ Arg::center(Cursor::Pos().x, 500), 60, 10 };

		// ボールを移動させる | Move the ball
		ball.moveBy(ballVelocity * Scene::DeltaTime());

		// ブロックを順にチェックする | Check bricks in sequence
		for (auto it = bricks.begin(); it != bricks.end(); ++it)
		{
			// ブロックとボールが交差していたら | If block and ball intersect
			if (it->intersects(ball))
			{
				// ブロックの上辺、または底辺と交差していたら | If ball intersects with top or bottom of the block
				if (it->bottom().intersects(ball) || it->top().intersects(ball))
				{
					// ボールの速度の Y 成分の符号を反転する | Reverse the sign of the Y component of the ball's velocity
					ballVelocity.y *= -1;
				}
				else // ブロックの左辺または右辺と交差していたら
				{
					// ボールの速度の X 成分の符号を反転する | Reverse the sign of the X component of the ball's velocity
					ballVelocity.x *= -1;
				}

				// ブロックを配列から削除する(イテレータは無効になる) | Remove the block from the array (the iterator becomes invalid)
				bricks.erase(it);

				// これ以上チェックしない | Do not check any more
				break;
			}
		}

		// 天井にぶつかったら | If the ball hits the ceiling
		if ((ball.y < 0) && (ballVelocity.y < 0))
		{
			// ボールの速度の Y 成分の符号を反転する | Reverse the sign of the Y component of the ball's velocity
			ballVelocity.y *= -1;
		}

		// 左右の壁にぶつかったら | If the ball hits the left or right wall
		if (((ball.x < 0) && (ballVelocity.x < 0))
			|| ((Scene::Width() < ball.x) && (0 < ballVelocity.x)))
		{
			// ボールの速度の X 成分の符号を反転する | Reverse the sign of the X component of the ball's velocity
			ballVelocity.x *= -1;
		}

		// パドルにあたったら | If the ball hits the left or right wall
		if ((0 < ballVelocity.y) && paddle.intersects(ball))
		{
			// パドルの中心からの距離に応じてはね返る方向(速度ベクトル)を変える | Change the direction (velocity vector) of the ball depending on the distance from the center of the paddle
			ballVelocity = Vec2{ (ball.x - paddle.center().x) * 10, -ballVelocity.y }.setLength(BallSpeedPerSec);
		}

		// パーティクルを更新する
		particleSystem.setPos(ball.center);
		particleSystem.update();

		// すべてのブロックを描画する | Draw all the bricks
		for (const auto& brick : bricks)
		{
			// ブロックの Y 座標に応じて色を変える | Change the color of the brick depending on the Y coordinate
			brick.stretched(-1).draw(HSV{ brick.y - 40 });
		}

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

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

		// パドルを描く | Draw the paddle
		paddle.rounded(3).draw();

		// パーティクルを描画する
		particleSystem.draw();
	}
}

15. カーソルを注目する矢印

Siv3D-Sample | カーソルを注目する矢印

16 クォータービュー

Siv3D-Sample | クォータービュー