Unity uGUI ボタンのイベントを一か所で受け取る

## はじめに
こんにちは。[CYBIRDエンジニア Advent Calendar](http://qiita.com/advent-calendar/2017/cybird)
16日目の@cy-tatsuya-sakaiです。15日目は@keitarouさんの[Protocol Buffers動かしてみる](https://qiita.com/keitarou/items/487972af127d8b720d4b)でした。

###タイトルの件
Unity uGUIのボタンのイベント設定、みなさんどうしてますか？
ボタンが複数あると、1つ1つインスペクタ上で設定したり、AddListener()したり。
面倒くさい！と思うことがたまにあります。

ということで、イベントを一か所で受け取れるようなスクリプトを書いてみました。

## ボタンのイベントを一か所で受け取る
###結果
こんな感じになりました。
![test.gif](https://qiita-image-store.s3.amazonaws.com/0/214438/c82194d5-604e-2772-d39f-ae5bd4791ffa.gif)

スクリプトは以下。クラス名は適当です。

```UIButtonList.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class UIButtonList : MonoBehaviour
{
	[System.Serializable]
	private class Pair
	{
		public string key;
		public Button btn;

		public Pair(Button btn)
		{
			key = "";
			this.btn = btn;
		}
	}


	[SerializeField]
	private List<Pair>  m_Pair;


	public UnityAction<string>  onClick { get; set; }


	void Awake()
	{
		// キーが引数になるクリックイベントを登録.
		foreach(Pair pair in m_Pair)
		{
			Button btn = pair.btn;
			if(btn == null) { continue; }
			btn.onClick.AddListener(() => OnClick(pair.key));
		}

		onClick = OnClick_Log;  // 動作確認用.
	}


	private void Setup()
	{
		// 子供にいるボタンを列挙.
		Button[] btnList = transform.GetComponentsInChildren<Button>();

		if(m_Pair == null) { m_Pair = new List<Pair>(); }
		foreach(Button btn in btnList)
		{
			if(m_Pair.FindIndex(x => x.btn == btn) < 0)
			{
				m_Pair.Add(new Pair(btn));
			}
		}

		// ボタンが無くなっていたらリストから削除.
		for(int i = m_Pair.Count - 1; i >= 0; i--)
		{
			if(m_Pair[i].btn == null)
			{
				m_Pair.RemoveAt(i);
			}
		}
	}


	public Button Get(string key)
	{
		// キーで取得.
		Pair pair = m_Pair.Find(x => x.key == key);
		if(pair == null) { return null; }
		return pair.btn;
	}


	private void OnClick(string key)
	{
		if(onClick == null) { return; }
		onClick(key);
	}
	private void OnClick_Log(string key)
	{
		Debug.Log(key);
	}


	#if UNITY_EDITOR
	[CustomEditor(typeof(UIButtonList))]
	public class UIButtonListEditor : Editor
	{
		public override void OnInspectorGUI()
		{
			UIButtonList btnList = target as UIButtonList;
			if(GUILayout.Button("Setup"))
			{
				btnList.Setup();
			}

			base.OnInspectorGUI();
		}
	}
	#endif
}
```
### 使い方
① UIButtonListを、UIオブジェクトのルートにアタッチ。
② インスペクタ上の、[Setup]ボタンをクリック。
③ ボタンが列挙されるので、PairのKeyに、任意の文字列を入力

ひとまずこれで実行すると、入力したKeyの値が渡されるイベントが各ボタンに割り当てられます。

実際に入力を受け取る際は、仮にシーン上のUIButtonListを直接アタッチするなら、
↓のような感じで、stringを引数に取るようなメソッドを指定できます。

```Test.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
	public UIButtonList btnList;


	void Start()
	{
		btnList.onClick = OnClick;	
	}


	private void OnClick(string key)
	{
		// 何らかの処理.
		switch(key)
		{
		case "a":
			break;
		case "b":
			break;
		case "c":
			break;
		case "d":
			break;
		}
	}
}
```

Keyでボタンを取得するメソッドもとりあえず準備しました。

```
Button btn = btnList.Get("a");
```

###やってること
すごく単純で、
① [Setup]ボタンを押したら、

```
GetComponentsInChildren<Button>();
```
で見つかったボタンをリストに追加。
② 実行時にAwake()で、リストに追加したボタンに対して

```
btn.onClick.AddListener(() => OnClick(pair.key));
```
でイベントをAddListener()しているだけです。
<b>ラムダ式</b>ってやつですね。引数が違うOnClick()を複数作っている感じです。便利。

ところで、[Setup]ボタンのインスペクタ拡張、<b>通常のスクリプト内に書ける</b>んですね。
全然知りませんでした！便利！

###割り切り
今回の実装では、

- OnClick()の引数にstringしか受け取れない
- UIオブジェクトのルートがボタンだった場合に列挙されない

等の制限があります。が、複雑になるくらいなら、これで良いのかなぁと思ったりしてます。

##まとめ
今回のスクリプトは試しに書いただけなので、実際に使って良かったとかでは無いのですが、
ボタンクリックで通過するメソッドが一か所に限定されるのは、気分的にはスッキリです。

あとは、もう少し汎用化して、テキストも列挙したい気がします。
テキストコンポーネントを簡単に取得出来たら、ローカライズのときに役に立ったりしないかなぁ。

[CYBIRDエンジニア Advent Calendar](http://qiita.com/advent-calendar/2017/cybird) 明日は、@zonさんのArduinoとかRaspberry Piの入門キットがうまく組み立てられなくて入門できない問題です。楽しみだ！

##参考
[【Unity】AddListenerで引数を渡す](http://blog.narumium.net/2017/04/15/%E3%80%90unity%E3%80%91addlistener%E3%81%A7%E5%BC%95%E6%95%B0%E3%82%92%E6%B8%A1%E3%81%99/)
[UnityのInspectorを拡張する。](https://qiita.com/sango/items/b705980ada56ba8ffa04)