对于Unity下例如按钮这种我们可以为它的监听事件手动进行赋值
如果需要通过编辑器扩展来动态为这些监听事件赋值应该如何去实现呢?
首先需要确认的是这些监听事件必须要符合一定的规则才能添加上:
1.必须是公共方法
2.不能带有返回值
大部分UI组件的参数只有一个,当然,你可以扩充UI组件增加更多参数。
我们主要使用的是UnityEventTools这个类中的方法。
监听事件的类型一共有以下几种模式:
public enum PersistentListenerMode
{
/// <summary>
/// <para>The listener will use the function binding specified by the even.</para>
/// </summary>
EventDefined,
/// <summary>
/// <para>The listener will bind to zero argument functions.</para>
/// </summary>
Void,
/// <summary>
/// <para>The listener will bind to one argument Object functions.</para>
/// </summary>
Object,
/// <summary>
/// <para>The listener will bind to one argument int functions.</para>
/// </summary>
Int,
/// <summary>
/// <para>The listener will bind to one argument float functions.</para>
/// </summary>
Float,
/// <summary>
/// <para>The listener will bind to one argument string functions.</para>
/// </summary>
String,
/// <summary>
/// <para>The listener will bind to one argument bool functions.</para>
/// </summary>
Bool,
}
int、float、string、bool这些用法类似,例如我想为一个button添加一个GameObject.SetActive的方法监听:
/// <summary>
/// 为Button添加控制UnityEvent bool的方法
/// </summary>
/// <param name="button"></param>
/// <param name="unityEvent"></param>
public static void DynamicAddBoolUnityEvent(Button button,
UnityAction<bool> unityEvent)
{
UnityEventTools.AddBoolPersistentListener(button.onClick, unityEvent,
true);
}
[MenuItem("Test/TestAddBool")]
public static void DoAddBool()
{
Button btn = GameObject.Find("Button").GetComponent<Button>();
DynamicAddUnityEventTools.DynamicAddBoolUnityEvent(btn, btn.gameObject.SetActive);
}
如果想添加不带参数的方法则使用void模式:
/// <summary>
/// 为Button添加控制UnityEvent void的方法
/// </summary>
/// <param name="button"></param>
/// <param name="unityEvent"></param>
public static void DynamicAddVoidUnityEvent(Button button,
UnityAction unityEvent)
{
UnityEventTools.AddVoidPersistentListener(button.onClick, unityEvent);
}
[MenuItem("Test/TestAddVoid")]
public static void DoAddVoid()
{
Button btn = GameObject.Find("Button").GetComponent<Button>();
Test test = btn.GetComponent<Test>();
DynamicAddUnityEventTools.DynamicAddVoidUnityEvent(btn, test.TestDebugVoid);
}
如果想添加某个指定脚本上的指定方法作为监听响应的方法时,则使用Object模式:
/// <summary>
/// 为Button添加脚本中自定义的方法
/// </summary>
/// <param name="button"></param>
/// <param name="callback"></param>
/// <param name="argument"></param>
public static void DynamicAddObjectUnityEvent<T>(Button button, UnityAction<T> callback, T argument)
where T : Object
{
UnityEventTools.AddObjectPersistentListener(button.onClick,
callback, argument);
}
[MenuItem("Test/TestAddObject1")]
public static void DoAddObject1()
{
Button btn = GameObject.Find("Button").GetComponent<Button>();
Test test = btn.GetComponent<Test>();
DynamicAddUnityEventTools.DynamicAddObjectUnityEvent(btn, test.TestDebugAddObject1,
btn.gameObject);
}
[MenuItem("Test/TestAddObject2")]
public static void DoAddObject2()
{
Button btn = GameObject.Find("Button").GetComponent<Button>();
Test test = btn.GetComponent<Test>();
DynamicAddUnityEventTools.DynamicAddObjectUnityEvent(btn, test.TestDebugAddObject2, btn);
}
这里有一个不太合理的地方就是它通过传入的argument参数作为类型的判断依据,所以导致无法在Editor下动态传入参数为None的形式,即它把参数的具体值和参数的类型放到了一个变量中去赋值。
internal void RegisterObjectPersistentListener<T>(int index, UnityAction<T> call, T argument) where T : UnityEngine.Object
{
if (call == null)
throw new ArgumentNullException(nameof (call), "Registering a Listener requires a non null call");
if (!this.ValidateRegistration(call.Method, call.Target, PersistentListenerMode.Object, (UnityEngine.Object) argument == (UnityEngine.Object) null ? typeof (UnityEngine.Object) : argument.GetType()))
return;
this.m_PersistentCalls.RegisterObjectPersistentListener(index, call.Target as UnityEngine.Object, (UnityEngine.Object) argument, call.Method.Name);
this.DirtyPersistentCalls();
}
public void RegisterObjectPersistentListener(
int index,
UnityEngine.Object targetObj,
UnityEngine.Object argument,
string methodName)
{
PersistentCall listener = this.GetListener(index);
listener.RegisterPersistentListener(targetObj, methodName);
listener.mode = PersistentListenerMode.Object;
listener.arguments.unityObjectArgument = argument;
}
public UnityEngine.Object unityObjectArgument
{
get
{
return this.m_ObjectArgument;
}
set
{
this.m_ObjectArgument = value;
this.m_ObjectArgumentAssemblyTypeName = value != (UnityEngine.Object) null ? value.GetType().AssemblyQualifiedName : string.Empty;
}
}
在传入时通过这一行传递值 listener.arguments.unityObjectArgument = argument; 而在对应的set方法里面它的类型是通过获取它的value.GetType().AssemblyQualifiedName来得到的。
最后如果是自己扩展的UI组件它的响应事件带有多个参数,则使用EventDefine类型。
例如扩展的Button组件如下:
using System;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class ButtonExtension : Button
{
[Serializable]
public class ButtonExtensionClickedEvent : UnityEvent<int,int> {}
[SerializeField]
private ButtonExtensionClickedEvent _onExtentionBtnClick = new ButtonExtensionClickedEvent();
public class MyClass
{
public int Value;
}
[Serializable]
public class ButtonExtensionClickedCustomEvent : UnityEvent<int,MyClass> {}
[SerializeField]
private ButtonExtensionClickedCustomEvent _onExtentionBtnCustomParamClick = new ButtonExtensionClickedCustomEvent();
public ButtonExtensionClickedEvent OnExtentionBtnClick => _onExtentionBtnClick;
public ButtonExtensionClickedCustomEvent OnExtentionBtnCustomParamClick => _onExtentionBtnCustomParamClick;
public override void OnPointerClick(PointerEventData eventData)
{
base.OnPointerClick(eventData);
Debug.Log($"On Button PointerClick");
OnExtentionBtnClick?.Invoke(1, 2);
OnExtentionBtnCustomParamClick?.Invoke(1, new MyClass() {Value = 2});
}
}
Editor绘制扩展脚本如下:
using UnityEditor;
using UnityEditor.UI;
[CustomEditor(typeof(ButtonExtension), true)]
[CanEditMultipleObjects]
public class ButtonExtensionEditor : SelectableEditor
{
SerializedProperty _onClickProperty;
SerializedProperty _onExtentionBtnClick;
SerializedProperty _onExtentionBtnCustomClick;
protected override void OnEnable()
{
base.OnEnable();
_onClickProperty = serializedObject.FindProperty("m_OnClick");
_onExtentionBtnClick = serializedObject.FindProperty("_onExtentionBtnClick");
_onExtentionBtnCustomClick = serializedObject.FindProperty("_onExtentionBtnCustomParamClick");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
EditorGUILayout.Space();
serializedObject.Update();
EditorGUILayout.PropertyField(_onClickProperty);
EditorGUILayout.PropertyField(_onExtentionBtnClick);
EditorGUILayout.PropertyField(_onExtentionBtnCustomClick);
serializedObject.ApplyModifiedProperties();
}
}
为它添加监听时则使用:
/// <summary>
/// 为Button添加控制UnityEvent EventDefine的方法
/// </summary>
/// <param name="button"></param>
/// <param name="unityEvent"></param>
public static void DynamicAddEventDefineUnityEvent<T1, T2>(UnityEvent<T1, T2> button,
UnityAction<T1, T2> unityEvent)
{
UnityEventTools.AddPersistentListener(button, unityEvent);
}
[MenuItem("Test/TestAddEventDefine")]
public static void DoAddEventDefine()
{
ButtonExtension btnExt = GameObject.Find("ButtonExtension").GetComponent<ButtonExtension>();
Button btn = GameObject.Find("Button").GetComponent<Button>();
Test test = btn.GetComponent<Test>();
DynamicAddUnityEventTools.DynamicAddEventDefineUnityEvent(btnExt.OnExtentionBtnClick, test.TestDebugEventDefine);
}
[MenuItem("Test/TestAddEventDefineCustom")]
public static void DoAddEventDefineCustom()
{
ButtonExtension btnExt = GameObject.Find("ButtonExtension").GetComponent<ButtonExtension>();
Button btn = GameObject.Find("Button").GetComponent<Button>();
Test test = btn.GetComponent<Test>();
DynamicAddUnityEventTools.DynamicAddEventDefineUnityEvent(btnExt.OnExtentionBtnCustomParamClick,
test.TestDebugEventCustomDefine);
}
如果有其他泛型参数可以自行添加即可。
最后Test脚本只是一些测试方法符合对应的规则即可:
using UnityEngine;
using UnityEngine.UI;
public class Test : MonoBehaviour
{
public void TestDebugAddObject1(GameObject gob)
{
}
public void TestDebugAddObject2(Button btn)
{
}
public void TestDebugVoid()
{
}
public void TestDebugEventDefine(int a, int b)
{
Debug.Log($"The Sum Is {a + b}");
}
public void TestDebugEventCustomDefine(int a, ButtonExtension.MyClass b)
{
Debug.Log($"The Sum Is {a + b.Value}");
}
}
完整工程示意截图如下: