はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】コンポーネントを指定しないメソッドの実行方法(SendMessage・MessageSystem)

はじめに

今回はコンポーネントに依存しないメソッドの実行方法についての記事になります!

UnityにはSendMessageという一つのメッセージで複数の関数を起動するメソッドが備わっています。

具体的にはゲームオブジェクトにアタッチされているすべての指定した名前と同じ全てのメソッドを呼び出すという動作をします。

おそらくよく使われるStartUpdateなどもこれで動作している?と勝手に思っていますが、正しいのはまだ分かっていないです。

これについて少し掘り下げてみましょう。

SendMessage

まずはSendMessageの詳細を見てみましょう。

public void SendMessage (string methodName, object value= null, SendMessageOptions options= SendMessageOptions.RequireReceiver);


ここでの引数の詳細は以下の通りです。

引数名 意味
methodName 呼び出すメソッドの名前
value 呼び出すメソッドに渡す値
options ターゲットのオブジェクトにメソッドが存在しない場合、エラーを発生させるかどうか


optionsSendMessageOptionsという列挙型を指定します。

名前 意味
RequireReceiver 必ず受信先がなくてはならない
DontRequireReceiver 受信先がなくてもよい

SendMessageの使用例

実際に使った例を書いてみました。

Sender.cs

using UnityEngine;

public class Sender : MonoBehaviour
{
    private void Start()
    {
        gameObject.SendMessage("OnRecieve",10);     //Reciever1: 10   Reciever2: 10
    }
}

Reciever1.cs

using UnityEngine;

public class Reciever1 : MonoBehaviour
{
    public void OnRecieve(int value)
    {
        Debug.Log("Reciever1: " + value);
    }
}

Reciever2.cs

using UnityEngine;

public class Reciever2 : MonoBehaviour
{
    public void OnRecieve(int value)
    {
        Debug.Log("Reciever2: " + value);
    }
}

f:id:hanaaaaaachiru:20190621150727p:plain

f:id:hanaaaaaachiru:20190621150827p:plain

ただ、このメッセージは非アクティブのゲームオブジェクトには送信されないことに気をつけてください。

MessageSystem

先程のSendMessageには、自分で名前を入力することやリファクタリングしにくい、引数が一つのみなどの欠点をいっぱい抱えていました。

これを解決するために作られたのがMessageSystemで、今から使うひとはこちらだけ覚えれば大丈夫です。

MessageSystemで重要な要素はIEventSystemHandlerインターフェイスExecuteEvents.Executeメソッドの二つです。

bool ExecuteEvents.Execute<T>(GameObject target, BaseEventData eventData,  ExecuteEvents.EventFunction<T> functor);

ただBaseEventDataExecuteEvents.EventFunction<T>もなかなかに分かりづらいので詳細を見てみましょう。

BaseEventDataクラスは新しい EventSystem 内の全イベントタイプに共通する基本イベントデータを内包したクラスを指しているらしく、コンストラクタは以下の通りです。

public BaseEventData (EventSystems.EventSystem eventSystem);

EventSystems.BaseEventData - Unity スクリプトリファレンス

ここで分かればよかったのですが、新しくEventSystemっていったいどんなや・・・という疑問が生まれてきてしまいました。

EventSystemuGUIを作成すると自動で作成されるキーボード、マウス、タッチやカスタムの入力に基づいて、アプリケーション内のオブジェクトにイベントを送信する方法みたいです。
イベントシステム - Unity マニュアル

だんだん覚えることが多くて辛くなってくるかもしれませんが、実際にBaseEventDataを使うことはほとんどなく、基本はeventDatanullにしておいて大丈夫だと思います。

ただ使いたい場合は、最低限EventSystem.currentで現在のEventSystemを返すstatic変数があることだけ覚えればOKだと思います。

eventData: new BaseEventData(EventSystem.current)

またExecuteEvents.EventFunction<T>対象のインスタンス・EventDataを引数に持つラムダ式を指定します。(別にラムダ式じゃなくても良いですが)

functor: (reciever, eventData) => reciever.OnRecieve()

細かい説明はほどほどにして、実際に使った例を見てたほうが分かりやすいと思うのでみてみましょう。

MessageSystemの使用例

まずは引数なしの基本的なものの例を書いてみました。

IRecieveMessage.cs

using UnityEngine.EventSystems;

public interface IRecieveMessage : IEventSystemHandler
{
    void OnRecieve();
}

Sender.cs

using UnityEngine;
using UnityEngine.EventSystems;

public class Sender : MonoBehaviour
{
    private void Start()
    {
        ExecuteEvents.Execute<IRecieveMessage>(
            target: gameObject,
            eventData: null,
            functor: (reciever, eventData) => reciever.OnRecieve()
            );
    }
}

Reciever1.cs

using UnityEngine;

public class Reciever1 : MonoBehaviour , IRecieveMessage
{
    public void OnRecieve()
    {
        Debug.Log("Recieve1");
    }
}

Reciever2.cs

using UnityEngine;

public class Reciever2 : MonoBehaviour , IRecieveMessage
{
    public void OnRecieve()
    {
        Debug.Log("Recieve2");
    }
}

f:id:hanaaaaaachiru:20190623203347p:plain

f:id:hanaaaaaachiru:20190623203414p:plain

引数ありの使用例

MessageSystemの場合は無制限に引数が指定でき、以下のように実装します。

IRecieveMessage.cs

using UnityEngine.EventSystems;

public interface IRecieveMessage : IEventSystemHandler
{
    void OnRecieve(int value, string text);
}

Sender.cs

using UnityEngine;
using UnityEngine.EventSystems;

public class Sender : MonoBehaviour
{
    private void Start()
    {
        ExecuteEvents.Execute<IRecieveMessage>(
            target: gameObject,
            eventData: null,
            functor: (reciever, eventData) => reciever.OnRecieve(10, "value: ")
            );
    }
}

Reciever1.cs

using UnityEngine;

public class Reciever1 : MonoBehaviour , IRecieveMessage
{
    public void OnRecieve(int value, string text)
    {
        Debug.Log("Recieve1: " + text + value);
    }
}

Reciever2.cs

using UnityEngine;

public class Reciever2 : MonoBehaviour, IRecieveMessage
{
    public void OnRecieve(int value, string text)
    {
        Debug.Log("Recieve1: " + text + value);
    }
}

f:id:hanaaaaaachiru:20190623203853p:plain

さいごに

これらを用いるとより疎結合ができる利点はありますが、特にSendMessageは自分でメソッドの名前の書き間違いには要注意です。

また使ってみた感じExecuteEvents.EventFunction<T>の第2引数のeventDataはほぼ使わない?というような気がしました。

もしこんな使い方があるというのを知っている方がいましたら、是非コメント等で教えていただけると嬉しいです!