UE5 runtime编辑器UI框架搭建思路(一)

前言

随着游戏制作技术的不断进步,游戏引擎也在不断地更新和优化。其中,UE5作为游戏开发中广泛应用的引擎之一,但是其基于umg的ui框架用起来并不容易,在一般的游戏开发中,umg可能基本够用了,但是随着ue在很多其他领域特别是数字孪生行业越来越广泛的使用,想使用umg搭建出一个拓展性较好的编辑器或者系统,难度还是比较大的,特别和前端的ui框架比起来,效果和拓展性都差很多,ue editor使用的slate框架在runtime下使用还需要用c++再次进行封装,使用起来也很麻烦。

我们团队也在做数字孪生相关工具的开发,整条链路非常长,基于ue的客户端只占其中很小一部分,我在制作客户端ui界面及其相关功能的时候,使用了ue商城里的quiet docking system作为基本框架,为了兼容我们自身链路的数据结构,进行了大量的改造和重构,由于工具还在开发中,没法把全部内容罗列出来,核心是复刻了大量的编辑器逻辑,包括资产库,地编工具,component组件,甚至runtime的蓝图,本片内容主要讲一下ui框架的构建思路,后续会分享整个产品的功能和模块,欢迎交流。

下面是还在开发中的截图:

在本文中,我们将探讨如何利用UE5的umg搭建出运行时编辑器UI框架,由于整个流程非常复杂和繁琐,所以就不提供具体步骤,只提供相关的思路,由于没法把开发过程截图,所以这里以quiet docking system这个组件为例梳理思路(毕竟也是基于这个框架进行的修改),需要对umg有一定程度的了解,先看效果:

这其中包括:灵活拓展的菜单栏,可拖拽的工具栏,列表组件,可灵活拓展的侧边栏,windows的标题栏,viewport的resize,多窗口的实现等等。。。,接下来就一个个介绍具体实现的思路,需要对umg有基本的了解。

基本框架布局

创建一个主widget,通过grid嵌套name slot+grid panel的方式决定整个主ui的基本布局,包括工具栏,菜单栏,viewport,侧边栏,菜单嵌套等等。。。

记得在ui最顶层包一个scale box,这样可以保证整个ui在不同dpi的屏幕上,文字大小显示的一致性

菜单栏

菜单栏是很常用的组件,这里使用umg模拟的话,基本是靠vertical box的层层嵌套来实现。

动图封面

结构如下:

来逐层剖析,从sub menu开始拼装,最终组合成菜单栏:

菜单栏子按钮sub button

样式:一般hover的高亮效果可以用button的style的设置,但是有时候不够灵活,除了使用button组件本身的on pressed/hovered/click等事件,有时候会需要整个widget的hover的回调,而并不是某个button的回调,可以使用event on mouse enter,和on mouse leave,而且要设置整个widget的focus,这个后面会用到。

再点击button或者点击其他除了widget以外的其他部分,widget都应该消失掉,重写on removed from focus path即可。

效果如下:hover和按钮消失。

动图封面

还有就是把icon,按钮需要执行的command,name或者其他需要的属性封装好,尽量做成可复用的组件,在umg preconstruct和construct的时候执行。

子菜单sub menu:

这部分比较简单,在每一个menu item下,添加一个slot,将已经封装好的sub menu嵌套进slot(直接用vertical box嵌套button即可):

注意menu item的slot的位置,锚点保证在右上角,这样可以保证弹出的菜单位置正确:

主菜单main menu

这样的话我们把menu list嵌套进main menu即可,所以最顶层的菜单栏的嵌套逻辑是:

sub button--sub menu--menu list--main menu button,最终再通过horizontalbox嵌套进菜单栏。

事件回调

由于菜单栏随时需要拓展功能,所以是肯定不能把按钮事件绑定写死,可以通过给每一个按钮设置command命令变量,绑定event dispatcher,主菜单往下递归绑定子菜单的事件,把所有的需要执行的命令统一写在主菜单里:

递归查找button:

绑定每一个sub menu button的代理事件:

通过switch on string来执行每个按钮的功能

工具栏

定义tool bar item

工具栏的使用方式多种多样,可能是checkbutton,可能需要drag drop,可能只是一个按钮,所以首先定义一个toolbar item的组件,把功能和样式封装好,包括mouse enter/leave,cursor的style,回调事件等等

通过蓝图进行widget的cursor切换:

动图封面

可以切换的cursor样式类型

再把一组tool bar item封装进一个widget,通过grid panel排列(由于需要灵活切换横向排列或者纵向排列,所以不能固定使用verticalbox或者horizontal box)

定义toolbar container:

container是用来排列toolbar item,嵌套逻辑是:

toolbar item----name slot----scroll box(选项太多导致屏幕排列不下的时候,可用于滚动)----border----canvas panel(用来支持scrollbox的滚动)

toolbar 拖拽:

动图封面

drag:override toolbar的mouse drag事件,首先通过mouse的position确定ui drag的位置,:

然后创建drag drop的operation(当然这里需要新建dragdrop的bp,用于临时储存drag drop的变量,这里就不在赘述),设置相应的payload和dragoffset, (drag visual是拖拽时跟随鼠标的umg,这里就默认umg本体)

dragdrop 相关文档​docs.unrealengine.com/5.0/en-US/creating-drag-and-drop-ui-in-unreal-engine/

drop:drop的时候要注意有两种情况,一种是drop在tool bar item container上需要做吸附,从新排列整个toolbar,还有就是drop在面板的其他位置,根据cursor的位置从新创建toolbar的widget,并且初始化

dock面板切换

动图封面

使用umg复刻docker的基本功能并不容易,

一个基础的面板主要有两个部分组成,一个是用于切换面板的tab,一个是用于嵌套ui内容的content,由于制作过程很复杂,同样在这里只能介绍一下思路:D

这里模拟docker的基本功能结构如下,主要是分为两部分,一个是用于切换,拖拽等功能的docker tab,还有一个是用于嵌套用户自定义的widget的作为容器的docker content,:

定义docker tab

docker tab是用于切换对应的content面板的tab,同样设置好widget的样式,我这里暴露了包括icon,content class等等,

定义docker content

docker content的结构比较简单,通过一个name slot嵌套用户自定义的widget,然后把docker content和docker tab都嵌套进docker widget中,然后再通过tab去切换

通过tab切换content

tab和content的切换的根本逻辑是通过找到tab所对应的guid,和content的guid相同的话就切换显示,否则不显示

流程如下:

找到tab对应的content widget

然后激活 self widget deactivate 其他的widget

这里要多说一句,新手可能不知道,在umg里去写mouse的相关操作,是要在umg graph的左上角去override mousebutton,umg中不像actor bp里事先已经预留好了各种keyboard和mouse的事件,如下图,包括按钮,滚轮,聚焦,失焦等众多事件

docker拖拽

动图封面

这部分很复杂OTL。。。。,逻辑很复杂,我还是只能提供相关思路,

先列出来初步的思路:

drag:通过drag docker的tab,判断准备drop的widget,根据类型不同判断是否要做吸附的操作

drop:

吸附的逻辑,主要是通过drop的位置判断docker最终所在的grid panel的row 和column:

但是这里比较蛋疼的是,这个框架在拖拽docker的时候,会先remove之前的docker,然后创建新的docker widget,这样会导致docker content内部的用户创建的widget从新初始化,所以用户widget内部的一些数据就会丢失,并且从新执行一遍构造函数,这部分的逻辑我还在修改,实现docker content widget数据的缓存。

viewport更新大小和位置

在模拟docker功能的时候,很重要的一点是模拟docker窗口缩放的功能,用umg实现这个效果的确是曲线救国,基本思路是:通过计算umg中viewport部分的ui的范围,相对于整个真实viewport的位置和大小,再去驱动post process material,或者调用c++的相关函数来实现viewport的功能。

动图封面

希望实现viewport的umg范围:

这里有举例两种方式:

通过post process material

通过get umg的space geometry和viewport的比值,得到目标viewport和原始viewport的大小,这里判断逻辑也有点繁琐,需要先判断目标viewport和原始viewport的宽高比的比值,来确定用x还是y作为缩放参考的标准,再去根据目标viewport的位置和大小,计算出相应缩放和偏移的value。

还要计算相机的fov变化,(ue的窗口fov是以x为基准的,改变窗口的宽度会影响fov,改变高度不会影响,但是会相应的裁切和补全画面)

然后在根据不同情况下的目标viewport和原始viewport的比值和相对位置,算出后期材质uv的缩放和偏移参数,

最终把材质给到level中的post process

通过调用win的接口实现

直接调用GEnging->GameViewport->SplitscreenInfo[0].PLayerData[0] 的相关参数即可即可

多窗口的实现

多窗口是一般基于客户端的编辑器很基本的功能,这里umg原生无法实现,我们需要借助插件:

使用起来很简单,就不过多赘述,最终多窗口的效果如下:

00:22

windows title bar的实现

首先在project setting 中把打包后的程序设置成无边框

然后在umg里添加windows title bar,这样可以很方便的设置程序左上角的icon

command bar

runtime下经常也会用到ue的command line功能,这个实现很简单,直接通过editable执行command命令即可,但是补全的功能还没做。

总结

由于使用umg在runtime下去模拟一个客户端编辑器的基本功能确实很繁琐,所以这里只介绍了基本思路,欢迎交流,当然做一个客户端的编辑器远不止于此,下一篇准备讲解一下runtime下场景编辑,场景搭建的一些实现思路,比如runtime的材质编辑,模型查看,gizmo,点选高亮,时间天气同步,世界大纲,资源池组织形式,读取保存,资源打包等等

猜你喜欢

转载自blog.csdn.net/ttod/article/details/135139142