六一的部落格


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



Widget Component / Health Bar


素材

用作生命条的前景图片和背景图片, UE可以修改颜色



导入素材

创建文件夹 Content/ExternalContent/HealthBar , 将图片导入



说明

  1. 生命条使用窗口部件实现, 有窗口部件组件, 可以拿来给AICharacter用
  2. 血量较多时不显示生命条, NPC差不多挂了不再显示, NPC距离较远时也不显示

    和NPC存在阻挡时显示
  3. 血量较低时, 剩余血量显示为红色
  4. 修改生命条背景和前景图片: 黑色用作背景, 白色用作前景

创建C++类

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

创建蓝图窗口

-
基类 STUHealthBarWidget
路径 UI
名称 WBP_HealthBar

设计WBP_HealthBar

  1. 添加 Canvas Panel

  2. 添加 Progress Bar

    -
    Percent 进度
    Fill Color and Opacity 填充色

    设置大小, 查看默认状态


    修改背景图片和背景色; 修改前景图片; 修改填充色; 修改变量名

    -
    Background Image > Image 背景图片 Content/ExternalContent/HealthBar/white
    Background Image > Tint 背景色 000000FF
    Fill Image > Image 前景图片 Content/ExternalContent/HealthBar/white
    Fill Image > Tint 前景色; 填充时, 和填充色叠加显示
    Fill Color and Opacity 填充色 FFFFFFFF
    变量名 HealthProgressBar



演示NPC顶部添加生命条


设置AICharacter

BP_STUAICharacter

  1. 添加窗口部件组件


  2. 设置窗口部件组件

    用来渲染窗口部件

    -
    Widget Class WBP_HealthBar
    Transform > Location > Z 120
    Draw as Desired Size 勾选; 按设计大小显示
    Space 默认值为World, 即在世界座标系中渲染窗口部件



设置行为树

BT_STUCharacter

屏蔽NPC移动, 便于测试



查看

  1. 在世界座标系中渲染, 正面查看


  2. 在世界座标系中渲染, 侧面查看



设置生命条在STUGameHUD中渲染

BP_STUAICharacter


  1. 生命条无侧面表示, 远处看较大


  2. 近处看生命条大小合适, 侧面显示ok


  3. 即使玩家操控的游戏角色和NPC在视线上有阻挡, 仍可见生命条


    本节不会解决该问题

也可以通过按摄像机角度旋转生命条来解决


移除窗口部件组件

BP_STUAICharacter


API导航


ProgressBar

  1. 设置进度百分比
    1/** Sets the current value of the ProgressBar. */
    2UFUNCTION(BlueprintCallable, Category="Progress")
    3void SetPercent(float InPercent);
  2. 设置填充色
    1/** Sets the fill color of the progress bar. */
    2UFUNCTION(BlueprintCallable, Category="Progress")
    3void SetFillColorAndOpacity(FLinearColor InColor);

WidgetComponent

  1. 设置渲染模式

    1void SetWidgetSpace( EWidgetSpace NewSpace ) { Space = NewSpace; }
    -
    World 在世界座标系中渲染
    Screen 在HUD中渲染
    1UENUM(BlueprintType)
    2enum class EWidgetSpace : uint8
    3{
    4    /** The widget is rendered in the world as mesh, it can be occluded like any other mesh in the world. */
    5    World,
    6    /** The widget is rendered in the screen, completely outside of the world, never occluded. */
    7    Screen
    8};
  2. 设置绘制大小

    按窗口部件设计中的大小进行绘制

    1/**  */
    2UFUNCTION(BlueprintCallable, Category = UserInterface)
    3void SetDrawAtDesiredSize(bool bInDrawAtDesiredSize) { bDrawAtDesiredSize = bInDrawAtDesiredSize; }
  3. 获取窗口部件

    1/** Returns the user widget object displayed by this component */
    2UFUNCTION(BlueprintCallable, Category=UserInterface, meta=(UnsafeDuringActorConstruction=true))
    3UUserWidget* GetUserWidgetObject() const;

PlayerController

如果玩家操控的游戏角色死亡, PlayerController会有观察Pawn, 不然, 当前Pawn

优先返回当前Pawn

1/** Returns the first of GetPawn() or GetSpectatorPawn() that is not nullptr, or nullptr otherwise. */
2APawn* GetPawnOrSpectator() const;
3
4APawn* APlayerController::GetPawnOrSpectator() const
5{
6    return GetPawn() ? GetPawn() : GetSpectatorPawn();
7}

World

获取PlayerController

1/** @return Returns the first player controller, or NULL if there is not one. */	
2APlayerController* GetFirstPlayerController() const;

FVector

计算距离

1/**
2 * Euclidean distance between two points.
3 *
4 * @param V1 The first point.
5 * @param V2 The second point.
6 * @return The distance between two points.
7 */
8static FORCEINLINE T Dist(const TVector<T> &V1, const TVector<T> &V2);
9static FORCEINLINE T Distance(const TVector<T> &V1, const TVector<T> &V2) { return Dist(V1, V2); }

类的派生关系

 1class UMG_API UWidgetComponent : public UMeshComponent
 2{
 3    // ...
 4};
 5
 6class ENGINE_API UMeshComponent : public UPrimitiveComponent
 7{
 8    // ...
 9};
10
11class ENGINE_API UPrimitiveComponent : public USceneComponent, public INavRelevantInterface, public IInterface_AsyncCompilation
12{
13    // ...
14};

实现STUHealthBarWidget


添加属性: 进度条

ShootThemUp: UI/STUHealthBarWidget.h

1class UProgressBar;
2
3 UPROPERTY(meta = (BindWidget))
4 UProgressBar *HealthProgressBar;

添加属性: 影响生命条可见性和填充色

-
PercentVisibilityThreshold 生命值百分比大于该数值时, 不显示生命条
PercentColorThreshold 生命值百分比大于该数值时, 使用默认填充色, 否则, 使用告警填充色
GoodColor 默认填充色
BadColor 生命值较低时, 使用的填充色

protected

ShootThemUp: UI/STUHealthBarWidget.h

 1UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
 2float PercentVisibilityThreshold = 0.8f;
 3
 4UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
 5float PercentColorThreshold = 0.3f;
 6
 7UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
 8FLinearColor GoodColor = FLinearColor::White;
 9
10UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
11FLinearColor BadColor = FLinearColor::Red;

添加接口: 设置生命条可见性和生命值百分比

public

ShootThemUp: UI/STUHealthBarWidget.h

1void SetHealthPercent(float HealthPercent);

NPC近乎死亡时, 不再显示生命条

ShootThemUp: UI/STUHealthBarWidget.cpp

 1#include "Components/ProgressBar.h"
 2
 3void USTUHealthBarWidget::SetHealthPercent(float HealthPercent)
 4{
 5    if (!HealthProgressBar) return;
 6
 7    HealthProgressBar->SetPercent(HealthPercent);
 8    const auto Visibility = (HealthPercent < PercentVisibilityThreshold) && !FMath::IsNearlyZero(HealthPercent);
 9    HealthProgressBar->SetVisibility(Visibility ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
10    HealthProgressBar->SetFillColorAndOpacity(HealthPercent > PercentColorThreshold ? GoodColor : BadColor);
11}

修改STUAICharacter


添加窗口部件组件

protected

ShootThemUp: AI/STUAICharacter.h

1class UWidgetComponent;
2
3UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
4UWidgetComponent *HealthWidgetComponent;

在构造函数中初始化并设置

ShootThemUp: AI/STUAICharacter.cpp

1#include "Components/WidgetComponent.h"
2
3// 构造函数
4
5HealthWidgetComponent = CreateDefaultSubobject<UWidgetComponent>("HealthWidgetComponent");
6HealthWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
7HealthWidgetComponent->SetDrawAtDesiredSize(true);
8HealthWidgetComponent->SetupAttachment(GetRootComponent());

覆写BeginPlay函数: 确定窗口部件组件不为空

protected

ShootThemUp: AI/STUAICharacter.h

1virtual void BeginPlay() override;

ShootThemUp: AI/STUAICharacter.cpp

1void ASTUAICharacter::BeginPlay()
2{
3    Super::BeginPlay();
4
5    check(HealthWidgetComponent);
6}

将生命值修改回调函数改为虚函数

STUAICharacter需要对其进行覆写

虚函数, 且访问属性为 protected

ShootThemUp: Player/STUBaseCharacter.h

1// private
2// void OnChangeHealth(float HealthDelta);
3
4// protected
5virtual void OnChangeHealth(float HealthDelta);

STUAICharacter覆写OnChangeHealth

protected

ShootThemUp: AI/STUAICharacter.h

1virtual void OnChangeHealth(float HealthDelta) override;

获取生命条窗口部件组件, 访问其包含的窗口部件, 调用提供的接口

ShootThemUp: AI/STUAICharacter.cpp

 1#include "UI/STUHealthBarWidget.h"
 2#include "Components/STUHealthComponent.h"
 3
 4void ASTUAICharacter::OnChangeHealth(float HealthDelta)
 5{
 6    Super::OnChangeHealth(HealthDelta);
 7
 8    const auto HealthBarWidget = Cast<USTUHealthBarWidget>(HealthWidgetComponent->GetUserWidgetObject());
 9    if (HealthBarWidget)
10    {
11        HealthBarWidget->SetHealthPercent(HealthComponent->GetHealthPercent());
12    }
13}

若NPC与玩家操控的游戏角色距离较远, 不显示生命条

  1. 添加属性: 生命条显示距离

    protected

    ShootThemUp: AI/STUAICharacter.h
    1UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
    2float HealthVisibilityDistance = 1000.0f;
  2. 添加美化函数

    private

    ShootThemUp: AI/STUAICharacter.h
    1void UpdateHealthWidgetVisibility();
    ShootThemUp: AI/STUAICharacter.cpp
    1void ASTUAICharacter::UpdateHealthWidgetVisibility()
    2{
    3    if (!GetWorld() || !GetWorld()->GetFirstPlayerController() || !GetWorld()->GetFirstPlayerController()->GetPawnOrSpectator()) return;
    4
    5    const auto Location = GetWorld()->GetFirstPlayerController()->GetPawnOrSpectator()->GetActorLocation();
    6    const auto Distance = FVector::Distance(Location, GetActorLocation());
    7
    8    HealthWidgetComponent->SetVisibility(Distance < HealthVisibilityDistance, true);
    9}
  3. 覆写Tick函数

    public

    ShootThemUp: AI/STUAICharacter.h
    1virtual void Tick(float DeltaTime) override;
    ShootThemUp: AI/STUAICharacter.cpp
    1void ASTUAICharacter::Tick(float DeltaTime)
    2{
    3    Super::Tick(DeltaTime);
    4
    5    UpdateHealthWidgetVisibility();
    6}

完善逻辑: NPC生命条组件拥有初始值

当前, NPC首次受伤之后, 才拥有正确的生命值

ShootThemUp: AI/STUAICharacter.cpp

1// BeginPlay
2
3const auto HealthBarWidget = Cast<USTUHealthBarWidget>(HealthWidgetComponent->GetUserWidgetObject());
4if (HealthBarWidget)
5{
6    HealthBarWidget->SetHealthPercent(HealthComponent->GetHealthPercent());
7}

查看

设置窗口部件


修改回合时长为120s Round Time

BP_STUGameModeBase

可以把玩家个数设多点, 方便测试 Players Num

  1. NPC未受伤时无生命条


  2. NPC受伤后出现生命条


  3. NPC生命值较低时, 生命条填充色为红色


  4. NPC死亡, 生命条消失


NPC受伤, 但与玩家操控的游戏角色距离较远时, 生命条不可见


显示NPC生命条


Widget Component / Health Bar


素材

用作生命条的前景图片和背景图片, UE可以修改颜色



导入素材

创建文件夹 Content/ExternalContent/HealthBar , 将图片导入



说明

  1. 生命条使用窗口部件实现, 有窗口部件组件, 可以拿来给AICharacter用
  2. 血量较多时不显示生命条, NPC差不多挂了不再显示, NPC距离较远时也不显示

    和NPC存在阻挡时显示
  3. 血量较低时, 剩余血量显示为红色
  4. 修改生命条背景和前景图片: 黑色用作背景, 白色用作前景

创建C++类

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

创建蓝图窗口

-
基类 STUHealthBarWidget
路径 UI
名称 WBP_HealthBar

设计WBP_HealthBar

  1. 添加 Canvas Panel

  2. 添加 Progress Bar

    -
    Percent 进度
    Fill Color and Opacity 填充色

    设置大小, 查看默认状态


    修改背景图片和背景色; 修改前景图片; 修改填充色; 修改变量名

    -
    Background Image > Image 背景图片 Content/ExternalContent/HealthBar/white
    Background Image > Tint 背景色 000000FF
    Fill Image > Image 前景图片 Content/ExternalContent/HealthBar/white
    Fill Image > Tint 前景色; 填充时, 和填充色叠加显示
    Fill Color and Opacity 填充色 FFFFFFFF
    变量名 HealthProgressBar



演示NPC顶部添加生命条


设置AICharacter

BP_STUAICharacter

  1. 添加窗口部件组件


  2. 设置窗口部件组件

    用来渲染窗口部件

    -
    Widget Class WBP_HealthBar
    Transform > Location > Z 120
    Draw as Desired Size 勾选; 按设计大小显示
    Space 默认值为World, 即在世界座标系中渲染窗口部件



设置行为树

BT_STUCharacter

屏蔽NPC移动, 便于测试



查看

  1. 在世界座标系中渲染, 正面查看


  2. 在世界座标系中渲染, 侧面查看



设置生命条在STUGameHUD中渲染

BP_STUAICharacter


  1. 生命条无侧面表示, 远处看较大


  2. 近处看生命条大小合适, 侧面显示ok


  3. 即使玩家操控的游戏角色和NPC在视线上有阻挡, 仍可见生命条


    本节不会解决该问题

也可以通过按摄像机角度旋转生命条来解决


移除窗口部件组件

BP_STUAICharacter


API导航


ProgressBar

  1. 设置进度百分比
    1/** Sets the current value of the ProgressBar. */
    2UFUNCTION(BlueprintCallable, Category="Progress")
    3void SetPercent(float InPercent);
  2. 设置填充色
    1/** Sets the fill color of the progress bar. */
    2UFUNCTION(BlueprintCallable, Category="Progress")
    3void SetFillColorAndOpacity(FLinearColor InColor);

WidgetComponent

  1. 设置渲染模式

    1void SetWidgetSpace( EWidgetSpace NewSpace ) { Space = NewSpace; }
    -
    World 在世界座标系中渲染
    Screen 在HUD中渲染
    1UENUM(BlueprintType)
    2enum class EWidgetSpace : uint8
    3{
    4    /** The widget is rendered in the world as mesh, it can be occluded like any other mesh in the world. */
    5    World,
    6    /** The widget is rendered in the screen, completely outside of the world, never occluded. */
    7    Screen
    8};
  2. 设置绘制大小

    按窗口部件设计中的大小进行绘制

    1/**  */
    2UFUNCTION(BlueprintCallable, Category = UserInterface)
    3void SetDrawAtDesiredSize(bool bInDrawAtDesiredSize) { bDrawAtDesiredSize = bInDrawAtDesiredSize; }
  3. 获取窗口部件

    1/** Returns the user widget object displayed by this component */
    2UFUNCTION(BlueprintCallable, Category=UserInterface, meta=(UnsafeDuringActorConstruction=true))
    3UUserWidget* GetUserWidgetObject() const;

PlayerController

如果玩家操控的游戏角色死亡, PlayerController会有观察Pawn, 不然, 当前Pawn

优先返回当前Pawn

1/** Returns the first of GetPawn() or GetSpectatorPawn() that is not nullptr, or nullptr otherwise. */
2APawn* GetPawnOrSpectator() const;
3
4APawn* APlayerController::GetPawnOrSpectator() const
5{
6    return GetPawn() ? GetPawn() : GetSpectatorPawn();
7}

World

获取PlayerController

1/** @return Returns the first player controller, or NULL if there is not one. */	
2APlayerController* GetFirstPlayerController() const;

FVector

计算距离

1/**
2 * Euclidean distance between two points.
3 *
4 * @param V1 The first point.
5 * @param V2 The second point.
6 * @return The distance between two points.
7 */
8static FORCEINLINE T Dist(const TVector<T> &V1, const TVector<T> &V2);
9static FORCEINLINE T Distance(const TVector<T> &V1, const TVector<T> &V2) { return Dist(V1, V2); }

类的派生关系

 1class UMG_API UWidgetComponent : public UMeshComponent
 2{
 3    // ...
 4};
 5
 6class ENGINE_API UMeshComponent : public UPrimitiveComponent
 7{
 8    // ...
 9};
10
11class ENGINE_API UPrimitiveComponent : public USceneComponent, public INavRelevantInterface, public IInterface_AsyncCompilation
12{
13    // ...
14};

实现STUHealthBarWidget


添加属性: 进度条

ShootThemUp: UI/STUHealthBarWidget.h

1class UProgressBar;
2
3 UPROPERTY(meta = (BindWidget))
4 UProgressBar *HealthProgressBar;

添加属性: 影响生命条可见性和填充色

-
PercentVisibilityThreshold 生命值百分比大于该数值时, 不显示生命条
PercentColorThreshold 生命值百分比大于该数值时, 使用默认填充色, 否则, 使用告警填充色
GoodColor 默认填充色
BadColor 生命值较低时, 使用的填充色

protected

ShootThemUp: UI/STUHealthBarWidget.h

 1UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
 2float PercentVisibilityThreshold = 0.8f;
 3
 4UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
 5float PercentColorThreshold = 0.3f;
 6
 7UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
 8FLinearColor GoodColor = FLinearColor::White;
 9
10UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
11FLinearColor BadColor = FLinearColor::Red;

添加接口: 设置生命条可见性和生命值百分比

public

ShootThemUp: UI/STUHealthBarWidget.h

1void SetHealthPercent(float HealthPercent);

NPC近乎死亡时, 不再显示生命条

ShootThemUp: UI/STUHealthBarWidget.cpp

 1#include "Components/ProgressBar.h"
 2
 3void USTUHealthBarWidget::SetHealthPercent(float HealthPercent)
 4{
 5    if (!HealthProgressBar) return;
 6
 7    HealthProgressBar->SetPercent(HealthPercent);
 8    const auto Visibility = (HealthPercent < PercentVisibilityThreshold) && !FMath::IsNearlyZero(HealthPercent);
 9    HealthProgressBar->SetVisibility(Visibility ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
10    HealthProgressBar->SetFillColorAndOpacity(HealthPercent > PercentColorThreshold ? GoodColor : BadColor);
11}

修改STUAICharacter


添加窗口部件组件

protected

ShootThemUp: AI/STUAICharacter.h

1class UWidgetComponent;
2
3UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
4UWidgetComponent *HealthWidgetComponent;

在构造函数中初始化并设置

ShootThemUp: AI/STUAICharacter.cpp

1#include "Components/WidgetComponent.h"
2
3// 构造函数
4
5HealthWidgetComponent = CreateDefaultSubobject<UWidgetComponent>("HealthWidgetComponent");
6HealthWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
7HealthWidgetComponent->SetDrawAtDesiredSize(true);
8HealthWidgetComponent->SetupAttachment(GetRootComponent());

覆写BeginPlay函数: 确定窗口部件组件不为空

protected

ShootThemUp: AI/STUAICharacter.h

1virtual void BeginPlay() override;

ShootThemUp: AI/STUAICharacter.cpp

1void ASTUAICharacter::BeginPlay()
2{
3    Super::BeginPlay();
4
5    check(HealthWidgetComponent);
6}

将生命值修改回调函数改为虚函数

STUAICharacter需要对其进行覆写

虚函数, 且访问属性为 protected

ShootThemUp: Player/STUBaseCharacter.h

1// private
2// void OnChangeHealth(float HealthDelta);
3
4// protected
5virtual void OnChangeHealth(float HealthDelta);

STUAICharacter覆写OnChangeHealth

protected

ShootThemUp: AI/STUAICharacter.h

1virtual void OnChangeHealth(float HealthDelta) override;

获取生命条窗口部件组件, 访问其包含的窗口部件, 调用提供的接口

ShootThemUp: AI/STUAICharacter.cpp

 1#include "UI/STUHealthBarWidget.h"
 2#include "Components/STUHealthComponent.h"
 3
 4void ASTUAICharacter::OnChangeHealth(float HealthDelta)
 5{
 6    Super::OnChangeHealth(HealthDelta);
 7
 8    const auto HealthBarWidget = Cast<USTUHealthBarWidget>(HealthWidgetComponent->GetUserWidgetObject());
 9    if (HealthBarWidget)
10    {
11        HealthBarWidget->SetHealthPercent(HealthComponent->GetHealthPercent());
12    }
13}

若NPC与玩家操控的游戏角色距离较远, 不显示生命条

  1. 添加属性: 生命条显示距离

    protected

    ShootThemUp: AI/STUAICharacter.h
    1UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
    2float HealthVisibilityDistance = 1000.0f;
  2. 添加美化函数

    private

    ShootThemUp: AI/STUAICharacter.h
    1void UpdateHealthWidgetVisibility();
    ShootThemUp: AI/STUAICharacter.cpp
    1void ASTUAICharacter::UpdateHealthWidgetVisibility()
    2{
    3    if (!GetWorld() || !GetWorld()->GetFirstPlayerController() || !GetWorld()->GetFirstPlayerController()->GetPawnOrSpectator()) return;
    4
    5    const auto Location = GetWorld()->GetFirstPlayerController()->GetPawnOrSpectator()->GetActorLocation();
    6    const auto Distance = FVector::Distance(Location, GetActorLocation());
    7
    8    HealthWidgetComponent->SetVisibility(Distance < HealthVisibilityDistance, true);
    9}
  3. 覆写Tick函数

    public

    ShootThemUp: AI/STUAICharacter.h
    1virtual void Tick(float DeltaTime) override;
    ShootThemUp: AI/STUAICharacter.cpp
    1void ASTUAICharacter::Tick(float DeltaTime)
    2{
    3    Super::Tick(DeltaTime);
    4
    5    UpdateHealthWidgetVisibility();
    6}

完善逻辑: NPC生命条组件拥有初始值

当前, NPC首次受伤之后, 才拥有正确的生命值

ShootThemUp: AI/STUAICharacter.cpp

1// BeginPlay
2
3const auto HealthBarWidget = Cast<USTUHealthBarWidget>(HealthWidgetComponent->GetUserWidgetObject());
4if (HealthBarWidget)
5{
6    HealthBarWidget->SetHealthPercent(HealthComponent->GetHealthPercent());
7}

查看

设置窗口部件


修改回合时长为120s Round Time

BP_STUGameModeBase

可以把玩家个数设多点, 方便测试 Players Num

  1. NPC未受伤时无生命条


  2. NPC受伤后出现生命条


  3. NPC生命值较低时, 生命条填充色为红色


  4. NPC死亡, 生命条消失


NPC受伤, 但与玩家操控的游戏角色距离较远时, 生命条不可见