【Unity】【エディタ】Edit ModeでスクリプトからUnityEventにリスナを登録して永続化する

UnityでEdit ModeでスクリプトからUnityEventにリスナを登録して永続化する方法についてまとめました。

Unity2020.3.15f2

やりたいこと

UnityEventクラスをシリアライズするとInspectorから視覚的にこのイベントのリスナを登録できます。

f:id:halya_11:20220310122701p:plain
UnityEvent

このリスナはPlay ModeにおいてはUnityEvent.AddListener()を呼ぶだけで簡単に登録できますが、Edit Modeでは登録の仕方が若干複雑になります。
そこで本記事ではEdit Modeにおけるリスナの登録方法についてまとめます。

引数なしのメソッドを登録する

まず引数なしのメソッドを登録するには、以下のようにUnityEditor.Events.UnityEventTools.AddPersistentListener()を使用します。

using UnityEngine;
using UnityEngine.Events;

public sealed class UnityEventTest : MonoBehaviour
{
    [SerializeField] private UnityEvent _event;

    public void Test()
    {
    }

#if UNITY_EDITOR
    [UnityEditor.MenuItem("CONTEXT/UnityEventTest/AddEvent")]
    private static void AddEvent(UnityEditor.MenuCommand command)
    {
        var component = (UnityEventTest)command.context;
        UnityEditor.Events.UnityEventTools.AddPersistentListener(component._event, component.Test);
    }
#endif
}

このスクリプトを適当なGameObjectにアタッチしてContext MenuからAdd Eventを選択すると、リスナが登録されることを確認できます。

f:id:halya_11:20220310123550p:plain
リスナを登録

引数ありUnityEventに引数ありのメソッドを登録する

次に引数ありのUnityEventに引数ありのメソッドを登録する場合について考えます。
この場合には以下の例のように、前節と同じメソッドを使って登録できます。

using UnityEngine;
using UnityEngine.Events;

public sealed class UnityEventTest : MonoBehaviour
{
    [SerializeField] private UnityEvent<int> _event;

    public void Test(int value)
    {
    }

#if UNITY_EDITOR
    [UnityEditor.MenuItem("CONTEXT/UnityEventTest/AddEvent")]
    private static void AddEvent(UnityEditor.MenuCommand command)
    {
        var component = (UnityEventTest)command.context;
        UnityEditor.Events.UnityEventTools.AddPersistentListener(component._event, component.Test);
    }
#endif
}

Context MenuからAddEventを選択すると正常にリスナが登録されていることを確認できます。

f:id:halya_11:20220310125501p:plain
登録された

引数なしのUnityEventに引数ありのメソッドと引数を設定する

次に引数なしのUnityEventに引数ありのメソッドを引数とともに登録する場合について考えます。
この場合には以下の例のように、UnityEditor.Events.UnityEventTools.AddIntPersistentListenerを使って登録できます。

using UnityEngine;
using UnityEngine.Events;

public sealed class UnityEventTest : MonoBehaviour
{
    [SerializeField] private UnityEvent _event;

    public void Test(int value)
    {
    }

#if UNITY_EDITOR
    [UnityEditor.MenuItem("CONTEXT/UnityEventTest/AddEvent")]
    private static void AddEvent(UnityEditor.MenuCommand command)
    {
        var component = (UnityEventTest)command.context;
        UnityEditor.Events.UnityEventTools.AddIntPersistentListener(component._event, component.Test, 123);
    }
#endif
}

登録結果は以下の通りとなります。

f:id:halya_11:20220310130059p:plain
登録結果

引数ありのUnityEventにプロパティを登録する

次に引数ありのUnityEventにプロパティを登録します。
これが少し厄介で、以下のようにプロパティのセッタのMethodInfoを取得して、それを呼ぶUnityActionを作る必要があります。

using System;
using UnityEngine;
using UnityEngine.Events;

public sealed class UnityEventTest : MonoBehaviour
{
    [SerializeField] private UnityEvent<int> _event;

    public int Test { get; set; }

#if UNITY_EDITOR
    [UnityEditor.MenuItem("CONTEXT/UnityEventTest/AddEvent")]
    private static void AddEvent(UnityEditor.MenuCommand command)
    {
        var component = (UnityEventTest)command.context;

        // SetterのMethodInfoを取得
        var setMethod = typeof(UnityEventTest).GetProperty("Test")?.GetSetMethod();
         if (setMethod != null)
         {
             // Setterを呼ぶUnityActionを作成
             var methodDelegate = (UnityAction<int>)Delegate.CreateDelegate(typeof(UnityAction<int>), component, setMethod);
             // リスナ登録
             UnityEditor.Events.UnityEventTools.AddPersistentListener(component._event, methodDelegate);
         }
    }
#endif
}

リスナを登録した結果は以下の通りです。

f:id:halya_11:20220310130553p:plain
登録結果

UnityEventCallStateを切り替える

さてここまででリスナの登録は一通りできるようになったと思いますので、最後にUnityEventCallStateを切り替える方法について紹介します。
UnityEventCallStateとはつまり下図の部分のことです。

f:id:halya_11:20220310130831p:plain
UnityEventCallState

これをEdit Modeで切り替えるには以下のようにUnityEvent.SetPersistentListenerState()を使用します。

UnityEvent ev;
ev.SetPersistentListenerState(0, UnityEventCallState.EditorAndRuntime);