Help us understand the problem. What is going on with this article?

【C++/OpenSiv3D】泡のアニメーション Texture

More than 1 year has passed since last update.

Siv3D-App-2019-12-01-17-51-44.gif Siv3D-App-2019-12-01-19-25-42.gif

泡が上昇するアニメーションを RenderTexture で生成し、そのテクスチャを UI やゲームの要素に使うと、面白い表現ができます。
この記事では、泡の生成、アニメーションの更新、Texture の取得までを 4 行で書けるようにする BubbleTexture クラスを実装します。(対象バージョン: OpenSiv3D v0.4.2 以上)

BubbleTexture クラスのソースコード

コピーすれば、すぐに使えます。

class BubbleTexture
{
private:

    // 泡を描画する内部テクスチャ
    MSRenderTexture m_texture;

    // 泡を表現する Circle の配列
    Array<Circle> m_bubbles;

    // 泡の発生レートの最大値(個/秒)
    static constexpr double MaxSpawnRate = 300.0;

    // 泡のスピードの最小値(ピクセル/秒)
    static constexpr double MinSpeed = 10.0;

    // 泡の発生レート(個/秒)
    double m_spawnRate = 10.0;

    // 泡のスピード(ピクセル/秒)
    double m_speed = 40.0;

    // 泡の最小サイズ
    double m_minSize = 0.0;

    // 泡の最大サイズ
    double m_maxSize = 0.0;

    // 泡発生更新用のカウンタ
    double m_timeAccum = 0.0;

public:

    BubbleTexture() = default;

    // コンストラクタ
    BubbleTexture(const Size& size, double spawnRate, double speed, double minSize, double maxSize)
        : m_texture(size)
        , m_spawnRate(Clamp(spawnRate, 0.0, MaxSpawnRate))
        , m_speed(Max(speed, MinSpeed))
        , m_minSize(minSize)
        , m_maxSize(maxSize)
    {
        // prewarm
        const double prewarmTime = (size.y + m_maxSize * 2) / speed;
        const double prewarmStepTime = 1.0 / 60.0;

        for (int32 i = 0; i < (prewarmTime / prewarmStepTime); ++i)
        {
            update(prewarmStepTime);
        }
    }

    // 泡の更新
    void update(double deltaTime)
    {
        // 泡の移動
        {
            const double speedBase = ((m_minSize + m_maxSize) * 0.5);

            for (auto& bubble : m_bubbles)
            {
                bubble.y -= deltaTime * m_speed * (bubble.r / speedBase);
            }
            m_bubbles.remove_if([maxSize = m_maxSize](const Circle& c) { return c.y < -maxSize; });
        }

        // 泡の発生
        {
            m_timeAccum += deltaTime;

            const double spawnTime = 1.0 / m_spawnRate;

            while (m_timeAccum > spawnTime)
            {
                const RectF spawnArea(-m_maxSize, m_texture.height() + m_maxSize, m_texture.width() + (m_maxSize * 2), m_maxSize);

                m_bubbles.emplace_back(RandomVec2(spawnArea), Random(m_minSize, m_maxSize));

                m_timeAccum -= spawnTime;
            }
        }
    }

    // 泡を内部テクスチャに描画
    void render(const ColorF& backgroundColor)
    {
        m_texture.clear(backgroundColor);
        {
            ScopedRenderTarget2D rt(m_texture);
            ScopedRenderStates2D blend(BlendState::Additive);

            for (const auto& bubble : m_bubbles)
            {
                const double a = EaseInQuad(0.15 + bubble.y / m_texture.height() * 0.45);
                bubble.draw(ColorF(1.0, a));
            }
        }
        Graphics2D::Flush();
        m_texture.resolve();
    }

    // 内部テクスチャを取得
    const Texture& getTexture() const
    {
        return m_texture;
    }
};

使用例 1

Siv3D-App-2019-12-01-17-51-44.gif

# include <Siv3D.hpp> // OpenSiv3D v0.4.2

/* ここに BubbleTexture クラス */

void Main()
{
    Scene::SetBackground(ColorF(0.4));

    const Font font(40, U"example/font/LogoTypeGothic/LogoTypeGothic.otf");

    constexpr Size size(240, 80);

    constexpr RoundRect button(280, 400, size, 20);

    BubbleTexture bubbleTexture(size, 10, 40, 2, 6);

    while (System::Update())
    {
        // 泡の更新
        {
            bubbleTexture.update(Scene::DeltaTime());
            bubbleTexture.render(ColorF(0.1, 0.3, 0.6));
        }

        // 描画
        {
            // 背景
            Circle(Scene::Center(), 800).draw(ColorF(1.0), ColorF(0.1, 0.3, 0.6));

            // ボタン
            button(bubbleTexture.getTexture())
                .draw()
                .drawFrame(2, 0, ColorF(1.0, 0.5));

            // ボタンの文字
            font(U"はじめる").drawAt(button.center());

            // カーソル
            if (button.mouseOver())
            {
                Cursor::RequestStyle(CursorStyle::Hand);
            }
        }
    }
}

使用例 2

Siv3D-App-2019-12-01-19-25-42.gif

# include <Siv3D.hpp> // OpenSiv3D v0.4.2

/* ここに BubbleTexture クラス */

void Main()
{
    Scene::SetBackground(ColorF(0.4));

    const Texture texture(Emoji(U"🐧"));

    constexpr Size size(180, 240);

    constexpr RoundRect rect(310, 200, size, 90);

    BubbleTexture bubbleTexture(Size(size.x * 2, size.y), 50, 90, 1, 4);

    while (System::Update())
    {
        // 泡の更新
        {
            bubbleTexture.update(Scene::DeltaTime());
            bubbleTexture.render(ColorF(0.0));
        }

        // 描画
        {
            Circle(Scene::Center(), 800).draw(ColorF(0.3, 0.0, 0.3), ColorF(0.0));

            Ellipse(rect.rect.bottomCenter(), rect.w / 4, rect.w / 12)
                .draw(ColorF(0.6));

            rect.draw(ColorF(0.1, 0.8, 0.2, 0.6));

            {
                ScopedRenderStates2D blend(BlendState::Additive);
                rect(bubbleTexture.getTexture()(0, 0, size)).draw();
            }

            texture.drawAt(rect.center().movedBy(0, Periodic::Sine0_1(5s) * 10));

            rect.draw(ColorF(0.1, 0.8, 0.2, 0.3));

            {
                ScopedRenderStates2D blend(BlendState::Additive);
                rect(bubbleTexture.getTexture()(size.x, 0, size)).draw();
            }

            rect.drawFrame(0.5, 0, ColorF(0.0, 0.5));

            Ellipse(rect.rect.topCenter().movedBy(0, 8), rect.w / 4, rect.w / 12)
                .draw(ColorF(0.6));
        }
    }
}
Reputeless
小さくなってもコードはモダン!▼ 未定義動作なしの名コーダー!▼ 実装はいつも 3 つ!!(GCC/Clang/MSVC)▼ OpenSiv3D, cppmap 作者
https://ryo-suzuki-contact.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away