六一的部落格


关关难过关关过,前路漫漫亦灿灿。



UMG Damage Effect Fix


验证

  • 游戏角色使用第一条生命受伤会有红屏告警
  • 游戏角色复活后, 再受伤会掉血条, 但没有红屏告警

回顾游戏角色受伤红屏实现

粒子系统 > 游戏角色受伤时添加闪烁红屏

  1. 健康组件提供委托成员OnChangeHealth, 当生命值发生改变时, 通知客户端
  2. STUPlayerHUDWidget在Initialize中注册了OnChangeHealth委托
  3. 需要注意的是, 随着游戏角色死亡, 其健康组件也被销毁: 每次重建游戏角色时, STUPlayerHUDWidget需要在其健康组件再次注册OnChangeHealth委托

解决思路

  1. 可以通过UUserWidget::GetOwningPlayer获得窗口组件当前PlayerController
  2. AController::OnNewPawn
    • 访问属性为 protected , 可通过GetOnNewPawnNotifier访问
    • 在AController::UnPossess和AController::Possess中通知客户端
  3. 我们可以注册AController::OnNewPawn服务, 在处理函数中检查是否已注册新Pawn的OnChangeHealth, 如果没有, 注册之

整理窗口部件类

-
STUAboveWidget 关卡使用的窗口部件: 根据游戏角色存活情况决定显示STUPlayerHUDWidget还是STUSpectatorWidget
STUSpectatorWidget 游戏角色死亡后, 使用该窗口部件
STUPlayerHUDWidget 游戏角色存活时, 显示该窗口部件
STUGameDataWidget 于STUPlayerHUDWidget中显示回合信息

创建窗口部件类

-
基类 UserWidget
路径 UI
名称 STUAboveWidget
属性 Public

实现STUAboveWidget

屏蔽USTUPlayerHUDWidget::IsPlayerAlive和USTUPlayerHUDWidget::IsPlayerSpectating, 在STUAboveWidget中实现

ShootThemUp: UI/STUPlayerHUDWidget.h

ShootThemUp: UI/STUPlayerHUDWidget.cpp

public

ShootThemUp: UI/STUAboveWidget.h

1UFUNCTION(BlueprintCallable)
2bool IsPlayerAlive() const;
3
4UFUNCTION(BlueprintCallable)
5bool IsPlayerSpectating() const;
6};

ShootThemUp: UI/STUAboveWidget.cpp

 1#include "Components/STUHealthComponent.h"
 2#include "STUUtils.h"
 3
 4bool USTUAboveWidget::IsPlayerAlive() const
 5{
 6    const auto HealthComponent = STUUtils::GetSTUPlayerComponent<USTUHealthComponent>(GetOwningPlayerPawn());
 7    return HealthComponent && !HealthComponent->IsDead();
 8}
 9
10bool USTUAboveWidget::IsPlayerSpectating() const
11{
12    const auto Controller = GetOwningPlayer();
13    return Controller && Controller->GetStateName() == NAME_Spectating;
14}

修改WBP_AboveHUD

修改基类为STUAboveWidget


修改WBP_PlayerHUD

  • 删除函数IsPlayerAlive


  • 删除弹药信息文本可见性绑定


修改STUPlayerHUDWidget


添加接口: 注册给定Pawn的OnChangeHealth

将当前Initialize注册逻辑移过来

private

ShootThemUp: UI/STUPlayerHUDWidget.h

1void OnRespawn(APawn *Pawn);
  • AController::UnPossess通知客户端时, 传入 nullptr
  • AController::Possess通知客户端时, 传入新Pawn

其实可以不用判断是否已注册委托, 此处仅作学习示例

ShootThemUp: UI/STUPlayerHUDWidget.cpp

 1void USTUPlayerHUDWidget::OnRespawn(APawn *Pawn)
 2{
 3    UE_LOG(LogTemp, Warning, TEXT("STUPlayerHUDWidget is trying to bind OnRespawn, Pawn equals nullptr : %i"), Pawn == nullptr);
 4
 5    const auto HealthComponent = STUUtils::GetSTUPlayerComponent<USTUHealthComponent>(Pawn);
 6    if (HealthComponent && !HealthComponent->OnChangeHealth.IsBoundToObject(this))
 7    {
 8        UE_LOG(LogTemp, Warning, TEXT("It's the first try"));
 9        HealthComponent->OnChangeHealth.AddUObject(this, &USTUPlayerHUDWidget::OnChangeHealth);
10    }
11}

屏蔽原注册逻辑, 改为调用OnRespawn

  1. 窗口部件的Initialize函数在控制器的Possess函数之后调用, 即控制器首次拥有Pawn时, 控制器提供的OnNewPawn委托还未注册上

    可以通过在Initialize和ASTUPlayerController::OnPossess中添加打印验证; 不建议覆写Possess, 而OnPossess在Possess中调用, 因此建议覆写OnPossess
  2. 如果此处不调用OnRespawn, 游戏角色复活前受到伤害无红屏告警

ShootThemUp: UI/STUPlayerHUDWidget.cpp

 1// Initialize
 2
 3/*
 4  const auto HealthComponent = STUUtils::GetSTUPlayerComponent<USTUHealthComponent>(GetOwningPlayerPawn());
 5  if (HealthComponent)
 6  {
 7  HealthComponent->OnChangeHealth.AddUObject(this, &USTUPlayerHUDWidget::OnChangeHealth);
 8  }
 9*/
10OnRespawn(GetOwningPlayerPawn());

注册AController::OnNewPawn

ShootThemUp: UI/STUPlayerHUDWidget.cpp

1// Initialize
2
3if (GetOwningPlayer())
4{
5    GetOwningPlayer()->GetOnNewPawnNotifier().AddUObject(this, &USTUPlayerHUDWidget::OnRespawn);
6}

查看

  1. 第一个框框中的日志对应在USTUPlayerHUDWidget::Initialize中显式调用OnRespawn
  2. 第二个框框为AController::UnPossess处通知客户端
  3. 第三个框框为AController::Possess处通知客户端



单玩家测试受伤红屏方法

  1. BP_STUGameModeBase 中设置 Players Num 为1

  2. BP_STUPlayerController 绑定键位

    添加事件: 按下 1 触发


    对控制器当前游戏角色造成伤害: 无法验证, 当前受到伤害时判断了阵营; 可以加个 Print String 验证触发


  3. WBP_PlayerHUD 中的受伤事件里添加打印


  4. 移除上述测试


说明

  1. 在STUGameModeBase中调用RestartPlayer, 会触发控制器调用Possess, 即触发控制器委托OnNewPawn通知客户端

    动态创建游戏角色 > AGameModeBase::RestartPlayer > AGameModeBase::RestartPlayerAtPlayerStart > AGameModeBase::FinishRestartPlayer

  2. AGameModeBase::RestartPlayerAtPlayerStartAController::OnPossess 中均有调用 AController::SetPawn , 这只是使得可以通过Controller获取Pawn

    AController::OnPossess 还设置了Pawn的Controller, 使得可以通过Pawn获取Controller

    AController::Possess 中广播的条件, 使用 AGameModeBase::RestartPlayer 重建游戏角色是满足的


修复游戏角色复活后受伤无红屏告警


UMG Damage Effect Fix


验证

  • 游戏角色使用第一条生命受伤会有红屏告警
  • 游戏角色复活后, 再受伤会掉血条, 但没有红屏告警

回顾游戏角色受伤红屏实现

粒子系统 > 游戏角色受伤时添加闪烁红屏

  1. 健康组件提供委托成员OnChangeHealth, 当生命值发生改变时, 通知客户端
  2. STUPlayerHUDWidget在Initialize中注册了OnChangeHealth委托
  3. 需要注意的是, 随着游戏角色死亡, 其健康组件也被销毁: 每次重建游戏角色时, STUPlayerHUDWidget需要在其健康组件再次注册OnChangeHealth委托

解决思路

  1. 可以通过UUserWidget::GetOwningPlayer获得窗口组件当前PlayerController
  2. AController::OnNewPawn
    • 访问属性为 protected , 可通过GetOnNewPawnNotifier访问
    • 在AController::UnPossess和AController::Possess中通知客户端
  3. 我们可以注册AController::OnNewPawn服务, 在处理函数中检查是否已注册新Pawn的OnChangeHealth, 如果没有, 注册之

整理窗口部件类

-
STUAboveWidget 关卡使用的窗口部件: 根据游戏角色存活情况决定显示STUPlayerHUDWidget还是STUSpectatorWidget
STUSpectatorWidget 游戏角色死亡后, 使用该窗口部件
STUPlayerHUDWidget 游戏角色存活时, 显示该窗口部件
STUGameDataWidget 于STUPlayerHUDWidget中显示回合信息

创建窗口部件类

-
基类 UserWidget
路径 UI
名称 STUAboveWidget
属性 Public

实现STUAboveWidget

屏蔽USTUPlayerHUDWidget::IsPlayerAlive和USTUPlayerHUDWidget::IsPlayerSpectating, 在STUAboveWidget中实现

ShootThemUp: UI/STUPlayerHUDWidget.h

ShootThemUp: UI/STUPlayerHUDWidget.cpp

public

ShootThemUp: UI/STUAboveWidget.h

1UFUNCTION(BlueprintCallable)
2bool IsPlayerAlive() const;
3
4UFUNCTION(BlueprintCallable)
5bool IsPlayerSpectating() const;
6};

ShootThemUp: UI/STUAboveWidget.cpp

 1#include "Components/STUHealthComponent.h"
 2#include "STUUtils.h"
 3
 4bool USTUAboveWidget::IsPlayerAlive() const
 5{
 6    const auto HealthComponent = STUUtils::GetSTUPlayerComponent<USTUHealthComponent>(GetOwningPlayerPawn());
 7    return HealthComponent && !HealthComponent->IsDead();
 8}
 9
10bool USTUAboveWidget::IsPlayerSpectating() const
11{
12    const auto Controller = GetOwningPlayer();
13    return Controller && Controller->GetStateName() == NAME_Spectating;
14}

修改WBP_AboveHUD

修改基类为STUAboveWidget


修改WBP_PlayerHUD

  • 删除函数IsPlayerAlive


  • 删除弹药信息文本可见性绑定


修改STUPlayerHUDWidget


添加接口: 注册给定Pawn的OnChangeHealth

将当前Initialize注册逻辑移过来

private

ShootThemUp: UI/STUPlayerHUDWidget.h

1void OnRespawn(APawn *Pawn);
  • AController::UnPossess通知客户端时, 传入 nullptr
  • AController::Possess通知客户端时, 传入新Pawn

其实可以不用判断是否已注册委托, 此处仅作学习示例

ShootThemUp: UI/STUPlayerHUDWidget.cpp

 1void USTUPlayerHUDWidget::OnRespawn(APawn *Pawn)
 2{
 3    UE_LOG(LogTemp, Warning, TEXT("STUPlayerHUDWidget is trying to bind OnRespawn, Pawn equals nullptr : %i"), Pawn == nullptr);
 4
 5    const auto HealthComponent = STUUtils::GetSTUPlayerComponent<USTUHealthComponent>(Pawn);
 6    if (HealthComponent && !HealthComponent->OnChangeHealth.IsBoundToObject(this))
 7    {
 8        UE_LOG(LogTemp, Warning, TEXT("It's the first try"));
 9        HealthComponent->OnChangeHealth.AddUObject(this, &USTUPlayerHUDWidget::OnChangeHealth);
10    }
11}

屏蔽原注册逻辑, 改为调用OnRespawn

  1. 窗口部件的Initialize函数在控制器的Possess函数之后调用, 即控制器首次拥有Pawn时, 控制器提供的OnNewPawn委托还未注册上

    可以通过在Initialize和ASTUPlayerController::OnPossess中添加打印验证; 不建议覆写Possess, 而OnPossess在Possess中调用, 因此建议覆写OnPossess
  2. 如果此处不调用OnRespawn, 游戏角色复活前受到伤害无红屏告警

ShootThemUp: UI/STUPlayerHUDWidget.cpp

 1// Initialize
 2
 3/*
 4  const auto HealthComponent = STUUtils::GetSTUPlayerComponent<USTUHealthComponent>(GetOwningPlayerPawn());
 5  if (HealthComponent)
 6  {
 7  HealthComponent->OnChangeHealth.AddUObject(this, &USTUPlayerHUDWidget::OnChangeHealth);
 8  }
 9*/
10OnRespawn(GetOwningPlayerPawn());

注册AController::OnNewPawn

ShootThemUp: UI/STUPlayerHUDWidget.cpp

1// Initialize
2
3if (GetOwningPlayer())
4{
5    GetOwningPlayer()->GetOnNewPawnNotifier().AddUObject(this, &USTUPlayerHUDWidget::OnRespawn);
6}

查看

  1. 第一个框框中的日志对应在USTUPlayerHUDWidget::Initialize中显式调用OnRespawn
  2. 第二个框框为AController::UnPossess处通知客户端
  3. 第三个框框为AController::Possess处通知客户端



单玩家测试受伤红屏方法

  1. BP_STUGameModeBase 中设置 Players Num 为1

  2. BP_STUPlayerController 绑定键位

    添加事件: 按下 1 触发


    对控制器当前游戏角色造成伤害: 无法验证, 当前受到伤害时判断了阵营; 可以加个 Print String 验证触发


  3. WBP_PlayerHUD 中的受伤事件里添加打印


  4. 移除上述测试


说明

  1. 在STUGameModeBase中调用RestartPlayer, 会触发控制器调用Possess, 即触发控制器委托OnNewPawn通知客户端

    动态创建游戏角色 > AGameModeBase::RestartPlayer > AGameModeBase::RestartPlayerAtPlayerStart > AGameModeBase::FinishRestartPlayer

  2. AGameModeBase::RestartPlayerAtPlayerStartAController::OnPossess 中均有调用 AController::SetPawn , 这只是使得可以通过Controller获取Pawn

    AController::OnPossess 还设置了Pawn的Controller, 使得可以通过Pawn获取Controller

    AController::Possess 中广播的条件, 使用 AGameModeBase::RestartPlayer 重建游戏角色是满足的