Loading

UE4-初学UMG(三)

创建主菜单

我们将新建一个UI界面,这个界面将专门在游戏开始时显示,并提供多个按钮供玩家使用。此外,我们还将介绍一些更为高级的UI布局选项,并介绍一些可能出现的常见问题。

我们已经创建了基本的UI,播放游戏后,我们可以在屏幕上看到UI,开枪,弹药数会减少,拾取弹药,弹药数会增加,现在,我们让界面更丰富一些,一般来说,项目刚开始时,会出现一个交互界面,可能是关卡选择菜单,或者设置菜单,我们创建一个最简单的主菜单

在Maps文件夹下创建一个新的level

image-20210618125006297

或者点击左上角File->new level

image-20210618125112819

选择empty level,保存到Maps

image-20210618125133646

于是就有了用来显示主菜单的关卡,它会在游戏开始前加载,我们还需要创建一个控件,因为我们需要在屏幕上显示一些内容

image-20210618125322072

打开它,我们需要一张背景图片,两个按钮,一个“新建游戏”按钮,一个“退出”按钮

我们的按钮将位于屏幕中间位置,并且垂直排列,所以我们需要vertical box,选中第一个按钮,选择wrap with vertical box

image-20210618125706460

这样就能把按钮添加到垂直框体中,选中另一个按钮,把它拖到垂直框体上

image-20210618125811149

现在这样vertical box看起来有点拥挤,我们选择size to content,这样按钮就会按我们的设置自动填充,接下来,我们给按钮添加一些文本内容

可以在Hierachy将text拖到button上方,也可以将text直接拖到视口中的button UI上面,文本都会自动变成button的子对象

修改一下文本内容

image-20210618130235873

现在我们要让它位于屏幕中央,之前已经操作过了,设置锚点->调整偏移量

image-20210618130434528

但这样看起来还是有点挤,我们可以调整一下button的布局,让它居中显示,并且增加一些边距

image-20210618130734194

image-20210618130915835

这里稍微注意一下,padding允许我们控件四周添加边距,或单独在某个边上添加边距

image-20210618130759586

为它们添加了空隙之后,显示效果就自然多了

再简单修改一下字体,将bold改为light

image-20210618131058273

image-20210618131125428

接下来还需要添加一张背景图片,我们在Anchors中选择右下角的这个

image-20210618131956678

它的作用就是让图片填满整个屏幕,选择它,我们会发现锚点被拆分到了界面的四个顶点上,我们把偏移量都设为0

image-20210618132333118

image-20210618132322710

如果我们调整屏幕大小,图片会始终占据整个屏幕,非常好,但是,它却覆盖了所有内容,这是一个问题,不过,解决方法有很多,我们可以设置深度优先级,它的主要作用是控制图片的远近次序,我们还可以将图片添加到面板中,然后在最底层显示,这里我们使用“深度优先级”解决这个问题

将Image的ZOrder改为-1000

image-20210618133149676

而Vertical Box的优先级为0

image-20210618133219775

所以Image会显示在最下方

image-20210618133246232

接下来我们选择一张图片作为背景图片

image-20210618133638435

垂直框体的位置还是有点不协调,而且它还少了一张背景图片,所以不太容易辨别,我们可以选择为它加一块背景板

此外,我们还可以锁定或隐藏控件,锁定控件之后它将无法被选中,如果控件太占地方,或者我们想显示其他控件,可以暂时将其隐藏,但在游戏中,控件仍然会显示,隐藏只在编辑器中有用

为主菜单添加逻辑

在创建完主菜单界面后,我们还需要为菜单添加蓝图逻辑。在这一过程中,我们将学习一些此前在创建用户界面时尚未接触过的引擎系统。

上一节我们创建了主菜单UI界面,接下来让我们为其添加逻辑。目前的问题有两个,一个是无论何时都会显示用户的HUD界面,第二个是我们无法显示主菜单UI,让我们先来解决第二个问题,我们将仿照之前的方式添加主菜单,方法是创建控件,然后将它添加到关卡蓝图的视口中,我们点击Blueprints -> Open level blueprint

image-20210619120200154

image-20210619120354169

一般来说不会经常用到关卡蓝图,但鉴于这是一个示例项目,而且我们这个项目非常简单,所以没有关系,只是为了显示界面采用的临时方法

这一步操作之前已经介绍过,不再赘述,控件就选择我们刚才创建的UMG_MainMenu

image-20210619120611346

我们可以点击play看一下效果

image-20210619121013613

我们可以看到主菜单了,但是由于某些原因,玩家HUD界面仍会显示,让我们来解决这个问题,玩家HUD之所以会在屏幕上显示是因为玩家位于关卡之中,如果点击play,观看右边的World Outliner,会发现玩家角色位于关卡中

image-20210619121655032

为什么呢,这个角色是游戏默认的Pawn,默认Pawn由“地图和模式”中的“游戏模式”指定,我们打开Edit -> Project Settings,找到Maps & Modes,就能找到默认的游戏模式

image-20210619121930690

如果展开高级菜单,你会发现默认Pawn类是FirstHourUMG

image-20210619122003509

无论创建哪张地图,游戏都会自动生成这个角色,但我们不希望这个,所以我们要自己创建游戏模式,然后覆盖现在这个

在根目录下新建一个Blueprints文件夹,在这个文件夹下添加一个游戏模式

image-20210619122359263

选择Game Mode Base

image-20210619122419061

将其命名为GM_MainMenu,表示这个游戏模式用于主菜单,然后打开它

在class defaults中将dafault pawn class改为None

image-20210619122611127

我们不希望生成任何pawn,完成这步后,编译保存,关闭它,游戏模式已经设置好了。

回到主界面,打开世界设置,找到GameMode Override,将其改为我们刚才创建的游戏模式

image-20210619123002161

点击play,就可以得到我们想要的效果

image-20210619123049080

角色HUD界面不会再显示,只有主菜单界面,当然,现在按钮还没有用,因为我们还没有关联逻辑,现在我们来添加逻辑

回到主菜单界面(UMG_MainMenu),进入Graph,这里有一些默认事件,我们不需要用到它们,需要自己创建按钮事件。我们会发现,左边有一些variables,里面有button和image,可我们并未创建这些变量,原因是,引擎会假设如果你创建了按钮,就会用到它,所有按钮都包含一个点击事件。我们选中一个button,在细节面板中,能找到许多事件

image-20210619124208759

每当按钮被点击时,关联的事件就会触发,分别有点击事件、按下事件、松开事件、悬浮事件、取消悬浮事件,这里我们要用的是点击事件,我们选择它,新建一个on clicked事件,每次点击“开始游戏”时,都会调用它,为“退出游戏”按钮执行相同操作

image-20210619124504000

先处理退出逻辑,因为它非常简单,直接搜索quit game

image-20210619124648792

每当点击这个按钮时,就会调用“退出游戏”节点,游戏就会退出,让我们播放游戏测试一下

QuitGame

很好,接下来处理“开始游戏”节点,当我们点击“开始游戏”时,需要打开一个新的地图,我们搜索open level,选择我们的游戏地图

image-20210619125426134

测试一下

NewGame

现在有个问题,打开新地图后,鼠标光标仍然在屏幕上,但地图已经加载完成了,并且可以使用,此外还有一个问题,如果播放游戏,不点击按钮,而是点击背景图片,鼠标光标就消失不见了,问题在于输入模式上,引擎中有三种输入模式,点击空白处,搜索input mode,我们可以找到“仅UI”、“仅游戏”、“游戏和UI”三种模式

image-20210619125928106

默认的输入模式是“仅游戏”,这意味着所有来自鼠标、手柄、周边设备以及键盘的输入,仅针对游戏启用,在这种模式下,通常是你的手柄或其他设备接受输入信号,也就是说,是你的游戏视口接受输入,而不是你的UI,这不是我们想要的效果,我们希望UI接受输入,我们来解决这个问题。

当我们离开主菜单时,我们要把输入模式设置为“仅游戏”,但是加载主菜单地图时,要设置成“仅UI”模式

再次进入关卡蓝图,在后面添加新节点

image-20210619142956586

它还需要一个player controller

image-20210619154746215

点击play,点击背景,我们发现鼠标光标依然消失了,但我们将鼠标移动到按钮上时,按钮高亮显示了

image-20210619154920862

那是因为UI接受了输入信息,但问题是光标还是没能显示,这很简单,搜索show mouse cursor,取消勾选context sensitive(因为它只能由player controller调用)

image-20210619155132943

我们也可以直接从player controller找到它

image-20210619155412405

因为我们要显示光标,所以勾选show mouse cursor

image-20210619155419868

测试一下,光标可以正常显示了,接下来,我们要把进入游戏后的输入模式改为“Game only”,要注意,我们不能在打开关卡后设置输入模式,因为在打开关卡后,不一定能保证执行后续逻辑,你会打开一个新的关卡。让我们在打开关卡前更改输入模式,逻辑和之前一样,复制这些节点,将ui only改为game only即可

image-20210619160104032

进入游戏后不需要显示光标,所以将取消勾选show mouse cursor

运行测试,进入游戏后光标消失,很好

image-20210619160222360

这节课的内容很重要,游戏中有三种输入模式,“ui only”仅允许UI UMG元素接受输入信息,"game only"会把所有输入信息发送给游戏逻辑,主要是发给玩家控制器,"game and ui"我们还没有讲到,它能将输入信息同时发送给UI和游戏逻辑,当你需要用键盘控制游戏,同时又要用鼠标控制UI时,你就需要用到这个模式,为了介绍这个模式,我们将创建一个高级菜单,我们将在游戏中创建一个暂停菜单,以介绍这三种输入模式。

创建暂停菜单

本模块将会用到之前介绍过的全部知识。我们将在游戏中添加一个UI界面,并用它在运行时中断游戏流程。

此时,我们已经创建好了主菜单,如果播放游戏,我们就可以与主菜单进行交互,我们可以退出游戏或开始游戏,在我们的项目中,但我们拾取医疗包或弹药时,UI会相应调整,然而,左上角仍然会显示一些信息,我们可以触发胜利事件,但不会显示结束界面,如果我们在游戏中卡住了,或者想放弃游戏,我们也没办法重新开始游戏,接下来我们将介绍如何创建暂停菜单,一个可以在项目运行时,在项目内部访问的菜单。

让我们回到游戏关卡,现在我们要添加一个玩家暂停事件,我们需要让玩家在按下某个按键后,能打开一个暂停菜单,为此,首先打开edit -> project settings,找到input,为暂停按键添加一个动作映射,映射的作用是,但我们按下某个按键或触发其他输入事件后,就会触发某些动作。添加一个动作映射,命名为pause,由于无法在编辑器模式中使用esc键,所以我用p键暂停

image-20210620001714750

现在我们多了一个暂停事件可以调用,问题是,暂停菜单应该放在哪里?玩家HUD界面位于玩家控制器中,因为它主要与玩家的逻辑相关联,例如,玩家的弹药和生命值,主菜单放置在关卡蓝图中,因为它不需要与其他逻辑相关联,我们的暂停菜单严格说不属于关卡,但也不属于玩家,我们可以为它新建一个蓝图,但我们的输入需要通过某个途径访问游戏,游戏中,所有输入都会通过玩家控制器来控制游戏,让我们用玩家控制器来实现暂停事件,我们需要新建一个玩家控制器,在blueprints文件夹下新建

image-20210620003010174

现在我们可以在这个玩家控制器中添加代码并实现我们的暂停事件,默认情况下,这个控制器不会和游戏挂钩,所以我们要打开游戏模式即GM_FirstHourUMG

image-20210620003629872

将Player Controller Class改为刚才创建的玩家控制器,修改完成后保存

点击play测试一下,我们可以在右侧的world outliner中找到它,证明我们使用的是正确的玩家控制器

image-20210620003829758

现在我们来创建暂停事件,回到blueprints,打开玩家控制器

image-20210620003954624

搜索pause就可以找到我们刚才创建的暂停事件,当我们按下暂停按钮后,我们希望触发某些逻辑,具体方法很简单,我们要创建一个控件,然后将它添加到屏幕上,可是我们还没创建暂停菜单的控件,但这不要紧,在本例中我们可以直接使用我们的主菜单控件,它的菜单按钮能开始新游戏,还能退出游戏,所以选择主菜单,因为它已经经过验证,再把它添加到视口中

image-20210620004944572

点击play,发现在游戏中按下p后,我们仍然可以移动,游戏并没有真正暂停,解决这个问题的办法有好几种,我们可以把输入模式设为“UI Only”,我们来试试

操作方法之前也讲过,不多赘述

image-20210620005545784

点击Play测试一下,按下p键后我们无法再移动,鼠标和键盘都无法操作角色,只有用户界面可以接受输入信息,太好了。游戏并没有真正暂停,因为背景中的对象仍在运动,但我们至少有了一个不错的暂停界面,点击new game,就会开始新的游戏,点击quit game,就会退出游戏。

现在我们希望真正暂停游戏,因为我们不希望人物在游戏暂停期间死亡,我们希望停止游戏的所有逻辑,很简单,搜索set game paused

image-20210620010352030

现在我们进入游戏,按下P,发现背景中的对象都停止运动了,但是我们无法回到暂停前的界面,只能选择new game或者quit game,我们可以在界面中添加一个关闭按钮,或者让游戏和UI同时接受输入信息。

取消set game paused节点的连接,进入游戏后按下P键后,我们再次按下p键,没有任何作用,那是因为我们将输入模式设置成了"ui only",此时的输入只对UI有效,而p键的按压事件只有在游戏输入模式中有效,我们将输入模式改为game and ui

image-20210620011114895

点击Play进行测试,我们发现可以在菜单界面再次按下P键,但它并不会退出,实际上,每当我们按下P键,都会创建新的菜单控件并且添加到视口中,背景图片的颜色会越来越深(重叠造成的),我们没有判断是否已经创建了主菜单控件,是否已经暂停,下一节我们将了解一些用于菜单界面的高级特性,比如当游戏暂停时如何触发一些事件,以及如何避免在游戏中重复创建已有的控件。

高级UI事件

UI初步完成后,我们将进一步介绍引擎的高级特性。我们将在游戏暂停时调用暂停事件,借助“Is Valid”来检查有效性并控制菜单的开关状态,在显示暂停菜单时使用自定义事件来隐藏我们的玩家界面。

我们现在遇到的问题是每次点击暂停按钮后,都要重新创建暂停界面,我们要避免重复创建,有两种解决方法,第一种是判断是否已显示控件,另一种是判断是否已暂停,还可以检查我们是否使用了"set game paused"按钮,我们将采用第一种方法。

每次按下暂停键,我们就要检查是否已经创建了暂停菜单,为此,我们需要为暂停界面添加一个引用,右键它的返回值,将它提升为变量,命名为PauseUIRef,我们可以检查它是否有效,将它拖到视口,搜索is valid

image-20210621105436301

这里选择下面那个,因为它比较简单。最后完成的效果是这样的

image-20210621105621704

每次按下暂停键,都判断该引用是否有效(即是否被创建),如果是无效的,就新建一个界面,为它创建一个引用,然后添加到视口中

另外一种逻辑是,如果暂停界面已经创建,就将它从父类移除,此处的父类是视口,要注意从父类移除并不意味着删除它,只是看不到它了而已,所以接下来要再将引用设为空

image-20210621110726603

蓝图已经构建完成了,每次按下暂停键,如果当前没有暂停界面,那就创建一个,如果有暂停界面,那就删除它,然后是设置输入模式,最后我们重新连上set game paused节点,测试一下。

我们进入暂停界面后再按p键,发现没有任何作用,这是因为在游戏暂停时(set game paused)输入检测也暂停了,我们点击第一个节点,在细节面板可以找到

image-20210621111209833

勾选execute when paused,意思是暂停时仍执行,再次运行。

此时我们在暂停界面再次按下p键,发现菜单界面消失了,可游戏并没有恢复,依然处于暂停状态,实际上,在删除暂停界面引用后,我们应该执行与创建界面相反的逻辑。

image-20210621112013623

将输入模式设置为"Game only",光标设置为不可见,也不需要暂停游戏,好的,让我们再次运行游戏。

我们发现现在可以随意按下p键去打开和关闭暂停界面,并且游戏会自动暂停和恢复,以上就是暂停菜单的基本工作原理,以及如何利用现有UI元素编写逻辑的方法,当你在处理现有UI元素时,要特别注意这部分逻辑,因为UI元素的创建和销毁会耗用很多资源

image-20210621133652923

因为我们的角色不会在游戏中频繁暂停游戏,所以创建和销毁UI没什么问题,但假如某个UI元素需要经常开关,那该怎么办呢?假设你有一个背包界面,或者有一个平显界面,需要经常打开和关闭,这时,你最好将它从父类中移除,但是不要释放引用对象,单纯让它被隐藏掉就行,之后你可以随时让它重新显示,只需将它重新添加到视口中即可。

下一个问题时,暂停游戏后,玩家界面仍然会显示,我们要确保屏幕上只有暂停菜单,让我们看看如何切换这些菜单,由于我们已经写好了逻辑,我们只需要切换这些逻辑就可以了。我们希望在暂停界面时,关闭平显界面,为此,我们需要以某种方式,与玩家UI引用通信,告诉它隐藏那些UI元素,在玩家类中,有一个玩家UI的引用,让我们新建一个事件,用于打开关闭界面

image-20210621143933235

拖出玩家引用,分别设置弹药文本和生命值文本的可见性为Hidden

但是我们无法通过玩家引用找到准星,这种办法行不通,事实上,这种方法很繁琐,因为要分别隐藏所有UI,但其实我们可以使用与之前类似的方法,直接删除UI或添加到视口

image-20210621144409071

这样,我们的UI不会被删除,只会被隐藏和重新添加到视口。

接下来,我们去到玩家控制器,添加开关逻辑,当我们恢复游戏时,需要显示玩家UI,所以调用On

image-20210621144727270

同样为打开暂停菜单时设置关闭UI界面,编译并运行

image-20210621145027085

image-20210621145041644

我们的目标实现了,切换玩家界面的办法就是,将之前在角色类中创建的UI从父节点中移除,或者将它添加到视口中,由于控件在创建后,即使不显示,也会保存在内存中,这点要记住,这也是为什么要将暂停菜单的引用设为空,这样之后他就会在内存中释放。

现在,我们基本已经创建了所有内容,我们有一个可以使用的暂停菜单,所有UI都能实时更新,只剩下几个问题需要处理一下,左上角还在显示那些多余的消息,而且仍然不能结束游戏,我们的游戏循环还未实现。

完成游戏反馈循环

我们将利用在本课程中所学到的所有知识,通过创建最终的控件来完成整个游戏反馈循环。我们将在项目中新建一个事件来专门处理这个控件,然后通过与这个控件交互来重新开始游戏或退出游戏。

最后,让我们添加一个结束游戏的UI界面,我们直接duplicate主菜单控件,命名为UMG_GameOverScreen,由于主菜单包含了我们需要的大部分控件,直接复制它能帮我们免去很多麻烦

背景图片有点显眼,我们把它clear掉,把透明度设为0.2

image-20210621150705947

并且加入一个文本框,按下图数据进行调整,锚点设在屏幕上方

image-20210621150752135

把颜色调整成绿色庆祝游戏通关,好了,文本已经设置完成,接下来要添加按钮,这次要添加三个按钮而不是两个,分别显示“主菜单”、“重新开始”、“退出游戏”,这里“新游戏”和“重新开始”的效果其实是一样的,我们只需要改变它的名称和内容即可。

image-20210621152318599

接下来需要再添加一个按钮,我们可以直接复制,改掉名称和内容。还需要调整一下按钮的位置,因为这是vertical box,所有按钮都是垂直排列的,我们可以通过在层级表中移动button节点来调整顺序,也可以通过视口中的箭头调整

image-20210621152925320

这样,界面就创建完成了,我们来到graph,这里已经有两个点击事件,我们再为main menu创建一个,它的逻辑和new game其实一样,只是打开的地图不同,把地图改为MainMenu就可以了

image-20210621153311374

菜单已经设计完成了,接下来就是让它在屏幕上显示,我们首先要找到游戏结束事件的触发位置,然后找到在哪里处理后续逻辑。我们知道触发游戏结束事件后,屏幕上会显示一些内容,我们要停止打印这些信息,查找打印节点很容易,只需打开window -> find in blueprints,搜索Print string

image-20210621153730954

双击函数就可以进入所在蓝图的对应位置,我们点进入这个Ammo的函数

image-20210621153839739

把这个节点删除,就不会显示获得子弹的文本了

image-20210621153854685

再把他们连起来就可以了,我们再去到 level end 下的Print String

image-20210621153949610

在游戏胜利后,我们将不再打印字符串,而是改为创建一个菜单

注意,我们可以在“BP_LevelEnd”蓝图中创建游戏结束菜单,但是,假设此时我们还想执行一些其他操作,比如,我们想保存分数、保存游戏、或执行一些网络操作,我们就不应该把这个逻辑和这个蓝图挂钩,因为它之后可能还会用于其他地方,相反,游戏模式可以和这张地图挂钩,我们可以把游戏结束事件的处理逻辑放在游戏模式中,找到主游戏模式

image-20210623130401212

打开蓝图编辑器,新建一个事件,命名为 HandleGameOver

image-20210621154617058

然后回到删除print String的地方,调用这个事件,它需要由游戏模式来调用,所以我们要get一下game mode,再转换成我们需要的FirstHourUMG

image-20210621154908461

处理完成后,我们再双击这个事件(Handle Game Over),我们就可以自动切回到游戏模式中的该事件所在位置,接下来让我们补充逻辑

image-20210621155514574

创建控件这个流程其实已经演示过很多次了,这里最后再总结一下:create widget -> add to viewport -> set input mode -> set show mouse cursor -> set game paused。

好的,让我们运行一下看看

image-20210621155504028

可以看到在到达胜利区域后,屏幕上可以显示结束菜单了,但这里还有一个小问题,那就是角色的HUD界面仍然显示,我们要把它去掉,这一步操作之前也已经做过了,我们可以直接去角色蓝图复制过来

image-20210621155650926

再次运行,角色HUD已经消失了

image-20210621155704197

通过在UMG系统中添加各类UI事件,整个游戏逻辑已实现正确循环

总结和展望

在这门课中,我们学习了关于UMG控件系统的基础知识,学习了UMG系统的布局方式,学了了一些内置的控件,并且学会了如何用他们创建用户界面,还学习了图表,这些图表可用于为用户界面编写逻辑,并调用自定义事件,我们创建了一个基本的游戏循环逻辑。

posted @ 2021-08-13 17:36  泠枫Jun  阅读(1035)  评论(0编辑  收藏  举报