【Unity】delegateとeventとUnityActionとUnityEventを大雑把に理解する

対象 delegateやevent、UnityAction、UnityEventを全く知らない人

書いている人 Unity歴9ヶ月

前提 Unityの初心者向けの参考書などを一冊やったことがある

環境 PC:M1 Mac Unityエディタ:Unity2021.2.7f1(シリコン)

参考 https://note.com/npaka/n/n988adf1ecdb4

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には、どんな関数でも入れる事ができるわけではない。delegateの型名で宣言した形の関数しか入れることができない。

例えば、以下の関数を作ってdelにいれようとすると、、

//10を返す関数
    public int ReturnNum()
    {
        return 10;
    }

エラーになってしまう。

return typeが間違ってますよと言われている。

これはDelegateで宣言した型の形が違うからエラーになってしまう。

public delegate void DelegateType(); //delegateの型名を宣言

上の形は返り値にvoidを指定しているから、void型の関数か登録できない。

なので、delegateの型を宣言する時にint型を指定してあげればint型を返す関数を入れることができる。

public delegate int DelegateType(); //delegateの型名を宣言

delegateの型を宣言する時にintを指定すれば今度はエラーが起きていない。

ちなみに型だけじゃなく、引数も入れてくださいって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つまでの関数も登録できる。リファレンスを参考にしてみてください。

void型の引数なし〜引数4つまでの関数を登録するならUnityActionが便利!

UnityActionとActionの違い
実はC#にもActionというものが用意されている。どちらもやってることは一緒で違いは正直わからない。C#のActionの場合引数16個まで入れられるところが違いかな?
とりあえずUnityAction使っとけばよいのではないかと思う。
参考https://www.urablog.xyz/entry/2016/09/11/080000

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クラスの参照を持たせて、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使うと他のクラスからは関数の登録と解除しかできないからこっちの方が安全ってことなのかな?

eventの詳しい使い方は以下のYouTubeが参考になるかもしれない。※英語です。

UnityEventとは

最後にUnityEvent。

UnityEventはリファレンス見る限りはシンプル。

公式リファレンス:https://docs.unity3d.com/ja/current/ScriptReference/Events.UnityEvent.html

UnityEventもeventのように他のクラスの関数を登録することができる。

普通のeventと違う点は、インスペクタから他の関数を設定できることだ。

UnityEvent自体はdelegateってわけじゃないので(たぶん)、UnityEventの場合はAddListener関数を呼び出してその中に関数を登録してあげる感じ。公式を見ると正確には引数にはUnityActionをとるみたい。つまり、引数なしのvoid型の関数だと登録できるっぽい。

公式から。引数はUnityActionになっている。

先ほど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();
インスペクタ上にUnityEventが表示された。

この形をどこかで見たことないだろうか?

そう、ButtonコンポーネントのOnClickに関数を登録する時とそっくりだ。

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を理解するとちょっとステップアップしたような気になるのは気のせいだろうか。うまく自分のプロジェクトでも使っていきたいところだ。少しでも理解していただければ幸いです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次