Unity快速入门之一 3D基础概念、Camera、Canvas RenderMode的几种方式对比_翕翕堂
Unity快速入门之二 GUI Transform 详解_翕翕堂
Unity快速入门之三 脚本与事件_翕翕堂
Unity快速入门之四 - Unity模型动画相关_翕翕堂-CSDN博客
资源管理待定……
……
这一篇主要是从代码了,代码内容是纯示例内容,可以有更多更好的思路扩展。
本篇幅会比较长,涉及到比较多的内容,请坐好准备发车~~~~
前置准备就需要自己准备好了:
1、准备 visual studio 2017 IDE。
2、安装 vs for unity 插件。
3、C#语法基础或其他语言基础。
目录
C# Script(脚本)
脚本创建
挂载脚本
前置场景与知识准备
Unity内置事件
Unity按钮(Button)点击(OnClick)事件
文本(Text)触发(Trigger)事件
自定义(Custom)触发(Trigger)事件
自定义事件系统
铺垫
EventManagerUnity(UnityEvent && UnityAction)
EventManagerDelegate
EventManagerFunc
EventManagerAction
EventManagerFunc
事件系统总结
Project视图下,Assets及其子目录,右键菜单->Create->C# Script:
建一个Script文件夹,创建一个C#脚本,默认脚本名字 New Behaviour Script,点击脚本,可以在 Inspector中可以显示脚本内容:
Hierarchy视图中选中一个3D对象,在其Inspector视图中,最下方点击 Add Componet 按钮,输入框中搜索刚才创建的脚本 NewBehaviourScript,就可以看到它了,点击挂在到这个对象上:
先用 vs2017打开这个脚本,双击上方图片中黄色位置即可(需要配置好环境,不展开了):
由于按钮是最通用的控件,从它开始切入。首先创建一个基础场景:
创建一个脚本 ButtonEvent,并放到 button event控件 上,先介绍几个脚本的基本内容:
MonoBehaviour:Unity对象的基础类,里面拥有很完整的生命周期,继承于这个类的脚本都可以当成组件挂载到具体的对象身上。
void Start():首次加载以及对象再次激活的时候会被调用,这次我们只要用到它就好了。
gameObject:此脚本组件说挂载的对象,由于我们要使用Button及其点击事件,Button也是一个独立组件,要由它来获取。
流程上:
按钮点击函数上代码实现方式,常见的四种:
这些都是基于C#语法的,语法规则就不做展开了。
下面开始说 Button怎么绑定点击响应事件,首先,保留脚本最简形式:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ButtonEvent : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
}
增加一个响应我们点击事件的成员函数作为处理函数:
public class ButtonEvent : MonoBehaviour
{
void Start()...
public void OnClickFunction00()
{
Debug.Log("AddListener - 委托成员函数 - OnClickFunction00");
}
}
在Start中找到我们要的按钮,并将处理函数绑定到按钮上:
// 使用控件需要添加的命名空间
using UnityEngine.UI;
public class ButtonEvent : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
// AddListener - 成员函数 - OnClickFunction
gameObject.GetComponent
由于使用了Butoon,这个属于 UnityEngine.UI 之下,所以开头要使用 using UnityEngine.UI。
获取组件通过 gameObject.GetComponent
也可以看下它的实现,我们点开Button看看:
可以看到Unity的UI Button,有一个OnClick,来自于ButtonClickedEvent,而它又来自于UnityEvent,另外Button继承于几个xxxHandler 接口。
我们继续点开UnityEvent看看:
看到 Reflection、MethodInfo、Invoke,大概就知道,这东西是靠反射来实现的,这里就不继续往 UnityEventBase展开了,但是可以给没接触过反射的朋友大概说下,反射(Reflection)就是通过拿到目标类及其类下所有的变量、函数等信息,做额外存储,在运行时可以通个这些额外存储的信息以及类的实例对象进行调用,但是需要占用额外内存,性能也比较低。
好了,我们继续。
它的目标函数怎么绑定的,这个很明显:public void AddListener(UnityAction call);
那么UnityAction是什么玩意儿,点进去看看:public delegate void UnityAction();
一个委托(Delegate),类似于C++的函数指针形式,但是使用的时候实际上是对象。
掌握了这些信息,我们可以继续拓展我们的事件了。(对于上面 反射、委托不懂想要深究的,可以找其他资料进行了解)
首先看下上面的结果:
没毛病,点击按钮,确实输出了我们需要的消息。既然 UnityAction 是个 delegate,那么我们应该可以直接使用委托匿名函数来作为处理函数。
{
// Start is called before the first frame update
void Start()
{
...
// AddListener - 委托匿名函数 - delegate
gameObject.GetComponent
也可以使用委托实例,这里显示的调用了 UnityAction,需要增加 using UnityEngine.Events:
// 使用UnityAction需要增加这个
using UnityEngine.Events
public class ButtonEvent : MonoBehaviour{
// Start is called before the first frame update
void Start()
{
...
// AddListener - 委托实例 - OnClickFunction01
gameObject.GetComponent
委托lambda:
{
// Start is called before the first frame update
void Start()
{
...
// AddListener - 委托lambda表达式 - lambda
gameObject.GetComponent
由于UnityAction是委托,所以可以不调用AddListener,直接使用多路广播,最后一次性绑定:
{
//创建UnityAction成员变量
public UnityAction m_untiyAction;
// Start is called before the first frame update
void Start()
{
// 多路广播,委托函数
m_untiyAction += OnClickFunction01;
// 委托匿名函数
m_untiyAction += delegate
{
Debug.Log("多路广播 - 匿名函数委托 - delegate");
};
// 委托实例 - OnClickFunction01
m_untiyAction += new UnityAction(OnClickFunction01);
// 委托lambda表达式
m_untiyAction += () => { Debug.Log("多路广播 - lambda表达式委托 - lambda"); };
// AddListener - UnityAction - delegate
// 将UnityAction绑定到AddListener
gameObject.GetComponent
放个完整的ButtonEvent代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
public class ButtonEvent : MonoBehaviour
{
//https://docs.unity3d.com/Manual/UnityEvents.html
public UnityAction m_untiyAction;
// Start is called before the first frame update
void Start()
{
// AddListener - 成员函数 - OnClickFunction
gameObject.GetComponent
上面已经讲了Button,但是如果文本我们也需要处理事件,可以按照上面的方法去查看,可以发现,是不存在类似于OnClick类似事件的。这里可以使用 EventTrigger:
Event Trigger | Unity UI | 1.0.0https://docs.unity3d.com/Packages/[email protected]/manual/script-EventTrigger.html创建一个TextEvent脚本,挂载到 text event 文本控件上,直接放代码,可以看完后面解释再回过来看代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
public class TextEvent : MonoBehaviour
{
//https://docs.unity3d.com/Packages/[email protected]/manual/script-EventTrigger.html
// Start is called before the first frame update
void Start()
{
// 创建事件触发的响应实例
EventTrigger.Entry entry = new EventTrigger.Entry();
entry.eventID = EventTriggerType.PointerClick;
// 添加事件响应的处理函数到响应实例,PS:此处也是AddListener,可以参考ButtonEvent,用多种方式注册监听函数
entry.callback.AddListener(delegate (BaseEventData data)
{
Debug.Log("文本被点击了 delegate");
});
entry.callback.AddListener(data =>
{
Debug.Log("文本被点击了 lambda");
});
// 给需要监听事件的对象,添加事件触发组件才能监听到事件
EventTrigger trigger = gameObject.AddComponent();
// 将响应实例添加到监听触发组件,PS:可以添加多个,比如下面重复加了两次,就触发两次。
trigger.triggers.Add(entry);
trigger.triggers.Add(entry);
}
}
可以看到,通过 EventTrigger.Entry 取代了 ButtonEvent 中的 Button 来作为事件绑定的宿主。
看下结果:
同样的我们可以进入到EventTrigger中看看:
可以看到,这里提示 delegates不要用了,推荐使用下面的triggers,而triggers是个Entry的List。
Entry又是由触发类型和触发事件组成,最后,触发事件又是来自于UnityEvent。
所以实质上用法是一样的,可以看到EventTrigger也是继承于 XXXHandler。
其他UI控件的响应就不做展开了,因为可以通过上面类似的办法处理,下面说说自定义触发事件,通过观察Button和EventTrriger,可以看到,都是通过UnityEvent、XXXHandler的方式实现的,所以我们自己来实现一个。
XXXHandler有哪些呢,官方链接:
Supported Events | Unity UI | 1.0.0https://docs.unity3d.com/Packages/[email protected]/manual/SupportedEvents.html同样的创建一个脚本 CustomTrigger,直接先放代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
// 参考EventTrriget,里面已经列下了大部分Unity : https://docs.unity3d.com/Packages/[email protected]/manual/script-EventTrigger.html
// 17种可以自定义扩展实现的事件:https://docs.unity3d.com/Packages/[email protected]/manual/SupportedEvents.html
public class CustomEventTrigger : MonoBehaviour, IPointerEnterHandler, IEventSystemHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler, IPointerClickHandler, IInitializePotentialDragHandler, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler, IScrollHandler, IUpdateSelectedHandler, ISelectHandler, IDeselectHandler, IMoveHandler, ISubmitHandler, ICancelHandler
{
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("\n1 CustomEventTrigger OnPointerClick--->"+ eventData.ToString());
}
public void OnPointerEnter(PointerEventData eventData)
{
Debug.Log("\n2 CustomEventTrigger OnPointerEnter:--->" + eventData.ToString());
}
public void OnPointerExit(PointerEventData eventData)
{
Debug.Log("\n3 CustomEventTrigger OnPointerExit:--->" + eventData.ToString());
}
public void OnPointerDown(PointerEventData eventData)
{
Debug.Log("\n4 CustomEventTrigger OnPointerDown:--->" + eventData.ToString());
}
public void OnPointerUp(PointerEventData eventData)
{
Debug.Log("\n5 CustomEventTrigger OnPointerUp:--->" + eventData.ToString());
}
public void OnInitializePotentialDrag(PointerEventData eventData)
{
Debug.Log("\n6 CustomEventTrigger OnInitializePotentialDrag:--->" + eventData.ToString());
}
public void OnBeginDrag(PointerEventData eventData)
{
Debug.Log("\n7 CustomEventTrigger OnBeginDrag:--->" + eventData.ToString());
}
private int _countDrag;
public void OnDrag(PointerEventData eventData)
{
if (_countDrag == 0)
Debug.Log("\n8 CustomEventTrigger OnDrag:--->" + eventData.ToString());
++_countDrag;
//if (++_countDrag == 15)
// _countDrag = 0;
}
public void OnEndDrag(PointerEventData eventData)
{
Debug.Log("\n9 CustomEventTrigger OnEndDrag:--->" + eventData.ToString());
}
public void OnDrop(PointerEventData eventData)
{
Debug.Log("\n10 CustomEventTrigger OnDrop--->" + eventData.ToString());
}
public void OnScroll(PointerEventData eventData)
{
Debug.Log("\n11 CustomEventTrigger OnScroll:--->" + eventData.ToString());
}
private int _countSelectedUpdate;
public void OnUpdateSelected(BaseEventData eventData)
{
if (_countSelectedUpdate == 0)
Debug.Log("\n12 CustomEventTrigger OnUpdateSelected:--->" + eventData.ToString());
++_countSelectedUpdate;
//if (++_countSelectedUpdate == 1000)
// _countSelectedUpdate = 0;
}
public void OnSelect(BaseEventData eventData)
{
Debug.Log("\n13 CustomEventTrigger OnSelect:--->" + eventData.ToString());
}
public void OnDeselect(BaseEventData eventData)
{
Debug.Log("\n14 CustomEventTrigger OnDeselect:--->" + eventData.ToString());
}
public void OnMove(AxisEventData eventData)
{
Debug.Log("\n15 CustomEventTrigger OnMove:--->" + eventData.ToString()
//+ ":currentInputModule(" + eventData.currentInputModule.ToString()
+ "),moveDir(" + eventData.moveDir.ToString()
+ "),moveVector(" + eventData.moveVector.ToString()
+ "),selectedObject(" + eventData.selectedObject.ToString());
}
public void OnSubmit(BaseEventData eventData)
{
Debug.Log("\n16 CustomEventTrigger OnSubmit:--->" + eventData.ToString());
}
public void OnCancel(BaseEventData eventData)
{
Debug.Log("\n17 CustomEventTrigger OnCancel:--->" + eventData.ToString());
}
// EventTrigger中做了封装,这里我们直接拆分实现
public class BaseEventTrigger : UnityEvent { }
BaseEventTrigger baseEventTrigger = new BaseEventTrigger();
public class PointEventTrigger : UnityEvent { }
PointEventTrigger pointEventTrigger = new PointEventTrigger();
public class AxisEventTrigger : UnityEvent { }
AxisEventTrigger axisEventTrigger = new AxisEventTrigger();
// Start is called before the first frame update
void Start()
{
pointEventTrigger.AddListener(OnPointerClick);
pointEventTrigger.AddListener(OnPointerEnter);
pointEventTrigger.AddListener(OnPointerExit);
pointEventTrigger.AddListener(OnPointerDown);
pointEventTrigger.AddListener(OnPointerUp);
pointEventTrigger.AddListener(OnInitializePotentialDrag);
pointEventTrigger.AddListener(OnBeginDrag);
pointEventTrigger.AddListener(OnDrag);
pointEventTrigger.AddListener(OnEndDrag);
pointEventTrigger.AddListener(OnDrop);
pointEventTrigger.AddListener(OnScroll);
pointEventTrigger.AddListener(OnUpdateSelected);
pointEventTrigger.AddListener(OnSelect);
pointEventTrigger.AddListener(OnDeselect);
axisEventTrigger.AddListener(OnMove);
pointEventTrigger.AddListener(OnSubmit);
pointEventTrigger.AddListener(OnCancel);
EventSystem.current.SetSelectedGameObject(gameObject);
}
}
目前来说共有17中XXXHandler,需要挨个实现,而UI能直接响应的是:
移入、移出、点击、按下、抬起、初始化拖动、开始拖动、拖动、结束拖动、拖入、滑动。
而下面的 更新选中、选中、移动、提交、取消 这些我们需要先设置下对象,使用:
EventSystem.current.SetSelectedGameObject(gameObject);
PS:可以试试将这个脚本挂在到之前创建的Cube上,可以看到Cube对选中到取消相关的事件也能产生反应。
上面的大部分都是使用Unity内置的,对UGUI有用,但是我们的事件通知触发这种形式绝不限于UI的使用,所以需要对事件有个统一管理的地方。这一部分的目的,是为了熟悉C#常用的几个主要语法和事件系统设计思路。
四个主要部分:
实现方式,有很多种,此篇介绍四种:
使用设计模式:
事件ID定义:EVENT_ID
// 事件注册ID
public enum EVENT_ID
{
ED_UNKNOW,
ED_NO_PARAM_NO_RETURN, // 无参无返回值事件
ED_PARAM_NO_RETURN, // 有参无返回值事件
ED_NO_PARAM_RETURN, // 无参有返回值事件
ED_PARAM_RETURN, // 有参有返回值事件
}
场景搭建
事件系统:EventMangerUnity.cs
由于UnityAction是 public delegate void UnityAction
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
public class EventManagerUnity
{
private static EventManagerUnity me = new EventManagerUnity();
public static EventManagerUnity GetMe()
{
return me;
}
// 有参无返回值事件
Dictionary> dataEventTriggers = new Dictionary>();
// 注册事件
public void RegEvent(EVENT_ID id, UnityAction
响应者:Sample_02_Unity_Cube01.c
这里使用lambda来实现事件处理。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sample_02_Unity_Cube01 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
EventManagerUnity.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN,
(object[] data) =>
{
Debug.Log("响应 无参无返回值事件");
});
EventManagerUnity.GetMe().RegEvent(EVENT_ID.ED_PARAM_NO_RETURN,
(object[] data) =>
{
Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", data[0], data[1]));
});
}
}
发起者:Sample_02_Unity_UI.cs
由于我们发起者的脚本是挂载Canvas上的,所以这里需要使用 gameObject.transform.Find("控件名称") 来找到对应的子按钮控件。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
public class Sample_02_Unity_UI : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
gameObject.transform.Find("点击发送Cube01事件1").GetComponent().onClick.AddListener(
()=>
{
Debug.Log("点击了【点击发送Cube01事件1】");
EventManagerUnity.GetMe().SendEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
});
gameObject.transform.Find("点击发送Cube01事件2").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件2】");
EventManagerUnity.GetMe().SendEvent(EVENT_ID.ED_PARAM_NO_RETURN, "Val Input", 1);
});
gameObject.transform.Find("销毁Cube01点击事件").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【销毁Cube01点击事件】");
EventManagerUnity.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
EventManagerUnity.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_NO_RETURN);
});
}
}
点击测试
依次点击 点击发送Cube01事件1、点击发送Cube01事件2、销毁Cube01点击事件。
然后再来各点击一次。查看输出结果:
结果很完全符合逻辑,在反注册后,按钮也只响应了点击,并没有触发事件。
场景搭建
事件系统:EventManagerDelegate.cs
这里我们考虑通知和调用的区分。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EventManagerDelegate
{
private static EventManagerDelegate me = new EventManagerDelegate();
public static EventManagerDelegate GetMe()
{
return me;
}
public delegate object DataEventFunctionRet(object[] data);
Dictionary dataEventTriggersRet = new Dictionary();
public void RegEvent(EVENT_ID id, DataEventFunctionRet action, bool isSend = true)
{
if (isSend)
{
if (!dataEventTriggersRet.ContainsKey(id))
dataEventTriggersRet.Add(id, new DataEventFunctionRet(action));
else
dataEventTriggersRet[id] += action;
Debug.Log(string.Format("注册 {0} 到 sendEventTriggersRet send:", id.ToString()));
}
else
{
if (!dataEventTriggersRet.ContainsKey(id))
dataEventTriggersRet.Add(id, new DataEventFunctionRet(action));
else
dataEventTriggersRet[id] = action;
Debug.Log(string.Format("注册 {0} 到 dataEventTriggersRet invoke:", id.ToString()));
}
}
public void UnRegEvent(EVENT_ID id)
{
if (dataEventTriggersRet.ContainsKey(id))
{
dataEventTriggersRet.Remove(id);
Debug.Log(string.Format("从 dataEventTriggersRet 反注册 {0}:", id.ToString()));
}
}
// 触发事件
public void SendEvent(EVENT_ID id, params object[] values)
{
if (dataEventTriggersRet.ContainsKey(id))
{
if (dataEventTriggersRet.ContainsKey(id))
{
Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0}:", id.ToString()));
dataEventTriggersRet[id].Invoke(values);
}
}
}
// 使用代理可以不用区分输入和输出数据结构,用一个函数既可以发送事件,也可以得到返回值
public object Invoke(EVENT_ID id, params object[] values)
{
if (dataEventTriggersRet.ContainsKey(id))
{
Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0}:", id.ToString()));
return dataEventTriggersRet[id].Invoke(values);
}
return null;
}
}
响应者:Sample_02_Delegate_Cube01.c
这里ED_NO_PARAM_NO_RETURN事件注册了两次来示例。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sample_02_Delegate_Cube01 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
EventManagerDelegate.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN,
(object[] data) =>
{
Debug.Log("响应 无参无返回值事件01");
return null;
});
EventManagerDelegate.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN,
(object[] data) =>
{
Debug.Log("响应 无参无返回值事件02");
return null;
});
EventManagerDelegate.GetMe().RegEvent(EVENT_ID.ED_PARAM_NO_RETURN,
(object[] data) =>
{
Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", data[0], data[1]));
return null;
});
EventManagerDelegate.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_RETURN,
(object[] data) =>
{
Debug.Log(string.Format("响应 无参有返回值【{0}】事件", "Ret Output"));
return "Ret Output";
}, false);
EventManagerDelegate.GetMe().RegEvent(EVENT_ID.ED_PARAM_RETURN,
(object[] data) =>
{
Debug.Log(string.Format("响应 有参【{0},{1}】有返回值【{2}】事件", data[0], data[1], "Ret Output"));
return "Ret Output";
}, false);
}
}
发起者:Sample_02_Delegate_UI.cs
需要注意这里Invoke时候的返回结果也需要处理打印出来。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
public class Sample_02_Delegate_UI : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
gameObject.transform.Find("点击发送Cube01事件1").GetComponent().onClick.AddListener(
()=>
{
Debug.Log("点击了【点击发送Cube01事件1】");
EventManagerDelegate.GetMe().SendEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
});
gameObject.transform.Find("点击发送Cube01事件2").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件2】");
EventManagerDelegate.GetMe().SendEvent(EVENT_ID.ED_PARAM_NO_RETURN, "Val input", 1);
});
gameObject.transform.Find("点击发送Cube01事件3").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件3】");
Debug.Log(string.Format("收到无参有返回值调用结果:{0}", EventManagerDelegate.GetMe().Invoke(EVENT_ID.ED_NO_PARAM_RETURN)));
});
gameObject.transform.Find("点击发送Cube01事件4").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件4】");
Debug.Log(string.Format("收到有参有返回值调用结果:{0}", EventManagerDelegate.GetMe().Invoke(EVENT_ID.ED_PARAM_RETURN, "Val input", 2)));
});
gameObject.transform.Find("销毁Cube01点击事件").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【销毁Cube01点击事件】");
EventManagerDelegate.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
EventManagerDelegate.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_NO_RETURN);
EventManagerDelegate.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_RETURN);
EventManagerDelegate.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_RETURN);
});
}
}
点击测试
依次点击 点击发送Cube01事件1、点击发送Cube01事件2、点击发送Cube01事件3、点击发送Cube01事件4、销毁Cube01点击事件。
然后再来各点击一次。查看输出结果:
场景搭建(同 EventManagerDelegate)
事件系统:EventManagerFunc.cs(同 EventManagerDelegate)
除了 dataEventTriggersRet 由于语法的不同,会导致流程上模板Func与委托(Delegate)会有微弱的不同,其余一样。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
// Func是包含返回值的,可以和delegate差不多
public class EventManagerFunc
{
private static EventManagerFunc me = new EventManagerFunc();
public static EventManagerFunc GetMe()
{
return me;
}
// public delegate TResult Func(T arg);
Dictionary> dataEventTriggersRet = new Dictionary>();
public void RegEvent(EVENT_ID id, Func func, bool isSend = true)
{
if (isSend)
{
if (!dataEventTriggersRet.ContainsKey(id))
dataEventTriggersRet.Add(id, new Func(func));
else
dataEventTriggersRet[id] += func;
Debug.Log(string.Format("注册 {0} 到 sendEventTriggersRet send:", id.ToString()));
}
else
{
if (!dataEventTriggersRet.ContainsKey(id))
dataEventTriggersRet.Add(id, new Func(func));
else
dataEventTriggersRet[id] = func;
Debug.Log(string.Format("注册 {0} 到 dataEventTriggersRet invoke:", id.ToString()));
}
}
public void UnRegEvent(EVENT_ID id)
{
if (dataEventTriggersRet.ContainsKey(id))
{
dataEventTriggersRet.Remove(id);
Debug.Log(string.Format("从 dataEventTriggersRet 反注册 {0}:", id.ToString()));
}
}
// 触发事件
public void SendEvent(EVENT_ID id, params object[] values)
{
if (dataEventTriggersRet.ContainsKey(id))
{
Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0}:", id.ToString()));
dataEventTriggersRet[id].Invoke(values);
}
}
// 使用代理可以不用区分输入和输出数据结构,用一个函数既可以发送事件,也可以得到返回值
public object Invoke(EVENT_ID id, params object[] values)
{
if (dataEventTriggersRet.ContainsKey(id))
{
Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0}:", id.ToString()));
return dataEventTriggersRet[id].Invoke(values);
}
return null;
}
}
响应者:Sample_02_F_A_Cube01.c
与 EventManagerDelegate 使用完全一致:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sample_02_F_A_Cube01 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
EventManagerFunc.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN,
(object[] data) =>
{
Debug.Log("响应 无参无返回值事件");
return null;
});
EventManagerFunc.GetMe().RegEvent(EVENT_ID.ED_PARAM_NO_RETURN,
(object[] data) =>
{
Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", data[0], data[1]));
return null;
});
EventManagerFunc.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_RETURN,
(object[] data) =>
{
Debug.Log(string.Format("响应 无参有返回值【{0}】事件", "Ret Output"));
return "Ret Output";
}, false);
EventManagerFunc.GetMe().RegEvent(EVENT_ID.ED_PARAM_RETURN,
(object[] data) =>
{
Debug.Log(string.Format("响应 有参【{0},{1}】有返回值【{2}】事件", data[0], data[1], "Ret Output"));
return "Ret Output";
}, false);
}
}
发起者:Sample_02_F_A_UI.cs
与 EventManagerDelegate 使用完全一致:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
public class Sample_02_F_A_UI : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
gameObject.transform.Find("点击发送Cube01事件1").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件1】");
EventManagerFunc.GetMe().SendEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
});
gameObject.transform.Find("点击发送Cube01事件2").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件2】");
EventManagerFunc.GetMe().SendEvent(EVENT_ID.ED_PARAM_NO_RETURN, "Val input", 1);
});
gameObject.transform.Find("点击发送Cube01事件3").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件3】");
Debug.Log(string.Format("收到无参有返回值调用结果:{0}", EventManagerFunc.GetMe().Invoke(EVENT_ID.ED_NO_PARAM_RETURN)));
});
gameObject.transform.Find("点击发送Cube01事件4").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件4】");
Debug.Log(string.Format("收到有参有返回值调用结果:{0}", EventManagerFunc.GetMe().Invoke(EVENT_ID.ED_PARAM_RETURN, "Val input", 2)));
});
gameObject.transform.Find("销毁Cube01点击事件").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【销毁Cube01点击事件】");
EventManagerFunc.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
EventManagerFunc.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_NO_RETURN);
EventManagerFunc.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_RETURN);
EventManagerFunc.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_RETURN);
});
}
}
点击测试
与 EventManagerDelegate 使用完全一致:
模板Action与Func用法完全一样,不同的是,Func模板最后一个参数是返回值,而Action没有返回值:
Func:public delegate TResult Func
Action:public delegate void Action
由于没有返回值只有Send,不详述了,按照UnityAction只写个EventManagerAction示例:
// Action基本与Func雷同,只是Action不能有返回值,如果扩展的话可以像UnityAction一样
public class EventManagerAction
{
private static EventManagerAction me = new EventManagerAction();
public static EventManagerAction GetMe()
{
return me;
}
// public delegate void Action(T obj);
Dictionary> dataEventTriggers;
public void RegEvent(EVENT_ID id, Action action)
{
if (!dataEventTriggers.ContainsKey(id))
dataEventTriggers.Add(id, action);
dataEventTriggers[id] += action;
}
public void UnRegEvent(EVENT_ID id)
{
if (dataEventTriggers.ContainsKey(id))
{
dataEventTriggers.Remove(id);
return;
}
}
public void SendEvent(EVENT_ID id, params object[] values)
{
if (dataEventTriggers.ContainsKey(id))
dataEventTriggers[id].Invoke(values);
}
}
场景搭建(同 EventManagerDelegate)
事件系统:EventManagerReflection.cs
这里增加了 RegEvent 和 MethodData,并且事件注册其实改为了对象注册。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Reflection;
using System.Text;
// 自定义特性
public class RegEvent : Attribute
{
public RegEvent(EVENT_ID id, bool isSend)
{
_id = id;
_isSend = isSend;
}
public EVENT_ID _id;
public bool _isSend;
}
public class EventManagerReflection
{
// 内部管理的函数数据,用于记录注册的函数和对象
private class MethodData
{
public MethodData(MethodInfo method, object obj)
{
_method = method;
_obj = obj;
}
public MethodInfo _method;
public object _obj;
}
private static EventManagerReflection me = new EventManagerReflection();
public static EventManagerReflection GetMe()
{
return me;
}
// public delegate void Action();
Dictionary> dataEventTriggersRet = new Dictionary>();
public void RegEvent(object obj)
{
Type type = obj.GetType();
MethodInfo[] methodInfos = type.GetMethods();
foreach (MethodInfo method in methodInfos)
{
RegEvent reg = method.GetCustomAttribute();
if (reg != null)
{
if (!dataEventTriggersRet.ContainsKey(reg._id))
dataEventTriggersRet.Add(reg._id, new List());
if (reg._isSend)
{
dataEventTriggersRet[reg._id].Add(new MethodData(method, obj));
Debug.Log(string.Format("注册 {0} 到 dataEventTriggersRet send:", reg._id.ToString()));
}
else
{
dataEventTriggersRet[reg._id].Clear();
dataEventTriggersRet[reg._id].Add(new MethodData(method, obj));
Debug.Log(string.Format("注册 {0} 到 dataEventTriggersRet invoke:", reg._id.ToString()));
}
}
}
}
public void UnRegEvent(EVENT_ID id)
{
if (dataEventTriggersRet.ContainsKey(id))
{
dataEventTriggersRet.Remove(id);
Debug.Log(string.Format("从 dataEventTriggersRet 反注册 {0}:", id.ToString()));
return;
}
}
public object SendEvent(EVENT_ID id, params object[] values)
{
if (dataEventTriggersRet.ContainsKey(id))
{
foreach (MethodData methodData in dataEventTriggersRet[id])
{
Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0} 事件的 {1} 函数:", id.ToString(), methodData._method.Name));
methodData._method.Invoke(methodData._obj, new object[] { values });
}
}
return null;
}
public object Invoke(EVENT_ID id, params object[] values)
{
if (dataEventTriggersRet.ContainsKey(id))
{
MethodData methodData = dataEventTriggersRet[id][0];
Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0} 事件的 {1} 函数:", id.ToString(), methodData._method.Name));
return methodData._method.Invoke(methodData._obj, new object[] { values });
}
return null;
}
}
响应者:Sample_02_Reflection_Cube01.c
与 EventManagerDelegate 使用完全一致:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
public class Sample_02_Reflection_UI : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
gameObject.transform.Find("点击发送Cube01事件1").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件1】");
EventManagerReflection.GetMe().SendEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
});
gameObject.transform.Find("点击发送Cube01事件2").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件2】");
EventManagerReflection.GetMe().SendEvent(EVENT_ID.ED_PARAM_NO_RETURN, "Val input", 1);
});
gameObject.transform.Find("点击发送Cube01事件3").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件3】");
Debug.Log(string.Format("收到无参有返回值调用结果:{0}", EventManagerReflection.GetMe().Invoke(EVENT_ID.ED_NO_PARAM_RETURN)));
});
gameObject.transform.Find("点击发送Cube01事件4").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【点击发送Cube01事件4】");
Debug.Log(string.Format("收到有参有返回值调用结果:{0}", EventManagerReflection.GetMe().Invoke(EVENT_ID.ED_PARAM_RETURN, "Val input", 2)));
});
gameObject.transform.Find("销毁Cube01点击事件").GetComponent().onClick.AddListener(
() =>
{
Debug.Log("点击了【销毁Cube01点击事件】");
EventManagerReflection.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
EventManagerReflection.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_NO_RETURN);
EventManagerReflection.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_RETURN);
EventManagerReflection.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_RETURN);
});
}
}
发起者:Sample_02_Reflection_UI.cs
这里与前面的差距就很大了:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sample_02_Reflection_Cube01 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
EventManagerReflection.GetMe().RegEvent(this);
}
[RegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN, true)]
public object OnHandleClick01(object[] data)
{
Debug.Log("响应 无参无返回值事件");
return null;
}
[RegEvent(EVENT_ID.ED_PARAM_NO_RETURN, true)]
public object OnHandleClick02_0(object[] data)
{
Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", data[0], data[1]));
return null;
}
[RegEvent(EVENT_ID.ED_NO_PARAM_RETURN, false)]
public object OnHandleClick03(object[] data)
{
Debug.Log(string.Format("响应 无参有返回值【{0}】事件", "Ret Output"));
return "Ret Output";
}
[RegEvent(EVENT_ID.ED_PARAM_RETURN, false)]
public object OnHandleClick04(object[] data)
{
Debug.Log(string.Format("响应 有参【{0},{1}】有返回值【{2}】事件", data[0], data[1], "Ret Output"));
return "Ret Output";
}
}
点击测试
问题记录:可变形参的使用考虑
/* 使用反射时遇到的可变形参处理问题记录:
*
[RegEvent(EVENT_ID.ED_PARAM_NO_RETURN)]
public object OnHandleClick02(object[] data)
{
Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", data[0], data[1]));
return null;
}
// 这里传入的是 1个string和1个int
gameObject.transform.Find("点击发送Cub01事件2").GetComponent().onClick.AddListener(
() => EventManagerReflection.GetMe().Invoke(EVENT_ID.ED_PARAM_NO_RETURN, "Val input", 1)
);
public object Invoke(EVENT_ID id, params object[] values)
{
……
// 这里会报 TargetParameterCountException: Number of parameters specified does not match the expected number.
return methodData._method.Invoke(methodData._obj, values);
……
}
直接使用values会报错,找到一个答案:https://stackoverflow.com/questions/61855500/targetparametercountexception-c-sharp
I you write: object[] Parms = new object[] { "oiad", "abdj", "i" };
that means the args of method invo are: public void invo(string s1, string s2, string s3)
if you have public void invo(object[] per)
you have to write object[] Parms = new object[] { new object[]{ "oiad", "abdj", "i"}};
所以有两种解决办法:
1、修改响应函数形参为:
[RegEvent(EVENT_ID.ED_PARAM_NO_RETURN, true)]
public object OnHandleClick02_1(string datas, int datai)
{
Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", datas, datai));
return null;
}
2、修改调用方法为:
public object Invoke(EVENT_ID id, params object[] values)
{
……
return methodData._method.Invoke(methodData._obj, new object[] { values });
……
}
* */
UnityEvent && UnityAction:
反射:内部实现使用的反射,不适合频繁的注册与反注册,内存和性能都比较低下。
模板:由于UnityEvent和UnityAction都是走模板,所以扩展比较呆板:
形参:想做到参数动态可变,需要特出处理传回的参数,比如object[]。
返回值:固定的void返回值,需要处理带返回值的函数,不是特别方便。
Delegate:
代理:代理本身性能要高于反射,类似于指针传递,也不用额外的反射数据。
形参:由于delegate也需要事先申明,需要特出处理传回的参数,比如object[]。
返回值:由于是自定义,也可以使用object来做到返回的通用性。
Func || Action:
Func和Delegate使用方式几乎可以做到一摸一样。
Action与Func相同,只是没有返回值。
Reflection:
反射:与UnityEvent反射问题雷同。
返回值:由于是自定义,也可以使用object来做到返回的通用性。
总结来说,UnityEvent和Reflection适合于Editor,delegate和Func适合于Runtime。