目录

前言

1. 处理玩家输入

EventSystem的作用

2.封装处理结果

3.传递包装好的数据(BaseEventData)

4.响应玩家输入


前言

在Unity场景中创建一个Canvas,可以发现,编辑器自动为我们创建了一个叫EventSystem的东西,我们可以发现这个EventSystem中默认包含两个组件:EventSystem和StandaloneInputModule,你可能想知道这两个东西是干啥的?没有它们不行么?它们工作的原理是什么?为什么点击一个按钮就可以触发其onClick事件?这个过程是什么?本文并不会教你怎么去制作UI界面、如何编写UI逻辑,而是刨一刨它背后的事件机制,带您一起揭露UGUI的神秘面纱,让您对UGUI的工作原理有一个更加深刻的认识。

UGUI全称即Unity Graphical User Interface(Unity图形用户交互系统),用户交互界面(UI)是游戏开发过程中不可忽略的一个部分,通过它可以接收玩家输入,影响游戏运行内容,并向玩家传递信息和反馈视觉效果。

概括来讲,就是它的工作流程大致是这样的:

  1. 处理玩家输入
  2. 把处理结果包装起来
  3. 把包装好的数据传给界面(其实就是一个函数传参)
  4. 响应玩家输入(例如监听button的onClick)

1. 处理玩家输入

InputModule便是负责处理玩家输入,玩家的输入分为好多种类型,键盘输入、鼠标输入、触屏输入等等,根据平台的不同输入形式也有所不同。

对于UI来说键盘输入大多数情况是用于Submit 和 Navigation,所以本篇文章还是以鼠标输入为主展开。

除了Unity提供的StandaloneInputModule和TouchInputModule之外,我们也可以通过泛化BaseInputModule来自定义InputModule

处理的过程其实就是重写父类的Process函数,在其内部对鼠标光标的各种状态进行计算和标记。

详细展开来看这个处理过程做了哪些事情?

  • 射线检测:EventSystem中的一个方法,以光标所在的屏幕坐标为起点向UI投一条射线,检查射线所穿过的所有UI控件(UI是分层的哦,在顶层的当然是先被照射的,当然免不了有些UI控间是忽略射线检测的),并提取其中第一个照射到的控件。
  • 检查鼠标按键的状态:鼠标左键、中键、右键
  • 处理鼠标按下(Press)、移动(进入(Enter)了哪个控件了又离开(Exit)哪个控件了)、拖拽(Drag),鼠标的三个按键都要做一下(移动的部分在处理鼠标中键和右键的时候不需要了)。

下面这幅图简单概括了一下EventSystem相关的UML类图:

EventSystem的作用

  • 管理游戏中的InputModule
  • 驱动InputModule的Update和Process
  • 做射线检查 和 射线结果的层级比较
  • ......

如果把InputModule比喻成车轮子,那么EventSystem就是发动机,发动机不转,轮子怎么能跑呢?别告诉我用手推。

2.封装处理结果

无论是哪一种InputModule,它们的共同目标便是将用户输入封装成一个EventData,其实在处理的过程中就顺带把要封装的封装了,而不是处理完了之后再统一封装,因为有时候后面的处理步骤是需要前面的步骤的处理结果的。

EventData的UML类图如下:

我们着重看一下PointerEventData, PointerEventData中封装了鼠标数据,如:

  • 当前光标指向的是哪个物体  - pointerPress
  • 是否正在拖拽 - dragging
  • 点击次数(双击的时候是2) - clickCount
  • 按下时坐标 - pressPosition
  • 当前坐标 - position
  • ......

Q&A

1. Unity如何知道光标指向了哪个控体?

答:一条射线打上去,看先射到了哪个,Raycaster就是干这个事的

2. 如何判断正在拖拽?

答:因为已经记录了按下时的坐标,又知道当前坐标,如果二者之差大于拖拽的阈值,则认为在拖拽,这个是在PointerInputModule里判断的。

3.传递包装好的数据(BaseEventData)

现在你知道Unity里通过 InputModule 把用户的输入处理完之后包装到BaseEventData里,那么接下来呢?这个EventData怎么处理?

答案当然是要传递给我们的UI组件了,然而如何传递呢?

我们知道,鼠标光标事件其实是多种多样的,比如:光标按下(Down)、抬起(Up)、进入(Enter)、离开(Exit),如果按下和抬起的时间很短又可以产生点击事件(Click),对于每一种事件都可以定义为一个接口,例如:IPointerDownHandler、IPointerUpHandler、IPointerEnterHandler、IPointerExitHandler、IPointerClickHandler,为了统一操作这些接口,我们让它们都继承自IEventSystemHandler。

不同的UI组件可以选择性的支持这些鼠标事件,比如有的控件我们就是不希望它响应点击事件,那么不让它实现IPointerClickHandler的接口就行了

这就有了下面这个类图:

注:上面这张图因画面仅提供了部分Pointer相关的几个接口,除此之外还有很多,可以参考源码中的IEventSystemHandler

理论上来讲,既然我们(通过raycaster)已经知道了光标落在了哪一个GameObject上,那么我们就可以调用该GameObject所实现的任意一个接口的接口函数。

因此举例,既然Button实现了IPointerClickHandler接口,那么对于一个按钮控件来说,当光标点击事件到来时,就可以调用到它的Button组件(as IPointerClickHandler)的OnPointerClick函数。

上面虽然只用一句话就描述完了,但是真正对于一个UI系统来说,由于事件种类的复杂性,所以还是要花一定的心思来想想如何架构的,UGUI中把事件的触发封装到了一个叫ExecuteEvents 的类里面。

ExecuteEvents中定义了一个叫 EventFunction<T> 的泛型委托,以及该委托的一堆实例(注),另外它还提供了两个静态方法 Execute 和 ExecuteHierachy,前者是目标控件身上所有实现了泛型T类型组件都 执行泛型T的接口函数,后者则是按着层级面板向上递归查询,直到找到一个实现T接口的组件后执行泛型T的接口函数(例如ScrollView的滚动)。

读上去不太容易理解,其实就是一个数据分发的过程,前者是给身上每一个有需要的组件都发,如果有组件接收则返回true,如果没有,那就当啥也没发生(返回false);后者是,如果它自己身上没有组件需要这个数据,就向上找它父物体,父物体也不要那就去找它父物体的父物体,这么一直向上找下去,直到找到有某位大哥接收了(返回这个大哥GameObject的引用)或者到了根节点没法再往上找了(最终数据还是没人要,返回null)。

(注)所谓的“一堆实例”是说,每一个IEventSystemHandler在这里都对应有一个委托实例。

下面是这两个方法的具体参数:

public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler{...
}public static GameObject ExecuteHierarchy<T>(GameObject root, BaseEventData eventData, EventFunction<T> callbackFunction) where T : IEventSystemHandler{...
}

对于一个已知的target(ExecuteHierarchy其实也是在寻找一个target),在C#中我们很容易使用GetComponents来获取其身上的所有组件,然后用 is 来判断是否实现了某个接口(T),如果实现了,则使用functor把eventData传递给这个组件,源码中是这样写的:

functor(arg, eventData)

这里的arg就是代表了组件列表中的其中一个组件

其实就相当于arg.OnPointerXXX(eventData)

functor的内部实现其实就是调用arg各自的接口函数了, 以IPointerClickHandler举例:

private static readonly EventFunction<IPointerClickHandler> s_PointerClickHandler = Execute;private static void Execute(IPointerClickHandler handler, BaseEventData eventData)
{TriggerExecuteEvent(GetEventName(typeof(IPointerClickHandler)), handler, eventData);handler.OnPointerClick(ValidateEventData<PointerEventData>(eventData));
}

这样就把eventData传给了我们的目标组件了

4. 响应玩家输入

经过上面说了一堆,你大概明白了,原来UnityUGUI是先把数据处理了一下,并标记了一些状态然后传递给UI组件,那么UI组件拿到这个数据后怎么响应呢?或者说,我们的游戏内容如何响应玩家的输入呢?

答案是:事件

我们前文中所讲的事件只是针对鼠标而言,鼠标点击了发出来了一个点击事件,鼠标进入了产生一个进入事件,这个事件需要在UI组件中定义出来,供我们开发业务逻辑时监听。

例如:按钮 Button 便定义了一个onClick事件,因此我们便可以使用

btn.onClick.AddListener(DoSomethingFunc);

来监听这个事件了。

那么何时会触发这个onClick呢?

回顾上一节我们讲的,UGUI把封装好的数据传递给UI组件,那么这些组件在调用其对应类型的接口函数时,便会触发相应事件。对于IPointerClickHandler按钮来说,鼠标点击一个按钮时,便会调用它的OnPointerClick接口,在OnPointerClick中释放onClick事件。

接下来便是监听onClick去编写业务逻辑了。

其他事件同理,美滋滋~

刨根系列 之 Unity3D UGUI 背后的工作原理相关推荐

  1. 解析 ChatGPT 背后的工作原理

    来源:大数据与机器学习文摘 本文约5000字,建议阅读10分钟本文解释了ChatGPT背后是如何工作的. ChatGPT 是 OpenAI 发布的最新语言模型,相较于前身 GPT-3 有显著提升.与许 ...

  2. 解析ChatGPT背后的工作原理

    点击上方"芋道源码",选择"设为星标" 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | ...

  3. javascript教程系列20: 前端必读,浏览器内部工作原理(转)

    目录 一.介绍 二.渲染引擎 三.解析与DOM树构建 四.渲染树构建 五.布局 六.绘制 七.动态变化 八.渲染引擎的线程 九.CSS2可视模型 英文原文:How Browsers Work: Beh ...

  4. 【Nginx系列】Nginx配置使用与工作原理

    热门系列: [Linux系列]Linux实践(一):linux常用命令 程序人生,精彩抢先看 目录 1.Nginx介绍 1.1 什么是Nginx? 1.2 Nginx能做什么 1.3 为什么要选择用N ...

  5. AI应用开发基础傻瓜书系列1-神经网络的基本工作原理

    Copyright © Microsoft Corporation. All rights reserved. 适用于License版权许可 更多微软人工智能学习资源,请见微软人工智能教育与学习共建社 ...

  6. 面试系列八 es写入数据的工作原理

    (1)es写数据过程 1)客户端选择一个node发送请求过去,这个node就是coordinating node(协调节点) 2)coordinating node,对document进行路由,将请求 ...

  7. 如何使用配置的方式修改SAP C4C UI的字段标签,以及背后的工作原理

    I was asked by one partner that it is expected to adapt the label of "New" button into &qu ...

  8. nodejs项目npm start背后的工作原理

    要获取更多Jerry的原创文章,请关注公众号"汪子熙":

  9. Google 搜索背后的工作原理

    http://www.cnblogs.com/shunyao8210/archive/2010/07/14/1777274.html

  10. Ceph分布式存储系列(一):Ceph工作原理及架构浅析梳理

    一.Ceph简介     众所周知,ceph是一种分布式存储系统,是有着"ceph之父"之称的Sage Weil读博期间的研究课题,项目诞生于2004年,在2006年基于开源协议开 ...

最新文章

  1. 如何让ssh登录时不提示是否要添加HostKey
  2. php生成 sku_高并发下,php与redis实现的抢购、秒杀功能
  3. 黑马程序员——java语言基础——面向对象
  4. win10配置gcc编译环境
  5. H264 编码+打包+解码相关知识
  6. android访问asset目录下的资源
  7. logstash multiline
  8. 今天突然出现了Property IsLocked is not available for Login '[sa]',我太阳,下面有绝招对付它!...
  9. react在线文件_在线IDE开发入门之从零实现一个在线代码编辑器
  10. 基于JSP的鲜花商城源码
  11. Inpaint 9 简体中文【订阅版+Win/Mac】
  12. abaqus与python后处理_abaqus用Python批量后处理教程!如何从abaqus导出python
  13. 针对LSB 信息隐藏的卡方分析算法实现
  14. poj 3345 Bribing FIPA 树形dp
  15. 多个vmdk合并成一个vmdk方法
  16. 我再copy回来。中海真是有心人。只是,你们在哪里?
  17. Linux: ubuntu Appium连接手机
  18. 实战:无线wifi密码破解与路由器入侵
  19. mysql gh ost 对比_GitHub开源MySQL Online DDL工具gh-ost参数解析
  20. @Autowired和@Resource区别

热门文章

  1. pt达人教你如何用pttools批量刷新pt站点数据
  2. 仿新浪微博布局学习——妙用TabHost
  3. Q7:难道不想手工搞个环境?
  4. CV战神常用代码-----kj15
  5. 抖音小程序Tiktok开发教程之 基础组件 01 text文本组件
  6. 用golang做繁简体转换
  7. 如何理解Scala:迷之翻转喵 —— 协变逆变全解析
  8. 农场花园种花偷花前后端完整项目
  9. 三维分布图 matlab,怎样用matlab画三维三点分布图
  10. 拉昆塔温德姆酒店中国首店即将亮相山东潍坊;复星旅文旗下Club Med落子北美市场 | 全球旅报...