【Untiy】複数のuGUI Buttonのクリックイベントを、一ヶ所で受ける方法

タイトルどおりだけど、何のこっちゃ・・

例えば、レベル選択画面ですよ。
画面にボタンを何十個も配置することあるでしょ?
onclick_0.png

こういう時、みんなはどうやってイベント拾ってますか?
ボタンにスクリプトを割り当てて、ボタン自身でコールバックを受けてますか?

私は、いま、とても悩んでいます。
だってゲーム画面って、たくさんのUIを配置することもあるでしょ?
それぞれ個々に処理をさせたら、ワケがわかんなくなるよね。
なので私は、UIのコールバックは、一ヶ所に集約したいのです。

そう、例えば、こんな風にUIを仕切るヤツに、引数付きでコールバックしてほしいのです。

UIEventHandler.cs

public void OnClickLevelButton( LevelInfo levelInfo ){
Debug.Log("LevelNo." + levelInfo.levelNo + "が、選択されました!");
}

ちなみに、これ、手動なら簡単です。
Inspectorでチマチマ設定すれば、やりたいことは出来ちゃいます。
onclick_1.png

ButtonのOnClickのコールバックに引数付きのメソッドを指定すると、引数も設定できるようになります。
onclick_2.png

Buttonオブジェクトに、レベル情報(LevelInfo.cs)をアタッチしておけば、
onclick_3.png

この引数部分には、自分自身のオブジェクトを設定するだけです。
onclick_4.png

これを、すべてのボタンに適用すると、UIEventHandler.OnClickLevelButton( LevelInfo )にコールバックされるようになります。

だけど、超だるい・・
この手動設定をスクリプトで出来ればいいんだよね。

で、試したんだけど、、、、
Button.onClick.AddListener()って、UnityActionしか受け付けてくれなくない?
UnityAction< T0 >は、だめなんですか?

やっぱり、個々のボタンでコールバック受けて、ボタンからUIEventHandler.OnClickLevelButton( LevelInfo )を呼ぶべきなのかな?

LevelButton.cs

using UnityEngine;
using System.Collections;

public class LevelButton : MonoBehaviour {
public UIEventHandler uiEventHandler;

public void OnClickLevelButton(){
LevelInfo levelInfo = gameObject.GetComponent< LevelInfo >();
uiEventHandler.OnClickLevelButton( levelInfo );
}
}

正直、これが一番いいような気もするんだけどね・・・

だけど、、だけど、、手動で設定するみたいに、直接、UIEventHandlerにコールバックさせたい・・

いろいろ悩んだ末に、たどりつきました。
EventTriggerで、PointerClickイベントを受けるというのはどうでしょう?
これだと、Callbackメソッドの引数(BaseEventData)から、クリックされたオブジェクトが分かるので、いろいろありがたいのです。

イメージとしては、こんな感じ。
ButtonにComponent > Event > Event Trigger をアタッチして、
[Add New Event Type]ボタンから「PointerClick」を選択し、
onclick_5.png

あとは、onClickと同じように、リスナーとなるUIEventHandlerとCallbackメソッドを追加。
onclick_6.png

この設定をスクリプトで書いていきます。

スクリプトはこの3つ。
・UIEventHandler.cs(UIイベントハンドラ)
・LevelInfo.cs(レベル番号などのレベル情報。Buttonにアタッチするスクリプト)
・LevelsPanel.cs(Buttonの親となるPanelにアタッチするスクリプト。ボタンを動的生成する)

・UIEventHandler.cs
UIイベントハンドラです。
ボタンクリックのコールバックを受けます。

using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections;

public class UIEventHandler : MonoBehaviour {
public void OnPointerClickLevelButton(BaseEventData eventData){
LevelInfo levelInfo = eventData.selectedObject.GetComponent< LevelInfo >();

Debug.Log ("LevelNo." + levelInfo.levelNo + "が、選択されました!");
}
}

・LevelInfo.cs
ボタンにアタッチするスクリプトです。
レベル番号がセットされたら、ボタンのテキストも更新します。

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class LevelInfo : MonoBehaviour {
// ボタンのテキスト
private Text levelText;

// レベル番号プロパティ
private int _levelNo;
public int levelNo{
set {
// レベル番号設定時に、ボタンのテキストも更新
this._levelNo = value;
levelText.text = value.ToString();
}
get { return this._levelNo; }
}
public void Awake(){
// 子オブジェクトからTextを探してセット
levelText = transform.FindChild ("Text").GetComponent< Text >();
}
}

・LevelsPanel.cs
Buttonの親となるPanelにアタッチするスクリプトです。
Start時に、ボタンを動的生成します。

using UnityEngine;
//using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System.Collections.Generic;

public class LevelsPanel : MonoBehaviour {
public UIEventHandler uiHandler; // UIイベントハンドラ
public int levelNum = 30; // レベルの数
public GameObject levelButtonPrefab; // レベルボタンのプレハブ

void Start () {
// UIイベントハンドラがセットされてなかったら、探してセット
if(uiHandler == null){
uiHandler = GameObject.Find ("UIEventHandler").GetComponent< UIEventHandler >();
}

// レベルボタン生成
GameObject buttonObj;
EventTrigger eventTrriger;
for (int i = 1; i <= levelNum; i++){
// プレハブからボタン生成し、自オブジェクト配下に配置
buttonObj = GameObject.Instantiate(levelButtonPrefab, Vector3.zero, Quaternion.identity) as GameObject;
buttonObj.name = "Level_" + i;
buttonObj.transform.SetParent(this.transform);
buttonObj.transform.localScale = Vector3.one;

// ボタンにアタッチされたLevelInfoコンポーネントを取得し、レベル番号を設定
LevelInfo levelInfo = buttonObj.GetComponent< LevelInfo >();
levelInfo.levelNo = i;

// EventTriggerコンポーネントをアタッチ
eventTrriger = buttonObj.AddComponent< EventTrigger >();

// PointerClickイベントのエントリー作成
EventTrigger.Entry entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.PointerClick;

// リスナー登録(UIEventHandlerのコールバックメソッドを指定)
entry.callback.AddListener(uiHandler.OnPointerClickLevelButton);

// デリゲートリストにエントリー登録
eventTrriger.delegates = new List< EventTrigger.Entry >();
eventTrriger.delegates.Add(entry);
}
}
}


なんとかやりたいことが出来ましたが、どうなのよ、コレ・・

一ヶ所で受け取りたいがために、非常に見づらいソースになったことは確かですw


この記事へのコメント