【Unity】【C#】UnityEvent, Action, delegate, interface でのコールバック実装方法とインスペクタでの登録 
2017/07/07 Fri [edit]
ここ最近、「スワイプ」や「長押し」イベントの検出を書いたが、そのコールバックのやり方については投げっぱなしだったので、改めてコードパターンとして簡単にまとめておこう。
(※) Unity 5.6.1p3 / Windows10(x64) で確認
■UnityEvent でコールバックする
UnityEvent は名前の通り Unity 特有のイベントコールバックの方法だ。その一番大きな特徴はインスペクタでコールバックを受信するメソッドを登録できることで、これを使えば毎回コードを書き換えないで済むというメリットがある。1度作ってしまえば、コンポーネントベースっぽい既存のものを組み合わせるだけでその機能が使えるので、再利用が非常にやりやすい。
引数をとるか否かで書き方が違うので、その辺りだけ覚えれば利用は非常に簡単だ。なお、UnityEvent の引数は4つまでになってるが、引数が多い場合は専用のクラスや構造体を作って、それを1つの引数にしてしまえば良いだけなので、それほど多く使う機会はないと思う。
●呼び出し側(イベント発生側)
using System;
using UnityEngine;
using UnityEngine.Events;
public class UnityEventCallbackSample : MonoBehaviour {
//引数なしの場合
public UnityEvent OnLongClick; //引数なし
//引数を1つとる場合
[Serializable]
public class SwipeHandler : UnityEvent<Vector2> //引数に Vector2 を1つとる
{
}
public SwipeHandler OnSwipe; //引数に Vector2 を1つとる(インスペクタに表示される)
//引数を2つとる場合
[Serializable]
public class ChangeHandler : UnityEvent<int, int> //引数に int を2つとる
{
}
public ChangeHandler OnChange; //引数に int を2つとる(インスペクタに表示される)
// Update is called once per frame
private void Update () {
if (イベントの発生条件など)
{
if (OnLongClick != null)
OnLongClick.Invoke(); //ここでコールバック(引数なし)
if (OnSwipe != null)
OnSwipe.Invoke(Vector2.up); //ここでコールバック(引数を1つとる)
if (OnChange != null)
OnChange.Invoke(1, 2); //ここでコールバック(引数を2つとる)
}
}
}
●コールバック受信側
using UnityEngine;
public class MainSample : MonoBehaviour {
public void OnLongClick() //このメソッドをインスペクタで登録する
{
//ここにコールバックされたときの処理
Debug.Log("OnLongClick called.");
}
public void OnSwipe(Vector2 dir) //このメソッドをインスペクタで登録する
{
//ここにコールバックされたときの処理
Debug.Log("OnSwipe called. dir = " + dir);
}
public void OnChange(int before, int after) //このメソッドをインスペクタで登録する
{
//ここにコールバックされたときの処理
Debug.Log("OnChange called. before = " + before + ", after = " + after);
}
}
コードはかなり簡略されたものなので、あくまでも雛形として自由に書き換えて使って欲しい。
UnityEvent の定義の仕方だが、引数なしのときはそのまま
//定義
public UnityEvent CallbackMethod;
//コールバックを実行するときは
CallbackMethod.Invoke();
のように書くが、引数が1つ以上ある場合、UnityEvent を継承したクラスを作る必要がある。そしてそのジェネリックなパラメータの列挙が引数に対応してる感じになる。
//定義
[Serializable]
public class CallbackHandler : UnityEvent<T1, T2, T3, T4> //引数1~4つまで
{
}
public CallbackHandler CallbackMethod; //インスペクタで表示される
//コールバックを実行するときは
CallbackMethod.Invoke(t1, t2, t3, t4);
また、コールバックを受信するメソッドを登録するとき(「+」ボタンを押し、コールバック受信するオブジェクトを登録>プルダウンからメソッドを選択)、引数なしの場合はリストに名前の順で出てくるが、引数が1つ以上で、かつ引数の型が合致しているものは、リストの上位の方に「Dynamic (引数の型)」のように出てくる。これを選択すれば、動的な引数の値を受信できる。それとは逆に、引数が1つで int や string などの場合(型による)、リストで下の方にある同じメソッド名を選択すると固定値にできる(値を入力できるようになる)。その他の詳しい内容は以下に参考URLを載せておこう。
(参考)
・【Unity】UnityEventの用法と用量
![]() 上の方にある Dynamic (型) を選ぶと、 動的に引数の値を変えられる | ![]() 引数が1つで int や string のときは(型による)、 下の方を選ぶと固定値を設定できる |
なお、コードからコールバックするメソッドを登録したい場合は、「UnityEvent.AddListener」を使う。その方法は以下のURLが参考になるだろう。資料は uGUI での例だが、考え方は同じだ。
(参考)
・uGUIのイベントコールバック登録をコードから行う方法とUnityActionデリゲート群
■Action でコールバックする
次に C# の標準機能(System)にある Action を使ってコールバックをやってみよう。実は Unity にも UnityAction というものが使えるが、基本的には機能は同じものなので、その辺りは自分で入れ替えて考えて欲しい。実際にそのままそっくり Action → UnityAction に書き換えても使える。ただし、現時点では Action は引数16個まで、UnityAction は引数4個までのようだ。しかし実際には上記の UnityEvent の使い方と同じように多くの引数を持ちたい場合、クラスや構造体にまとめて1つの引数にしてしまえば、少なくても問題はない。使い方も引数増えたら、ジェネリックなパラメタを増やす感じになるので、表記上も簡単だ。UnityEvent のときと全く同じ動作をする例を Action で書き直してみよう。
●呼び出し側(イベント発生側)
using System;
using UnityEngine;
public class ActionCallbackSample : MonoBehaviour {
//引数なしの場合
public static Action OnLongClick; //引数なし
//引数を1つとる場合
public static Action<Vector2> OnSwipe; //引数に Vector2 を1つとる
//引数を2つとる場合
public static Action<int, int> OnChange; //引数に int を2つとる
// Update is called once per frame
private void Update()
{
if (イベントの発生条件など)
{
if (OnLongClick != null)
OnLongClick(); //ここでコールバック(引数なし)
if (OnSwipe != null)
OnSwipe(Vector2.up); //ここでコールバック(引数を1つとる)
if (OnChange != null)
OnChange(1, 2); //ここでコールバック(引数を2つとる)
}
}
}
●コールバック受信側
using UnityEngine;
public class MainSample : MonoBehaviour {
void OnEnable()
{
//コールバックするメソッドを登録
ActionCallbackSample.OnLongClick += this.OnLongClick;
ActionCallbackSample.OnSwipe += this.OnSwipe;
ActionCallbackSample.OnChange += this.OnChange;
}
void OnDisable()
{
//コールバックするメソッドを解除
ActionCallbackSample.OnLongClick -= this.OnLongClick;
ActionCallbackSample.OnSwipe -= this.OnSwipe;
ActionCallbackSample.OnChange -= this.OnChange;
}
public void OnLongClick()
{
//ここにコールバックされたときの処理
Debug.Log("OnLongClick called.");
}
public void OnSwipe(Vector2 dir)
{
//ここにコールバックされたときの処理
Debug.Log("OnSwipe called. dir = " + dir);
}
public void OnChange(int before, int after)
{
//ここにコールバックされたときの処理
Debug.Log("OnChange called. before = " + before + ", after = " + after);
}
}
コードはかなり簡略されたものなので、あくまでも雛形として自由に書き換えて使って欲しい。
定義の仕方を除いて、一番大きな違いは、コールバック受信側(MainSample)の「OnEnable()」「OnDisable()」でコールバックメソッドの登録と解除を行ってる点だ。これはよく使われる定型パターンというだけで、必ずしもここでやらないといけないというわけではない。ちなみにメソッド登録に「+=」を使っているが、これを複数回やると、コールバックも複数回発生する。誤動作を防ぐために、登録したか否かのフラグで管理した方が安全なこともある。アプリで必ず1回のみなら Awake(), OnDestroy() でも良いと思う。
あと、定義部分でフィールドを static で宣言しているが、これもコード簡略のためで、インスタンスでも構わない。というより、システムで全体でのイベントとしてユニークで良いのなら static を、オブジェクトごとにイベントの発生とコールバック受信したいのならインスタンスにすることになるだろう。その場合は Awake() でコンポーネント取得、またはインスペクタでインスタンス登録などの方法を用いれば良い。
●コールバックの定義部分をインスタンスにする
//呼び出し側(イベント発生側)
public class ActionCallbackSample : MonoBehaviour {
//引数なしの場合
public Action OnLongClick; //インスタンスを登録する
//(以下略)・・・
}
//コールバック受信側
public class MainSample : MonoBehaviour {
public ActionCallbackSample callback; //インスペクタで登録する
void Awake()
{
if (callback == null) //登録されてない場合はコンポーネント取得を試みる
callback = GetComponent<ActionCallbackSample>();
}
void OnEnable()
{
callback.OnLongClick += this.OnLongClick;
//(以下略)・・・
}
void OnDisable()
{
callback.OnLongClick -= this.OnLongClick;
//(以下略)・・・
}
//(以下略)・・・
}
●delegate でコールバックする
この方法が本来言語に備わっている機能での書き方とも言える。上記の Action や UnityEvent は delegate の機能をラップして簡単に扱う、または短く表記するようにしたものと考えてもいいだろう。そのため、他のプラットフォーム・言語に移植するときなどは互換性が高いかも知れない。ここでもこれまでの2例と全く同じ動作をするコードを書いてみよう。
●呼び出し側(イベント発生側)
using UnityEngine;
public class DelegateCallbackSample : MonoBehaviour {
//引数なしの場合
public delegate void LongClickHandler();
public static event LongClickHandler OnLongClick; //引数なし
//引数を1つとる場合
public delegate void SwipeHandler(Vector2 dir);
public static event SwipeHandler OnSwipe; //引数に Vector2 を1つとる
//引数を2つとる場合
public delegate void ChangeHandler(int befor, int after);
public static event ChangeHandler OnChange; //引数に int を2つとる
// Update is called once per frame
private void Update()
{
if (イベントの発生条件など)
{
if (OnLongClick != null)
OnLongClick(); //ここでコールバック(引数なし)
if (OnSwipe != null)
OnSwipe(Vector2.up); //ここでコールバック(引数を1つとる)
if (OnChange != null)
OnChange(1, 2); //ここでコールバック(引数を2つとる)
}
}
}
●コールバック受信側
using UnityEngine;
public class MainSample : MonoBehaviour {
void OnEnable()
{
//コールバックするメソッドを登録
DelegateCallbackSample.OnLongClick += this.OnLongClick;
DelegateCallbackSample.OnSwipe += this.OnSwipe;
DelegateCallbackSample.OnChange += this.OnChange;
}
void OnDisable()
{
//コールバックするメソッドを解除
DelegateCallbackSample.OnLongClick -= this.OnLongClick;
DelegateCallbackSample.OnSwipe -= this.OnSwipe;
DelegateCallbackSample.OnChange -= this.OnChange;
}
public void OnLongClick()
{
//ここにコールバックされたときの処理
Debug.Log("OnLongClick called.");
}
public void OnSwipe(Vector2 dir)
{
//ここにコールバックされたときの処理
Debug.Log("OnSwipe called. dir = " + dir);
}
public void OnChange(int before, int after)
{
//ここにコールバックされたときの処理
Debug.Log("OnChange called. before = " + before + ", after = " + after);
}
}
コードはかなり簡略されたものなので、あくまでも雛形として自由に書き換えて使って欲しい。
上記の Action での書き方とあまり変わらないので、それほど説明はいらないだろう。引数のパラメタがそのまま定義で書かれているだけだ。
定義部分の static をインスタンスにする方法も Action での書き方を参考にして欲しい。考え方は全く同じだ。用途によって使い分けた方が良いだろう。
●interface でコールバックする
この方法はオマケなので無視して良い(笑)。C# では delegate を使った方が楽なので、わざわざこういう書き方をする人はいないだろう。どちらかと言うと Java での書き方に近い。まぁ、delegate を持たない他の言語に移植するときとか、こういう書き方もあるという手法を学ぶのには良いだろう。
●呼び出し側(イベント発生側)
using UnityEngine;
public class InterfaceCallbackSample : MonoBehaviour
{
//引数なしの場合
public interface ILongClickHandler {
void OnLongClick();
}
public static ILongClickHandler LongClickHandler;
//引数を1つとる場合
public interface ISwipeHandler {
void OnSwipe(Vector2 dir);
}
public static ISwipeHandler SwipeHandler;
//引数を2つとる場合
public interface IChangeHandler {
void OnChange(int before, int after);
}
public static IChangeHandler ChangeHandler;
// Update is called once per frame
private void Update()
{
if (イベントの発生条件など)
{
if (LongClickHandler != null)
LongClickHandler.OnLongClick(); //ここでコールバック(引数なし)
if (SwipeHandler != null)
SwipeHandler.OnSwipe(Vector2.up); //ここでコールバック(引数を1つとる)
if (ChangeHandler != null)
ChangeHandler.OnChange(1, 2); //ここでコールバック(引数を2つとる)
}
}
}
●コールバック受信側
using UnityEngine;
public class MainSample : MonoBehaviour,
InterfaceCallbackSample.ILongClickHandler,
InterfaceCallbackSample.ISwipeHandler,
InterfaceCallbackSample.IChangeHandler
{
void OnEnable()
{
//コールバックするメソッドを登録
InterfaceCallbackSample.LongClickHandler = this;
InterfaceCallbackSample.SwipeHandler = this;
InterfaceCallbackSample.ChangeHandler = this;
}
void OnDisable()
{
//コールバックするメソッドを解除
InterfaceCallbackSample.LongClickHandler = null;
InterfaceCallbackSample.SwipeHandler = null;
InterfaceCallbackSample.ChangeHandler = null;
}
public void OnLongClick()
{
//ここにコールバックされたときの処理
Debug.Log("OnLongClick called.");
}
public void OnSwipe(Vector2 dir)
{
//ここにコールバックされたときの処理
Debug.Log("OnSwipe called. dir = " + dir);
}
public void OnChange(int before, int after)
{
//ここにコールバックされたときの処理
Debug.Log("OnChange called. before = " + before + ", after = " + after);
}
}
コードはかなり簡略されたもので、あくまでも前述の3例に似せた書き方にしているので、実際に使うにはもう少し手を加えた方が良いだろう。内容的には前述の3例と違い、コールバックメソッドの登録が単ーとなる。
実際の所、interface にはメソッド名の縛りがあるので(delegate はメソッド名が異なっていてもシグネチャが合ってれば使える)、コールバックとしては自由度が低い。とは言え、例えば複数のシステム間で必ず決まったメソッド名でコールバックしたい場合などはこちらでも良い。ただし、delegate の例のように複数のメソッドを登録することはできないので(※上記の例の場合)注意。複数のメソッドを登録できるようにするなら、以下のようにそれぞれのコールバックの参照(LongClickHandler, SwipeHandler, ChangeHandler)を List などにして、AddListener(登録するメソッド)、RemoveListener(解除するメソッド) のような仕組みを作った方が良いだろう。もちろんイベント発生時にはそれら List を回して、登録してあるすべてのコールバックメソッドを実行する必要がある(null がないように注意)。
●interface でのコールバック登録を複数対応にする
using System.Collections.Generic;
using UnityEngine;
//呼び出し側(イベント発生側)
public class InterfaceCallbackSample : MonoBehaviour {
//(以上略)・・・
//コールバックの参照リスト
static List<ILongClickHandler> longClickListener = new List<ILongClickHandler>();
//コールバックを登録
public static void AddLongClickListener(ILongClickHandler callback)
{
longClickListener.Add(callback);
}
//コールバックを解除
public static void RemoveLongClickListener(ILongClickHandler callback)
{
longClickListener.Remove(callback);
}
//(以下略)・・・
// Update is called once per frame
private void Update()
{
if (イベントの発生条件など)
{
foreach (var callback in longClickListener) //※↓本来は null チェックした方が良い
callback.OnLongClick(); //ここでコールバック(引数なし)
//(以下略)・・・
}
}
//(以下略)・・・
}
//コールバック受信側
public class MainSample : MonoBehaviour,
InterfaceCallbackSample.ILongClickHandler,
(以下略)・・・
{
void OnEnable()
{
InterfaceCallbackSample.AddLongClickListener(this);
//(以下略)・・・
}
void OnDisable()
{
InterfaceCallbackSample.RemoveLongClickListener(this);
//(以下略)・・・
}
//(以下略)・・・
}
定義部分の static をインスタンスにする方法は Action での書き方を参考にして欲しい。考え方は全く同じだ。用途によって使い分けた方が良いだろう。
(関連記事)
【Unity】【C#】インスペクタでの UnityEvent のコールバック登録の有無を調べる
【Unity】EventTrigger のコールバック引数変わった?
【Unity】【C#】長押し(ロングタップ)を取得してコールバックする
【Unity】【C#】スワイプ(フリック)を判定、方向を取得してコールバックする
【Unity】【C#】ピンチ操作を取得してコールバックする
【Unity】タブ切り替えの UI をコードを書かないで作る
【Unity】【C#】VideoPlayer で動画の終了判定をする
【Java】interface を使って簡単なコールバック機能を作る
- 関連記事
トラックバック
トラックバックURL
→http://fantom1x.blog130.fc2.com/tb.php/252-2f656b0b
この記事にトラックバックする(FC2ブログユーザー)
| h o m e |