Created
August 9, 2020 02:59
-
-
Save wesleywh/1c56d880c0289371ea2dc47661a0cdaf to your computer and use it in GitHub Desktop.
This will copy UnityEvents from one UnityEvent to Another. It should copy all the settings exactly as is. Tested on Unity2018.4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static void CopyUnityEvents(object sourceObj, string source_UnityEvent, object dest, bool debug = false) | |
{ | |
FieldInfo unityEvent = sourceObj.GetType().GetField(source_UnityEvent, E_Helpers.allBindings); | |
if (unityEvent.FieldType != dest.GetType()) | |
{ | |
if (debug == true) | |
{ | |
Debug.Log("Source Type: " + unityEvent.FieldType); | |
Debug.Log("Dest Type: " + dest.GetType()); | |
Debug.Log("CopyUnityEvents - Source & Dest types don't match, exiting."); | |
} | |
return; | |
} | |
else | |
{ | |
SerializedObject so = new SerializedObject((Object)sourceObj); | |
SerializedProperty persistentCalls = so.FindProperty(source_UnityEvent).FindPropertyRelative("m_PersistentCalls.m_Calls"); | |
for (int i = 0; i < persistentCalls.arraySize; ++i) | |
{ | |
Object target = persistentCalls.GetArrayElementAtIndex(i).FindPropertyRelative("m_Target").objectReferenceValue; | |
string methodName = persistentCalls.GetArrayElementAtIndex(i).FindPropertyRelative("m_MethodName").stringValue; | |
MethodInfo method = null; | |
try | |
{ | |
method = target.GetType().GetMethod(methodName, E_Helpers.allBindings); | |
} | |
catch | |
{ | |
foreach (MethodInfo info in target.GetType().GetMethods(E_Helpers.allBindings).Where(x => x.Name == methodName)) | |
{ | |
ParameterInfo[] _params = info.GetParameters(); | |
if (_params.Length < 2) | |
{ | |
method = info; | |
} | |
} | |
} | |
ParameterInfo[] parameters = method.GetParameters(); | |
switch(parameters[0].ParameterType.Name) | |
{ | |
case nameof(System.Boolean): | |
bool bool_value = persistentCalls.GetArrayElementAtIndex(i).FindPropertyRelative("m_Arguments.m_BoolArgument").boolValue; | |
var bool_execute = System.Delegate.CreateDelegate(typeof(UnityAction<bool>), target, methodName) as UnityAction<bool>; | |
UnityEventTools.AddBoolPersistentListener( | |
dest as UnityEventBase, | |
bool_execute, | |
bool_value | |
); | |
break; | |
case nameof(System.Int32): | |
int int_value = persistentCalls.GetArrayElementAtIndex(i).FindPropertyRelative("m_Arguments.m_IntArgument").intValue; | |
var int_execute = System.Delegate.CreateDelegate(typeof(UnityAction<int>), target, methodName) as UnityAction<int>; | |
UnityEventTools.AddIntPersistentListener( | |
dest as UnityEventBase, | |
int_execute, | |
int_value | |
); | |
break; | |
case nameof(System.Single): | |
float float_value = persistentCalls.GetArrayElementAtIndex(i).FindPropertyRelative("m_Arguments.m_FloatArgument").floatValue; | |
var float_execute = System.Delegate.CreateDelegate(typeof(UnityAction<float>), target, methodName) as UnityAction<float>; | |
UnityEventTools.AddFloatPersistentListener( | |
dest as UnityEventBase, | |
float_execute, | |
float_value | |
); | |
break; | |
case nameof(System.String): | |
string str_value = persistentCalls.GetArrayElementAtIndex(i).FindPropertyRelative("m_Arguments.m_StringArgument").stringValue; | |
var str_execute = System.Delegate.CreateDelegate(typeof(UnityAction<string>), target, methodName) as UnityAction<string>; | |
UnityEventTools.AddStringPersistentListener( | |
dest as UnityEventBase, | |
str_execute, | |
str_value | |
); | |
break; | |
case nameof(System.Object): | |
Object obj_value = persistentCalls.GetArrayElementAtIndex(i).FindPropertyRelative("m_Arguments.m_ObjectArgument").objectReferenceValue; | |
var obj_execute = System.Delegate.CreateDelegate(typeof(UnityAction<Object>), target, methodName) as UnityAction<Object>; | |
UnityEventTools.AddObjectPersistentListener( | |
dest as UnityEventBase, | |
obj_execute, | |
obj_value | |
); | |
break; | |
default: | |
var void_execute = System.Delegate.CreateDelegate(typeof(UnityAction), target, methodName) as UnityAction; | |
UnityEventTools.AddPersistentListener( | |
dest as UnityEvent, | |
void_execute | |
); | |
break; | |
} | |
} | |
} | |
} |
This is good stuff. I'll have to try this out when I get a chance. Thanks for this!
Well I did have another round on the script and encapsulated the SerializedProperty
references into a struct
. This allowed me to turn the local functions into static methods - C# 7.3 doesn't have static local methods.
Okay so here's the version which incorporates all that but does away with removing the old calls because it would remove everything without copying the calls otherwise. It's trivial to delete events anyway.
using Object = UnityEngine.Object;
private struct PersistentCall
{
private SerializedProperty callProperty;
private string propertyPathBase;
private SerializedProperty target;
private SerializedProperty methodName;
private SerializedProperty mode;
private SerializedProperty callState;
private SerializedProperty args;
private SerializedProperty objectArg;
private SerializedProperty objectArgType;
private SerializedProperty intArg;
private SerializedProperty floatArg;
private SerializedProperty stringArg;
private SerializedProperty boolArg;
internal PersistentCall(in SerializedProperty callProperty, in string propertyPathBase)
{
// Read and cache
this.callProperty = callProperty;
this.propertyPathBase = propertyPathBase;
target = callProperty?.FindPropertyRelative("m_Target");
methodName = callProperty?.FindPropertyRelative("m_MethodName");
mode = callProperty?.FindPropertyRelative("m_Mode");
callState = callProperty?.FindPropertyRelative("m_CallState");
args = callProperty?.FindPropertyRelative("m_Arguments");
objectArg = args?.FindPropertyRelative("m_ObjectArgument");
objectArgType = args?.FindPropertyRelative("m_ObjectArgumentAssemblyTypeName");
intArg = args?.FindPropertyRelative("m_IntArgument");
floatArg = args?.FindPropertyRelative("m_FloatArgument");
stringArg = args?.FindPropertyRelative("m_StringArgument");
boolArg = args?.FindPropertyRelative("m_BoolArgument");
}
internal static void MemberwiseClone(in PersistentCall src, in PersistentCall dst)
{
// Write
dst.target.objectReferenceValue = src.target.objectReferenceValue;
dst.methodName.stringValue = src.methodName.stringValue;
dst.mode.enumValueIndex = src.mode.enumValueIndex;
dst.callState.enumValueIndex = src.callState.enumValueIndex;
dst.objectArg.objectReferenceValue = src.objectArg.objectReferenceValue;
dst.objectArgType.stringValue = src.objectArgType.stringValue;
dst.intArg.intValue = src.intArg.intValue;
dst.floatArg.floatValue = src.floatArg.floatValue;
dst.stringArg.stringValue = src.stringArg.stringValue;
dst.boolArg.boolValue = src.boolArg.boolValue;
}
public override string ToString() => $"[{(UnityEventCallState)callState.enumValueIndex}] {target.objectReferenceValue}.{methodName.stringValue}({GetParamSignature(mode.enumValueIndex)})";
private string GetParamSignature(in int enumIndex)
{
switch (enumIndex)
{
case 0: // Event Defined
return $"{GetEventType(this)} (dynamic call)".I();
case 1: // void
return typeof(void).I();
case 2: // Object
return $"{objectArg.objectReferenceValue.GetType()} = {objectArg.objectReferenceValue}".I();
case 3: // int
return $"{typeof(int)} = {intArg.intValue}".I();
case 4: // float
return $"{typeof(float)} = {floatArg.floatValue}".I();
case 5: // string
return $"{typeof(string)} = {stringArg.stringValue}".I();
case 6: // bool
return $"{typeof(bool)} = {boolArg.boolValue}".I();
default:
return string.Empty;
}
Type GetEventType(in PersistentCall self)
{
var @object = self.callProperty.serializedObject.targetObject;
var names = self.propertyPathBase.Split('.');
var result = @object.GetType()
.GetField(names.FirstOrDefault(),
BindingFlags.NonPublic
| BindingFlags.Public
| BindingFlags.Instance)?.GetValue(@object);
while (!(result is UnityEventBase))
{
result = result.GetType()
.GetField(names.LastOrDefault(),
BindingFlags.NonPublic
| BindingFlags.Public
| BindingFlags.Instance)?.GetValue(result);
}
return result?.GetType();
}
}
}
/// <returns>A log of all transferred calls, empty if there were none.</returns>
private static string TransferPersistentCalls(
in Object source, in Object destination, in string srcEventName, in string dstEventName, bool removeOldCalls = false, in bool dryRun = true)
{
const string CallsPropertyPathFormat = "{0}.m_PersistentCalls.m_Calls";
var srcPropertyPath = string.Format(CallsPropertyPathFormat, srcEventName);
var dstPropertyPath = string.Format(CallsPropertyPathFormat, dstEventName);
var src = new SerializedObject(source);
var dst = new SerializedObject(destination);
var srcCalls = src.FindProperty(srcPropertyPath);
var dstCalls = dst.FindProperty(dstPropertyPath);
var dstCallsOriginalCount = dstCalls.arraySize;
var log = string.Empty;
for (var srcIndex = 0; srcIndex < srcCalls.arraySize; srcIndex++)
{
#region Init Source
var srcCallProperty = srcCalls.GetArrayElementAtIndex(srcIndex);
var srcCall = new PersistentCall(srcCallProperty, srcEventName);
var logLine = $"({srcIndex}) {srcCall}\n";
#endregion
if (!dryRun)
{
SerializedProperty dstCallProperty;
#region Check if the Call already Exists in the Destination
if (dstCallsOriginalCount > 0)
{
dstCallProperty = dstCalls.GetArrayElementAtIndex(srcIndex);
// If we are satisfied that the call is exactly the same, skip ahead.
if (SerializedProperty.DataEquals(srcCallProperty, dstCallProperty))
{
log += logLine;
continue;
}
}
#endregion
// Only unique properties beyond this point. Append with care.
#region Copy Properties from Source to Destination
var dstIndex = dstCallsOriginalCount + srcIndex;
dstCalls.InsertArrayElementAtIndex(dstIndex);
dstCallProperty = dstCalls.GetArrayElementAtIndex(dstIndex);
var dstCall = new PersistentCall(dstCallProperty, dstEventName);
PersistentCall.CloneValues(srcCall, dstCall);
#endregion
}
log += logLine.B();
}
log += $"\n(<b>Bold</b> = not already present in {dstEventName.I()}.)\n";
if (!dryRun)
{
if (removeOldCalls && (dstCalls.arraySize > 0))
{
srcCalls.ClearArray();
}
src.ApplyModifiedProperties();
if (source != destination)
{
dst.ApplyModifiedProperties();
}
}
return log;
}
The issue with removing the old calls came about when the source and destination were equal. Hence, this is the modification:
if (!dryRun)
{
if (removeOldCalls && (dstCalls.arraySize > 0))
{
srcCalls.ClearArray();
}
src.ApplyModifiedProperties();
if (source != destination)
{
dst.ApplyModifiedProperties();
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Admittedly pretty rough but it works.
B()
andI()
are extension methods for surrounding the string with<b>
and<i>
tags.This script copies the entire property tree representing the Unity Event irrespective of the types of the calls, their modes, or their argument values. It supports undo and redo, and allows dry runs and removing calls from the source. Currently, works on Unity Events on the same object, but it should be easy to use it for Unity Events on multiple objects.
Can only be used in an Editor script, or using
#if UNITY_EDITOR
.