目录

[TOC]

一、角色健康栏–蓝图版

  1. 修改STUHealthComponent:添加函数GetHealthPercent

    UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
    class SHOOTTHEMUP_API USTUHealthComponent : public UActorComponent{
        ...
    public:	
        // 获取角色当前生命值
        float GetHealth() const { return Health; }
       
        // 获取角色当前生命值百分比
        UFUNCTION(BlueprintCallable, Category = "Health")
        float GetHealthPercent() const { return Health / MaxHealth; }
       
    	...
    };
    
  2. 创建文件夹UI,然后在该目录下创建用户界面/控件蓝图,重命名为WBP_PlayerHUD

  3. 基于STUGameHUD,创建蓝图类BP_STUGameHUD

    1. 路径:UI
  4. 修改BP_STUGameHUD

    image-20230212124240282

  5. 将HUD类设置为BP_STUGameHUD,在游戏中可以通过Shift+F1解放鼠标

  6. 修改WBP_PlayerHUD

    1. 添加进度条控件,将填充颜色设为绿色
    2. 进度/百分比属性创建绑定,函数重命名为Get_HealthPercent

    image-20230212125221710

  7. 修改BP_BaseCharacter/HealthComponent中,有关自动回复的参数

    image-20230212125403134

二、角色健康栏–C++版

  1. 创建C++类STUPlayerHUDWidget,继承于UserWidget

    1. 目录:ShootThemUp/Source/ShootThemUp/Public/UI
  2. 修改STUPlayerHUDWidget

    #pragma once
       
    #include "CoreMinimal.h"
    #include "Blueprint/UserWidget.h"
    #include "STUPlayerHUDWidget.generated.h"
       
    UCLASS()
    class SHOOTTHEMUP_API USTUPlayerHUDWidget : public UUserWidget {
        GENERATED_BODY()
    public:
        UFUNCTION(BlueprintCallable, Category = "UI")
        float GetHealthPercent() const;
    };
    

    ```c++ #include “UI/STUPlayerHUDWidget.h” #include “Components/STUHealthComponent.h”

    float USTUPlayerHUDWidget::GetHealthPercent() const { const auto Player = GetOwningPlayerPawn(); if (!Player) return 0.0f;

    const auto Component = Player->GetComponentByClass(USTUHealthComponent::StaticClass());
    const auto HealthComponent = Cast<USTUHealthComponent>(Component);
    if (!HealthComponent) return 0.0f;
       
    return HealthComponent->GetHealthPercent(); }
    
  3. 修改STUGameHUD

    #pragma once
       
    #include "CoreMinimal.h"
    #include "GameFramework/HUD.h"
    #include "STUGameHUD.generated.h"
       
    UCLASS()
    class SHOOTTHEMUP_API ASTUGameHUD : public AHUD {
        GENERATED_BODY()
       
    protected:
        UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "UI")
        TSubclassOf<UUserWidget> PlayerHUDWidgetClass;
       
        virtual void BeginPlay() override;
    };
    

    ```c++ #include “Blueprint/UserWidget.h”

    void ASTUGameHUD::BeginPlay() { Super::BeginPlay(); auto PlayerHUDWidget = CreateWidget(GetWorld(), PlayerHUDWidgetClass); if (PlayerHUDWidget) { PlayerHUDWidget->AddToViewport(); } }

  4. 修改WBP_PlayerHUD

    1. 修改父类:在文件/重设蓝图父项中,修改父类为STUPlayerHUDWidget
    2. 修改图表/Get_HealthPercent函数:将计算过程隐藏与C++类中

    image-20230212131517786

  5. 修改BP_STUGameHUD

    1. 将事件图表中的蓝图删除,因为我们已经在C++中实现了
    2. 类默认值/PlayerHUDWidgetClass设置为WBP_PlayerHUD
      1. 可以通过修改这个属性,选择创建哪一种UI界面

三、每种武器的瞄准UI

  1. UI/Images迁移到本项目中

  2. 修改WBP_PlayerHUD

    1. 添加图像控件,选择RifleCrossHair,然后勾选大小到内容
    2. 将锚点修改为中央位置X(Y)均设置为0,对齐设置为(0.5,0.5)

    image-20230212215759334

  3. 修改STUCoreType:创建新类型FWeaponUIData

    USTRUCT(BlueprintType)
    struct FWeaponUIData {
        GENERATED_USTRUCT_BODY()
       
        // 武器的图标
        UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "UI")
        UTexture2D* MainIcon;
           
        // 武器的瞄准线图标
        UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "UI")
        UTexture2D* CrossHairIcon;
    };
    
  4. 修改STUBaseWeapon:添加武器的显示UI数据

    ```c++ UCLASS() class SHOOTTHEMUP_API ASTUBaseWeapon : public AActor { …

    public: FWeaponUIData GetUIData() const { return UIData; }

    protected: // 武器的显示UI UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = “UI”) FWeaponUIData UIData; };

  5. 修改STUWeaponComponent:添加UIDatagetter函数

    UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
    class SHOOTTHEMUP_API USTUWeaponComponent : public UActorComponent {
        GENERATED_BODY()
       
    public:
        // 获取武器UI数据
        bool GetWeaponUIData(FWeaponUIData& UIData) const;
    };
    
    bool USTUWeaponComponent::GetWeaponUIData(FWeaponUIData& UIData) const {
        if (!CurrentWeapon) return false;
        UIData = CurrentWeapon->GetUIData();
        return true;
    }
    
  6. 修改STUPlayerHUDWidget:添加UIDatagetter函数

    ...
    #include "STUCoreTypes.h"
       
    UCLASS()
    class SHOOTTHEMUP_API USTUPlayerHUDWidget : public UUserWidget {
        GENERATED_BODY()
    public:
        UFUNCTION(BlueprintCallable, Category = "UI")
        float GetHealthPercent() const;
       
        UFUNCTION(BlueprintCallable, Category = "UI")
        bool GetWeaponUIData(FWeaponUIData& UIData) const;
    };
    
    bool USTUPlayerHUDWidget::GetWeaponUIData(FWeaponUIData& UIData) const {
        const auto Player = GetOwningPlayerPawn();
        if (!Player) return false;
       
        const auto Component = Player->GetComponentByClass(USTUWeaponComponent::StaticClass());
        const auto WeaponComponent = Cast<USTUWeaponComponent>(Component);
        if (!WeaponComponent) return false;
       
        return WeaponComponent->GetWeaponUIData(UIData);
    }
    
  7. 修改STUGameHUD/DrawHUD

    ```c++ void ASTUGameHUD::DrawHUD() { Super::DrawHUD(); // DrawCrossHair(); }

  8. 修改WBP_PlayerHUD

    1. 图像框/外观/笔刷属性进行绑定,函数重命名为Get_CrossHairImage

    image-20230212220456411

  9. 为每种武器设置UIData

    1. BP_STURifleWeaponRifleMainIconRifleCrossHair
    2. BP_STULauncherWeaponLauncherMainIconLauncherCrossHair
  10. 修改SkySphere:清除Direction Light ActorSun Height设置为-1

    image-20230212224516937

四、实战作业:武器弹药库UI

  1. 修改STUBaseWeapon:添加AmmoDatagetter

    ```c++ UCLASS() class SHOOTTHEMUP_API ASTUBaseWeapon : public AActor { …

    public: FAmmoData GetAmmoData() const { return CurrentAmmo; } };

  2. 修改STUWeaponComponent:添加CurrentAmmoDatagetter函数

    UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
    class SHOOTTHEMUP_API USTUWeaponComponent : public UActorComponent {
        GENERATED_BODY()
       
    public:
        // 获取武器弹药库数据
        bool GetCurrentWeaponAmmoData(FAmmoData& AmmoData) const;
    };
    
    bool USTUWeaponComponent::GetCurrentWeaponAmmoData(FAmmoData& AmmoData) const {
        if (!CurrentWeapon) return false;
        AmmoData = CurrentWeapon->GetAmmoData();
        return true;
    }
    
  3. 修改STUPlayerHUDWidget:添加UIDatagetter函数,将获取武器组件的功能抽象出来

    class USTUWeaponComponent;
       
    UCLASS()
    class SHOOTTHEMUP_API USTUPlayerHUDWidget : public UUserWidget {
        GENERATED_BODY()
    public:
        UFUNCTION(BlueprintCallable, Category = "UI")
        float GetHealthPercent() const;
       
        UFUNCTION(BlueprintCallable, Category = "UI")
        bool GetCurrentWeaponUIData(FWeaponUIData& UIData) const;
       
        UFUNCTION(BlueprintCallable, Category = "UI")
        bool GetCurrentWeaponAmmoData(FAmmoData& AmmoData) const;
       
    private:
        USTUWeaponComponent* GetWeaponComponent() const;
    };
    
    bool USTUPlayerHUDWidget::GetCurrentWeaponUIData(FWeaponUIData& UIData) const {
        const auto WeaponComponent = GetWeaponComponent();
        if (!WeaponComponent) return false;
       
        return WeaponComponent->GetCurrentWeaponUIData(UIData);
    }
       
    bool USTUPlayerHUDWidget::GetCurrentWeaponAmmoData(FAmmoData& AmmoData) const {
        const auto WeaponComponent = GetWeaponComponent();
        if (!WeaponComponent) return false;
       
        return WeaponComponent->GetCurrentWeaponAmmoData(AmmoData);
    }
       
    USTUWeaponComponent* USTUPlayerHUDWidget::GetWeaponComponent() const {
        const auto Player = GetOwningPlayerPawn();
        if (!Player) return nullptr;
       
        const auto Component = Player->GetComponentByClass(USTUWeaponComponent::StaticClass());
        const auto WeaponComponent = Cast<USTUWeaponComponent>(Component);
        return WeaponComponent;
    }
    
  4. 修改WBP_PlayerHUD

    1. 修改Get_CrossHairImage

      image-20230212223130250

      image-20230212223111905

    2. 添加控件:

      1. 水平框:勾选大小到内容锚点右下角位置(-50,-50)对齐(1,1)

      2. 文本:默认为0 / 0,绑定为函数,重命名为Get_AmmoText

        image-20230212225150325

      3. 间隔区外观/尺寸(30,1)

      4. 图像:预览图为RifleMainIcon,绑定为函数,重命名为Get_WeaponIcon

        image-20230212224248561

五、观察者模式的UI

  1. 修改STUPlayerHUDWidget:添加判断角色是否存活 & 是否处于观察者模式

    UCLASS()
    class SHOOTTHEMUP_API USTUPlayerHUDWidget : public UUserWidget {
        ...
    public:
        // 玩家是否存活
        UFUNCTION(BlueprintCallable, Category = "UI")
        bool IsPlayerAlive() const;
       
        // 玩家是否处于观察者模式
        UFUNCTION(BlueprintCallable, Category = "UI")
        bool IsPlayerSpectating() const;
       
    private:
        USTUHealthComponent* GetHealthComponent() const;
    };
    

    ```c++ // 玩家是否存活 bool USTUPlayerHUDWidget::IsPlayerAlive() const { const auto HealthComponent = GetHealthComponent(); return HealthComponent && !HealthComponent->IsDead(); }

    // 玩家是否处于观察者模式 bool USTUPlayerHUDWidget::IsPlayerSpectating() const { const auto Controller = GetOwningPlayer(); return Controller && Controller->GetStateName() == NAME_Spectating; }

  2. 修改WBP_PlayerHUD

    1. ProgressBar_0:绑定可视性属性,函数重命名为Is_Player_Alive

      image-20230212230605800

    2. Image_0:绑定为Is_Player_Alive

    3. 水平框:绑定为Is_Player_Alive

  3. 创建用户界面/控件蓝图WBP_SpectatorHUD

    1. 添加控件循环动态流览图显示图像块数量14周期3颜色红色
    2. 添加控件文本框:默认文本为您已死亡
    3. 添加控件覆层,并将上述两个控件作为覆层的子控件:位置(0,0)尺寸(200,200)
  4. 修改WBP_PlayerHUD

    1. 添加控件WBP_SpectatorHUD

      1. 锚点居中位置(0,0)对齐(0.5,0.5)
      2. 绑定可视性属性,函数重命名为Is_Player_Spectating

      image-20230212231752381

  5. 修改WBP_SpectatorHUD

    1. 选中文本框,添加动画TextBlinking

      1. 添加轨道/文本DeadTextBlock

      2. 添加轨道颜色和不透明度,在该轨道上添加关键帧

        image-20230212232412243

    2. 修改事件图表:在初始化时开始播放动画

      image-20230212232625622

六、重构,组装游戏

  1. 创建C++类STUUtils

    1. 目录:ShootThemUp/Source/ShootThemUp/Public
  2. 修改STUUtils:将两个component的getter合并为一个模板函数

    #pragma once
       
    class STUUtils {
    public:
        template<typename T>
        static T* GetSTUPlayerComponent(APawn* PlayerPawn) {
            if (!PlayerPawn) return nullptr;
       
            const auto Component = PlayerPawn->GetComponentByClass(T::StaticClass());
            return Cast<T>(Component);
        }
    };
    
  3. 修改STUPlayerHUD:使用STUUtils获取两个component

    #pragma once
       
    #include "CoreMinimal.h"
    #include "Blueprint/UserWidget.h"
    #include "STUCoreTypes.h"
    #include "STUPlayerHUDWidget.generated.h"
       
    UCLASS()
    class SHOOTTHEMUP_API USTUPlayerHUDWidget : public UUserWidget {
        GENERATED_BODY()
    public:
        UFUNCTION(BlueprintCallable, Category = "UI")
        float GetHealthPercent() const;
       
        UFUNCTION(BlueprintCallable, Category = "UI")
        bool GetCurrentWeaponUIData(FWeaponUIData& UIData) const;
       
        UFUNCTION(BlueprintCallable, Category = "UI")
        bool GetCurrentWeaponAmmoData(FAmmoData& AmmoData) const;
       
        // 玩家是否存活
        UFUNCTION(BlueprintCallable, Category = "UI")
        bool IsPlayerAlive() const;
       
        // 玩家是否处于观察者模式
        UFUNCTION(BlueprintCallable, Category = "UI")
        bool IsPlayerSpectating() const;
    };
    

    ```c++ #include “UI/STUPlayerHUDWidget.h” #include “Components/STUHealthComponent.h” #include “Components/STUWeaponComponent.h” #include “STUUtils.h”

    float USTUPlayerHUDWidget::GetHealthPercent() const { const auto HealthComponent = STUUtils::GetSTUPlayerComponent(GetOwningPlayerPawn()); if (!HealthComponent) return 0.0f;

    return HealthComponent->GetHealthPercent(); }
    

    bool USTUPlayerHUDWidget::GetCurrentWeaponUIData(FWeaponUIData& UIData) const { const auto WeaponComponent = STUUtils::GetSTUPlayerComponent(GetOwningPlayerPawn()); if (!WeaponComponent) return false;

    return WeaponComponent->GetCurrentWeaponUIData(UIData); }
    

    bool USTUPlayerHUDWidget::GetCurrentWeaponAmmoData(FAmmoData& AmmoData) const { const auto WeaponComponent = STUUtils::GetSTUPlayerComponent(GetOwningPlayerPawn()); if (!WeaponComponent) return false;

    return WeaponComponent->GetCurrentWeaponAmmoData(AmmoData); }
    

    // 玩家是否存活 bool USTUPlayerHUDWidget::IsPlayerAlive() const { const auto HealthComponent = STUUtils::GetSTUPlayerComponent(GetOwningPlayerPawn()); return HealthComponent && !HealthComponent->IsDead(); }

    // 玩家是否处于观察者模式 bool USTUPlayerHUDWidget::IsPlayerSpectating() const { const auto Controller = GetOwningPlayer(); return Controller && Controller->GetStateName() == NAME_Spectating; }

  4. 修改STUBaseWeapon:删除每次射击后调用LogAmmo()

    ```c++ void ASTUBaseWeapon::DecreaseAmmo() { CurrentAmmo.Bullets–;

    if (IsClipEmpty() && !IsAmmoEmpty()) ChangeClip(); }
    
  5. 修改WBP_PlayerHUD的命名:

    image-20230212234154832