Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LyraUI

该项目将Lyra中UI模块提取出独立成插件,以便于添加到不同项目中。这篇文档主要讲解如何将该插件接入项目以及一些说明。

Setup

打开插件:

Project Setting设置:

  • 需要配置输入和默认UIPolicy,具体配置在下文中。

测试场景:/All/Plugins/GameCommonUIBase/MainMenu

Widget

GameUIMessagingSubsystem管理GameUIPolicy,GameUIPolicy决定使用什么样的PrimaryGameLayout,接着PrimaryGameLayout会有多个Layer,每个Layer是一个Switcher,用于控制当前Layer下面显示什么Panel。

GameUIManagerSubsystem

GameUIManager的主要作用就是负责UIPolicy的加载与切换,同时在玩家加入游戏时(本地多人)可以调用NotifyPlayerAdded等函数执行一些操作,并且调用UIPolicy中对应的方法。

同时每帧都会设置所有玩家的PrimaryGameLayout和HUD的可见性同步。

GameUIPolicy

GameUIPolicy的工作,一言以蔽之就是根据本地玩家的状态(添加、删除或销毁)和本地多人交互的模式(单人、轮换和多人),来决定将RootLayout向视口加入或删除。并且向外提供一些get函数。 另外需要注意UCLASS宏内的Within = GameUIManagerSubsystem说明GameUIPolicy只能由GameUIManagerSubsystem的实例创建,不能单独创建。

需要在Project Setting中配置:

PrimaryGameLayout

PrimaryGameLayout负责Layer的注册,以及Widget到Layer的push和remove。此外还有布尔类型的值bIsDormant和相关函数,用于给GameUIPolicy判断本地玩家的RootLayout是否显示在屏幕上。

PrimaryGameLayout就是一个UCommonUserWidget。

当前默认的布局如下:

  • GameLayer是游戏内UI,类似于HUD,通常用于显示游戏状态,即生命条、武器槽等。
  • GameMenu是游戏内的菜单,即背包等。
  • Menu是游戏菜单,例如游戏开始界面、设置界面等。
  • Modal是游戏弹窗,例如保存游戏或者更改游戏设置时弹窗的确定弹窗之类。

每个层就是一个UCommonActivatableWidgetStack,内部包含了一个Switcher,将Panel添加到Layer时,会将Panel添加到Switcher里,再通过设置switcher的index来切换Panel。

每一个Layer对应着一个GameplayTag:

将Panel加到Layer中使用:

将Panel移除Layer使用:

Split Screen

Lyra中为了实现分屏UI,设计了GameUIPolicy和PrimaryGameLayout。这里可以简单理解:

  • 在多人游戏且在同一台机器上运行时,UI的规则是GameUIPolicy A,每一个玩家有一个PrimaryGameLayout A,也就是UI布局。
  • 在单人游戏时,UI的规则GameUIPolicy B。现在的玩家的UI布局就是PrimaryGameLayout B

这样有些抽象,可以看下图:

由此可以知道一个玩家对应自己UI布局,在项目中使用LocalPlayer来定义的玩家信息。

根据不同玩家加载不同的UI布局触发流程:

  • UGameUIGameInstance::AddLocalPlayer 有本地玩家加入到游戏
  • UGameUIManagerSubsystem::NotifyPlayerAdded
  • UGameUIPolicy::NotifyPlayerAdded 监听本地玩家控制器设置
  • AGameUIPlayerController::ReceivedPlayer 本地玩家控器设置完成
  • UGameUILocalPlayer::OnPlayerControllerSet 触发委托
  • UGameUIPolicy::CreateLayoutWidget 创建布局UI(PrimaryGameLayout)
  • UGameUIPolicy::AddLayoutToViewport 添加布局UI到视窗

所以需要需要将以下文件中的实现复制到项目中对应的文件中去:

  • GameUILocalPlayer
  • GameUIGameInstance
  • GameUIPlayerController

同时需要在Project Setting和GameMode中设置对应。

值得注意的是如果游戏不需要分屏UI这种功能时,GameUIPolicy是可以不需要的,在游戏开始时直接将PrimaryGameLayout加载的视窗渲染即可。

Panel

因为Panel都是有Switcher管理的,所以所有的Panel都需要继承UGameUIActivatablePanel,也就是需要继承至UCommonActivatableWidget。这样才能有Switcher控制显示隐藏。

CommonActivatableWidget

  • CommonActivatableWidget在Create出来后默认是Deactivate状态,需要手动Activate:
  • 也可以在Details-Activation中勾选Auto Activate,勾选后会在Widget Create后自动Activate,Remove后自动Deactivate。
  • CommonActivatableWidget可以覆写OnActivated和OnDeactivated来配置改变Activated状态后的回调:
  • 必须实现GetDesiredFocusTarget方法,用于激活面板时聚焦那个Widget。当CommonActivatableWidget(及其子类对象)成为主激活控件时,可以在蓝图中通过重写这个函数来设置默认聚焦的控件对象。
    • 请注意: 当勾选了bAutoRestoreFocus时,系统在恢复聚焦时会先检查是否保存了有效的聚焦对象(Widget)。如果该对象不存在,系统才会调用 GetDesiredFocusTarget。
    • RequestRefreshFocus 允许在满足特定条件(当前节点是叶节点)的情况下,重新设置焦点到期望的焦点目标,而不需要对每个元素都进行Wrap.
    • 蓝图:
  • 即使CommonActivatableWidget被Collapsed了,如果CommonActivatableWidget没有被Deactivate的话,依然会影响输入路由。
  • 默认情况下CommonActivatableWidget的Activate与Deactivate是不会影响Widget的Visibility的,可以在Details-Activation中单独配置:
  • OnHandleBackAction :当CommonActivatableWidget(及其子类对象)被激活为主控件时,通常需要处理返回操作的相关逻辑。为了方便处理返回事件,CommonUI提供了一些配置,当触发返回输入时,可以调用BP_OnHandleBackAction函数来优化返回事件的处理逻辑。这样可以使返回事件的绑定逻辑更加清晰和易于维护。

Slot Panel

其中有一个特殊的Panel - GameUIHUDLayoutPanel,用于制作游戏中的HUD也就是战斗中UI。特殊点是它使用GameUISlotPointWidget对子Widget进行占位,使用GameplayTag对应子Widget,动态加载不同的子Widget。

这样做到子Widget和Panel之间的完全解耦。

其中WBP_GameHUDLayout如下图:

在GameUISlotPointWidget对应配置:

加载GameplayTag对应的widget和卸载:

子Widget:

最终显示效果:

其原理是在GameUISlotPointWidget在构造时,会去执行RebuildWidget方法,在该方法把SlotPointTag注册到GameUISlotSubSystem中,然后存储起来。

当调用GameUISlotSubSystem::RegisterSlot时,也就是上图。会去查询SlotPointTag是否有GameUISlotPointWidget注册过。如果有就将子widget创建出来。

值得注意的是,如果没有需要Panel中布局是一定的,内容会根据情况不同的需求时。直接使用完整的panel,做会更加只管一些,不一定非要用这中方式。

Message

调用方式:

也可以直接调用:

  • UGameUIMessagingSubsystem::ShowConfirmation
  • UGameUIMessagingSubsystem::ShowError

需要在Project Setting中配置默认的Panel:

Widget

在Widget目录下有一些复写的基础控件

InputActionWidget

一般会配合着按键使用,作为子Widget放在按键的Widget蓝图中。如果配置Input Actions也就是下图:

意味着指定按键对应该触发点击事件。如果不指定就是默认按键触发点击事件。

Button

基础按键结构如下:

Tab

设置选项卡配置:

每一个选项都要可以设置:

  • 选项按钮
  • 选项内容Widget

需要将内容控制的Switcher注册到选项列表组件中:

Widget Focus

Input

在Common UI中一定不要使用任何关于Enhanced Input相关的设置,Common UI对Enhanced Input的适配并没有非常的完善。如果Gameplay中使用了Enhanced Input,也会跟UI的输入打架。所以这里就是使用Common Input即可。

在引擎中,处理输入的流程大概如下:

CommonUI主要对上图中的两个红色区域进行了一些自定义输入的扩展。

Setup

  • 首先新建一个输入数据表
  • 在该表中新建4个按键行为
  • 接着创建InputData
  • 默认有设置对应的InputActionTable,这个表中几乎有所有按键的设置,如果需要可以直接复制其中的选项到我们新建的表即可。
  • 在Project Setting中就可以设置输入数据
  • 接着创建Controller Data
  • 该数据是设置不同平台的不同设配的控制数据,这里PC就可以创建两个
  • 设置按键与图片
  • 在设置Gamepad Name时,需要和Project Setting中对应平台的Default Gamepad Name一致

Panel

GameUIActivatablePanel的InputConfig中可以配置该Widget的输入模式,一旦Widget被Activate,会立刻切换至该Widget的输入模式:

  • Default:默认是GamePlay和Widget都可以接收输入。
  • Game and Menu:同理。
  • Game:只有Gameplay能够接收输入,自动隐藏鼠标。
  • Menu:只有Widget能够接收输入,对玩家的控制等都会被屏蔽,且会自动显示鼠标。 也可以配置鼠标的捕获模式:

  • NoCapture:鼠标不会捕获此窗口的鼠标事件。
  • CapturePermanently:此窗口会捕获玩家的鼠标事件。
  • CapturePermanentlyIncludingInitialMouseDown:会捕获玩家的鼠标事件,并且除了鼠标点击外还能够处理鼠标按下的事件。
  • CaptureDuringMouseDown:鼠标按下触发捕获,抬起releases。
  • CaptureDuringRightMouseDown:鼠标右键按下触发捕获。

CommonAnalogCursor

为了让Gamepad操作沿用鼠标的处理逻辑,CommonUI引入了合成光标FCommonAnalogCursor。它可以通过摇杆/Dpad导航到一个可交互的UI控件上,然后通过点击“EKeys::Virtual_Accept”按键来模拟鼠标左键点击UI

继承结构:

为了在使用Gamepad操作时显示光标,我们需要执行以下控制台命令:

  • CommonUI.AlwaysShowCursor 1
  • CommonInput.EnableGamepadPlatformCursor 1

FAnalogCursor类中实现了摇杆移动多少,鼠标就移动多少的逻辑。对于不想通过导航来选中UI的情况,可以考虑使用这种方式。目前在Common中,它只被当做调试时可用的选项。如果想要了解更多,请在FCommonAnalogCursor中搜索关键字bIsAnalogMovementEnabled

通过Gamepad按键来模拟鼠标点击的执行流程如下:

光标位置的更新逻辑在FCommonAnalogCursor::Tick函数中实现。该逻辑将光标移动到当前聚焦的UI控件的中心点,以方便我们后续通过“EKeys::Virtual_Accept”按键来点击该UI.

光标移动到聚焦控件的简化逻辑如下:

Default Input

如果想要修改默认按键映射,也就是默认手柄确认按键是A,如果想要修改为Y的话。直接修改Input Data中的配置并不能生效。使用Common Input的话,需要修改源码,也就是下面这里:

所以并不建议修改默认按键映射。

EKeys::Virtual_Accept是一个虚拟按键,它在不同的平台可能对应不同的具体按键。默认情况下,EKeys::Virtual_Accept对应于“Gamepad_FaceButton_Bottom”

InputCoreTypes.cpp

const FKey EKeys::Virtual_Accept = FPlatformInput::GetGamepadAcceptKey();
ControlType EKey Key File
Snoy Gamepad_FaceButton_Bottom X
XBOX360 Gamepad_FaceButton_Bottom A
Switch Gamepad_FaceButton_Right A SwitchPlatformInput.cpp

输入预处理层的扩展

CommonUI对输入预处理进行了扩展,新增了两个输入预处理类:

  • FCommonAnalogCursor:这个类主要实现了通过光标模拟鼠标点击UI和使用摇杆滚动UI的逻辑。

  • FCommonEnhancedInputProcessor:用于CommonUI监听和处理输入设备切换事件以及过滤指定输入类型的处理。

  • GameViewportClient层输入的扩展:CommonUI在GameViewportClient层进行了自定义扩展,主要实现了UI绑定按键的功能。它会根据UI的激活状态、UI的层级以及所绑定按键的状态来执行UI的按键绑定逻辑。

Display Name

在按键上会被设置:

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages