[UE5] ブループリントをスマートに！役立つ自作ノードアイデア集

BP関数ライブラリで簡単に作れる、便利な自作ノードを紹介します！

```C++:AC2024FunctionLibrary.h
UCLASS()
class UAC2024FunctionLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
}
```

`BlueprintFunctionLibrary` は、静的関数を追加するだけで簡単にブループリント公開ができるクラスです。今回は UE5 と C++ の機能を利用して、いくつか便利なノード作成を解説します。

# Merge
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/08878a15-c9ed-e718-b36c-69f4ebb0c7f1.png)
2つの実行ピンを統合し、結果を `bool` で返すノードです。
ブループリントでは、返り値を実行ピンで分離することが多いですが、その後に共通処理を行いたい場合がよくあります。このノードを使えば、複雑な処理をシンプルにまとめることが可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/1eae1d1f-9508-0807-9e1c-dbadb196ddca.png)
例えば入力イベントを取得する場合。
`Press` と `Release` を統合して、ひとつの結果として扱えます。

```C++:UAC2024FunctionLibrary.h
UENUM()
enum class EAC02024Bool : uint8
{
	A UMETA( DisplayName = "True" ),
	B UMETA( DisplayName = "False" ),
};

class UAC2024FunctionLibrary : public UBlueprintFunctionLibrary
{
    // 略...

	UFUNCTION( BlueprintCallable, Category = "Utilities|FlowControl",
		meta = ( ExpandEnumAsExecs = "InputPin", KeyWords = "branch" ) )
	static bool Merge( EAC02024Bool InputPin );
};
```
```C++:UAC2024FunctionLibrary.cpp
bool UAC2024FunctionLibrary::Merge( const EAC02024Bool InputPin )
{
	return InputPin == EAC02024Bool::A;
}
```

とってもカンタン！

## 一時変数を作りたくない
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/6a34ea1c-eba4-5d0f-14ba-129ba25eacb3.png)
これでも結果は変わらないんですが、イベントグラフだとクラスのメンバー変数を使用する必要がありますから。変数リストが汚れるのは気になりますしね。

# Has Any Chance
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/1634e4ed-28f2-c4ad-50a0-152194a88241.png)
ランダムな結果や、再現性が難しい実装をテストする際に役立つノードです。フラグの切り替えは後述のコンソールコマンドで実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/e3aec42e-3a06-4997-0719-b4934bd5e1dc.png)
このノードでは、フラグの切り替えによってどんな結果も `成功` `失敗` で上書きできるため、デバッグが非常に簡単になります。<font color="orange">**パリィのようなジャスト入力も目押しに頼らなくていいんです。**</font>

```C++:UAC2024FunctionLibrary.h
class UAC2024FunctionLibrary : public UBlueprintFunctionLibrary
{
    // 略...
    UFUNCTION( BlueprintCallable, meta = ( CompactNodeTitle = "Any Chance" ) )
	static bool HasAnyChance( bool bDefaultValue );
};
```
```C++:UAC2024FunctionLibrary.cpp
namespace
{
TOptional<bool> AnyChance;
}	 // namespace

static FAutoConsoleCommand AnyChanceCommand( TEXT( "AnyChance" ), TEXT( "enable/disable any condition" ),
	FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda(
		[]( const TArray<FString>& Args, UWorld* World, FOutputDevice& Output )
		{
			if ( Args.IsValidIndex( 0 ) )
			{
				int32 Flag = 0;
				FDefaultValueHelper::ParseInt( Args[0], Flag );

				AnyChance = static_cast<bool>( Flag );
			}
			else
			{
				AnyChance.Reset();
			}
		} ),
	ECVF_Cheat );

bool UAC2024FunctionLibrary::HasAnyChance( const bool bDefaultValue )
{
	// 値が存在しない（NullOptである）場合はDefaultValueを返す
	return AnyChance.Get( bDefaultValue );
}
```

## コンソールからフラグを切り替える
コンソールコマンドは開発者がエンジンのさまざまな機能や設定にアクセスし、ゲームの挙動を調整したりデバッグしたりするための強力なツールです。ゲームまたはエディタ内のコンソールウィンドウにコマンドを入力することで動作します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/b315eea0-0c1d-2194-7b79-7da330565aab.png)
ゲーム内では `@` キーを押すことで、画面下部にコンソールウィンドウを表示します。
- `AnyChance 1` すべてのフラグを強制的に ON
- `AnyChance 0` すべてのフラグを強制的に OFF
- `AnyChance` フラグの上書きをリセット

### FAutoConsoleCommand
UE5 でコンソールコマンドを簡単に定義するためのテンプレートクラスです。これを使えば、デバッグや設定変更用のカスタムコマンドを手軽に追加できます。さらに `TAutoConsoleVariable` を活用すれば、値を設定するコマンドも簡単に作成可能です。

## TOptional とは？
`TOptional` は、UE5 標準のテンプレートクラスで、「値が存在するか」を表現するためのクラスです。C++ 標準の `std::optional` に似ており、値が存在しない場合にも一つの変数で対応できる便利な仕組みです。

```C++
// OLD
bool bAnyChance = false;
bool bValidAnyChance = false;

// NEW
TOptional<bool> AnyChance;
```
これら 2 つの状態を、ひとつの変数として管理できます。

## BlueprintCallable にする理由

https://x.com/seiko_dev/status/1860887943696863672

`Break BasicParticleData` から繋いだ `Position` `Velocity` が、<font color="orange">**それぞれ別々の配列要素を指す場合がある**</font>、ってやつですね。

- `BlueprintPure` 関数は、副作用を持たない計算用の関数として設計されています
- この特性から、エンジンは必要に応じて関数を再評価することがあります
- たとえば同じピンから複数回使用される場合、関数の呼び出しも複数回実行される可能性があります

乱数を扱う場合は `BlueprintCallable` を使い、結果をキャッシュすることで安全性を確保できます。

# DrawDebugName
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/1eec81be-62c0-338f-2e0c-0f069cd601cf.png)
トレースなどのデバッグ表示を可視化するノードです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/f92ab2e6-b683-dd67-b0c9-62bd1d846944.png)
デバッグ情報は常に表示されると邪魔になる一方で、確認作業には欠かせないものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/32e67300-d6fb-5d73-33e2-3d53c515d4db.png)

```C++:UAC2024FunctionLibrary.h
class UAC2024FunctionLibrary : public UBlueprintFunctionLibrary
{
	// 略...
	UFUNCTION( BlueprintPure )
	static EDrawDebugTrace::Type DrawDebugName( FName DebugName, float& OutDuration );
};
```
```C++:UAC2024FunctionLibrary.cpp
namespace
{
TMap<FName, float> DebugDrawNames;
}	 // namespace

static FAutoConsoleCommand DebugDrawNameCommand( TEXT( "DebugDrawName" ), TEXT( "visualize debug drawings" ),
	FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda(
		[]( const TArray<FString>& Args, UWorld* World, FOutputDevice& Output )
		{
			if ( Args.IsValidIndex( 0 ) )
			{
				const FName DebugName = *Args[0];

				if ( Args.IsValidIndex( 1 ) )
				{
					float Duration = 0.f;
					FDefaultValueHelper::ParseFloat( Args[1], Duration );

					DebugDrawNames.FindOrAdd( DebugName ) = Duration;
				}
				else
				{
					DebugDrawNames.Remove( DebugName );
				}
			}
		} ),
	ECVF_Cheat );

EDrawDebugTrace::Type UAC2024FunctionLibrary::DrawDebugName( const FName DebugName, float& OutDuration )
{
	if ( const float* Duration = DebugDrawNames.Find( DebugName ) )
	{
		OutDuration = *Duration;
		return OutDuration > 0.f ? EDrawDebugTrace::Type::ForDuration : EDrawDebugTrace::Type::ForOneFrame;
	}

	return EDrawDebugTrace::None;
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/39f8685a-a4b8-73e7-1eb2-e8d4ceedab8d.png)
名前ごとにフィルタをかけて、表示のオン/オフを切り替えることが可能です。

:::note info
`GameplayTag` を使用して階層ごとに管理すれば、さらに便利になるかもしれません。
:::

# まとめ
自分の中の＼あったらいいな／をカタチにしてみました。
「こんなノードも便利だよ」「こういう作り方もある」など、ぜひ教えてしていただけると嬉しいです！