[UE4] EditorScripting機能を使って、外部からMaterial Instanceのパラメータを確認・変更する方法について

# はじめに
一定程度の規模のプロジェクトになると、おそらく大量のMaterial Instanceの管理に頭を悩ませることになるかと思います。
参考：[大規模タイトルにおけるエフェクトマテリアル運用](https://www.slideshare.net/EpicGamesJapan/sqex-ue4dd)

そして、アセット一つずつ開いて不正なパラメータのチェックやパラメータ変更などを行うのはとても大変なので、日に日に「めんどくさい…自動化したい…」という思いが募ってくると思います。**そんな時に便利なのが、もうお馴染み（？）のEditor Scripting機能です。** この機能を使うことで、Material Instance の編集エディタを開かなくてもツールからパラメータを確認・変更することが可能になります。

[公式ドキュメント「ブループリントを使用してエディタをスクリプト化する」](https://docs.unrealengine.com/ja/Engine/Editor/ScriptingAndAutomation/Blueprints/index.html)
[公式ドキュメント「エディタの起動時にブループリントを実行する」](https://docs.unrealengine.com/ja/Engine/Editor/ScriptingAndAutomation/Blueprints/StartupObjects/index.html)
[エディタ上で動作するツール・エディタ拡張をUMGで簡単に作れる Editor Utility Widget について](https://qiita.com/EGJ-Kaz_Okada/items/9f530db3b53d0fde3f20)


本記事では、Editor Scripting機能によるMaterial Instanceのパラメータ確認・編集ツールの作り方の基礎について触れつつちょっとした注意点についてご紹介します。なお、Editor Utility Blueprint / Widget の使い方については説明しません。そんなの知らないよ！という方は 上記のリンク先にてご確認ください。

検証バージョン UE4.25.3

# Material Instanceのパラメータを確認・設定する方法について
少し小難しい導入でしたが、実装自体はとても簡単です！なぜなら、**専用の関数・ノードが既に用意されているからです！**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/ebfd7800-41a4-32e9-680c-97046518165a.png)

- Get
    - `UMaterialEditingLibrary::GetMaterialInstanceScalarParameterValue`
    - `UMaterialEditingLibrary::GetMaterialInstanceTextureParameterValue`
    - `UMaterialEditingLibrary::GetMaterialInstanceVectorParameterValue`
    - `UMaterialEditingLibrary::GetMaterialInstanceStaticSwitchParameterValue`

- Set
    - `UMaterialEditingLibrary::SetMaterialInstanceScalarParameterValue`
    - `UMaterialEditingLibrary::SetMaterialInstanceTextureParameterValue`
    - `UMaterialEditingLibrary::SetMaterialInstanceVectorParameterValue`

Q :あれ？Static Switch Parameter を 変更する関数・ノードはないの？
A :良い質問ですね！ 
[Material Instance の Static Switch Parameter を 変更する関数・ノードを作る方法について](https://qiita.com/EGJ-Kaz_Okada/private/4fd6db895b398893cbbb)

## Material Instanceのパラメータを確認する方法

例えば、指定のMaterial InstanceにおけるVector Parameter「Color」の値を確認したい場合は
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/f84f024c-8f61-3382-e47d-772c38d1f720.png)
以下のようにノードを組むと…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/7371c7e0-cf7b-e651-2aff-52ac40b3af15.png)
このようにサクッと実現できちゃいます！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/fa76bf19-9fa7-46c7-fbf0-20f00290b28e.png)

## Material Instanceのパラメータを変更する方法
パラメータ変更に関しては少し注意が必要です。

例えば、指定のMaterial InstanceにおけるVector Parameter「Color」の値を変更するために以下のノードを実行すると…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/2b11dee6-d23f-4015-05d2-d9d1ed20e5d5.png)
となります。
![cccc.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/0dbd591a-6d8c-d014-36b3-62cd31d9e924.gif)

ここで問題となるのは以下の2点。

- ファイル編集フラグ(Dirty)が立たない
- コンテンツブラウザ上でカーソルを合わせる等の操作をしないとサムネイルに反映されない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/7250aaef-f24f-3698-7e5d-d66fdff7b085.png)
※ ファイル編集フラグ(Dirty)が立っている場合

**特に前者の「ファイル編集フラグ(Dirty)が立たない」はかなり危険です！！！ 様々な重大な問題が発生する可能性があります！**

<details><summary>発生する問題の例（少し長いので折り畳み）</summary><div>

### ファイル編集フラグ(Dirty)が立たないことで発生する問題の例
例えば、Material Instance の Texture Parameter を SetMaterialInstanceTextureParameterValue ノードで別のテクスチャを使用するようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/25f79582-51c0-759c-ff08-053644541b5b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/519f5859-8831-ec2a-482e-2a86e3b2b167.png)
その後、変更後のMaterial Instance を開いてみると…確かに別のテクスチャを使用するようになっていますが…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/5e828fd3-0f9c-96ac-5992-16a6c6c42ec3.png)
なんと、Reference Viewer で見ると変更後のテクスチャが見当たりません！！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/1996fb23-11b6-4181-f2af-d89bc504c7c1.png)

**やばい！！！！**


ベースとなるMaterialの設定などで細かい部分は変わってきますが、少なくとも下図のように変更後のテクスチャが紐づく状態にしないと様々な問題が発生する可能性があります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/5d075179-0c76-b57c-f9de-4e1740860496.png)

例えば、先ほどの参照関係がおかしいMaterialを使ったStatic Meshを配置したレベルを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/43f03c04-2acc-54be-b059-55247c50115b.png)
そして、Standaloneで実行すると…変更後のテクスチャが使われません！これは参照関係が壊れているためです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/394233c8-4836-3674-209d-c8d20c0c920f.png)

</div></details>

この問題を回避する方法はいくつかあります。まずはパラメータ変更後に `Save Asset` や `Save Loaded Asset` ノードなどを用いてアセットの保存処理を走らせる方法です。保存処理を走らせることで先ほど挙げた2つの問題を同時に解決できます。なお、**Only if is Dirty フラグは必ず無効にしてください**。 今回保存する対象は Dirtyフラグが立っていないアセットなので、このフラグが有効になっていると保存処理が走りません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/b946d2f2-bc95-c61c-1429-7780e0cc5846.png)

次に、`Update Material Instance` ノードを使ってDirtyフラグを立てる方法です。このノードを呼ぶことでDirtyフラグを立てるのと同時にMaterial Instanceが編集されたことを各所に通知してくれます。ツールによる変更結果を保存前に確認することができるので、個人的にはこちらの方が安全でオススメです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/592d2624-dc2a-6dc5-6d17-763c556478c5.png)

```Engine\Source\Editor\MaterialEditor\Private\MaterialEditingLibrary.cpp
void UMaterialEditingLibrary::UpdateMaterialInstance(UMaterialInstanceConstant* Instance)
{
	if (Instance)
	{
		Instance->MarkPackageDirty();
		Instance->PreEditChange(nullptr);
		Instance->PostEditChange();

		Instance->UpdateStaticPermutation();
		Instance->UpdateParameterNames();

		// update the world's viewports
		FEditorDelegates::RefreshEditor.Broadcast();
		FEditorSupportDelegates::RedrawAllViewports.Broadcast();
	}
}
```

よし、これで全て解決！…では実はありません。

このままでは、**現在開いている Material Instance 編集エディタに変更が完全には反映されません…**。具体的には、下図のようにパラメータ部分の表示が更新されません。（エディタを開き直したら反映された状態になっているので、あくまでツールを使ったパラメータ変更を行う際に開いている編集エディタ限定の話です）
![bbbb.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/6286eb52-640e-ed59-6f68-8ae2cf0aae2a.gif)

### ツールを使った Material Instance のパラメータ変更を編集エディタに反映する方法について
現状は残念ながらC++が必要になります。と言っても、見ての通り既存の関数を一つ呼ぶだけです。

```MyBlueprintFunctionLibrary.h
#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"

UCLASS()
class ●●●●●_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

	UFUNCTION(BlueprintCallable)
	static void RebuildMaterialInstanceEditors(UMaterialInstance* MaterialInstance);
}
```

```MyBlueprintFunctionLibrary.cpp
#include "MyBlueprintFunctionLibrary.h"
#include "Materials/MaterialInstance.h"
#include "MaterialEditingLibrary.h"

void UMyBlueprintFunctionLibrary::RebuildMaterialInstanceEditors(UMaterialInstance* Instance)
{
	UMaterialEditingLibrary::RebuildMaterialInstanceEditors(Instance->GetBaseMaterial());
}
```

```Build.cs
"MaterialEditor" モジュールを追加
```
実装するにあたって参考にした箇所
`UMaterialEditingLibrary::UpdateMaterialFunction`, `UMaterialEditingLibrary::RecompileMaterial`

そして上記のコードで作った `Rebuild Material Instance Editors` ノードを使うと… 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/51300f89-5ddc-10b9-11ee-3c87cf8195ae.png)
このように現在開いているMaterial Instance 編集エディタにパラメータ変更が完全に反映されるようになります！
![bbbbafa.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/30ac74fa-a6bc-666e-5b9f-5e8fd8f66c57.gif)

# まとめ

- Material Instanceのパラメータ編集・取得ノード・関数は既に用意されているので、それらとEditor Scripting機能と併用することで作業効率を向上させるツールを簡単に作ることができます
- Getは簡単ですが、Setに関しては幾つか注意する必要があります。本記事で紹介している `Update Material Instance` や `Rebuild Material Instance Editors` を併用しましょう

少し文字・画像・コードが多くなってしまいましたが、実際にやってみると単純で簡単なことだと分かると思います！是非一度 オレオレ Material Instance 管理ツールを作ってみてください！

# おまけ Material Instance編集用ノード・関数について
今回紹介したパラメータ取得・変更ノード以外にも、Material Instanceが持つ様々な項目を取得・変更するノードが標準で用意されています！
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/c0542cf0-e621-7709-aa2b-261f2572e75d.png)

これらは `UMaterialEditingLibrary` クラスで実装されています。是非実際に使ってみたり処理内容を見たりしてみてください！
C++：[公式APIドキュメント「Material Editing」](https://docs.unrealengine.com/en-US/BlueprintAPI/MaterialEditing/index.html)
Python：[unreal.MaterialEditingLibrary](https://docs.unrealengine.com/en-US/PythonAPI/class/MaterialEditingLibrary.html)

おしまい