IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【UE·底层篇】Slate源码分析——点击事件的触发流程梳理 -> 正文阅读

[游戏开发]【UE·底层篇】Slate源码分析——点击事件的触发流程梳理

我们都知道Slate是UE4的UI底层框架。但是UE4的UMG和Unity的UGUI比起来还是有很多不足,所以在实际开发的过程中经常需要对Slate进行改造。本文将带大家由浅入深地阅读Slate源码里关于点击事件相关的部分


基础知识回顾

SetFocus

SetFocus是UWidget类里面的方法,最终会调用到FSlateApplicationSetUserFocus方法。SetFoucs顾名思义,就是设置聚焦,当widget获得聚焦或失去聚焦时会触发相关事件。功能说明如图,首先有两个面板A和B,按下A的按钮时调用A面板的SetFocus,按下B的按钮时调用B面板的SetFocus。

在这里插入图片描述

游戏运行,按下A的按钮,Focus相关事件调用顺序如下:

  • A的OnFocusChanging
  • A的OnFocusReceived

此时再按下B的按钮,Focus相关事件调用顺序如下:

  • A的OnFocusChanging
  • B的OnFocusChanging
  • A的OnFocusLost
  • B的OnFocusReceived

注意SetFocus并不会引起改变任何widget的响应功能,这点和等会要提的CaptureMouse有很大不同。

CaptureMouse

CaptureMouse是FReply里的方法,参数为TSharedRef,用于锁定某个Widget。调用CaptureMouse后整个游戏将只能和锁定的Widget进行响应,该Widget的父级和子级也是不能响应的,直到调用ReleaseMouseCapture(需要注意的是如果在ue4编辑器下调用了CaptureMouse,那编辑器自己的按钮也是不能响应的。)

FakingTouch

在ue4里,鼠标点击和Touch(移动设备的触屏)是两种不同的操作。为了在编辑器下模拟Touch,通常会勾选ProjectSettings里的UseMouseForTouch。这个情况下要想判断是真正的Touch还是模拟的Touch,可以调用FSlateApplicationIsFakingTouchEvents

FPointerEvent

FPointerEvent 点输入事件类, 用于处理鼠标以及移动设备的触摸按键,继承自FInputEvent输入事件类。

PreviewMouseButtonDown

PreviewMouseButtonDown事件的调用时机在MouseButtonDown和TouchStart前面,和其他事件不同的地方是它是从后往前遍历的。如图,B面板在A面板上面,两个面板都接入了OnMouseButtonDown事件并且都返回Handle,同时A面板接入了PreviewMouseButtonDown事件也返回FReply::Handle

在这里插入图片描述

运行游戏,点击B面板,结果为:

  • 调用了A面板的PreviewMouseButtonDown
  • A和B的MouseButtonDown都没有被调用

调用栈总览

下面以UserWidget为例,看一下它的TouchStart事件在PC上的调用流程

在这里插入图片描述

首先是平台层,根据不同的硬件设备和操作系统调用不同平台的API。window平台调用的是WindowsApplication类。在这个类里面会接受玩家输入,判断玩家输入类型,最终来到Slate层的SlateApplication的OnMouseDown并且传入玩家输入相关的数据。

OnMouseDown判断了一下是FakingTouch,接着调用了OnTouchStarted
在这里插入图片描述
OnTouchStarted根据传入的数据生成了一个PointerEvent,接着调用ProcessTouchStartedEvent
在这里插入图片描述

ProcessTouchStartedEvent调用了SlateUser的NotifyTouchStarted,这里面会记录每根手指头的Touch位置信息。然后调用ProcessMouseButtonDownEvent

在这里插入图片描述
在这里插入图片描述
ProcessMouseButtonDownEvent里会判断当前有没有Caputure某个Widget,有的话调用FEventRouter::Route处理点击事件,没有的话则调用RoutePointerDownEvent

RoutePointerDownEvent最后也会调用FEventRouter::Route。
在这里插入图片描述

FEventRouter::Route里则会调用SWidget的OnTouchStarted。SWidget是SObjectWidget的基类,OnTouchStated里面默认不会做任何事情。SObjectWidget重写了OnTouchStarted,最终调用了UserWidget的NativeOnTouchStarted。

这里面最复杂且最核心的三个方法为ProcessMouseButtonDownEventRoutePointerDownEvent以及FEventRouter::Route,后面将进行更深入的分析。


Slate中几个重要类的含义

在细读源码之前,还需要把几个重要类的概念搞明白。

FSlateUser

SlateUser用于处理每个玩家的输入。对于手游来说,SlateUser只有一个。对于主机和PC游戏来说,本地每多一个硬件输入设备(比如插多个手柄),SlateUser就会多一个。SlateUser里方法的功能主要分为以下几部分:

  • 处理Focus相关的功能。设置、清除Focus等。
  • 处理Capture相关的功能。设置、释放Capture,获得当前Capture的WidgetPath等。
  • 记录和获取点击位置。
  • 处理拖拽相关的功能。获得拖拽操作类FDragDropOperation,判断当前是否有拖拽等。

FWidgetPath、FArrangedChildren、FArrangedWidget

我们都知道,UI是一个树形结构。使用编辑器下的WidgetReflector可以清楚地看到当前选中的widget所在的树形结构。

在这里插入图片描述
在代码里就是用WidgetPath、ArrangedChildren、ArrangedWidget这三个类来表示这个结构。

ArrangedWidget是树的单个节点,包含一个SWidget和一个Geometry。

在这里插入图片描述

ArrangedChildren是节点数组的封装,它包含了一个ArrangedWidget的TArray,里面还有一些增删查的方法。

WidgetPath代表着一个Widget树的分支,它不仅包含了一个ArrangedChildren,还包含了一个树根节点的SWindow指针。(如果根节点不是SWindow则为空)

它们的关系如下:

在这里插入图片描述

FDirectPolicy、FToLeafmostPolicy、FTunnelPolicy、FBubblePolicy

这四个类位于FEventRouter内部。代表着遍历WidgetPath的四种策略。每一种策略类构造函数里都有WidgetPath。

在这里插入图片描述

这四种遍历策略的区别如下:

  • FDirectPolicy只查找根节点,用于拖拽
  • FToLeafmostPolicy只查找最后的叶子节点,用于Capture
  • FTunnelPolicy从根节点往下遍历,用于PreviewMouseButton
  • FBubblePolicy从叶子节点往上遍历,用于上面提到的以外的情况
    在这里插入图片描述

在FEventRouter::Route里会根据遍历策略进行遍历,当RoutingPolicy的ShouldKeepGoing返回false且Reply为Handle时结束遍历。
在这里插入图片描述

FHittestGrid

FHittestGrid,是用于检测玩家当前会点击哪个widget的网格。包含在SWindow里面。FHittestGrid里的格子是FCell类。如图,最大的网格才是FHittestGrid,每个小格子是FCell。
在这里插入图片描述
上面也提到,WidgetPath是一个Widget树的一个分支。那么,代码里是怎么知道玩家当前选中的是哪个Widget?知道选中的Widget以后又怎么构造出一个WidgetPath呢?

相关的方法是FSlateApplication的LocateWidgetInWindow
在这里插入图片描述
接下来简单分析GetBubblePath里的逻辑(因为代码较多就不贴了,原理就是网格法来进行空间划分,详细的可以看我文末的学习资料)。

  • 根据点击的位置算出行数、列数,得出在哪个Cell
  • 取出Cell里的Widget列表进行遍历,判断点是否在Widget内
  • 得出第一个符合条件的Widget
  • 根据Widget的父Widget构造整个WidgetPath

核心方法分析

接下来将对调用栈里最核心的三个方法进行分析。
在这里插入图片描述

ProcessMouseButtonDownEvent

由于代码量较多,接下来用伪代码进行说明。

{
	if(SlateUser是否正在拖拽 == false)
	{
		if(SlateUser是否有Capture)
		{
			取出Capture的WidgetPath;
			FEventRouter::Route对WidgetPath进行遍历,策略为FToLeafmostPolicy,遍历成功时的委托为发送PreviewMouseButtonDown事件;
			if(PreviewMouseButtonDown返回的Reply没有Handle)
			{
				FEventRouter::Route对WidgetPath进行遍历,策略为FToLeafmostPolicy,遍历成功时的委托为
				{
					if(当前输入为Touch)发送TouchStart事件;
					if(当前输入不是Touch)发送MouseButtonDown事件;
				}
			}
		}
        else
        {
			拿到顶层SWindow,从SWindow的HittestGrid获取WigdetPath;
            调用RoutePointerDownEvent,传入WidgetPath和输入信息;
        }
	}
}

RoutePointerDownEvent

注意遍历策略和上面的方法使用的是不一样的。

{
	SlateUser记录点击位置;
	FEventRouter::Route对WidgetPath进行遍历,策略为FTunnelPolicy,遍历成功时的委托为发送PreviewMouseButtonDown事件;
    if(PreviewMouseButtonDown返回的Reply没有Handle)
    {
        FEventRouter::Route对WidgetPath进行遍历,策略为FBubblePolicy,遍历成功时的委托为
        {
            if(当前输入为Touch)发送TouchStart事件;
            if(当前输入不是Touch)发送MouseButtonDown事件;
        }
        if(如果是真Touch)
        {
			对每一个WidgetPath上的Widget发送OnMouseEnter事件;        
        }
    }
    处理鼠标的Focus;
}

FEventRouter::Route

Route里面就很简单了。根据传入的策略进行遍历,每次遍历后执行委托,委托返回的Reply为Handle时且遍历策略的ShouldKeepGoing返回false时结束遍历。
在这里插入图片描述
最后还会调用到Application的ProcessReply:
在这里插入图片描述
ProcessReply代码也比较多,但是和本文主要讨论的点击流程关系不大,这里简单说下这里面都做了什么事情:

  • 处理Capture是否需要释放
  • 处理Focus释放需要释放
  • 处理拖拽
  • 处理MouseLeave
  • 处理Navigation

(注:UI的Navigation是只使用键盘或者手柄的游戏才有的一种系统)


总结

以上就是Widget收到点击事件的全流程分析。总结如下,在没有开启Capture的情况下 ,流程为:

  • 根据点击位置,从HittestGrid获取WidgetPath
  • 对WidgetPath根节点往下遍历,触发PreviewMouseButtonDown事件,如果返回的Replay为Handle则返回,没有则下一步
  • 对WidgetPath叶子节点往上遍历,触发TouchStart或MouseButtonDown事件
  • 调用SWidget的OnTouchStart或OnMouseButtonDown方法

学习资料


关于作者

  • 水曜日鸡,喜欢ACG的游戏程序员。曾参与索尼中国之星项目《硬核机甲》的开发。 目前在某大厂做UE4项目。

CSDN博客:https://blog.csdn.net/j756915370
知乎专栏:https://zhuanlan.zhihu.com/c_1241442143220363264
游戏同行聊天群:891809847

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-01-01 14:17:32  更:2022-01-01 14:19:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 20:58:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码