コンソールコマンドの引数の入力候補を出す

#はじめに
この記事を見ているということは、自分で追加したコンソールコマンドを使っていて引数に文字列やEnumを指定する場合、毎回自分で入力しないといけないのが面倒くさいと思ったことがあるのではないでしょうか？

``` 
UENUM(BlueprintType)
enum class ETestCommandArg : uint8
{
	Unreal,
	Engine,
	Epic,
};

UCLASS()
class CONSOLECOMMANDTEST_API UConsoleCommandTestObject : public UObject
{
	GENERATED_BODY()

public:
	UFUNCTION(Exec)
	static void AutoCompleteTest(ETestCommandArg Type);
};
```
例えばこちらの`AutoCompleteTest`というコンソールコマンドを使おうとすると、引数の`Type`を毎回入力する羽目になります。
![1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/52f2ed64-5279-5827-9b77-ec72b00b21fd.png)

今回は下の画像のように引数の入力候補が表示されるようする方法をご紹介します。
![2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/fc18f0ae-1907-ef85-07af-8f8768d80a68.png)

#やり方(C++を使わない場合)
![3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/b5946fb9-d287-24fa-6bde-d621e6b4bc07.png)
プロジェクト設定にある`ManualAutoCompleteList`にデータを追加します。
![4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/b9817a51-9dca-9edf-f89d-0288d77b2230.png)
このように`Command`にコマンド名と入力候補を、`Desc`にコマンドの横に表示される説明文を設定します。
![5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/88b8a55b-fea1-d181-9403-1ef7bc0cb1fe.png)
設定すると上の画像の様に入力候補が表示されわざわざ文字列を入力する必要がなくなりました。
しかし、今回の様に引数が1つで候補があまり多くない場合は良いですが、候補が数十個あったり引数が複数あった場合はその分`Manual Auto Complete List`に追加しなければならないデータが多くなるため、あまり現実的ではありません。

#やり方(C++を使う場合)
そこで、C++の出番です！！入力候補が多くても、引数が複数個あってもC++を使えば楽勝です。

まずは、`UConsole`にある入力補間のデータを登録するデリゲートに自前の関数をバインドします。
[ConsoleCommandTest.h](https://github.com/Naotsun19B/ConsoleCommandTest/blob/1e211bdcc3db050a76c5e7b2a01482b76551a193/Source/ConsoleCommandTest/ConsoleCommandTest.h#L10)
[ConsoleCommandTest.cpp](https://github.com/Naotsun19B/ConsoleCommandTest/blob/1e211bdcc3db050a76c5e7b2a01482b76551a193/Source/ConsoleCommandTest/ConsoleCommandTest.cpp#L67)

``` ConsoleCommandTest.h
class FConsoleCommandTest : public IModuleInterface
{
public:
	// IModuleInterface interface.
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
	virtual bool IsGameModule() const override;
	// End of IModuleInterface interface.

private:
	// Register the predictive conversion of console commands.
	void HandleRegisterConsoleAutoCompleteEntries(TArray<FAutoCompleteCommand>& AutoCompleteList) const;

private:
	// Delegate handle for registering hot-reloaded classes that have been added.
	FDelegateHandle BuildConsoleEntriesHandle;
};
```

``` ConsoleCommandTest.cpp
void FConsoleCommandTest::StartupModule()
{
	BuildConsoleEntriesHandle = UConsole::RegisterConsoleAutoCompleteEntries.AddRaw(this, &FConsoleCommandTest::HandleRegisterConsoleAutoCompleteEntries);
}

void FConsoleCommandTest::ShutdownModule()
{
	UConsole::RegisterConsoleAutoCompleteEntries.Remove(BuildConsoleEntriesHandle);
}
```
バインドするタイミングはどこでも良いのですが、今回はモジュールクラス内でバインドすることにします。
一応お行儀よくデリゲートハンドルを取っておいて終了時にアンバインドしています。

ここまでやると`HandleRegisterConsoleAutoCompleteEntries`がプレイを開始して最初にコンソールコマンドを入力し始めた時に呼ばれるようになります。

引数の`AutoCompleteList`は先程のプロジェクト設定の`ManualAutoCompleteList`が渡されるので、そこにデータを追加していきます。
[ConsoleCommandTest.cpp](https://github.com/Naotsun19B/ConsoleCommandTest/blob/1e211bdcc3db050a76c5e7b2a01482b76551a193/Source/ConsoleCommandTest/ConsoleCommandTest.cpp#L82)

``` ConsoleCommandTest.cpp
void FConsoleCommandTest::HandleRegisterConsoleAutoCompleteEntries(TArray<FAutoCompleteCommand>& AutoCompleteList) const
{
	auto AddAutoCompleteEntries = [&AutoCompleteList](const TArray<FString>& CommandElements) -> int32
	{
		const FString Command = FString::Join(CommandElements, TEXT(" "));
		int32 Index = 0;
		for (; Index < AutoCompleteList.Num(); Index++)
		{
			if (AutoCompleteList.IsValidIndex(Index))
			{
				if (AutoCompleteList[Index].Command == Command)
				{
					break;
				}
			}
		}

		FColor AutoCompleteCommandColor = FColor::Green;
		if (const auto* ConsoleSettings = GetDefault<UConsoleSettings>())
		{
			AutoCompleteCommandColor = ConsoleSettings->AutoCompleteCommandColor;
		}
		
		const int32 NewIndex = ((Index < AutoCompleteList.Num()) ? Index : AutoCompleteList.AddDefaulted());
		AutoCompleteList[NewIndex].Command = Command;
		AutoCompleteList[NewIndex].Color = AutoCompleteCommandColor;
		return NewIndex;
	};
	
	// AutoCompleteTest
	{
		const FString FunctionName = GET_FUNCTION_NAME_STRING_CHECKED(UConsoleCommandTestObject, AutoCompleteTest);
		const FString Description = TEXT("A test to try predictive conversion of console commands");
		
		{
			const int32 NewIndex = AddAutoCompleteEntries({ FunctionName });
			AutoCompleteList[NewIndex].Desc = Description;
		}
		
		if (const UEnum* Enum = StaticEnum<ETestCommandArg>())
		{
			for (int32 Index = 0; Index < Enum->NumEnums() - 1; Index++)
			{
				const FString EnumString = Enum->GetDisplayNameTextByIndex(Index).ToString();
				const int32 NewIndex = AddAutoCompleteEntries({ FunctionName, EnumString });
				AutoCompleteList[NewIndex].Desc = Description;
			}
		}
	}
}
```
まず、`AddAutoCompleteEntries`というラムダ関数を定義していますが、こちらは`ManualAutoCompleteList`に既にデータがある場合はそのデータの、ない場合は新しく追加したデータのインデックスを返します。
返ってきたインデックスの`FAutoCompleteCommand`の`Desc`に説明文を設定しています。

C++を使うと入力候補の文字の色を変えられますが、今回は全てエンジンと同じ色にするため、プロジェクト設定の値を使用しています。
こちらの色はプロジェクト設定の`AutoCompleteCommandColor`から変更できます。
![6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325493/32bc3815-fb90-b7de-9f90-0d5bb3f8cabd.png)

[ConsoleCommandTest.Build.cs](https://github.com/Naotsun19B/ConsoleCommandTest/blob/1e211bdcc3db050a76c5e7b2a01482b76551a193/Source/ConsoleCommandTest/ConsoleCommandTest.Build.cs#L21)

``` ConsoleCommandTest.Build.cs 
PrivateDependencyModuleNames.AddRange(new string[]
{
	"EngineSettings",
});
```
最後に、`UConsoleSettings`を使用するために、`EngineSettings`モジュールを使うのでbuild.csで追加しておきます。
ここまでやるとプロジェクト設定でデータを追加した時と同様に入力候補が表示されるようになります。

#おわりに
少し手間がかかることではありますが、一度作っておくとその後の開発効率を上げられるので積極的に取り入れていきたいところです。
コンソールコマンドで文字列を何度も打つことに疲れた方の役に立てれば嬉しいです。

この記事で紹介したプロジェクトは以下でダウンロードできます。
https://github.com/Naotsun19B/ConsoleCommandTest