UE4 UDeveloperSettingsでプロジェクト設定追加を試してみる

# 概要
UnrealEngineでのプロジェクト設定追加を UDeveloperSettings を継承したクラスで試してみたメモです。

## 更新履歴
| 日付 | 内容 |
|:----|:-----|
|2020/03/10| 4.26での変更点について|
|2020/04/19| パッケージ時のワーニングについて追記|

## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.25, 4.26


## 参考
[【UE4】UDeveloperSettingsでプロジェクト設定に項目を追加する](https://historia.co.jp/archives/18025/)
[UE4公式：UDeveloperSettings](https://docs.unrealengine.com/en-US/API/Runtime/DeveloperSettings/Engine/UDeveloperSettings/index.html)
[UE4公式：コンフィギュレーションファイル](https://docs.unrealengine.com/ja/ProductionPipelines/ConfigurationFiles/index.html)




# プロジェクト設定への追加
## C++クラスの定義
UDeveloperSettingsを継承したクラスを定義します。
プロジェクト設定に反映させるメンバを`UPROPERTY(EditAnywhere, Config)`で定義します。
以下サンプルコード

```cpp:MyDeveloperSettings.h
#pragma once

#include "CoreMinimal.h"
#include "Engine/DeveloperSettings.h"
#include "MyDeveloperSettings.generated.h"

UCLASS(config = GameTest, defaultconfig)
class MYPROJECT_API UMyDeveloperSettings : public UDeveloperSettings
{
	GENERATED_BODY()

public:
	/** プロジェクトまたはエディターの設定の設定コンテナー名を取得します */
	virtual FName GetContainerName() const override { return( Super::GetContainerName() ); }
	/** 設定のカテゴリ、エディタ、エンジン、ゲームなどの高レベルのグループ化を取得します。 */
	virtual FName GetCategoryName() const override { return("MyCategory"); }
	/** 設定のセクションの一意の名前は、クラスのFNameを使用します。 */
	virtual FName GetSectionName() const override { return( Super::GetSectionName() ); }

#if WITH_EDITOR
	/** セクションテキストを取得し、デフォルトでクラスDisplayNameを使用します。 */
	virtual FText GetSectionText() const override { return( FText::FromString(TEXT("MySectionText")) ); }
	/** セクションの説明を取得し、デフォルトでクラスToolTipを使用します。 */
	virtual FText GetSectionDescription() const override { return( FText::FromString(TEXT("MySectionDescription")) ); }
#endif


	UPROPERTY(EditAnywhere, Config)
	bool MyVarBool = false;

	UPROPERTY(EditAnywhere, Config)
	int MyVarInt = 1;

	UPROPERTY(EditAnywhere, Config)
	float MyVerFloat = 2.0f;

	UPROPERTY(EditAnywhere, Config)
	FVector MyVerVec;

	UPROPERTY(EditAnywhere, Config)
	FString MyVerStr;

};

```

## 実行結果
プロジェクト設定を見ると以下の様になります。
![MyProjectSettings.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/8bd99dd8-e7a5-d5ea-2953-95655d959c0c.jpeg)

値を変えるとiniファイルへ反映されます。

```ini:DefaultGameTest.ini
[/Script/MyProject.MyDeveloperSettings]
MyVerFloat=2.500000
MyVarInt=10
```

## メンバへのアクセス方法
以下の様にアクセスができます。

```cpp:.cpp
int _MyVarInt = GetDefault<UMyDeveloperSettings>()->MyVarInt;
```

UEエディタでプレイ中にプロジェクト設定にて値を変更しても再度アクセスすれば反映されています。


## UCLASSのメタ情報について
`UCLASS`のメタ情報でiniファイルが決まるようです。
サンプルコードでは `config = GameTest` としたので `DefaultGameTest.ini`に設定されましたが、ここを変更するとが可能です
　
`defaultconfig` は `Engine\Source\Runtime\CoreUObject\Public\UObject\ObjectMacros.h` の701行目付近にコードがあります。

```cpp:ObjectMacros.h
///構築時にオブジェクト構成をロードします。 これらのフラグはサブクラスに継承されます。
///構成プロパティを含むクラス。 使用法config = ConfigNameまたはconfig = inherit（基本クラスから構成名を継承します）。
config,
///クラスごとではなく、オブジェクトごとにオブジェクト構成を処理します。
perObjectConfig,
///構成へのシリアル化時にベース/デフォルトのiniでチェックを実行する必要があるかどうかを決定します
configdonotcheckdefaults,
///オブジェクト設定をデフォルトINIにのみ保存し、ローカルINIには保存しません。
defaultconfig,
```


## UPROPERTYのメタ情報について
`UPROPERTY` のメタ情報にてエディタの再起動(`ConfigRestartRequired`)やコンソール変数との連携(`ConsoleVariable`)設定ができます。

```cpp:.h
// この変数を変更するとエディタの再起動を求められます。
UPROPERTY(EditAnywhere, Config, meta = (ConfigRestartRequired = true))
	bool MyVarBool;

// この変数にてコンソール変数MyConsoleVarとの連携ができます。
UPROPERTY(EditAnywhere, Config, meta = (ConsoleVariable="MyConsoleVar"))
	float MyVerFloat = 2.0f;
```


## クラス追加テスト
`UDeveloperSettings` を継承したクラスをさらに追加してみます。カテゴリ名を一緒にすると以下の様になります。
以下、追加クラスのコードです。

```cpp:MyDeveloperSettings2.h
UCLASS(config = GameTest, defaultconfig)
class MYPROJECT2_API UMyDeveloperSettings2 : public UDeveloperSettings
{
	GENERATED_BODY()

public:
	/** プロジェクトまたはエディターの設定の設定コンテナー名を取得します */
	virtual FName GetContainerName() const override { return( Super::GetContainerName() ); }
	/** 設定のカテゴリ、エディタ、エンジン、ゲームなどの高レベルのグループ化を取得します。 */
	virtual FName GetCategoryName() const override { return("MyCategory"); }
	/** 設定のセクションの一意の名前は、クラスのFNameを使用します。 */
	virtual FName GetSectionName() const override { return( Super::GetSectionName() ); }
	
#if WITH_EDITOR
	/** セクションテキストを取得し、デフォルトでクラスDisplayNameを使用します。 */
	virtual FText GetSectionText() const override { return( FText::FromString(TEXT("MySectionText2")) ); }
	/** セクションの説明を取得し、デフォルトでクラスToolTipを使用します。 */
	virtual FText GetSectionDescription() const override { return( FText::FromString(TEXT("MySectionDescription2")) ); }
#endif


	UPROPERTY(EditAnywhere, Config)
	int MyParamInt = 11;

	UPROPERTY(EditAnywhere, Config)
	float MyParamFloat = 22.2f;
};
```

プロジェクト設定は以下の様になります。
同じカテゴリに複数セクションができます。
![MyDeveloperSettings2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/b5c6fbd5-40ae-2d53-1030-12444992bde3.jpeg)



# コンソール変数との連携について
メタ情報`ConsoleVariable`を指定するとコンソール変数との連携ができるようになります。
以下サンプルコード

```cpp:MyDeveloperSettings.h
UCLASS(config = GameTest, defaultconfig)
class MYPROJECT_API UMyDeveloperSettings : public UDeveloperSettings
{
	GENERATED_BODY()

public:
//  .. 省略..

#if WITH_EDITOR
	// コンソール変数からのインポート
	void Import();
	// コンソール変数へのエクスポート
	void Export();
#endif

	UPROPERTY(EditAnywhere, Config, meta = (ConsoleVariable="MyConsoleVar"))
	float MyVerFloat = 2.0f;
};
```

```cpp:MyDeveloperSettings.cpp

#if WITH_EDITOR
// コンソール変数からのインポート
void UMyDeveloperSettings::Import()
{
	ImportConsoleVariableValues();
}


// コンソール変数へのエクスポート
void UMyDeveloperSettings::Export()
{
	FProperty* _Property = FindField<FProperty>(UMyDeveloperSettings::StaticClass(), TEXT("MyVerFloat"));
	if (_Property) {
		ExportValuesToConsoleVariables(_Property);
	}
}

#endif

```

テストコードの `Import()`を実行するとメタで指定されているコンソール変数が存在する場合、`MyConsoleVar`の値が`MyVerFloat` へ反映されます。

テストコードの `Export()`を実行するとメタで指定されているコンソール変数が存在する場合、`MyConsoleVar`に `MyVerFloat` の値が反映されます。

コンソール変数をプロジェクト設定経由で初期設定するなどの使い道があるかと思います。


# その他
## 4.26での変更点について

```text:4.26リリースノート
Moved DeveloperSettings into its own module so it can be used outside of Engine.
When inheriting from UDeveloperSettings, you will need to add DeveloperSettings as a module dependency. 

DeveloperSettingsを独自のモジュールに移動して、Engineの外部で使用できるようにしました。
UDeveloperSettingsから継承する場合は、DeveloperSettingsをモジュールの依存関係として追加する必要があります。
```

リリースノートによると4.26から独自モジュールになったようです。
使用するには以下の様にモジュールを追加する必要があります。

```cs:MyProject.Build.cs
PublicDependencyModuleNames.AddRange(
	new string[]
	{
		"Core",
		"InputCore",
		"DeveloperSettings",	// ←追加
	}
```


## パッケージ時のワーニングについて
パッケージ作成時に以下のようなワーニングが出る場合があります。

```text:ワーニング
WARNING: The config file 'TestProject/Config/DefaultMyDeveloperSettings.ini' will be staged, but is not whitelisted or blacklisted. Add +WhitelistConfigFiles=TestProject/Config/DefaultMyDeveloperSettings.ini or +BlacklistConfigFiles=TestProject/Config/DefaultMyDeveloperSettings.ini to the [Staging] section of DefaultGame.ini

```

そのまま `DefaultGame.ini`へ設定を追加するとワーニングが消えます。

```ini:DefaultGame.ini
[Staging]
+WhitelistConfigFiles=TestProject/Config/DefaultMyDeveloperSettings.ini
```

あるいは

```ini:DefaultGame.ini
[Staging]
+BlacklistConfigFiles=TestProject/Config/DefaultMyDeveloperSettings.ini
```

パッケージビルドに含む場合は `WhitelistConfigFiles` となります。


# まとめ
パラメータ設定はいろいろ方法がありますが、用途によって使い分けていく必要があると思います。この.iniからのアクセスは恐らく遅いため初回起動時の設定パラメータなどの使用が良いかと思われます。（ここらへんはポリシーと工夫次第ではありますが）