暂停窗口
2023年11月22日 2024年1月11日
Game Pause
说明
根据上节游戏状态, 会有3个窗口部件
游戏状态 | 窗口部件 | |
---|---|---|
InProgress | WBP_PlayerHUDWidget | |
Pause | WBP_PauseHUDWidget | 暂停, 可选择继续 - 本节内容 |
GameOver | 游戏结束, 显示统计数据 - 下节内容 |
本节实现暂停窗口: 游戏中按下 P
显示暂停窗口, 可于暂停窗口选择回到游戏
概览
- 在STUGameHUD创建三种窗口部件
- 依据游戏状态, 设置窗口部件可见性
- 实现暂停窗口部件
- 实现键位绑定
API导航
TMap
关联容器
- | |
---|---|
Add | 添加元素 |
[] | 键作为下标, 获取值 |
Contains | 查询是否存在给定键 |
TPair
键值对
- | |
---|---|
::Value | 获得键值对的值 |
UserWidget
可见性
-
UUserWidget::SetVisibility
设置窗口组件可见性
1/** Sets the visibility of the widget. */ 2virtual void SetVisibility(ESlateVisibility InVisibility) override;
-
ESlateVisibility
可见选项
- Visible 可见 Hidden 隐藏 1/** Is an entity visible? */ 2UENUM(BlueprintType) 3enum class ESlateVisibility : uint8 4{ 5 /** Visible and hit-testable (can interact with cursor). Default value. */ 6 Visible, 7 /** Not visible and takes up no space in the layout (obviously not hit-testable). */ 8 Collapsed, 9 /** Not visible but occupies layout space (obviously not hit-testable). */ 10 Hidden, 11 /** Visible but not hit-testable (cannot interact with cursor) and children in the hierarchy (if any) are also not hit-testable. */ 12 HitTestInvisible UMETA(DisplayName = "Not Hit-Testable (Self & All Children)"), 13 /** Visible but not hit-testable (cannot interact with cursor) and doesn't affect hit-testing on children (if any). */ 14 SelfHitTestInvisible UMETA(DisplayName = "Not Hit-Testable (Self Only)") 15};
初始化
1virtual bool Initialize();
PlayerController
键位绑定所在
1/** Allows the PlayerController to set up custom input bindings. */ 2virtual void SetupInputComponent();
成员InputComponent没在头文件中找到定义
鼠标受控
bShowMouseCursor | |
---|---|
true | 显示鼠标箭头 |
false | 不显示鼠标箭头 |
1/** Whether the mouse cursor should be displayed. */ 2UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=MouseInterface) 3uint32 bShowMouseCursor:1;
设置
1/** Setup an input mode. */ 2virtual void SetInputMode(const FInputModeDataBase& InData);
FInputModeDataBase派生类 | |
---|---|
FInputModeUIOnly | 只有UI可以响应用户输入 |
FInputModeGameOnly | 只有玩家输入和PlayerController可以响应用户输入 |
FInputModeGameAndUI | UI优先响应用户输入, 如果UI未作出处理, 玩家输入和PlayerController可以响应用户输入 |
1/** Data structure used to setup an input mode that allows only the UI to respond to user input. */ 2struct ENGINE_API FInputModeUIOnly : public FInputModeDataBase 3{ 4 // ... 5}; 6 7/** Data structure used to setup an input mode that allows only the player input / player controller to respond to user input. */ 8struct ENGINE_API FInputModeGameOnly : public FInputModeDataBase 9{ 10 // ... 11}; 12 13 14/** Data structure used to setup an input mode that allows the UI to respond to user input, and if the UI doesn't handle it player input / player controller gets a chance. */ 15struct ENGINE_API FInputModeGameAndUI : public FInputModeDataBase 16{ 17 // ... 18};
GameModeBase
暂停游戏
- | |
---|---|
PC | 给出暂停行为的主导者, 用于权限检查 |
CanUnpauseDelegate | 委托, 用于检查是否可以暂停 |
会暂停所有Actor的Tick
1/** 2 * Adds the delegate to the list if the player Controller has the right to pause 3 * the game. The delegate is called to see if it is ok to unpause the game, e.g. 4 * the reason the game was paused has been cleared. 5 * @param PC the player Controller to check for admin privs 6 * @param CanUnpauseDelegate the delegate to query when checking for unpause 7 */ 8virtual bool SetPause(APlayerController* PC, FCanUnpause CanUnpauseDelegate = FCanUnpause());
清除暂停
使所有Actor的Tick运行起来
1/** 2 * Checks the list of delegates to determine if the pausing can be cleared. If 3 * the delegate says it's ok to unpause, that delegate is removed from the list 4 * and the rest are checked. The game is considered unpaused when the list is 5 * empty. 6 */ 7virtual bool ClearPause();
Button
类似的委托还有OnPressed, OnReleased, OnHovered等等
1DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonClickedEvent); 2 3/** Called when the button is clicked */ 4UPROPERTY(BlueprintAssignable, Category="Button|Event") 5FOnButtonClickedEvent OnClicked;
在STUGameHUD中管理窗口部件
添加属性: 保存暂停窗口部件类型
protected
ShootThemUp: UI/STUGameHUD.h
1UPROPERTY(EditDefaultsOnly, BlueprintReadWrite) 2TSubclassOf<UUserWidget> PauseWidgetClass;
添加属性: 游戏状态-窗口部件对象键值对
使用UPROPERTY宏托管指针
private
ShootThemUp: UI/STUGameHUD.h
1UPROPERTY() 2TMap<ESTUMatchState, UUserWidget *> GameWidgets;
添加美化接口: 创建窗口部件并添加到视口, 初始化键值对, 并设置窗口部件对象均不可见
private
ShootThemUp: UI/STUGameHUD.h
1void InitGameWidgets();
- 在BeginPlay中屏蔽PlayerHUDWidget初始化
ShootThemUp: UI/STUGameHUD.cpp
1/* 2auto PlayerHUDWidget = CreateWidget<UUserWidget>(GetWorld(), PlayerHUDWidgetClass); 3if (PlayerHUDWidget) 4{ 5 PlayerHUDWidget->AddToViewport(); 6} 7*/
- 在BeginPlay中调用
ShootThemUp: UI/STUGameHUD.cpp
- 实现
ShootThemUp: UI/STUGameHUD.cpp
1void ASTUGameHUD::InitGameWidgets() 2{ 3 GameWidgets.Add(ESTUMatchState::InProgress, CreateWidget<UUserWidget>(GetWorld(), PlayerHUDWidgetClass)); 4 GameWidgets.Add(ESTUMatchState::Pause, CreateWidget<UUserWidget>(GetWorld(), PauseWidgetClass)); 5 6 for (auto GameWidgetPair : GameWidgets) 7 { 8 const auto GameWidget = GameWidgetPair.Value; 9 if (!GameWidget) continue; 10 11 GameWidget->AddToViewport(); 12 GameWidget->SetVisibility(ESlateVisibility::Hidden); 13 } 14}
添加属性: 指向当前可见窗口部件
- 初始指向空
- 当GameMode调用StartPlay时, 第一次进行设置
- 使用UPROPERTY宏托管指针
private
ShootThemUp: UI/STUGameHUD.h
1UPROPERTY() 2UUserWidget *CurrentWidget = nullptr;
游戏状态发生改变时, 修改窗口部件可见性
ShootThemUp: UI/STUGameHUD.cpp
1// OnMatchStateChanged 2 3if (CurrentWidget) 4{ 5 CurrentWidget->SetVisibility(ESlateVisibility::Hidden); 6} 7 8if (GameWidgets.Contains(NewState)) 9{ 10 CurrentWidget = GameWidgets[NewState]; 11} 12 13if (CurrentWidget) 14{ 15 CurrentWidget->SetVisibility(ESlateVisibility::Visible); 16}
设计暂停窗口部件
创建C++类
- | |
---|---|
基类 | UUserWidget |
路径 | UI |
名称 | STUPauseWidget |
属性 | Public |
创建蓝图窗口部件
去到 Content/UI
, 创建蓝图窗口部件, 基类STUPauseWidget, 命名为WBP_PauseWidget
设置WBP_PauseWidget
-
添加
Canvas Panel
-
添加
Background Blur
模糊背景, 后期处理特效- Anchors 平铺 Offset 均为0 BlurStrength 模糊强度: 4 -
添加垂直对齐盒
- Anchors 居中 Position X 0 Position Y 0 Alignment X = Y = 0.5 Size To Content 勾选 -
添加文本
- Text GAME PAUSE Font > Size 100 -
添加分隔符
- Size > Y 纵向间距: 40 -
添加按钮
默认为变量, 重命名为
ClearPauseButton
- Horizontal Alignment 居中 Vertical Alignment 居中 -
为按钮添加上级: 大小盒
- Width 300 Height 100 -
设置ClearPauseButton
- Hex Linear Normal FF0010FF 常规 Hover 000000FF 鼠标悬浮 Pressed FF0010FF 点击时 -
添加文本
添加键位绑定
Edit > Project Settings.. > Engine > Input
- | |
---|---|
键位 | P |
绑定类型 | 动作映射 Action Mapping |
键位描述 | PauseGame |
STUGameModeBase实现设置/取消暂停功能
- 在基类函数基础上, 更新游戏状态
- 注意到, 返回类型为布尔, 即存在设置不成功的情况
public
ShootThemUp: STUGameModeBase.h
1virtual bool SetPause(APlayerController* PC, FCanUnpause CanUnpauseDelegate = FCanUnpause()) override; 2virtual bool ClearPause() override;
ShootThemUp: STUGameModeBase.cpp
1bool ASTUGameModeBase::SetPause(APlayerController* PC, FCanUnpause CanUnpauseDelegate) 2{ 3 const auto PauseSet = Super::SetPause(PC, CanUnpauseDelegate); 4 5 if (PauseSet) 6 { 7 SetMatchState(ESTUMatchState::Pause); 8 } 9 10 return PauseSet; 11} 12 13bool ASTUGameModeBase::ClearPause() 14{ 15 const auto PauseCleared = Super::ClearPause(); 16 17 if (PauseCleared) 18 { 19 SetMatchState(ESTUMatchState::InProgress); 20 } 21 22 return PauseCleared; 23}
绑定键位描述
考虑到游戏角色会死亡, 而复活冷却也可以使用暂停功能: 为PlayerController添加键位绑定
- 添加回调函数
private
ShootThemUp: Player/STUPlayerController.h
1void OnPauseGame();
- 覆写SetupInputComponent: 绑定键位
protected
ShootThemUp: Player/STUPlayerController.h
1virtual void SetupInputComponent() override;
ShootThemUp: Player/STUPlayerController.cpp
1void ASTUPlayerController::SetupInputComponent() 2{ 3 Super::SetupInputComponent(); 4 if (!InputComponent) return; 5 InputComponent->BindAction("PauseGame", IE_Pressed, this, &ASTUPlayerController::OnPauseGame); 6}
- 实现回调函数
ShootThemUp: Player/STUPlayerController.cpp
1#include "GameFrameWork/GameModeBase.h" 2#include "Engine/World.h" 3 4void ASTUPlayerController::OnPauseGame() 5{ 6 if (!GetWorld() || !GetWorld()->GetAuthGameMode()) return; 7 8 GetWorld()->GetAuthGameMode()->SetPause(this); 9}
实现STUPauseWidget
获取按钮
和蓝图窗口部件中按钮的变量名一致
protected
ShootThemUp: UI/STUPauseWidget.h
1class UButton; 2 3UPROPERTY(meta = (BindWidget)) 4UButton *ClearPauseButton;
添加点击按钮的回调函数
private
ShootThemUp: UI/STUPauseWidget.h
1UFUNCTION() 2void OnClearPause();
ShootThemUp: UI/STUPauseWidget.cpp
1#include "GameFramework/GameModeBase.h" 2 3void USTUPauseWidget::OnClearPause() 4{ 5 if (!GetWorld() || !GetWorld()->GetAuthGameMode()) return; 6 7 GetWorld()->GetAuthGameMode()->ClearPause(); 8}
为按钮点击事件绑定回调函数
覆写Initialize
public
ShootThemUp: UI/STUPauseWidget.h
1virtual bool Initialize() override;
注意: 未调用基类Initialize, ClearPauseButton为空
ShootThemUp: UI/STUPauseWidget.cpp
1#include "Components/Button.h" 2 3bool USTUPauseWidget::Initialize() 4{ 5 bool InitStatus = Super::Initialize(); 6 7 if (ClearPauseButton) 8 { 9 ClearPauseButton->OnClicked.AddDynamic(this, &USTUPauseWidget::OnClearPause); 10 } 11 12 return InitStatus; 13}
查看
-
设置
BP_STUGameHUD
- Pause Widget Class WBP_PauseWidget -
设置
BP_STUGameModeBase
- Round Time 30 -
鼠标不在按钮上
-
鼠标在
Continue
上
当前
- 运行游戏后, 需要鼠标点击一下才能进入游戏
- 按下
P
后, 需要按下Shift-F1
使鼠标脱离控制 - 点击
Continue
后, 还需鼠标点击一下
STUPlayerController设置鼠标受控
private
ShootThemUp: Player/STUPlayerController.h
1#include "STUCoreTypes.h" 2 3void OnMatchStateChanged(ESTUMatchState NewState);
ShootThemUp: UI/STUPauseWidget.cpp
1void ASTUPlayerController::OnMatchStateChanged(ESTUMatchState NewState) 2{ 3 if (NewState == ESTUMatchState::InProgress) 4 { 5 bShowMouseCursor = false; 6 SetInputMode(FInputModeGameOnly()); 7 } 8 else 9 { 10 bShowMouseCursor = true; 11 SetInputMode(FInputModeUIOnly()); 12 } 13}
添加BeginPlay函数, 在其中注册游戏状态改变服务
protected
ShootThemUp: Player/STUPlayerController.h
1virtual void BeginPlay() override;
ShootThemUp: Player/STUPlayerController.cpp
1// #include "GameFrameWork/GameModeBase.h" 2#include "Engine/World.h" 3#include "STUGameModeBase.h" 4 5void ASTUPlayerController::BeginPlay() 6{ 7 Super::BeginPlay(); 8 9 if (GetWorld()) 10 { 11 const auto GameMode = Cast<ASTUGameModeBase>(GetWorld()->GetAuthGameMode()); 12 if (GameMode) 13 { 14 GameMode->OnMatchStateChanged.AddUObject(this, &ASTUPlayerController::OnMatchStateChanged); 15 } 16 } 17}
说明
如果只设置bShowMouseCursor
- 游戏开始时, 仍需鼠标点击一下
- 按下
P
之后, 鼠标箭头出现, 但活动区域仅限视口, 且鼠标悬浮在按钮之上时, 按钮未变成黑色
Selected Viewport
- 鼠标尝试点击按钮, 这才和之前
Shift-F1
效果一样, 鼠标脱离控制
修改
之前误把Initialize声明在 private
区域, 移动到 public
ShootThemUp: UI/STUPlayerHUDWidget.h