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

【C++/OpenSiv3D】シーン切り替え時のエフェクトをカスタマイズする

OpenSiv3D の SceneManager を使うと、シーン遷移時に自動でフェードイン/アウトのエフェクトが入ります。このときの挙動をカスタマイズするには、drawFadeIn(double t) const overridedrawFadeOut(double t) const override を実装します。この記事では、シーン切り替え時のエフェクトのカスタマイズ例を紹介します。(対象: OpenSiv3D v0.4.2~)

時計

時計のように回転しながら背景色に切り替わります。
SceneTransition1.gif

namespace SceneTransition
{
    class Clock
    {
    private:

        ColorF m_backgroundColor = ColorF(0.5, 0.8, 1.0);

    public:

        void fadeIn(double t)
        {
            const double r = Scene::Center().length();
            Circle(Scene::Center(), r).drawPie(0_deg, -(1.0 - t) * 360_deg, m_backgroundColor);
        }

        void fadeOut(double t)
        {
            const double r = Scene::Center().length();
            Circle(Scene::Center(), r).drawPie(0_deg, t * 360_deg, m_backgroundColor);
        }
    };
}

ステンシル

キャラクターのシルエットを使って背景色に切り替わります。
SceneTransition2.gif

namespace SceneTransition
{
    class Stencil
    {
    private:

        Texture m_texture;

        RenderTexture m_screenTexture;

        ColorF m_backgroundColor = ColorF(0.8, 0.4, 0.0);

        void fade(double t)
        {
            m_screenTexture.clear(m_backgroundColor);
            {
                ScopedRenderTarget2D rt(m_screenTexture);
                BlendState b;
                b.writeR = b.writeG = b.writeB = false;
                b.srcAlpha = b.dstAlpha = Blend::One;
                b.opAlpha = BlendOp::RevSubtract;
                ScopedRenderStates2D blend(b);

                m_texture.scaled(EaseInSine(t) * 8.0).drawAt(Scene::Center());
            }

            m_screenTexture.draw();
        }

    public:

        Stencil()
            : m_screenTexture(Scene::Size())
        {
            m_texture = Texture(Emoji(U"🦊"));
        }

        void fadeIn(double t)
        {
            fade(t);
        }

        void fadeOut(double t)
        {
            fade(1.0 - t);
        }
    };
}

使い方

  1. SceneManager<State, Data> の共通データ Data に、上記クラスを sceneTransition という名前で追加します。

  2. 各シーンで drawFadeIn(double t) const overridedrawFadeOut(double t) const override を実装し、その中で draw() を呼んだあとに sceneTransition.fadeIn(t) または sceneTransition.fadeOut(t) を呼びます。

  3. シーン遷移時間を changeScene() の第二引数で適宜調整します。

使い方サンプル

シーン遷移のサンプル に、今回のエフェクトを組み込みます。

# include <Siv3D.hpp>

namespace SceneTransition
{
    class Clock
    {
    private:

        ColorF m_backgroundColor = ColorF(0.5, 0.8, 1.0);

    public:

        void fadeIn(double t)
        {
            const double r = Scene::Center().length();
            Circle(Scene::Center(), r).drawPie(0_deg, -(1.0 - t) * 360_deg, m_backgroundColor);
        }

        void fadeOut(double t)
        {
            const double r = Scene::Center().length();
            Circle(Scene::Center(), r).drawPie(0_deg, t * 360_deg, m_backgroundColor);
        }
    };

    class Stencil
    {
    private:

        Texture m_texture;

        RenderTexture m_screenTexture;

        ColorF m_backgroundColor = ColorF(0.8, 0.4, 0.0);

        void fade(double t)
        {
            m_screenTexture.clear(m_backgroundColor);
            {
                ScopedRenderTarget2D rt(m_screenTexture);
                BlendState b;
                b.writeR = b.writeG = b.writeB = false;
                b.srcAlpha = b.dstAlpha = Blend::One;
                b.opAlpha = BlendOp::RevSubtract;
                ScopedRenderStates2D blend(b);

                m_texture.scaled(EaseInSine(t) * 8.0).drawAt(Scene::Center());
            }

            m_screenTexture.draw();
        }

    public:

        Stencil()
            : m_screenTexture(Scene::Size())
        {
            m_texture = Texture(Emoji(U"🦊"));
        }

        void fadeIn(double t)
        {
            fade(t);
        }

        void fadeOut(double t)
        {
            fade(1.0 - t);
        }
    };
}

// シーンの名前
enum class State
{
    Title,
    Game
};

// ゲームデータ
struct GameData
{
    // ハイスコア
    int32 highScore = 0;

    SceneTransition::Stencil sceneTransition;
};

// シーン管理クラス
using MyApp = SceneManager<State, GameData>;


// タイトルシーン
class Title : public MyApp::Scene
{
private:

    Rect m_startButton = Rect(Arg::center = Scene::Center().movedBy(0, 0), 300, 60);
    Transition m_startTransition = Transition(0.4s, 0.2s);

    Rect m_exitButton = Rect(Arg::center = Scene::Center().movedBy(0, 100), 300, 60);
    Transition m_exitTransition = Transition(0.4s, 0.2s);

public:

    Title(const InitData& init)
        : IScene(init) {}

    void update() override
    {
        m_startTransition.update(m_startButton.mouseOver());
        m_exitTransition.update(m_exitButton.mouseOver());

        if (m_startButton.mouseOver() || m_exitButton.mouseOver())
        {
            Cursor::RequestStyle(CursorStyle::Hand);
        }

        if (m_startButton.leftClicked())
        {
            changeScene(State::Game, 3s);
        }

        if (m_exitButton.leftClicked())
        {
            System::Exit();
        }
    }

    void draw() const override
    {
        const String titleText = U"ブロックくずし";
        const Vec2 center(Scene::Center().x, 120);
        FontAsset(U"Title")(titleText).drawAt(center.movedBy(4, 6), ColorF(0.0, 0.5));
        FontAsset(U"Title")(titleText).drawAt(center);

        m_startButton.draw(ColorF(1.0, m_startTransition.value())).drawFrame(2);
        m_exitButton.draw(ColorF(1.0, m_exitTransition.value())).drawFrame(2);

        FontAsset(U"Menu")(U"はじめる").drawAt(m_startButton.center(), ColorF(0.25));
        FontAsset(U"Menu")(U"おわる").drawAt(m_exitButton.center(), ColorF(0.25));

        Rect(0, 500, Scene::Width(), Scene::Height() - 500)
            .draw(Arg::top = ColorF(0.0, 0.0), Arg::bottom = ColorF(0.0, 0.5));

        const int32 highScore = getData().highScore;
        FontAsset(U"Score")(U"High score: {}"_fmt(highScore)).drawAt(Vec2(620, 550));
    }

    void drawFadeIn(double t) const override
    {
        draw();
        getData().sceneTransition.fadeIn(t);
    }

    void drawFadeOut(double t) const override
    {
        draw();
        getData().sceneTransition.fadeOut(t);
    }
};

// ゲームシーン
class Game : public MyApp::Scene
{
private:

    // ブロックのサイズ
    static constexpr Size blockSize = Size(40, 20);

    // ボールの速さ
    static constexpr double speed = 480.0;

    // ブロックの配列
    Array<Rect> m_blocks;

    // ボールの速度
    Vec2 m_ballVelocity = Vec2(0, -speed);

    // ボール
    Circle m_ball = Circle(400, 400, 8);

    // パドル
    Rect m_paddle = Rect(Arg::center(Cursor::Pos().x, 500), 60, 10);

    // スコア
    int32 m_score = 0;

public:

    Game(const InitData& init)
        : IScene(init)
    {
        // 横 (Scene::Width() / blockSize.x) 個、縦 5 個のブロックを配列に追加する
        for (auto p : step(Size((Scene::Width() / blockSize.x), 5)))
        {
            m_blocks << Rect(p.x * blockSize.x, 60 + p.y * blockSize.y, blockSize);
        }
    }

    void update() override
    {
        // パドルを操作
        m_paddle = Rect(Arg::center(Cursor::Pos().x, 500), 60, 10);

        // ボールを移動
        m_ball.moveBy(m_ballVelocity * Scene::DeltaTime());

        // ブロックを順にチェック
        for (auto it = m_blocks.begin(); it != m_blocks.end(); ++it)
        {
            // ボールとブロックが交差していたら
            if (it->intersects(m_ball))
            {
                // ボールの向きを反転する
                (it->bottom().intersects(m_ball) || it->top().intersects(m_ball) ? m_ballVelocity.y : m_ballVelocity.x) *= -1;

                // ブロックを配列から削除(イテレータが無効になるので注意)
                m_blocks.erase(it);

                // スコアを加算
                ++m_score;

                // これ以上チェックしない  
                break;
            }
        }

        // 天井にぶつかったらはね返る
        if (m_ball.y < 0 && m_ballVelocity.y < 0)
        {
            m_ballVelocity.y *= -1;
        }

        if (m_ball.y > Scene::Height())
        {
            changeScene(State::Title, 3s);
            getData().highScore = Max(getData().highScore, m_score);
        }

        // 左右の壁にぶつかったらはね返る
        if ((m_ball.x < 0 && m_ballVelocity.x < 0) || (Scene::Width() < m_ball.x && m_ballVelocity.x > 0))
        {
            m_ballVelocity.x *= -1;
        }

        // パドルにあたったらはね返る
        if (m_ballVelocity.y > 0 && m_paddle.intersects(m_ball))
        {
            // パドルの中心からの距離に応じてはね返る向きを変える
            m_ballVelocity = Vec2((m_ball.x - m_paddle.center().x) * 10, -m_ballVelocity.y).setLength(speed);
        }
    }

    void draw() const override
    {
        FontAsset(U"Score")(m_score).drawAt(Scene::Center().x, 30);

        // すべてのブロックを描画する
        for (const auto& block : m_blocks)
        {
            block.stretched(-1).draw(HSV(block.y - 40));
        }

        // ボールを描く
        m_ball.draw();

        // パドルを描く
        m_paddle.draw();
    }

    void drawFadeIn(double t) const override
    {
        draw();
        getData().sceneTransition.fadeIn(t);
    }

    void drawFadeOut(double t) const override
    {
        draw();
        getData().sceneTransition.fadeOut(t);
    }
};

void Main()
{
    // 使用するフォントアセットを登録
    FontAsset::Register(U"Title", 120, U"example/font/AnnyantRoman/AnnyantRoman.ttf");
    FontAsset::Register(U"Menu", 30, Typeface::Regular);
    FontAsset::Register(U"Score", 36, Typeface::Bold);

    // 背景色を設定
    Scene::SetBackground(ColorF(0.2, 0.8, 0.4));

    // シーンと遷移時の色を設定
    MyApp manager;
    manager
        .add<Title>(State::Title)
        .add<Game>(State::Game)
        .setFadeColor(ColorF(1.0));

    while (System::Update())
    {
        if (!manager.update())
        {
            break;
        }
    }
}
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