[unity]引数や戻り値の取れる UnityEvent

UI/Button の Click イベントでは、インスペクタで実行する関数を呼び出せます。

ここに登録する関数は、1つだけ引数を取るメソッドも登録できるようです。
その場合、インスペクタで引数の内容も設定することになります。

ちなみに、今回出来る事に気づきましたが、実は理由がよくわかっていなかったり…。

ClickTest.Click("引数") でコール

これはこれで便利ですが、引数は1つしか取れず、クラスを渡すこともできません。
また、プログラムで動的な値を入れたい場合なども、この方法では不可能です。

ここでは、それを可能にする方法と、その実例を示します。

サンプルプロジェクト

サンプル1:通常のクリックイベント (SampleScene.unity)

本番を紹介する前に、基本イベントの動作について紹介しておきます。

引数なし

3つのボタン(GameButton。GameButton2 ではありません)があります。ボタンを押すと、下の欄にその金額が表示されます。
3つのメソッドを、それぞれのボタンが呼び出しています。

GameButton.cs

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[SerializeField]
UnityEvent OnClick = null;
void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
{
OnClick?.Invoke();
}
[SerializeField] UnityEvent OnClick = null; void IPointerClickHandler.OnPointerClick(PointerEventData eventData) { OnClick?.Invoke(); }
    [SerializeField]
    UnityEvent OnClick = null;

    void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
    {
        OnClick?.Invoke();
    }

ClickTest.cs

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
using TMPro;
using UnityEngine;
public class ClickTest : MonoBehaviour
{
[SerializeField]
TextMeshProUGUI Text = null;
public void Click100()
{
Text.SetText("select price: $100");
}
public void Click10000()
{
Text.SetText("select price: $10,000");
}
public void Click500000()
{
Text.SetText("select price: $500,000");
}
}
using TMPro; using UnityEngine; public class ClickTest : MonoBehaviour { [SerializeField] TextMeshProUGUI Text = null; public void Click100() { Text.SetText("select price: $100"); } public void Click10000() { Text.SetText("select price: $10,000"); } public void Click500000() { Text.SetText("select price: $500,000"); } }
using TMPro;
using UnityEngine;

public class ClickTest : MonoBehaviour
{
    [SerializeField]
    TextMeshProUGUI Text = null;

    public void Click100()
    {
        Text.SetText("select price: $100");
    }

    public void Click10000()
    {
        Text.SetText("select price: $10,000");
    }

    public void Click500000()
    {
        Text.SetText("select price: $500,000");
    }
}
Doller100
Doller10000
Doller500000

引数あり

メソッド1つで、3つのボタンそれぞれに対応するパターンです。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
using TMPro;
using UnityEngine;
public class ClickTest : MonoBehaviour
{
[SerializeField]
TextMeshProUGUI Text = null;
public void Click(string price)
{
Text.SetText($"select price: {price}");
}
}
using TMPro; using UnityEngine; public class ClickTest : MonoBehaviour { [SerializeField] TextMeshProUGUI Text = null; public void Click(string price) { Text.SetText($"select price: {price}"); } }
using TMPro;
using UnityEngine;

public class ClickTest : MonoBehaviour
{
    [SerializeField]
    TextMeshProUGUI Text = null;

    public void Click(string price)
    {
        Text.SetText($"select price: {price}");
    }
}
Doller100
Doller10000
Doller500000

ソースコードで AddListner するならこう書きます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
button.onClick.AddListener(() => onClick("$100"));
button.onClick.AddListener(() => onClick("$100"));
button.onClick.AddListener(() => onClick("$100"));

サンプル2:カスタムクリックイベント (SampleScene2.unity)

こちらが本番です。

GameButton2 クラスの、以下のコードが肝です。
OnClick?.Invoke() で、ボタンに表示されているテキストをそのまま渡しています。
こうすることで、インスペクタに引数の値を記述する必要がなくなります。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[Serializable]
public class OnClickEvent : UnityEvent<string> {}
[SerializeField]
OnClickEvent OnClick = null;
void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
{
OnClick?.Invoke(TxtDesc.text);
}
[Serializable] public class OnClickEvent : UnityEvent<string> {} [SerializeField] OnClickEvent OnClick = null; void IPointerClickHandler.OnPointerClick(PointerEventData eventData) { OnClick?.Invoke(TxtDesc.text); }
    [Serializable]
    public class OnClickEvent : UnityEvent<string> {}

    [SerializeField]
    OnClickEvent OnClick = null;

    void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
    {
        OnClick?.Invoke(TxtDesc.text);
    }
Doller100,10000,500000 全て同じ Click

注意点

OnClick を設定する際、気をつけるべきポイントがあります。
string を引数に持つ時、Click イベントは2か所から選べてしまいますが、上の Dynamic string にある Click イベントを選んでください。
Static Parameters を選んでしまうと、サンプル1と同じ状態になってしまいます。

初めてこれをやってしまった時、どんなに頑張っても期待した引数が届かず、悩んだ記憶があります…。
なお、引数がクラスなど Static Parameters で受け付けられない型の場合は Dynamic にのみ表示されます。

仕組み

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[Serializable]
public class OnClickEvent : UnityEvent<string> {}
[Serializable] public class OnClickEvent : UnityEvent<string> {}
   [Serializable]
    public class OnClickEvent : UnityEvent<string> {}

この宣言によってカスタムイベントをインスペクタに登録できるようになります。
<string> は引数の型です。引数の数に制限はなく、クラスを渡すことも可能です。
例えば、以下のように3か所を変更することで、Click に TextMeshProUGUI と文字列の 2 つを渡すことができます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
GameButton2.cs:
public class OnClickEvent : UnityEvent<string> {}
public class OnClickEvent : UnityEvent<TextMeshProUGUI, string> {}
OnClick?.Invoke(TxtDesc.text);
OnClick?.Invoke(TxtDesc, TxtDesc.text);
ClickTest.cs:
public void Click(string text)
public void Click(TextMeshProUGUI tobj, string text)
GameButton2.cs: public class OnClickEvent : UnityEvent<string> {} ↓ public class OnClickEvent : UnityEvent<TextMeshProUGUI, string> {} OnClick?.Invoke(TxtDesc.text); ↓ OnClick?.Invoke(TxtDesc, TxtDesc.text); ClickTest.cs: public void Click(string text) ↓ public void Click(TextMeshProUGUI tobj, string text)
GameButton2.cs:
    public class OnClickEvent : UnityEvent<string> {}
    ↓
    public class OnClickEvent : UnityEvent<TextMeshProUGUI, string> {}

    OnClick?.Invoke(TxtDesc.text);
    ↓
    OnClick?.Invoke(TxtDesc, TxtDesc.text);

ClickTest.cs:
    public void Click(string text)
    ↓
    public void Click(TextMeshProUGUI tobj, string text)

クラスを渡せるので、戻り値をクラスメンバに返す、といった使い方も可能です。
かなり自由度が増すので、是非使い方を覚えておくといいでしょう。

コードからイベントを登録したい場合

特に難しいことはなく、UI/Button の onClick 同様、AddListener で登録します。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[SerializeField]
ClickTest CTest;
void Awake()
{
this.OnClick.AddListener(CTest.Click);
}
[SerializeField] ClickTest CTest; void Awake() { this.OnClick.AddListener(CTest.Click); }
[SerializeField]
ClickTest CTest;

void Awake()
{
    this.OnClick.AddListener(CTest.Click);
}

コードで登録する場合、インスペクタからはクリックで何が実行されるか見えなくなりますが、大量にボタンがある時など、いちいちマウスオペレーションでイベントを登録せずに済むのは便利です。

どちらがいい、というものではありませんが、同じプロジェクト内では作り方を共通化しておきたいですね。
コードが得意な人(チーム)と、GUI が得意な人でどちらを選ぶか変わるでしょう。

3件のコメント

    1. 引数をクラスで渡してそのメンバに結果を渡してあげる、というのはどうでしょう?

      1. なるほど!
        UnityEventの引数は基本的に1個しか取れないので、引数と戻り値を持った独自クラスを作れば行けるかもしれないですね。
        ありがとうございます。

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA