Search code examples
c#unity-game-enginegenericsdynamic-programming

UnityEventTools AddPersistantListener With Dynamic Type


Sorry if the following is confusing...

QUESTION:

How do I go about adding a new persistent unity event action to a UnityEvent from a generic type with a dynamic entry?

EXAMPLES:

I have the following function:

public static void SetUnityEvent<T>(T targetEvent, UnityEventEntry entry) where T : UnityEventBase
{
  ...
}

and inside that function from the UnityEventEntry object I'm trying to add a new action to the UnityEvent but I'm not sure how to do that with a dynamic type:

UnityEventTools.AddObjectPersistentListener(
   targetEvent,
   unityaction,
   <what do I add here>
);

This may not be the right thing to do at all. However, this is what I mean by a dynamic parameter:

enter image description here

How would I add a new action item into a UnityEvent that is including that dynamic parameter?

Any help would be greatly appreciated.

HOW I'M CURRENT WRITING IT:

public static void SetUnityEvent<T>(T targetEvent, UnityEventEntry entry) where T : UnityEventBase
{
    UnityAction void_execute = (UnityAction)Delegate.CreateDelegate(typeof(UnityAction), entry.component, entry.function);
    UnityEventTools.AddObjectPersistentListener(
       targetEvent,
       void_execute,
       <What do i add>?
    );
}

IMPORTANT NOTE:

If I write it like:

UnityEventTools.AddVoidPersistentListener(
                    targetEvent,
                    void_execute
                );

That will work, but that's for voids and will not include that dynamic parameter. Not sure how else to do it.

Also trying to add it like:

UnityEventTools.AddPersistentListener(
  targetEvent as UnityEvent,
  void_execute
);

Will result in targetEvent being a null.


Solution

  • I got it! Only 2 weeks worth of digging and coding to get up to this point. Simple when you think about it but hard when this is 100% undocumented anywhere on the web it seems like.

    Here is what you want to do.

    NOTE: The following will be defined like the following:

    target = GameObject target of the same component you're putting this event on
    targetComponent = The Component you're putting this event on
    unityEventName = The name of the UnityEvent on the "targetComponent"
    

    My script also takes advantage of the entry item which is a class that is initialized like the following:

    public GameObject target = null;
    public Component component = null;
    public MethodInfo function = null;
    public object parameter = null;
    public int callState = -1;
    public PersistentListenerMode mode = PersistentListenerMode.Void;
    
    1. Setup a "dummy" event like the following:
    MethodInfo tmpFunc = GetMethodInfo(target, "SetActive", PersistentListenerMode.Bool);
    UnityAction<bool> tmp_event_execute = System.Delegate.CreateDelegate(typeof(UnityAction<bool>), entry.target, tmpFunc) as UnityAction<bool>;
    UnityEventTools.AddBoolPersistentListener(
        targetEvent,
        tmp_event_execute,
        false
    );
    

    This can be any event, it doesn't matter. I just use SetActive with the GameObject because it seems to be a universal thing you can do with anything.

    1. Modify that "dummy" listener entry using SerializedProperties:
    // Get the listener entry to modify
    SerializedObject so = new SerializedObject(targetComponent);
    SerializedProperty persistentCalls = so.FindProperty(unityEventName).FindPropertyRelative("m_PersistentCalls.m_Calls");
    SerializedProperty listenerEntry = persistantCalls.GetArrayElementAtIndex(persistantCalls.arraySize - 1);
    
    // Get the individual items on that listener entry
    SerializedProperty listener_Target = listenerEntry.FindPropertyRelative("m_Target");
    SerializedProperty listener_CallState = listenerEntry.FindPropertyRelative("m_CallState");
    SerializedProperty listener_FunctionName = listenerEntry.FindPropertyRelative("m_MethodName");
    SerializedProperty listener_Mode = listenerEntry.FindPropertyRelative("m_Mode");
    SerializedProperty listener_Arg = listenerEntry.FindPropertyRelative("m_Arguments");
    
    // Modify the entry with the new target values (Notice for dynamic events I don't include an argument/parameter)
    listener_Target.objectReferenceValue = (entry.component == null) ? entry.target : (UnityEngine.Object)entry.component;
    listener_FunctionName.stringValue = entry.function.Name;
    listener_Mode.enumValueIndex = (int)entry.mode;
    
    listenerEntry.serializedObject.ApplyModifiedProperties();
    

    Then boom! After doing that you now have an added persistant listener with a dynamic variable included all done via an Editor script. Also because you modify the "dummy" listener in place there is nothing to cleanup afterwards.