2024-10-22
Unreal Engine
00

目录

前置知识:SceneCaptureComponent2D
前期准备
物品蓝图:BP_Look
数据表格&结构
展示UI
蓝图构建
物品蓝图:BP_Look
接口设置
函数设置
SceneCaptureComponent2D
UI组件
UI动作
函数重载
UI聚焦
退出UI
检视物品
总结

虚幻引擎的物品展示UI构建实例,允许玩家通过控制器对场景内的可互动物品进行检视。

在游戏中经常有如下的场景互动:

  • 玩家可以对某些物品使用互动按钮
  • 互动后,游戏会暂停,会出现检视相关UI,允许玩家近距离观察物品
  • 玩家可以使用鼠标旋转物品模型,或者使用滚轮拉近/拉远与物品之间的距离

image.png

这种功能非常有意思,我觉得很适合作为练手的方式。

前置知识:SceneCaptureComponent2D

SceneCaptureComponent2D 是 Unreal Engine 中用于捕获当前场景渲染的组件,它可以用来生成动态的纹理或视频流,以实现例如实时反射、监视器显示等功能。该组件通常被附加到场景中的物体上,并且可以设置其捕获视野、分辨率等参数。

主要功能与使用场景:

  1. 实时反射:可以用于创建动态反射材质,比如镜子或者水面。
  2. 监控摄像头:在场景中加入SceneCaptureComponent2D作为摄像头,将捕捉的画面投射到另一个显示屏或表面上。
  3. 动态纹理:生成一个实时更新的纹理,可用于HUD、材质或其他视觉效果。

常用属性:

  • Capture Source:设置捕获的内容源,例如捕获场景颜色、深度等。
  • Texture Target:捕捉到的画面会输出为纹理,通常用一个RenderTarget2D来存储。
  • FOV Angle (视野角度):决定摄像头的视角宽度。

示例:

要使用SceneCaptureComponent2D捕捉场景并将其渲染到一个RenderTarget2D,通常需要以下步骤:

  1. 在场景中的某个Actor上添加一个SceneCaptureComponent2D
  2. 创建一个RenderTarget2D资源并将其分配给SceneCaptureComponent2DTexture Target
  3. 使用材质将捕获的纹理应用到需要显示捕捉内容的物体上。

这使得你可以在场景中实时显示不同的视角画面或者创建视觉特效。

前期准备

基于SceneCaptureComponent2D,我们可以使用这样的思路:

  • 给要检视的模型套上一个SpringArm
  • 在SpringArm的尽头装上一个SceneCaptureComponent2D的摄像头
  • 模型互动时能够打开检视的UI
  • 将SceneCaptureComponent2D捕获到的画面传递给UI上的检视部分
  • UI上鼠标的互动能够影响SpringArm的旋转角度和长短

物品蓝图:BP_Look

新建一个Actor蓝图BP_Look,我们用这个蓝图来创建可以互动的几何体。

为这个蓝图的root添加一个静态几何体组件,为静态几何体添加弹簧臂组件,最后在弹簧臂上添加一个场景捕获组件2D。

image.png

注意

在我们的需求里,SceneCaptureComponent2D只会拍摄我们想要的模型,其他内容会被忽略。并且,弹簧臂具有如下特性:它会进行碰撞检测,装在自己身上的摄像头与其他物体发生碰撞时,它会自动改变长度,以规避碰撞。然而,在当前的模型中,由于弹簧臂默认位置在(0,0,0),它会和地板发生碰撞检测,并会无限制地缩短——而且无用,因为它的根是在地面上的,因此你在蓝图中可能能看到摄像头正常地放在弹簧臂末端,但是拖进Level里弹簧臂的长度就归零了。因此,我们需要对摄像头SceneCaptureComponent2D进行特殊的碰撞处理。

打开编辑 - 项目设置 - 引擎 - 碰撞,新建一个Object通道,我将其命名为LookCamera,并将默认响应设置成Ignore忽略。之后,在弹簧臂的Detail页面,在摄像机碰撞 - 探头通道中,将通道设置为LookCamera。最后,在蓝图的StaticMesh的Detail面板中,设置其碰撞预设为Custom,并将LookCamera设置为Block阻挡。这样,除了这个StaticMesh,没有物体会与摄像机发生碰撞检测,并且弹簧臂又能检测摄像机和网格体的检测,防止它们相互重叠。

数据表格&结构

可以注意到,蓝图里的静态几何体组件并没有捆绑具体的组件,这是为了我们能够方便地复用组件。就好像一个函数,我们传入一个参数,它就能打印“这个参数是:xxx”一样,如果所有可以互动的物体都可以使用这一个蓝图,只是静态网格体不同,我们就可以复用很多代码,并且维护更加方便。

如果我希望这个互动UI上显示:模型,模型名字,模型介绍,那么我们就需要将这些数据统一存放起来,这就是数据表格(DataTable)的作用了。

分别新建如下文件:

  • 右键 - 蓝图 - 结构:BP_DTStruture
  • 右键 - 其他 - 数据表格 - 从BP_DTStruture选取行结构:BP_DTLook

双击结构BP_DTStruture,我们对结构进行设置。它相当于对整个表的表头进行设置。我们想要的是模型、模型名字、模型介绍,那么我们表头可以这样设置:

  • ItemName:文本
  • ItemIntro:文本(可以是多行文本)
  • ItemModel:静态网格体

image.png

这样我们就得到了一个基本的结构。我们可以为其添加默认值,以防止数据表格里某些行缺少内容导致游戏崩溃。

双击数据表格BP_DTLook,我们已经能够看到对应的表格结构了,接下来我们可以填充文本。我添加了一张桌子,并对其进行了命名、添加了介绍。右键这行,选择重命名,将其命名为Show_Table

image.png

展示UI

创建一个新的用户控件:UI_LookGUI。

双击蓝图打开,添加如下内容:

  • 画布画板

  • 右键画布画板 - 包裹 - 背景模糊

  • 在画布画板中添加:图像,我将大小设置成了800×800800 \times 800

    image.png

  • 在画布画板中添加两个文本,用于显示标题和简介。

全部完成后的实例如下:

image.png

蓝图构建

物品蓝图:BP_Look

接口设置

在类设置中添加之前已实现的互动接口Interact,这样我们对它按E就能激活互动。

之后,我们在事件图表中,设置当对其进行互动时,允许打开我们的展示UI:UI_LookGUI。

image.png

注意:由于UI并不由Actor类管理,因此我们需要先get player controller,再从它拉出线来创建create widget节点。

函数设置

蓝图BP_Look开始时自带了一个函数:ConstructionScript,这个构造函数代表了这里面的内容会在该蓝图被创建时执行。我们可以用它来达到获取数据表格中内容的目的。

首先为蓝图创建一个变量,属性为“命名”,名称为ModelID,并将其调整为可编辑实例。这样,我们在将蓝图拖入编辑器之后,通过修改ModelID,就能让它自动化地选择使用哪个模型。

image.png

之后,为蓝图创建以下函数,并设置为纯函数:

  • GetModelTitle
  • GetModelIntro
  • GetModelMesh

这三个内容都差不多,主要是通过ModelID查询数据表,并返回对应的内容。我们以GetModelTitle为例:

image.png

最后,我们需要在构造函数中,将蓝图的值设置为数据图表的值。

image.png

这样,我们在将这个蓝图拖入编辑器,设置好对应的ModelID之后,它就会自动地变成我们想要的模型。

SceneCaptureComponent2D

SceneCaptureComponent2D有一个特性,即show only components。它可以允许开发者自由设置这个数组里的元素,并且SceneCaptureComponent2D的摄像机只会捕捉这个元素。我们只需要将show only components设置为仅含有当前蓝图中的static mesh,即可达到只显示当前内容,而不把背景也拍进去的目的。

回到ConstructionScript构造函数,在之后先将show only components清空,再将当前蓝图中的static mesh添加进去。

最后,我们还需要将渲染的内容投射到一个画布渲染目标(CavasRenderTarget2D)上。右键新建 - 纹理 - 画布渲染目标,创建BP_LookCavas2D。

回到ConstructionScript构造函数,将SceneCaptureComponent2D绑定到BP_LookCavas2D上(节点:setTextureTarget):

image.png

注意:你也可以在SceneCaptureComponent2D的Detail面板中直接指定:

image.png

但是可以注意到,此时捕获到的内容,是含有背景/skybox的。我们显然是希望得到一个干净的背景,因此我们这里引入Material来进行过滤。

右键BP_LookCavas2D,选择创建材质BP_LookCavas2D_Mat,并将其打开。在打开的Material面板中,将材质 - 材质域的值改为“用户界面”。之后,将RGB与最终颜色相连,将A(透明度)取反后与不透明度相连。

image.png

这样,我们就完成了对背景的过滤,获得了一个干净的桌子的样式。

UI组件

打开UI组件UI_LookGUI,我们接下来需要将已经处理好的内容放在UI上。

打开它的事件图表,能看到Event Pre Construct和 Event Construct。这两个的作用都是构造函数,主要是变量的创建与否。我们使用Event Construct进行操作。

首先,打开UI后,我们要把光标的显示打开。并且,我们需要禁用用户输入。这是为了防止我们的光标在UI上动的时候,人物的视角也跟着晃动。将Focus Widget设置为Self,Focus机制会在下文“UI聚焦”中解释。

image.png

通常来说,我们要对image和文本的值进行初始化,也是要在Construct函数中进行操作的。但是这里我们直接使用绑定功能,从而达到同样的效果。

进入设计器,点击图像组件,在Detail里找到笔刷 - 图像,将图像设置为BP_LookCavas2D_Mat。

在左侧变量中新建变量bp,并将类型直接设置为BP_Look,并勾选“生成时公开”。回到蓝图BP_Look的事件图表中,找到Create UI Look GUI Widget节点,右键刷新,会发现出现了新的Bp引脚。我们将Self传入。

image.png

点击标题文本,在内容 - 文本中,点击绑定 - 创建绑定,打开一个新的函数。函数的返回值就是Title的文本内容。在函数中,我们将变量Bp拉入,并使用Get Model Title函数,并将返回值赋给Return Node的返回值中。这样,我们就将被互动的模型的Title信息传入了UI中。详情文本的传入原理也是相同的。

image.png

完成了之后,基本上一个大致的功能就做好了,接下来就是在UI上让弹簧臂可以对鼠标的动作产生响应。

image.png

UI动作

针对UI的设计可以分为以下几个目标:

  • 能够退出UI
  • 在UI上能够移动光标检视物品模型
  • 在UI上能够滚动滚轮调整摄像头距离

函数重载

UI的事件图表允许你对一些鼠标/键盘等的输入动作进行重载,也就是当你触发了这些动作时,它会执行对应重载函数中的组件。

image.png

UI聚焦

UI是有聚焦这个概念的。好比windows操作系统中的窗口,聚焦(Focus)决定了你是否能够将你的输入传递给你所聚焦的元素上。

然而,如果你手把手地和我一起做到一个步骤,你会发现:有时UI似乎并不会响应你的某些操作。例如,如果我在函数重载中选择将鼠标按键按下的动作重载为一个打印"Mouse Down"的动作:

image.png

image.png

当且仅当我点击实际上UI存在的元素(例如Image,以及标题、简介)时,才会有打印出现。如果我点击空白部分,它将不会有任何字符串打印输出。这是因为聚焦机制导致了点击空白部分时,我们的聚焦自动还给了Game,而不是UI组件。然而我们已经设置了SetInputModeUIOnly,导致游戏也无法接收到我们的输入,因此哪里都无法正常工作。

解决方法是,不让空白区域能够转移聚焦,或者说“让空白区域不再空白”。回到设计器,并点击画布画板,在“可视性”中将其设置为“可视”。这样,我们的点击动作就会点击到画布画板上,而画布画板属于UI组件,聚焦就不会还给Game了。

退出UI

由于我们设定了SetInputModeUIOnly,不建议使用增强输入系统来绑定退出按键。当然,如果为了UI专门重新写一套增强输入系统,然后在UI打开时切换自然是可以的,但是我们在此简化做法。

回到重载界面,我们使用OnKeyDown,即按键按下时,并对Q键进行按键触发。不使用ESC主要是因为它会导致虚幻引擎游戏预览的弹出。

image.png

检视物品

在我的设想中,检视物品的行为应当是这样的:

  • 我鼠标正常移动时,它不该进行移动。
  • 我长按鼠标移动时,它可以被我拖动。
  • 我松开鼠标时,它不再可以被我拖动。

综上所述,我觉得我需要一个锁,长按鼠标时解锁,移动鼠标就能够旋转模型;松开鼠标时上锁,移动鼠标不能够旋转模型。

这时建立一个变量:SpringArmMovable,布尔类型,默认为false。UI的重载允许我们鼠标按下和鼠标松开时进行操作:

image.png

以按下为例,可以判定是否为左键,如果是左键即将SpringArmMovable设置为true。松开与其类似,只不过将SpringArmMovable设置为false。

image.png

锁已上好,接下来考虑如何移动鼠标来完成模型旋转。我们可以在“鼠标移动时”的重载中检查锁,并控制SpringArm的旋转(Rotator)。

image.png

这个复杂的蓝图就干了以下几件事:

  • 检查锁状态,确认是否可以移动
  • 获得SpringArm当前的旋转角度
  • 获取鼠标Move的Delta角度
  • 反转鼠标Y轴的输入以符合用户操作直觉
  • 根据鼠标的移动确定模型的旋转量
    • X,即Roll,不改变,直接原值设置
    • Y,即Pitch,是竖直方向的旋转,与鼠标Y轴的值相加。对其大小进行边界限制防止产生视觉错误。
    • Z,即Yaw,是水平方向的旋转,与鼠标X轴的值相加。

如果你认为旋转速度太快了,可以给Cursor Delta再乘一个系数,让它旋转的慢一些。

至此,能够对其进行旋转。

再考虑如何调整SpringArm的长度。重载中也有“鼠标滚轮上”(OnMouseWheel)这样的控件。我们可以通过这个重载来对滚轮的动作进行处理和映射。

image.png

其中,乘-10是为了更快地切换距离(乘10),因为滚轮只会返回1和-1两个值;并且让上下滚轮与放大缩小的动作更符合操作直觉(乘-1)。

总结

至此,我们完成了一个检视物品的蓝图的制作,我们只需要在数据图表中填入我们想要的模型、模型的名字、需要展示的介绍的内容,就可以将蓝图拖入Level,并使用命名来规定对应的数据,非常方便。

并且,其实还有别的功能可以使用,比如我们可以在数据图表中预先填入弹簧臂的相对位置、最大最小的长度,以方便我们进行调用,因为大多数时候我们对弹簧臂的位置还有范围的规定是不适用于所有模型的。

Enjoy Gaming。

本文作者:Jeff Wu

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!