【Unity】SendMessage怖い、専用アトリビュート作ったらどうだろう?【結局微妙】

More than 5 years have passed since last update.


はじめに

はじめに断っておきますが、結論は微妙です。

SendMessage怖いですね!


SendMessageというメソッド

SendMessageというメソッドがあります。

同じオブジェクトについている他のコンポーネントのメソッドを呼び出したり、あるgameObjectに付与されている任意のコンポーネントのメソッドを呼び出せます。

呼び出すためにメソッドの名前を文字列で指定します。こんな感じです。


メッセージを受ける側

using UnityEngine;

public class Receiver : MonoBehaviour
{
public void ReseiveMessage ()
{
Debug.Log ("receive");
}
}



メッセージを送る側

using UnityEngine;

public class Sender : MonoBehaviour
{
[SerializeField]
GameObject anotherReceiver;

void Start ()
{
// (1) 同じGameObjectについている
// ReceiverのReseiveMessageを呼び出す
SendMessage ("ReseiveMessage");

// (2) インスペクターから設定した、
// anotherReceiverという名前のGameObject、
// これについているReceiverクラスに送る
anotherReceiver.SendMessage ("ReseiveMessage");
}
}


"receive"って二回表示されると思います。


SendMessage怖い

SendMessage、あまり書かないようにしています。めちゃめちゃ怖いです。

なぜ怖いか例を上げて説明します。先ほどのコードのReceiverクラス、ReseiveMessageってなっています。これ間違えていますね。きっと、ReceiveMessageとしたかったのでしょう(わざとですよ!)

さて、修正しましょう。ReceiverクラスのReseiveMessageというメソッドをReceiveMessageに直して、めでたしめでたし。とはなりません。

Senderクラスの呼び出し側も修正しなくてはいけません。いけないのですが、うっかり修正し忘れてしまうこともありますよね。

その場合、SendMessage経由のメソッドは呼ばれません。

メソッド名を変更する場合、IDEのリファクタリング機能を用いてそのメソッドを呼び出している箇所を一括で変更することが可能です。それを使わなくともメソッド名を変えた場合、コンパイルエラーになって、変わったメソッドを呼び出す箇所で「そんなメソッドないよ」とIDEが注意してくれます。注意されている箇所を直せばいいですね。

ところが、SendMessageなどのメソッド名を引数に渡している場合、その文字列がリファクタリング機能で変更されないし、コンパイルエラーにもならないため間違いに気づくことができません。

これは非常に怖いですね。


とりあえずAttributeを作ってつけます?

さきほどの問題は、「このメソッドはSendMessage経由で呼ばれている」ということが伝われば、ある程度解決します。メソッド名を変更する際「このメソッド名を変えたら、SendMessage呼び出し箇所のメソッド名文字列も変えないと!」って気づけますから。

Attributeを作ってみます。

using System;

[AttributeUsage(AttributeTargets.Method)]
public class CalledFromSendMessageAttribute : Attribute { }

これをSendMessageメソッドで呼ばれるメソッドに付けます。

using UnityEngine;

public class Receiver : MonoBehaviour
{
[CalledFromSendMessage]
public void ReceiveMessage ()
{
Debug.Log ("receive");
}
}

このようにすれば、Receiverクラスを読む人に「ReceiveMessageメソッドはSendMessage経由で呼ばれるから、メソッド名を変えたら気をつけないといけないな!」と伝えることが可能ですね。


実は落とし穴だらけ

先ほどのアトリビュートを使う方法。

これは、「ルールをチーム全体で徹底しないと不具合をふせげない」という大きい落とし穴があります。

例えば、新しく加わったメンバーがCalledFromSendMessageというアトリビュートがついても、その意味がわからずうっかりメソッド名を変えてしまうかもしれません。

例えば、うっかりSendMessage経由で呼ぶメソッドに、CalledFromSendMessageアトリビュートを付け忘れてしまうかもしれません。

それに送る側がメソッド名を打ち間違えることは、どうしようもできません。

先ほどのCalledFromSendMessageを作って運用することで、事故を防げることはあるかもしれませんが、事故を完全になくすことはできませんね。


結論

専用アトリビュートを作っても、微妙!

RequireComponentGetComponentメソッド使いましょう!

あとどっちにせよ、タイポは気をつけましょう。

あとSendMessageを受信できるコンポーネントが複数ある場合にも注意が必要。