LoginSignup
8
2

More than 3 years have passed since last update.

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

Posted at

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));
        }
    }
}
8
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2