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

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

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));
        }
    }
}
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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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