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();
}
}