When I call Button.onClick.addListener(MyMethod), it won’t be persistent and it won’t appear in inspector (of course event variable is serialized).
Because I’m sure there will be people asking why I need this (and telling me I don’t need this), I’m answering in advance: I want to fill some events using reflection and method attributes. For example menu: class MainMenu contains methods for each button, these methods have attribute “MenuItem” and my system just goes through all methods, pick ones with the attribute, instantiate prefab of a button and attach delegate. Everything is working, problem is, however, that, as stated above, it’s not persistent.
Reason why I need it to be persistent is simple - I want to execute whole process only in editor and cachce values in serialized field. Because I don’t want to slow down startup of the game.
My searching ability are quite bad, so even thought I spent lot of time in the docs, I couldn’t find it. (Funny is that I couldn’t find it even though I knew the name of the class from you reply until I used file search :3.)
I’m sorry, but I still cannot figure out how to use it, to be more precise, how to create correct UnityAction instance.
For non-persistent listener, all I needed to do is create delegate and then create new UnityAction instance: new UnityEngine.Events.UnityAction(myDelegate); However when I use such UnityAction with AddPersistentListener method, an error appears: “ArgumentException: Could not register callback Invoke on MenuButtonBinder+ButtonClickedDelegate. The class MenuButtonBinder+ButtonClickedDelegate does not derive from UnityEngine.Object”. (MenuButtonBinder.ButtonClickedDelegate is my delegate type returning void and taking no argument)
What it means is quite clear, IMHO - constructor of UnityAction takes the delegate as object and method Invoke as method to be called instead of obtaining the object and MethodInfo from the delegate. I get it. However, how can create correct UnityAction? I know I can do it with new UnityAction(MyMethodName), but as stated above, I’m using reflection, so all I have is object and MethodInfo instance. (Btw. I’m creating the delegate with System.Delegate.CreateDelegate.)
I cannot find any information about creating the UnityAction except what MonoDevelop auto-complete functionality gives me - and it claims that it takes two arguments, object and IntPtr. However even when I provide these data (I created IntPtr from MethodInfo instance), it won’t compile with error that method name is required.
Thank you very much skalev, this worked beautifully. I’ll paste a full code snippet here in case anyone needs it:
using System;
using UnityEngine.Events;
using UnityEditor.Events;
using UnityEngine;
public class EventTest : MonoBehaviour {
public UnityEvent BigExplosionEvent;
void Start()
{
if (BigExplosionEvent == null)
BigExplosionEvent = new UnityEvent();
var targetInfo = UnityEvent.GetValidMethodInfo(this, nameof(ExplodeMe), new Type[0]);
UnityAction methodDelegate = Delegate.CreateDelegate(typeof(UnityAction), this, targetInfo) as UnityAction;
UnityEventTools.AddPersistentListener(BigExplosionEvent, methodDelegate);
}
public void ExplodeMe()
{
Debug.Log("I just blew up!");
}
}
How would I be able to set the execution property of the registered Persistent Listener from the default “Runtime Only” to “Editor and Runtime” by script to actually be able to invoke the registered events in editor mode?
public void RegisterEvents()
{
// registers persistent listener as "Runtime only"
UnityEventTools.AddVoidPersistentListener(modulesManager.OnModuleVariantsShowEvent, OnShowModuleVariants);
}
public void OnShowModuleVariants()
{
Debug.Log("OnShowModuleVariants");
// ...
}
Oh well… going through the UnityEvent class code I found the SetPersistentListenerState method.
With this I’m able to change the Call States of my Persistent Listeners:
public void RegisterEvents()
{
// registers persistent listener as "Runtime only"
UnityEventTools.AddVoidPersistentListener(modulesManager.OnModuleVariantsShowEvent, OnShowModuleVariants);
for (var i = 0; i < modulesManager.OnModuleVariantsShowEvent.GetPersistentEventCount(); i++)
{
modulesManager.OnModuleVariantsShowEvent.SetPersistentListenerState(i, UnityEventCallState.EditorAndRuntime);
}
}
public void OnShowModuleVariants()
{
Debug.Log("OnShowModuleVariants");
// ...
}
Think it would still be quite a nice addition to be able to set the Call State right away when adding it through UnityEventTools.AddPersistentListener.
Yes, it would be great. I don’t understand why Unity hides persistent listener methods in the UnityEventTools class when UnityEvent has an internal void AddPersistentListener(UnityAction call, UnityEventCallState callState) that does basically the same and accepts callState as a parameter.
Thanks. It works.
Do you have any idea how to make it stick?
I am trying to do the same thing you did here but through Editor. This way if I save the scene the Events remain there.
A year late, but I’ll add the answer: you need to create an Editor extension class that checks the Persistent Listeners in the base class and adds it if there’s no method with its name in it, all by reflection.
I’ll paste part of the code I made.
using HurricaneVR.Framework.Core;
using HurricaneVR.Framework.Core.Grabbers;
using System;
using UnityEditor;
using UnityEditor.Events;
using UnityEngine.Events;
[CustomEditor(typeof(NetworkedGrabbable))]
public class NetworkedGrabbableInspector : Editor
{
private NetworkedGrabbable targetReference;
private void OnEnable() {
//Retrieve references from the base class and the Grabbable class
targetReference = target as NetworkedGrabbable;
var grabbable = targetReference.GetComponent<HVRGrabbable>();
//Check if the Grab event in the NetworkedGrabbable class is added as a persistent listener
bool hasGrabbedEvent = false;
for (int i = 0; i < grabbable.Grabbed.GetPersistentEventCount() && !hasGrabbedEvent ; i++) {
if (grabbable.Grabbed.GetPersistentMethodName(i) == nameof(targetReference.Grab))
hasGrabbedEvent = true;
}
//Check if the Release event in the NetworkedGrabbable class is added as a persistent listener
bool hasReleasedEvent = false;
for (int i = 0; i < grabbable.Released.GetPersistentEventCount() && !hasReleasedEvent; i++) {
if (grabbable.Released.GetPersistentMethodName(i) == nameof(targetReference.Release))
hasReleasedEvent = true;
}
//If there is no persistent listener named "Grab" from "NetworkGrabbable", add it.
if (!hasGrabbedEvent) {
var methodInfo = UnityEvent.GetValidMethodInfo(targetReference, nameof(targetReference.Grab), new Type[0]);
var method = Delegate.CreateDelegate(typeof(UnityAction<HVRGrabberBase, HVRGrabbable>), targetReference, nameof(targetReference.Grab)) as UnityAction<HVRGrabberBase, HVRGrabbable>;
UnityEventTools.AddPersistentListener<HVRGrabberBase, HVRGrabbable>(grabbable.Grabbed, method);
}
//If there is no persistent listener named "Release" from "NetworkGrabbable", add it.
if (!hasReleasedEvent) {
var methodInfo = UnityEvent.GetValidMethodInfo(targetReference, nameof(targetReference.Release), new Type[0]);
var method = Delegate.CreateDelegate(typeof(UnityAction<HVRGrabberBase, HVRGrabbable>), targetReference, nameof(targetReference.Release)) as UnityAction<HVRGrabberBase, HVRGrabbable>;
UnityEventTools.AddPersistentListener<HVRGrabberBase, HVRGrabbable>(grabbable.Released, method);
}
}
}
So, to explain: I have a “Grabbable” class, which has a “Grabbed” event. I want to add a persistent event to it whenever I add the class I made “NetworkedGrabbable”.
To do this, I create an Editor class “NetworkedGrabbableInspector”, in which I look for the base class method name (e.g. “Grab” in “NetworkedGrabbable”) in the “Grabbed” event of the “Grabbable” class.
If it’s not there, then I’ll just create a delegate (with Delegate.CreateDelegate) with the method, and use “AddPersistentListener”.