ScriptableObjectとEditor拡張で作るButton一括設定くん

# はじめに
自分のScriptableObject活用法について定期的に書いています。
今回はScriptableObjectをEditor拡張に活用する方法について書きます。

過去記事
[1 ScriptTemplateでScripableObjectのための環境構築](https://qiita.com/keidroid/items/40db91531c32a13c257f)
[2 ScriptableObjectを設定ファイルとして扱う](https://qiita.com/keidroid/items/ed5e12a3d5b603905e22)
[3 ScriptableObjectをマスターデータとして扱う](https://qiita.com/keidroid/items/1745665c0786cd6cf800)


# 4. ScriptableObjectとEditor拡張

## 4.1 uGUIのButton設定が面倒かもしれない問題

uGUIで新規にButtonを作成すると以下のようなButtonコンポーネントが作成されます。

<img width="261" alt="スクリーンショット 2017-12-21 2.23.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/729cb0c1-5718-f539-0099-3344b3850636.png">

初期設定は以下となります。

|name|value|
|:---|:---|
|Transition | Color Tint|
|Target Graphic|(自身のオブジェクト)|
|Normal Color|#FFFFFFFF|
|Highlighted Color|#F5F5F5FF|
|Pressed Color|#C8C8C8FF|
|Disabled Color|#C8C8C880|
|Color Multipler|1.0|
|Fade Duration|0.1|

上の設定はいかにもデフォルトという感じなので、
ゲームの雰囲気に合わせて変更すると思います。
しかし再度Buttonを新規作成すると、また初期設定でButtonが作成されます。

Buttonの数がどんどん増えるときちんと設定をしたコンポーネントかわからなくなる問題があります。
自分はScriptableObject+Editor拡張で、
UIの設定漏れを防いでいるのでその方法を書きます。

## 4.2 UI(Button)の設定を持つScriptableObject

UnityEngine.UI.ButtonのAPIリファレンスを見ると、
ボタンの上記設定は
Selectable.TransitionとColorBlockというプロパティであることがわかります。
https://docs.unity3d.com/ScriptReference/UI.Button.html

変更が必要な分を抜き出してScriptableObjectにします。

``` ButtonThemeConfig.cs
using UnityEngine;
using UnityEngine.UI;

[CreateAssetMenu(menuName = "Sample4/UI/ButtonThemeConfig")]
public class ButtonThemeConfig : ScriptableObject
{
	public enum Theme
	{
		Default,
		Green
	};

	public Selectable.Transition transition;

	[Header("ColorBlock")]
	public ColorBlock colorBlock;

	public static ButtonThemeConfig Load(Theme theme)
	{
		if (theme == Theme.Green) {
			return Resources.Load<ButtonThemeConfig>("ButtonThemeConfig/Green");
		}
		return Resources.Load<ButtonThemeConfig>("ButtonThemeConfig/Default");
	}
}
```

Default(青色)とGreen(緑色)の2つ用意しました。

<img width="640" alt="スクリーンショット 2017-12-21 2.32.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/c7fb0636-6a70-1379-9f85-bf750a124134.png">



## 4.3 特定のコンポーネントを一括設定するスクリプト

以下の処理を行うEditorWindowを作成します。

1. シーン内の特定コンポーネント(今回はButton)を全て取得する
2. 1で取得したコンポーネントをリスト化する
3. ボタンが押されたときにScriptableObjectの値をComponentに反映


＃ 作成時点ではUnity5.3を利用していたのでScrollViewを使用していますが
＃ Unity5.6以降はTreeViewという機能でもっとしっかりしたリストが作成できるらしいです..

```Editor/ButtonConfigWindow.cs
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;

public class ButtonConfigWindow : EditorWindow
{
	//ただの幅
	static class ColumnWidth
	{
		public const int Name = 150;
		public const int Target = 150;
		public const int Interactable = 100;
		public const int Transition = 100;
		public const int Color = 25;
		public const int ColorPadding = 4; //目分量
		public const int Theme = 75;
	}

	Vector2 panel;

	[MenuItem("Window/Sample4/ButtonConfigWindow")]
	static void Open()
	{
		EditorWindow.GetWindow<ButtonConfigWindow>();
	}

	void OnGUI()
	{
		panel = EditorGUILayout.BeginScrollView(panel, GUI.skin.box);
		{
			//シーン内のButtonを全て取得
			Button[] buttons = Resources.FindObjectsOfTypeAll(typeof(Button)) as Button[];
			if (buttons == null || buttons.Length == 0) {
				return;
			}

			EditorGUILayout.BeginHorizontal(GUI.skin.box);
			GUILayout.Label("Name", GUILayout.Width(ColumnWidth.Name));
			GUILayout.Label("Target Graphic", GUILayout.Width(ColumnWidth.Target));
			GUILayout.Label("Interactable", GUILayout.Width(ColumnWidth.Interactable));
			GUILayout.Label("Transition", GUILayout.Width(ColumnWidth.Transition));
			GUILayout.Label("Colors", GUILayout.Width((ColumnWidth.Color + ColumnWidth.ColorPadding) * 4));
			GUILayout.Label("Theme", GUILayout.Width(ColumnWidth.Theme * 2));
			EditorGUILayout.EndHorizontal();

			foreach (Button button in buttons) {
				EditorGUILayout.BeginHorizontal(GUI.skin.box);

				GUILayout.Label(button.name, GUILayout.Width(ColumnWidth.Name));
				GUILayout.Label(button.targetGraphic.name, GUILayout.Width(ColumnWidth.Target));

				//Transition
				button.interactable = EditorGUILayout.Toggle(button.interactable, GUILayout.Width(ColumnWidth.Transition));
				button.transition = (Selectable.Transition) EditorGUILayout.EnumPopup(button.transition,  GUILayout.Width(ColumnWidth.Transition));

				//ColorBlock
				ColorBlock colorBlock = button.colors;
				colorBlock.normalColor = EditorGUILayout.ColorField(GUIContent.none, button.colors.normalColor, false, true, false, null, GUILayout.Width(ColumnWidth.Color));
				colorBlock.highlightedColor = EditorGUILayout.ColorField(GUIContent.none, button.colors.highlightedColor, false, true, false, null, GUILayout.Width(ColumnWidth.Color));
				colorBlock.pressedColor = EditorGUILayout.ColorField(GUIContent.none, button.colors.pressedColor, false, true, false, null, GUILayout.Width(ColumnWidth.Color));
				colorBlock.disabledColor = EditorGUILayout.ColorField(GUIContent.none, button.colors.disabledColor, false, true, false, null, GUILayout.Width(ColumnWidth.Color));

				//Theme選択
				if (GUILayout.Button("Default", GUILayout.Width(ColumnWidth.Theme))) {
					ButtonThemeConfig config = ButtonThemeConfig.Load(ButtonThemeConfig.Theme.Default);
					button.transition = config.transition;
					colorBlock = config.colorBlock;
					EditorUtility.SetDirty(button);
				}
				if (GUILayout.Button("Green", GUILayout.Width(ColumnWidth.Theme))) {
					ButtonThemeConfig config = ButtonThemeConfig.Load(ButtonThemeConfig.Theme.Green);
					button.transition = config.transition;
					colorBlock = config.colorBlock;
					EditorUtility.SetDirty(button);
				}
				button.colors = colorBlock;
				EditorGUILayout.EndHorizontal();
			}
		}
		EditorGUILayout.EndScrollView();
	}
}


```

## 4.4 使用方法

4.3のMenuItemで指定した項目を呼び出すと、シーン内のButtonコンポーネント一覧を表示します。
Window > Sample4 > ButtonConfigWindow

赤枠内(一番上のボタン)のボタンが設定漏れでデフォルト設定になっていることがわかります。

<img width="640" alt="スクリーンショット 2017-12-21 2.46.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/4e6a9792-2078-28b6-fa53-2611aba58d7d.png">

Themeのボタンを押すと設定が反映されます。

<img width="640" alt="スクリーンショット 2017-12-21 2.46.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/090c8eef-eefb-7e9d-1d61-89777c0c5bfb.png">

# まとめ

ScriptableObjectの活用法をとりあえず4つ書きました。

個人的にScriptableObjectは
MonoBehaviourと同じくらいUnityの要素としては大事だと思います。
(利用範囲が多いのはもちろん、UnityのObjectについて理解が深まったり、データの効率化について考えたり云々)

興味があったら以下も合わせて読んで下さい。

[1 ScriptTemplateでScripableObjectのための環境構築](https://qiita.com/keidroid/items/40db91531c32a13c257f)
[2 ScriptableObjectを設定ファイルとして扱う](https://qiita.com/keidroid/items/ed5e12a3d5b603905e22)
[3 ScriptableObjectをマスターデータとして扱う](https://qiita.com/keidroid/items/1745665c0786cd6cf800)
4 ScriptableObjectとEditor拡張