DxLibでエフェクトを簡単にしたい

どうもこんにちはカレンダー主催者です。4日の遅刻ですさーせんm(_ _)m
最近C#のほうでやってるもんで、C++の書き方といい、エラーといいで悩まされてました。
本題はいります

# 基本設計
DXライブラリとともによく出てくるライブラリ「Siv3D」(C++/DirectX 12)には、`Effect`クラスが存在します。そちらは、[こちら](https://siv3d.github.io/ja-jp/tutorial/effect/)を参照してください。
今回は、これぐらい気軽にエフェクトを扱えるようにしたいと思います。言語はC++です。Cでやる場合は頑張れ。

設計としては、

エフェクト管理にエフェクトを追加
↓
メインループ内でエフェクトが有効な間、実行する

こんな感じです。

ちなみにDxLibじゃなくても動きます。
# 実装
突貫でやったんでヘッダーにコード直書きです。
## 管理クラス
```c++:Effect.h
#include <vector>
#include <functional>

class Effect {
private:
	std::vector<std::function<bool(int)>> EffectList;
	std::vector<bool> Valid, Finished;
	std::vector<int> Counter;

public:
	/// <summary>
	/// エフェクトを有効にする
	/// </summary>
	/// <param name="UpdateFunction">エフェクト実体</param>
	/// <returns>エフェクトのインデックス</returns>
	int Add(std::function<bool(int)> UpdateFunction) {
		EffectList.push_back(UpdateFunction);
		Counter.push_back(0);
		Valid.push_back(true);
		Finished.push_back(false);
		return EffectList.size() - 1;
	}

	/// <summary>
	/// エフェクトの一時停止
	/// </summary>
	/// <param name="index">一時停止するエフェクトのインデックス</param>
	void Pause(int index) {
		if (index >= EffectList.size() || Finished[(int)index]) return;
		Valid[(int)index] = false;
	}

	/// <summary>
	/// 一時停止したエフェクトの再開
	/// </summary>
	/// <param name="index">再開するエフェクトのインデックス</param>
	void Resume(int index) {
		if (index >= EffectList.size() || Finished[(int)index]) return;
		Valid[(int)index] = true;
	}

	/// <summary>
	/// エフェクトの停止
	/// </summary>
	/// <param name="index">停止するエフェクトのインデックス</param>
	void Stop(int index) {
		if (index >= EffectList.size() || Finished[(int)index]) return;
		Valid[(int)index] = false;
		Finished[(int)index] = true;
	}

	/// <summary>
	/// 停止されているエフェクトを一番最初から再開する
	/// </summary>
	/// <param name="index">再開するエフェクトのインデックス</param>
	void Restart(int index) {
		Counter[(int)index] = 0;
		Valid[(int)index] = true;
		Finished[(int)index] = false;
	}

	/// <summary>
	/// <para>エフェクトを処理する</para>
	/// <para>メインループ内で呼ぶ</para>
	/// </summary>
	void Update() {
		if (EffectList.size() == 0) {
			return;
		}

		for (int i = 0; EffectList.size() > i; i++) {
			if (!Valid[i]) {
				continue;
			}
			bool r = EffectList[i](Counter[i]); //trueのときに終了する
			Counter[i]++;
			if (r) {
				Finished[i] = true;
				Valid[i] = false;
			}
		}
	}
};
```

```c++:maincpp
#include "DxLib.h"
#include "Effect.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	if (DxLib_Init() == -1){
		return -1;
	}
	ChangeWindowMode(TRUE);
	SetDrawScreen(DX_SCREEN_BACK);

	Effect effect;

	while (ScreenFlip() == 0 && ClearDrawScreen() == 0 && ProcessMessage() == 0) {
		effect.Add([](int t) { //エフェクトの実体が定義された関数を渡す　ラムダ式で直接実装することも可能
				DrawPixel(t, t, 0xffffff);
				return false;//終了条件
			});
		effect.Update();
	}

	DxLib_End();

	return 0;
}
```

画面に(0, 0)から斜め45度に線が入っていくと思います。

# 応用
画像を一定時間ごとに点滅させてみましょう。

## 実装
```c++
class TextureFlash {
	int Time, Interval;
	int CenterPosX, CenterPosY;
	std::vector<byte> FlashPattern;
	double Rate, Angle;
	int texHandle;

public:

	TextureFlash(int CenterPosX, int CenterPosY, std::string texture, int Count, int Interval, std::vector<byte> FlashAlphaPattern, double Rate = 1.0, double Angle = 0) {
		Time = Count; //実行時間
		this->Interval = Interval; //点滅の間隔
		this->CenterPosX = CenterPosX; // 画像の中心座標
		this->CenterPosY = CenterPosY; // 画像の中心座標
		this->Rate = Rate; // 画像の拡大比率
		this->Angle = Angle; // 画像の回転角度
		texHandle = LoadGraph(texture.c_str());
		FlashPattern = FlashAlphaPattern;
	}


	bool Update(int Counter) {
		SetDrawBlendMode(DX_BLENDMODE_ALPHA, FlashPattern[(Counter / Interval) % FlashPattern.size()]);
		DrawRotaGraph(CenterPosX, CenterPosY, Rate, Angle, texHandle, true);
		if (Time == 0) return false; // Time == 0のとき、常に実行
		return Counter >= Time;
	}

};
```

```c++
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	if (DxLib_Init() == -1)
		return -1;
	ChangeWindowMode(TRUE);
	SetDrawScreen(DX_SCREEN_BACK);

	Effect effect;
	TextureFlash flash(320, 240, "Grapic.png", 0, 60, { 255, 0 }); // 透明度を0%と100%で切り替える

	effect.Add([&](int t) { return flash.Update(t); });

	while (ScreenFlip() == 0 && ClearDrawScreen() == 0 && ProcessMessage() == 0) {
		effect.Update();
	}

	DxLib_End();

	return 0;
}
```

これで画像が、画面の中央で一定間隔で無限に点滅してるかと思います。

## 実行結果
GIFのいい感じの取り方わからなかったんでGyazoに上げてます。
[サンプル映像](https://gyazo.com/7be3ffbc2573c0cceb3077204c125333)

# 最後に
今回は画像の点滅を実装しましたが、フェードイン／アウトや、一定時間ごとに画像の切り替えでアニメーションを実装できたりと、できることの幅は広いです。様々なエフェクトやアニメーションを実装してみてください。

<details>
<summary>最後にC#版も載せておきます</sumarry>

```c#
using System.Collections.Generic;
using System;

namespace DxLibEffect
	public class EffectAdmin
	{
		private List<Func<int, bool>>  EffectList = new List<Func<int, bool>>();
		private List<bool> Valid = new List<bool>();
		private List<bool> Finished = new List<bool>();
		public uint EffectCount { get { return (uint)EffectList.Count; } }

		private List<int> Counter = new List<int>();

		public uint Fire(Func<int, bool> UpdateFunc)
		{
			EffectList.Add(UpdateFunc);
			Counter.Add(0);
			Valid.Add(true);
			Finished.Add(false);
			return (uint)EffectList.Count - 1;
		}

		public void Pause(uint index)
		{
			if (index >= EffectCount || Finished[(int)index]) return;
			Valid[(int)index] = false;
		}

		public void Resume(uint index)
		{
			if (index >= EffectCount || Finished[(int)index]) return;
			Valid[(int)index] = true;
		}

		public void Stop(uint index)
		{
			if (index >= EffectCount || Finished[(int)index]) return;
			Valid[(int)index] = false;
			Finished[(int)index] = true;
		}

		public void Restart(uint index)
		{
			Counter[(int)index]= 0;
			Valid[(int)index] = true;
			Finished[(int)index] = false;
		}

		public void Update()
		{
			if(EffectList == null)
			{
				return;
			}
			for(var i = 0; EffectList.Count > i; i++)
			{
				if (!Valid[i])
				{
					continue;
				}
				bool r = EffectList[i](Counter[i]);
				Counter[i]++;
				if (r)
				{
					Finished[i] = true;
					Valid[i] = false;
				}
			}
		}
	}
}
```

正直C#が使いやすくてですね、それでしばらくC++を触ってなかったわけです。
</details>

# 参考
[std::functionでのメンバ関数キャプチャの罠](https://qiita.com/grainrigi/items/1aeeaf19d75d9827d037)
[Siv3d エフェクト](https://siv3d.github.io/ja-jp/tutorial/effect/)