[UE5](C++)自作Curveクラスを作成してカーブエディターで利用する

確認UE5: 5.4.4

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/4df66d31-43c7-32e0-0766-fbd1a605b271.png)
グラフベースでタイムライン等の制御に利用できる便利なCurveVector、CurveLinearColor。

運用していると、下記のようになる事が偶にあります。ありました。
* CurveVectorではカーブが3つ。今回5つのパラメーターがあるので CurveVector を2つ使います
    * 1つのカーブアセットで編集したい……アセット切り替えたくない……
* X は "Intensity"、Y は "Attenuation" として内部的には扱います
    * "X, Y, Z" ではなくて、実際のパラメーターの名前にしたい……どれがどれだっけ、になる……
 
このような場合に、Curveアセットの種類を自作していい感じにします


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/14297a18-1aa6-f09c-ffe8-e635e0974cf1.png)
このような形で、上図はカーブを6つ用意、それぞれに名前を設定して分かりやすくしています。

# 実装コード
C++で UCurveBase を継承するだけで利用可能です。

``` cpp:PostProcessCurve.h
UCLASS()
class UPostProcessCurve : public UCurveBase
{
	GENERATED_UCLASS_BODY()

	UPROPERTY()
	FRichCurve FloatCurves[6]; // todo 要素番号を定数化

	UFUNCTION(BlueprintCallable, Category="Math|Curves")
	float GetValue(int32 CurveIndex, float InTime) const;


	// Begin FCurveOwnerInterface
	virtual TArray<FRichCurveEditInfoConst> GetCurves() const override;
	virtual TArray<FRichCurveEditInfo> GetCurves() override;
	virtual FLinearColor GetCurveColor(FRichCurveEditInfo CurveInfo) const override;
	virtual bool IsValidCurve( FRichCurveEditInfo CurveInfo ) override;

	bool operator == (const UPostProcessCurve& Curve) const;
};
```

``` cpp:PostProcessCurve.cpp
UPostProcessCurve::UPostProcessCurve(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
#if WITH_EDITOR
	// カーブの初期値を設定したい場合に
	bool bIsEmpty = true;
	for (int32 Index = 0 ; Index < UE_ARRAY_COUNT(FloatCurves) ; ++Index)
	{
		if (FloatCurves[Index].GetNumKeys() > 0)
		{
			bIsEmpty = false;
			break;
		}
	}
	if (bIsEmpty)
	{
		FloatCurves[0].AddKey(0.0f, 6500.0f);
		FloatCurves[1].AddKey(0.0f, 0.5f);
		FloatCurves[2].AddKey(0.0f, 1.0f);
		FloatCurves[3].AddKey(0.0f, 0.0f);
		FloatCurves[4].AddKey(0.0f, 0.0f);
		FloatCurves[5].AddKey(0.0f, 0.0f);
	}
#endif
}

float UPostProcessCurve::GetValue(int32 CurveIndex, float InTime) const
{
	if ((CurveIndex < 0) || (CurveIndex >= UE_ARRAY_COUNT(FloatCurves)))
	{
		ensure(false);
		return 0;
	}
	return FloatCurves[CurveIndex].Eval(InTime);
}

// カーブエディターで表示される名前
static const FName PostProcessCurveWhiteTempName(TEXT("WhiteTemp"));
static const FName PostProcessCurveBloomName(TEXT("Bloom"));
static const FName PostProcessCurveLensFlareName(TEXT("LensFlare"));
static const FName PostProcessCurveVignetteName(TEXT("Vignette"));
static const FName PostProcessCurveFringeName(TEXT("Fringe"));
static const FName PostProcessCurveMotionBlurName(TEXT("MotionBlur"));

TArray<FRichCurveEditInfoConst> UPostProcessCurve::GetCurves() const
{
	TArray<FRichCurveEditInfoConst> Curves;
	Curves.Add(FRichCurveEditInfoConst(&FloatCurves[0], PostProcessCurveWhiteTempName));
	Curves.Add(FRichCurveEditInfoConst(&FloatCurves[1], PostProcessCurveBloomName));
	Curves.Add(FRichCurveEditInfoConst(&FloatCurves[2], PostProcessCurveLensFlareName));
	Curves.Add(FRichCurveEditInfoConst(&FloatCurves[3], PostProcessCurveVignetteName));
	Curves.Add(FRichCurveEditInfoConst(&FloatCurves[4], PostProcessCurveFringeName));
	Curves.Add(FRichCurveEditInfoConst(&FloatCurves[5], PostProcessCurveMotionBlurName));
	return Curves;
}

TArray<FRichCurveEditInfo> UPostProcessCurve::GetCurves()
{
	TArray<FRichCurveEditInfo> Curves;
	Curves.Add(FRichCurveEditInfo(&FloatCurves[0], PostProcessCurveWhiteTempName));
	Curves.Add(FRichCurveEditInfo(&FloatCurves[1], PostProcessCurveBloomName));
	Curves.Add(FRichCurveEditInfo(&FloatCurves[2], PostProcessCurveLensFlareName));
	Curves.Add(FRichCurveEditInfo(&FloatCurves[3], PostProcessCurveVignetteName));
	Curves.Add(FRichCurveEditInfo(&FloatCurves[4], PostProcessCurveFringeName));
	Curves.Add(FRichCurveEditInfo(&FloatCurves[5], PostProcessCurveMotionBlurName));
	return Curves;
}
FLinearColor UPostProcessCurve::GetCurveColor(FRichCurveEditInfo CurveInfo) const
{
	// カーブエディターで表示される色
	if (CurveInfo.CurveName == PostProcessCurveWhiteTempName)       return FLinearColor(0.8f, 0.2f, 0.2f);
	else if (CurveInfo.CurveName == PostProcessCurveBloomName)      return FLinearColor(0.8f, 0.8f, 0.2f);
	else if (CurveInfo.CurveName == PostProcessCurveLensFlareName)  return FLinearColor(0.2f, 0.8f, 0.2f);
	else if (CurveInfo.CurveName == PostProcessCurveVignetteName)   return FLinearColor(0.2f, 0.8f, 0.8f);
	else if (CurveInfo.CurveName == PostProcessCurveFringeName)     return FLinearColor(0.2f, 0.2f, 0.8f);
	else if (CurveInfo.CurveName == PostProcessCurveMotionBlurName) return FLinearColor(0.8f, 0.2f, 0.8f);

	return Super::GetCurveColor(CurveInfo);
}

bool UPostProcessCurve::IsValidCurve( FRichCurveEditInfo CurveInfo )
{
	for (const auto& Curve : FloatCurves)
	{
		if (CurveInfo.CurveToEdit == &Curve)
		{
			return true;
		}
	}
	return false;
}

bool UPostProcessCurve::operator == (const UPostProcessCurve& Curve) const
{
	for (int32 Index = 0 ; Index < UE_ARRAY_COUNT(FloatCurves) ; ++Index)
	{
		if (Curve.FloatCurves[Index] != FloatCurves[Index])
		{
			return false;
		}
	}
	return true;
}
```

上記のように UCurveBaseを継承したクラスを用意すると、Editorでのカーブアセット作成時にクラスが表示されて使用可能になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/0785df19-4422-2b2e-e08b-c38b539d35ae.png)