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

#SceneManager

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

```cpp: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;
};

```

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

```cpp: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)**　(遷移開始～遷移終了)
になります

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

#FadeInとFadeOut関数の作成

```cpp: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`である関数をうけとり、コールバックする`FadeIn`と`FadeOut`の関数を作ります。

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

##templateにして複数引数への対応
```cpp: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可変長引数を使うのも良いです。自分はそうしています。（融通が利くので）

#フェード用の関数をいろいろ作ってみる
```cpp:Fade.hpp
namespace Fade
{
	//フェードの種類
	void Default(double t);
	void SmoothCircle(double t);
	void Siv3DKun(double t,bool in);
}
```
今回は3種類のフェード関数を作ってみます。

```cpp: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
		);
	}
}
```

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

#実際に使ってみる

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

```cpp: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`の関数を作った場合、間違った引数を使ってエラーがでる場合があり危険です。
最低限の型制約をしておくと良いでしょう。

```cpp:Fade.hpp
#include<type_traits>

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

template<class F, class... Args>
using is_invocable = is_invocable_impl<void, F, Args...>;

template<class Func, class... Args>
auto FadeIn(Func func, double t, Args&&...args)->std::enable_if_t <is_invocable<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_invocable<Func, double, Args...>::value>
{
	func(t, std::forward<Args>(args)...);
}

```