delegateやeventさらにはUnityAction、UnityEventと聞くと何がなんやらさっぱりで、そろそろ理解したいと思ったのでまとめていく。とはいえ、そこまで細かくは理解してないので大雑把に把握したい方向け。
これらを理解する上で最重要なのはdelegate。これを理解することで、うんと他のものの理解も早まると思うのでこいつはしっかり押さえていこう。
delegateとは
まずは、delegateついて説明する。
自分はdelegateと聞くといまだに拒否反応が起こるが、関数を入れられる便利な変数と理解している。なんでわざわざそんなことすんのかって話だけど変数にしておくことで、これを他の関数の引数に入れられるからかな?他にもありそうだがそんな感じで理解して欲しい。
delegateを使うにはまず以下のようにdelegateの型を宣言する必要がある。
アクセス修飾子 delegate 返り値の型 delegateの型名(引数)
文章で書くと難しいので、適当に文字を当てはめてみる
public delegate void DelegateType()
ちょっと見やすくなったかな。
これでdelegateの型を作る事ができた。そしたらこの型を使って今度はdelegateを作っていく。
アクセス修飾子 delegateの型名 delegate名
上のような形で作成するので
public DelegateType del
こんな感じ。
つまり、public delegate void DelegateType()
とまずDelegateTypeというdelegateの型を宣言してから
public DelegateType del
とdelegate名を宣言する。つまり、delegateを宣言するのに2行ほど使わなければならない。
public delegate void DelegateType(); //delegateの型名を宣言
public DelegateType del; //delegate名を宣言
ポイントとしては、delegate
って書かれている行はdelegateの型名を宣言してるってことかな。(匿名関数を作る時にもdelegateって登場するみたいだけどそれとは別)
宣言した後は、このdel
に対して関数を登録してあげる。
del = 関数
でdelegateに関数を登録してあげる。登録する時は関数の後に( )は必要ない。
登録した後は、del()
として実際の関数を呼び出すように使うことができる。
以下のスクリプトを作成して、適当なGameObjectにアタッチしてから実行してみる。うまくいけば、登録している関数が呼ばれるはずだ。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestDelegate : MonoBehaviour
{
public delegate void DelegateType(); //delegateの型名を宣言
public DelegateType del; //delegate名を宣言
void Start()
{
//delegateに関数を登録 関数の後の()は必要ない
del = DisplayText;
//delgateを発動 登録されている関数が呼ばれる
del();
}
//Delegateに入れる関数
public void DisplayText()
{
Debug.Log("DisplayTextが呼ばれました");
}
}
Console画面に表示されたのでうまくいったみたいだ。
delegateには、どんな関数でも入れる事ができるわけではない。delegateの型名で宣言した形の関数しか入れることができない。
例えば、以下の関数を作ってdelにいれようとすると、、
//10を返す関数
public int ReturnNum()
{
return 10;
}
エラーになってしまう。
これはDelegateで宣言した型の形が違うからエラーになってしまう。
public delegate void DelegateType(); //delegateの型名を宣言
上の形は返り値にvoid
を指定しているから、void型の関数か登録できない。
なので、delegateの型を宣言する時にint型を指定してあげればint型を返す関数を入れることができる。
public delegate int DelegateType(); //delegateの型名を宣言
ちなみに型だけじゃなく、引数も入れてくださいってdelegateに指定していれば引数を取る関数しか登録できないぞ。宣言したdelegate型と全く同じ型のメソッドしか登録できないと覚えておこう。
ちなみにdelegateには関数をたくさん入れる事ができて、delegateを発動した時に登録している関数を一気に発動させることができる。そこら辺はまた別記事でも。
UnityActionとは
上のdelegateの説明を見て、使うの結構面倒臭いな〜なんて思ったりすると思う。たぶん先人達もそう思ったのだろう。
そこで次はUnityAction。これはUnityに用意されたdelegateを便利にするモノらしい。
UnityActionは理解すれば簡単で、単にvoid型のdelegateのことだ。
これを使うと、delegate void UnityAction
と宣言してUnityAction del
と使うことができる。これで1行で使える。上で使ったスクリプトをUnityActionを使って書き換えてみる。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events; //UnityAction使うにはこれ忘れないように
public class TestDelegate : MonoBehaviour
{
//public delegate int DelegateType(); //delegateの型名を宣言
//public DelegateType del; //delegate名を宣言
public UnityAction del; //これ一行でdelegateが作れる!
void Start()
{
//delegateに関数を登録
del = DisplayText;
//delgateを発動 登録されている関数が呼ばれる
del();
}
//Delegateに入れる関数
public void DisplayText()
{
Debug.Log("DisplayTextが呼ばれました");
}
}
さっきまでdelegateを使うのに2行で宣言していたところを、UnityActionを使えば1行で書くことができる。
public delegate void DelegateType(); //delegateの型名を宣言
public DelegateType del; //delegate名を宣言
//この2行を以下の1行で表せる。
public UnityAction del;
いってしまえばUnityActionはdelegate型を宣言する時に名前をUnityActionとして作成したものだということだ。
public delegate void UnityAction();
public UnityAction del;
とりあえず、省略して書けるからめっちゃ便利だよということだ。
また、UnityActionを使うときは、using UnityEngine.Events;
の名前空間を書くのを忘れないようにしよう。
ちなみにUnityActionはvoid型であれば引数4つまでの関数も登録できる。リファレンスを参考にしてみてください。
eventとは
eventはC#の機能で、delegateを修飾するもの。修飾するとはどういうことか。見ていこう。
まず、public delegate void OnDethDelegate();
とdelegateの型を宣言してからpublic event OnDethDelegate OnDethEvent;
とevent修飾子をつけて使用する。
※ここではplayerが死んだら発動するeventとして定義しているのでOnDethEventという名称にしている。詳しくは下の例を見てほしい。
event修飾子をつけてdelegateを宣言したら、それを発動してあげる。発動させるには以下のように書けば良い。
ちなみに、よくeventを発動させることを発火とか言ったりする。
delegateが空だった場合はエラーになるので、以下のようにif文で「eventが空じゃなかったら発動する」と記載してある例をよく見るのでこちらの方がよいかも。OnDeth?.Invoke()
と?
を使って省略して書くこともできる。
//delegateが空だったら発動しないようにする。
if(OnDethEvent != null){
OnDethEvent();
}
//省略した書き方
OnDethEvent?.Invoke()
普通のdelegateとどう違うのかと言われると、event修飾子をつけると他のクラスから、そのdelegateを発動することはできず、関数を登録するときも=
が使えず、+= OR -=
しか使えないことだろうか。
個人的にeventの使い方は、クラスの依存関係をしっかりさせたい時に使うのかな〜って感じ。
eventの使用例
例えば、プレイヤーが死んだ場合にGameOverなどのUI画面を表示させたい時などを見てみる。
通常プレイヤーとUIクラスは別で管理すると思う。「プレイヤーが死んだらUIを表示する」為には、プレイヤークラスにUIクラスの参照を持たせて、UIクラスの関数を呼ぶかもしれない。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//プレイヤークラス
public class Player : MonoBehaviour
{
public GameUI gameUI; //UIクラスの参照
void Start()
{
Deth();
}
//プレイヤーが死んだらGameOverのUIを発動させる。
void Deth()
{
//GameUIクラスからGameOverのパネルを表示させる関数を発動
gameUI.DisplayGameOver();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameUI : MonoBehaviour
{
//UIクラス
//死んだらGameOverの表示をする
public void DisplayGameOver()
{
gameObject.SetActive(true);
}
}
ただ、この場合PlayerクラスがGameUIクラスの参照をもっているので何らかの修正等がかかってUIクラスを消したとすると、エラーになってしまう。
つまり、gameUI.DisplayGameOver();
の部分がエラーになる。なぜならgameUIクラスはなくなってしまったからだ。
こういう時にeventを使って解決していく。これを行うと、eventを宣言しているクラスからはどのクラスの関数が使われているのはわからないのでエラーにならない。つまり、UIクラスを削除しても問題ない。これで依存関係を解決することができる。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events; //これ忘れずに!
//プレイヤークラス
public class Player : MonoBehaviour
{
public delegate void OnDethDelegate(); //delegateの型を宣言
public static event OnDethDelegate OnDethEvent; //eventの宣言 staticをつけて直接呼べるようにする
public void Update()
{
//スペースキーを押したらDeth()を呼ぶ。
if (Input.GetKey(KeyCode.Space))
{
Deth();
}
}
//プレイヤーが死んだらGameOverのUIを発動させる。
public void Deth()
{
//イベントを発動
OnDethEvent?.Invoke();
//こう書いてもいい
//if (OnDethEvent != null)
//{
// OnDethEvent.Invoke();
//}s
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameUI : MonoBehaviour
{
//UIクラス
//死んだらGameOverの表示をする
public void DisplayGameOver()
{
gameObject.SetActive(true);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PanelManager : MonoBehaviour
{
//パネルを管理するクラス
public GameUI gameUI;
public void Start()
{
//イベント登録
Player.OnDethEvent += OpenPanel;
//↓ "="は使えないし、"event"を発動させることはできない
//Player.OnDethEvent = OpenPanel;
//Player.OnDethEvent();
}
//GameOverのパネルを表示させる関数をgameUIクラスから発動する
public void OpenPanel()
{
gameUI.DisplayGameOver();
}
}
上では新しくPanelManagerクラスとパネルを管理するクラスを用意して、そこでeventを呼び出してGameOverのパネルを開く関数を登録した。Player.OnDethEvent += OpenPanel;
の部分。
=
は使用できないこととevent()
と直接発動することはできないので注意!
※他クラスの場合で自クラスの場合は大丈夫なはず...
//↓ "="は使えないし、"event"を発動させることはできない
//Player.OnDethEvent = OpenPanel;
//Player.OnDethEvent();
また、public static event OnDethDelegate OnDethEvent;
とstatic
をつけてクラスから直接eventを呼べるようにした。※staticに関してはググってください。
これで、スペースキーを押すと以下のようにパネルが表示される。
ちなみに、event修飾子をつけたdelegateに対して他クラスから関数を登録することを、よくサブスクリプション的な概念で購読と表したりするらしい。イベントを買うってことなのかな?
ちなみにActionを使って一行で宣言することもできる。というか2行書くのは面倒だしこっち使う方が便利。
public static event Action OnDethEvent;
とすれば1行でOK。
もちろん同じ型の関数しかいれられない点には注意。
delegateでも同じことできるけど、event使うと他のクラスからは関数の登録と解除しかできないからこっちの方が安全ってことなのかな?
UnityEventとは
最後にUnityEvent。
UnityEventはリファレンス見る限りはシンプル。
公式リファレンス:https://docs.unity3d.com/ja/current/ScriptReference/Events.UnityEvent.html
UnityEventもeventのように他のクラスの関数を登録することができる。
普通のeventと違う点は、インスペクタから他の関数を設定できることだ。
UnityEvent自体はdelegateってわけじゃないので(たぶん)、UnityEventの場合はAddListener関数を呼び出してその中に関数を登録してあげる感じ。公式を見ると正確には引数にはUnityActionをとるみたい。つまり、引数なしのvoid型の関数だと登録できるっぽい。
先ほどeventでやったことをUnityEventに置き換えてみる。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
//プレイヤークラス
public class Player : MonoBehaviour
{
//UnityEventを宣言
public static UnityEvent unityEvent = new UnityEvent();
public void Update()
{
if (Input.GetKey(KeyCode.Space))
{
Deth();
}
}
//プレイヤーが死んだらGameOverのUIを発動させる。
public void Deth()
{
//Eventを発火
unityEvent.Invoke();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameUI : MonoBehaviour
{
//UIクラス
//死んだらGameOverの表示をする
public void DisplayGameOver()
{
gameObject.SetActive(true);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PanelManager : MonoBehaviour
{
//パネルを管理するクラス
public GameUI gameUI;
public void Start()
{
//イベント登録
Player.unityEvent.AddListener(OpenPanel);
}
public void OpenPanel()
{
gameUI.DisplayGameOver();
}
}
変更する箇所はプレイヤークラスとPanelManagerクラスのみ。
プレイヤークラスではpublic static UnityEvent unityEvent = new UnityEvent();
とUnityEventを宣言してあげる。UnityEventはnewを使ってインスタンスを作る必要があるっぽい。
またパネルを管理するクラスの中ではPlayer.unityEvent.AddListener(OpenPanel);
とAddListenerメソッドを呼んでその中に関数を入れてあげる。
今までやってきたことと少し違うのは、UnityEventにはActionを入れているのでUnityEventを発火する時に?
をつけたりしてEventが空かどうか確認する必要がないことだ。UnityEventに登録されたActionが空の可能性はあるが、UnityEvent自体にエラーは起きない。
インスペクタから関数登録してみる
このままだと、普通のeventとほとんど変わらないのでインスペクタから関数を登録する例を見ていく。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
//プレイヤークラス
public class Player : MonoBehaviour
{
//UnityEventを宣言
[SerializeField]
public UnityEvent unityEvent = new UnityEvent();
public void Update()
{
if (Input.GetKey(KeyCode.Space))
{
Deth();
}
}
//プレイヤーが死んだらGameOverのUIを発動させる。
public void Deth()
{
//Eventを発火
unityEvent.Invoke();
}
}
以下のようにUnityEventの前に[SerializeField]
をつけることで、インスペクタ上にUnityEventを表示させることができる。
[SerializeField]
public UnityEvent unityEvent = new UnityEvent();
この形をどこかで見たことないだろうか?
そう、ButtonコンポーネントのOnClickに関数を登録する時とそっくりだ。
実はこのButtonもUnityEventを使用している。
ButtonのOnClick()に関数を登録する時と同じように、UnityEventにも関数を登録することができる。
インスペクタ上に表示されたUnityEventにパネルを開く関数を登録して再生してみよう。うまく動くのが確認できるはずだ。
ただ、どんな関数でも登録することができるわけではないので注意が必要だ。
詳しいことは以下のブログがかなり参考になると思うのでチェックしてみてください。
参考:https://www.urablog.xyz/entry/2016/09/11/080000
まとめ
- delegateは関数を登録できる変数みたいなもの。delegate型を宣言してから使う。
- UnityActionは、ただのvoid型のdelegate。一行で宣言できるので便利。
- eventはdelegateを修飾するもの。他クラスからは
+=
や-=
でしか関数を登録できない。クラスの依存関係を解決したい時に便利? - UnityEventはインスペクタから関数を登録できるようになる便利なやつ。
全くわからないところから、大雑把に理解できていれば幸いです。delegateを理解するとちょっとステップアップしたような気になるのは気のせいだろうか。うまく自分のプロジェクトでも使っていきたいところだ。少しでも理解していただければ幸いです。