C++
ゲーム制作
Siv3D

Siv3D:SceneManagerのシーン遷移のフェード管理

SceneManager

Siv3Dでゲーム制作をするとき、プロジェクトの大きさ次第では、HamFrameworkのham::SceneManagerを使う人は多いかと思います。

Main.cpp
using MyApp = ham::SceneManager<String>;

class GameMain : public MyApp::Scene
{
private:
public:
    void init()override;//OpenSiv3Dはコンストラクタ
    void update()override;
    void draw()const override;
};

その際、シーン遷移のフェード中の処理もオーバーライドできるのはご存知でしょうか?

Main.cpp
class GameMain : public MyApp::Scene
{
private:
public:
    void init()override;
    void update()override;
    void draw()const override;

    //フェード中の処理
    void updateFadeIn(double t)override;
    void updateFadeOut(double t)override;
    void drawFadeIn(double t)const override;
    void drawFadeOut(double t)const override;
};

この際にそれぞれのメソッドの引数double tに入ってくるのは
[0.0,1.0) (遷移開始~遷移終了)
になります

今回はこのうちのdrawFadeIndrawFadeOutを利用することでシーン遷移のフェード演出の変更をしてみるのですが、普段自分がやっている管理方法を紹介します。

FadeInとFadeOut関数の作成

Fade.hpp
#include<functional>

void FadeIn(std::function<void(double)> func, double t)
{
    func(1.0 - t);
}
auto FadeOut(std::function<void(double)> func, double t)
{
    func(t);
}

第一引数がdoubleである関数をうけとり、コールバックするFadeInFadeOutの関数を作ります。

FadeInとFadeOutは動きが逆になればよいだけなので、
FadeInでは1.0-t
FadeOutではt
渡しています。

templateにして複数引数への対応

Fade.hpp
template<class Func, class... Args>
void FadeIn(Func func, double t, Args&&...args)
{
    func(1.0 - t, std::forward<Args>(args)...);
}

template<class Func, class... Args>
void FadeOut(Func func, double t, Args&&...args)
{
    func(t, std::forward<Args>(args)...);
}

受け取る関数が他に引数を必要とする場合も想定するならばtemplate可変長引数を使うのも良いです。自分はそうしています。(融通が利くので)

フェード用の関数をいろいろ作ってみる

Fade.hpp
namespace Fade
{
    //フェードの種類
    void Default(double t);
    void SmoothCircle(double t);
    void Siv3DKun(double t,bool in);
}

今回は3種類のフェード関数を作ってみます。

Fade.cpp
#include"Fade.hpp"
#include<Siv3D.hpp>

namespace
{
    //微調整
    bool FadeBase(double& t)
    {
        if (t > 0.75)
        {
            Window::BaseClientRect().draw(ColorF(0.0));
            return false;
        }

        t *= (1 / 0.75);
        return true;
    }

    //マスク処理
    void StencilMask(std::function<void()> base, std::function<void()>drawFunc, StencilFunc stencilFunc, uint8 stencilValue = 1)
    {
        Graphics2D::SetStencilState(StencilState::Replace);
        Graphics2D::SetStencilValue(stencilValue);

        base();

        Graphics2D::SetStencilState(StencilState::Test(stencilFunc));

        drawFunc();

        Graphics2D::SetStencilState(StencilState::Default);

    };
}

namespace Fade
{
    //デフォルト
    void Default(double t)
    {
        if (!::FadeBase(t))
            return;
        Window::BaseClientRect().draw(ColorF(0.0, t));
    }

    //3次関数的に広がる円形マスク
    void SmoothCircle(double t)
    {
        if (!::FadeBase(t))
            return;

        static auto func = [=](double t)
        {
            return ((t - 0.3f)*(t - 0.3f)*(t - 0.3f) + 0.027) / 0.37f;
        };

        ::StencilMask(
            [t] { Circle(Window::BaseCenter(), Window::BaseWidth() * func(1.0 - t)).draw(); },
            [] { Window::BaseClientRect().draw(ColorF(0.0, 1)); },
            StencilFunc::NotEqual
        );
    }

    //Siv3Dくんマスク
    void Siv3DKun(double t, bool in)
    {
        static Texture tex(L"Example/siv3d-kun.png");
        if (!::FadeBase(t))
            return;

        ::StencilMask(
            [t, in]{
            tex.scale(4 * EaseIn(Easing::Quad, 1 - t))
                .rotate((in ? (1.0 - t) : t) * 10)
                .drawAt(Window::BaseCenter());
            },
            [t] { Window::BaseClientRect().draw(ColorF(0.0, t * 4.0)); },
            StencilFunc::NotEqual
        );
    }
}

こんな感じで自分オリジナルの関数を作っていきます。

実際に使ってみる

さっき作った関数を実際に使ってみましょう。

main.cpp
#include"Fade.hpp"

class GameMain : public MyApp::Scene
{
private:
public:
    void init()override;
    void update()override;
    void draw()const override;

    //フェード中の処理
    void drawFadeIn(double t)const override 
    {
        this->draw();
        FadeIn(Fade::Siv3DKun, t,true);//←ここを変えるだけで変更できる
    }
    void drawFadeOut(double t)const override 
    {
        this->draw();
        FadeOut(Fade::Siv3DKun, t,false);//←ここを変えるだけで変更できる
    }

};

例えばFadeIn(Fade::Siv3DKun, t,true)のところを、FadeIn(Fade::Default,t)等に変更するだけで簡単に演出を切り替え可能なので非常に便利です。
this->draw()を呼ばないとフェード中に元の描画がされないので呼び忘れないように注意が必要です。

参考

https://github.com/Siv3D/Reference-JP/wiki/シーン切り替え(シルエット)

超おまけ

以下はちょっとレベルの上がった話なのでスルーするか、コピペして使うか、頑張って勉強してください。

FadeIn/Out関数の型制約

templateでFadeIn/Outの関数を作った場合、間違った引数を使ってエラーがでる場合があり危険です。
最低限の型制約をしておくと良いでしょう。

Fade.hpp
#include<type_traits>

//メタ関数作成
//std::is_callable(C++17/VS2017なら使える)
template<class AlwaysVoid, class F, class... Args>
struct is_callable_impl :std::false_type
{};
template<class F, class... Args>
struct is_callable_impl<
    std::void_t<decltype(std::declval<F>()(std::declval<Args>()...))>, F, Args...
> :std::true_type
{};

template<class F, class... Args>
using is_callable = is_callable_impl<void, F, Args...>;

template<class Func, class... Args>
auto FadeIn(Func func, double t, Args&&...args)->std::enable_if_t <is_callable<Func, double, Args...>::value>
{
    func(1.0 - t, std::forward<Args>(args)...);
}
template<class Func, class... Args>
auto FadeOut(Func func, double t, Args&&...args)->std::enable_if_t <is_callable<Func, double, Args...>::value>
{
    func(t, std::forward<Args>(args)...);
}