六一的部落格


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



Level Selection


素材

-
frame.png 用作选中关卡高亮框
level1.jpg 关卡1预览图
level2.jpg 关卡2预览图
level3.jpg 关卡3预览图


预览图和关卡没有关系, 就只是三张看上去就不一样的图





导入素材

创建文件夹 ExternalContent/LevelImages , 将素材导入该文件夹



说明

  1. 会在主菜单页面显示3个关卡, 供玩家选择

    若玩家选择其一, 进入该关卡游玩; 若玩家未作出选择, 进入默认关卡

  2. 选中关卡高亮

  3. 关卡有预览图片, 以及显示名称


API导航


Image

设置显示图片

1/**
2 * Sets the Brush to the specified Texture.
3 *
4 * @param Texture Texture to use to set on Brush.
5 * @param bMatchSize If true, image will change its size to texture size. If false, texture will be stretched to image size.
6 */
7UFUNCTION(BlueprintCallable, Category="Appearance")
8virtual void SetBrushFromTexture(UTexture2D* Texture, bool bMatchSize = false);

Text

FName > FText

1/**
2 * Generate an FText representing the pass name
3 */
4static FText FromName( const FName& Val);

创建测试关卡

一共3个可选关卡

-
DefaultMap
CubeLevel
SphereLevel
  1. 通过 File > New Level.. 创建关卡, 选择 Basic, 保存到 Content/Levels , 命名为 CubeLevel

  2. 设置 GameMode OverrideBP_STUGameModeBase , 在关卡中添加4个 PlayerStart 以及一个立方体

    Place Actors > Shapes > Cube


  3. 拷贝 CubeLevel , 命名为 SphereLevel

    Ctrl-D

    移除立方体, 在关卡中添加一个球体 Place Actors > Shapes > Sphere


注意:

  1. 有PlayerStart使得NPC可以动态创建, 但没有添加导航, 所以测试时他们无法移动
  2. 添加立方体和球体在于之后进入不同关卡能够判断

创建C++类

-
基类 UserWidget
路径 Menu/UI
名称 STULevelItemWidget
属性 Public

创建蓝图窗口部件

-
基类 STULevelItemWidget
路径 Menu/UI
名称 WBP_LevelItem

设计WBP_LevelItem

  1. 添加 Canvas Panel

  2. 添加 Overlay

    勾选 Size To Content . 如果未添加 Canvas Panel , 无该选项. 以下级最大大小为准


    Overlay的存在使得备选关卡的元素可以组合在一起, 由4部分组成, 依次添加, 均作为变量

    • 高亮框
    • 关卡预览图片
    • 关卡显示名称
    • 按钮
    变量名称
    高亮框 FrameImage
    关卡预览图片 LevelImage
    关卡显示名称 LevelNameTextBlock
    按钮 LevelSelectButton
  3. 为Overlay添加上级大小盒


    -
    Width Override 250
    Height Override 250



高亮框

-
Alignment 平铺: 高亮框大小小于关卡预览图片
Image 选择 Content/ExternalContent/LevelImages/frame
Visibility Hidden: 默认不可见, 选择后修改可见性



关卡预览图片

-
Padding 15: 和高亮框留出间隔
Alignment 居中
Brush > Image 选择预览图片: Content/ExternalContent/LevelImages/level1



关卡显示名称

勾选 Is Variable

-
Horizontal Alignment 左对齐
Vertical Alignment 靠近底部
Padding > Left 22: 和左边界间隔
Padding > Bottom 20: 和底部间隔



按钮

-
Alignment 平铺: 整个框框的点击都有效
Render Opacity 0: 渲染按钮时使之透明
Cursor 鼠标悬浮在按钮之上时, 显示为小手



修改WBP_Menu设计

  1. 添加水平盒

    勾选 Is Variable , 设置变量名称为LevelItemsBox


    将在代码中, 往里添加可选关卡

  2. 添加分隔符

    -
    Appearance > Size > Y 40: 纵向间隔


  3. 往水平盒中添加WBP_LevelItem, 查看预览效果


如果窗口部件的功能继续增加, 考虑创建名为LevelSelector的窗口部件, 封装往其中添加关卡项的逻辑; 而在主菜单添加LevelSelector


关卡选项存放在STUGameInstance


添加数据结构: 存放关卡选项信息

ShootThemUp: STUCoreTypes.h

 1USTRUCT(BlueprintType)
 2struct FLevelData
 3{
 4    GENERATED_USTRUCT_BODY()
 5
 6    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
 7    FName LevelName = NAME_None;
 8
 9    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
10    FName LevelDisplayName = NAME_None;
11
12    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
13    UTexture2D *LevelThumb;
14};

高亮框图片默认


添加数据成员: 保存关卡选项信息

之后会在BP_STUGameInstance中设置

protected

ShootThemUp: STUGameInstance.h

1#include "STUCoreTypes.h"
2
3UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
4TArray<FLevelData> SelectedLevels; /* LevelsData */

提供接口: 获取关卡选项信息

public

ShootThemUp: STUGameInstance.h

1TArray<FLevelData> GetSelectedLevels() const { return SelectedLevels; }

在STUMenuWidget中初始化关卡选项

STUMenuWidget会创建窗口部件, 并根据配置初始化元素


STULevelItemWidget添加元素, 并提供设置接口

  1. 添加属性

    protected

    ShootThemUp: Menu/UI/STULevelItemWidget.h
     1class UButton;
     2class UImage;
     3class UTextBlock;
     4
     5UPROPERTY(meta = (BindWidget))
     6UImage *FrameImage;
     7
     8UPROPERTY(meta = (BindWidget))
     9UImage *LevelImage;
    10
    11UPROPERTY(meta = (BindWidget))
    12UTextBlock *LevelNameTextBlock;
    13
    14UPROPERTY(meta = (BindWidget))
    15UButton *LevelSelectButton;
  2. 添加接口: 设置元素

    public

    ShootThemUp: Menu/UI/STULevelItemWidget.h
    1#include "STUCoreTypes.h"
    2
    3void SetLevelData(const FLevelData &Data);
    ShootThemUp: Menu/UI/STULevelItemWidget.cpp
     1#include "Components/Image.h" 
     2#include "Components/TextBlock.h"
     3
     4void USTULevelItemWidget::SetLevelData(const FLevelData &Data)
     5{
     6    if (LevelImage)
     7    {
     8        LevelImage->SetBrushFromTexture(Data.LevelThumb);
     9    }
    10
    11    if (LevelNameTextBlock)
    12    {
    13        LevelNameTextBlock->SetText(FText::FromName(Data.LevelDisplayName));
    14    }
    15}

STUMenuWidget创建窗口部件, 并初始化元素

  1. 添加属性: 保存关卡选项类型

    protected

    ShootThemUp: Menu/UI/STUMenuWidget.h
    1UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
    2TSubclassOf<UUserWidget> SelectedLevelWidgetClass; /* LevelItemWidgetClass */
  2. 添加属性: 水平盒

    protected

    ShootThemUp: Menu/UI/STUMenuWidget.h
    1class UHorizontalBox;
    2
    3UPROPERTY(meta = (BindWidget))
    4UHorizontalBox *LevelItemsBox;
  3. 添加辅助函数: 获取游戏实例

    private

    ShootThemUp: Menu/UI/STUMenuWidget.h
    1class USTUGameInstance;
    2
    3USTUGameInstance *GetSTUGameInstance() const;
    ShootThemUp: Menu/UI/STUMenuWidget.cpp
    1USTUGameInstance *USTUMenuWidget::GetSTUGameInstance() const
    2{
    3    if (!GetWorld()) return nullptr;
    4
    5    return GetWorld()->GetGameInstance<USTUGameInstance>();
    6}
  4. 添加美化函数: 创建关卡选项, 并添加到水平盒

    private

    ShootThemUp: Menu/UI/STUMenuWidget.h
    1void InitLevelItemsBox(); /* InitLevelItems */
    在NativeOnInitialized中调用

    ShootThemUp: Menu/UI/STUMenuWidget.cpp
    1// NativeOnInitialized
    2InitLevelItemsBox();
    实现

    ShootThemUp: Menu/UI/STUMenuWidget.cpp
     1#include "Components/HorizontalBox.h"
     2#include "Menu/UI/STULevelItemWidget.h"
     3
     4void USTUMenuWidget::InitLevelItemsBox()
     5{
     6    if (!LevelItemsBox) return;
     7    if (!GetSTUGameInstance()) return;
     8
     9    const auto SelectedLevels = GetSTUGameInstance()->GetSelectedLevels();
    10    checkf(SelectedLevels.Num() != 0, TEXT("Levels data must not be empty!"));
    11    // if (!SelectedLevels.Num()) return;
    12
    13    LevelItemsBox->ClearChildren();
    14
    15    for (auto LevelData : SelectedLevels)
    16    {
    17        const auto LevelItemWidget = CreateWidget<USTULevelItemWidget>(GetWorld(), SelectedLevelWidgetClass);
    18        if (!LevelItemWidget) continue;
    19
    20        LevelItemWidget->SetLevelData(LevelData);
    21
    22        LevelItemsBox->AddChild(LevelItemWidget);
    23    }
    24}

点击关卡选项时, 更新STUGameInstance的关卡信息


修改STUGameInstance存放关卡的结构体

ShootThemUp: STUGameInstance.h

  1. 修改关卡信息类型


    1// protected
    2
    3UPROPERTY(EditDefaultsOnly)
    4FName StartupLevelName = NAME_None;

    改为

    1// private
    2FLevelData StartupLevel;
  2. 屏蔽获取关卡名接口, 添加获取关卡接口

    由调用处自行取出分量

    1// FName GetStartupLevelName() const { return StartupLevelName; }
    2FLevelData GetStartupLevel() const { return StartupLevel; }
  3. 修改GetStartupLevelName调用处

    ShootThemUp: Menu/UI/STUMenuWidget.cpp

     1// OnStartGame
     2
     3// if (STUGameInstance->GetStartupLevelName().IsNone())
     4if (STUGameInstance->GetStartupLevel().LevelName.IsNone())
     5{
     6    // ...
     7}
     8
     9// UGameplayStatics::OpenLevel(GetWorld(), STUGameInstance->GetStartupLevelName());
    10UGameplayStatics::OpenLevel(GetWorld(), STUGameInstance->GetStartupLevel().LevelName);
  4. 添加设置接口

    public

    1void SetStartupLevel(const FLevelData &LevelData) { StartupLevel = LevelData; }

定义委托类型

若关卡被选中, 广播选中关卡信息

ShootThemUp: STUCoreTypes.h

1DECLARE_MULTICAST_DELEGATE_OneParam(FOnLevelSelectedSignature, const FLevelData &);

为STULevelItemWidget实现按钮点击回调函数

  1. 添加属性: 保存关卡选项, 用以委托处理函数返回

    private

    ShootThemUp: Menu/UI/STULevelItemWidget.h
    1#include "STUCoreTypes.h"
    2
    3FLevelData LevelData;
  2. 修改SetLeverlData接口: 初始化LevelData成员

    ShootThemUp: Menu/UI/STULevelItemWidget.cpp
    1// SetLevelData
    2LevelData = Data;
  3. 添加数据成员

    public

    ShootThemUp: Menu/UI/STULevelItemWidget.h
    1FOnLevelSelectedSignature OnLevelItemSelected;
  4. 实现按钮回调函数

    private

    ShootThemUp: Menu/UI/STULevelItemWidget.h
    1UFUNCTION()
    2void OnLevelSelect(); /* OnLevelItemClicked */
    ShootThemUp: Menu/UI/STULevelItemWidget.cpp
    1void USTULevelItemWidget::OnLevelSelect()
    2{
    3    OnLevelItemSelected.Broadcast(LevelData);
    4}
  5. 覆写NativeOnInitialized, 注册按钮回调函数

    protected

    ShootThemUp: Menu/UI/STULevelItemWidget.h
    1virtual void NativeOnInitialized() override;
    ShootThemUp: Menu/UI/STULevelItemWidget.cpp
     1#include "Components/Button.h"
     2
     3void USTULevelItemWidget::NativeOnInitialized()
     4{
     5    Super::NativeOnInitialized();
     6
     7    if (LevelSelectButton)
     8    {
     9        LevelSelectButton->OnClicked.AddDynamic(this, &USTULevelItemWidget::OnLevelSelect);
    10    }
    11}

在STUMenuWidget中注册关卡选中事件


STULevelItemWidget提供设置高亮框可见性接口

public

ShootThemUp: Menu/UI/STULevelItemWidget.h

1void SetFrameImageVisibility(bool Visible); /* SetSelected */

ShootThemUp: Menu/UI/STULevelItemWidget.cpp

1void USTULevelItemWidget::SetFrameImageVisibility(bool Visible)
2{
3    if (!FrameImage) return;
4    FrameImage->SetVisibility(Visible ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
5}

STUMenuWidget添加数组, 存放已创建的关卡选项

private

ShootThemUp: Menu/UI/STUMenuWidget.h

1class USTULevelItemWidget;
2
3UPROPERTY()
4TArray<USTULevelItemWidget *> LevelItemWidgets;

ShootThemUp: Menu/UI/STUMenuWidget.cpp

1// InitLevelItemsBox
2LevelItemWidgets.Add(LevelItemWidget);

STULevelItemWidget添加接口, 获取LevelData

public

ShootThemUp: Menu/UI/STULevelItemWidget.h

1FLevelData GetLevelData() const { return LevelData; }

实现处理函数

private

ShootThemUp: Menu/UI/STUMenuWidget.h

1#include "STUCoreTypes.h"
2
3void OnLevelSelected(const FLevelData &LevelData);

ShootThemUp: Menu/UI/STUMenuWidget.cpp

 1void USTUMenuWidget::OnLevelSelected(const FLevelData &LevelData)
 2{
 3    if (!GetSTUGameInstance()) return;
 4    GetSTUGameInstance()->SetStartupLevel(LevelData);
 5
 6    for (auto LevelItem : LevelItemWidgets)
 7    {
 8        if (LevelItem)
 9        {
10            LevelItem->SetFrameImageVisibility(LevelItem->GetLevelData().LevelName == LevelData.LevelName);
11        }
12    }
13}

注册服务

ShootThemUp: Menu/UI/STUMenuWidget.cpp

1// InitLevelItemsBox
2LevelItemWidget->OnLevelItemSelected.AddUObject(this, &USTUMenuWidget::OnLevelSelected);

设置默认关卡, 标记上次打开关卡

每回去到主关卡, 都会调用NativeOnInitialized, 而游戏实例中存储的StartupLevel设置后一直有值

  1. 开始时, 设置默认关卡
  2. 之后, StartupLevel保存上次打开关卡

ShootThemUp: Menu/UI/STUMenuWidget.cpp

 1// InitLevelItemsBox
 2
 3if (GetSTUGameInstance()->GetStartupLevel().LevelName.IsNone())
 4{
 5    OnLevelSelected(GetSTUGameInstance()->GetSelectedLevels()[0]);
 6}
 7else
 8{
 9    OnLevelSelected(GetSTUGameInstance()->GetStartupLevel());
10}

查看

  1. 设置关卡选项类

    WBP_Menu

    -
    Selected Level Widget Class WBP_LevelItem


  2. 配置关卡选项

    BP_STUGameInstance


  3. 查看


如果关卡选项关卡名重复, 选择其中一个, 其他重复的也会高亮, 这是因为关卡使用关卡名标识, 没有标识ID的原因


配置时给出提示

ShootThemUp: STUGameInstance.h

1UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (ToolTip = "Level names must be unique!"))
2TArray<FLevelData> SelectedLevels;

主菜单支持选择关卡


Level Selection


素材

-
frame.png 用作选中关卡高亮框
level1.jpg 关卡1预览图
level2.jpg 关卡2预览图
level3.jpg 关卡3预览图


预览图和关卡没有关系, 就只是三张看上去就不一样的图





导入素材

创建文件夹 ExternalContent/LevelImages , 将素材导入该文件夹



说明

  1. 会在主菜单页面显示3个关卡, 供玩家选择

    若玩家选择其一, 进入该关卡游玩; 若玩家未作出选择, 进入默认关卡

  2. 选中关卡高亮

  3. 关卡有预览图片, 以及显示名称


API导航


Image

设置显示图片

1/**
2 * Sets the Brush to the specified Texture.
3 *
4 * @param Texture Texture to use to set on Brush.
5 * @param bMatchSize If true, image will change its size to texture size. If false, texture will be stretched to image size.
6 */
7UFUNCTION(BlueprintCallable, Category="Appearance")
8virtual void SetBrushFromTexture(UTexture2D* Texture, bool bMatchSize = false);

Text

FName > FText

1/**
2 * Generate an FText representing the pass name
3 */
4static FText FromName( const FName& Val);

创建测试关卡

一共3个可选关卡

-
DefaultMap
CubeLevel
SphereLevel
  1. 通过 File > New Level.. 创建关卡, 选择 Basic, 保存到 Content/Levels , 命名为 CubeLevel

  2. 设置 GameMode OverrideBP_STUGameModeBase , 在关卡中添加4个 PlayerStart 以及一个立方体

    Place Actors > Shapes > Cube


  3. 拷贝 CubeLevel , 命名为 SphereLevel

    Ctrl-D

    移除立方体, 在关卡中添加一个球体 Place Actors > Shapes > Sphere


注意:

  1. 有PlayerStart使得NPC可以动态创建, 但没有添加导航, 所以测试时他们无法移动
  2. 添加立方体和球体在于之后进入不同关卡能够判断

创建C++类

-
基类 UserWidget
路径 Menu/UI
名称 STULevelItemWidget
属性 Public

创建蓝图窗口部件

-
基类 STULevelItemWidget
路径 Menu/UI
名称 WBP_LevelItem

设计WBP_LevelItem

  1. 添加 Canvas Panel

  2. 添加 Overlay

    勾选 Size To Content . 如果未添加 Canvas Panel , 无该选项. 以下级最大大小为准


    Overlay的存在使得备选关卡的元素可以组合在一起, 由4部分组成, 依次添加, 均作为变量

    • 高亮框
    • 关卡预览图片
    • 关卡显示名称
    • 按钮
    变量名称
    高亮框 FrameImage
    关卡预览图片 LevelImage
    关卡显示名称 LevelNameTextBlock
    按钮 LevelSelectButton
  3. 为Overlay添加上级大小盒


    -
    Width Override 250
    Height Override 250



高亮框

-
Alignment 平铺: 高亮框大小小于关卡预览图片
Image 选择 Content/ExternalContent/LevelImages/frame
Visibility Hidden: 默认不可见, 选择后修改可见性



关卡预览图片

-
Padding 15: 和高亮框留出间隔
Alignment 居中
Brush > Image 选择预览图片: Content/ExternalContent/LevelImages/level1



关卡显示名称

勾选 Is Variable

-
Horizontal Alignment 左对齐
Vertical Alignment 靠近底部
Padding > Left 22: 和左边界间隔
Padding > Bottom 20: 和底部间隔



按钮

-
Alignment 平铺: 整个框框的点击都有效
Render Opacity 0: 渲染按钮时使之透明
Cursor 鼠标悬浮在按钮之上时, 显示为小手



修改WBP_Menu设计

  1. 添加水平盒

    勾选 Is Variable , 设置变量名称为LevelItemsBox


    将在代码中, 往里添加可选关卡

  2. 添加分隔符

    -
    Appearance > Size > Y 40: 纵向间隔


  3. 往水平盒中添加WBP_LevelItem, 查看预览效果


如果窗口部件的功能继续增加, 考虑创建名为LevelSelector的窗口部件, 封装往其中添加关卡项的逻辑; 而在主菜单添加LevelSelector


关卡选项存放在STUGameInstance


添加数据结构: 存放关卡选项信息

ShootThemUp: STUCoreTypes.h

 1USTRUCT(BlueprintType)
 2struct FLevelData
 3{
 4    GENERATED_USTRUCT_BODY()
 5
 6    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
 7    FName LevelName = NAME_None;
 8
 9    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
10    FName LevelDisplayName = NAME_None;
11
12    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
13    UTexture2D *LevelThumb;
14};

高亮框图片默认


添加数据成员: 保存关卡选项信息

之后会在BP_STUGameInstance中设置

protected

ShootThemUp: STUGameInstance.h

1#include "STUCoreTypes.h"
2
3UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
4TArray<FLevelData> SelectedLevels; /* LevelsData */

提供接口: 获取关卡选项信息

public

ShootThemUp: STUGameInstance.h

1TArray<FLevelData> GetSelectedLevels() const { return SelectedLevels; }

在STUMenuWidget中初始化关卡选项

STUMenuWidget会创建窗口部件, 并根据配置初始化元素


STULevelItemWidget添加元素, 并提供设置接口

  1. 添加属性

    protected

    ShootThemUp: Menu/UI/STULevelItemWidget.h
     1class UButton;
     2class UImage;
     3class UTextBlock;
     4
     5UPROPERTY(meta = (BindWidget))
     6UImage *FrameImage;
     7
     8UPROPERTY(meta = (BindWidget))
     9UImage *LevelImage;
    10
    11UPROPERTY(meta = (BindWidget))
    12UTextBlock *LevelNameTextBlock;
    13
    14UPROPERTY(meta = (BindWidget))
    15UButton *LevelSelectButton;
  2. 添加接口: 设置元素

    public

    ShootThemUp: Menu/UI/STULevelItemWidget.h
    1#include "STUCoreTypes.h"
    2
    3void SetLevelData(const FLevelData &Data);
    ShootThemUp: Menu/UI/STULevelItemWidget.cpp
     1#include "Components/Image.h" 
     2#include "Components/TextBlock.h"
     3
     4void USTULevelItemWidget::SetLevelData(const FLevelData &Data)
     5{
     6    if (LevelImage)
     7    {
     8        LevelImage->SetBrushFromTexture(Data.LevelThumb);
     9    }
    10
    11    if (LevelNameTextBlock)
    12    {
    13        LevelNameTextBlock->SetText(FText::FromName(Data.LevelDisplayName));
    14    }
    15}

STUMenuWidget创建窗口部件, 并初始化元素

  1. 添加属性: 保存关卡选项类型

    protected

    ShootThemUp: Menu/UI/STUMenuWidget.h
    1UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
    2TSubclassOf<UUserWidget> SelectedLevelWidgetClass; /* LevelItemWidgetClass */
  2. 添加属性: 水平盒

    protected

    ShootThemUp: Menu/UI/STUMenuWidget.h
    1class UHorizontalBox;
    2
    3UPROPERTY(meta = (BindWidget))
    4UHorizontalBox *LevelItemsBox;
  3. 添加辅助函数: 获取游戏实例

    private

    ShootThemUp: Menu/UI/STUMenuWidget.h
    1class USTUGameInstance;
    2
    3USTUGameInstance *GetSTUGameInstance() const;
    ShootThemUp: Menu/UI/STUMenuWidget.cpp
    1USTUGameInstance *USTUMenuWidget::GetSTUGameInstance() const
    2{
    3    if (!GetWorld()) return nullptr;
    4
    5    return GetWorld()->GetGameInstance<USTUGameInstance>();
    6}
  4. 添加美化函数: 创建关卡选项, 并添加到水平盒

    private

    ShootThemUp: Menu/UI/STUMenuWidget.h
    1void InitLevelItemsBox(); /* InitLevelItems */
    在NativeOnInitialized中调用

    ShootThemUp: Menu/UI/STUMenuWidget.cpp
    1// NativeOnInitialized
    2InitLevelItemsBox();
    实现

    ShootThemUp: Menu/UI/STUMenuWidget.cpp
     1#include "Components/HorizontalBox.h"
     2#include "Menu/UI/STULevelItemWidget.h"
     3
     4void USTUMenuWidget::InitLevelItemsBox()
     5{
     6    if (!LevelItemsBox) return;
     7    if (!GetSTUGameInstance()) return;
     8
     9    const auto SelectedLevels = GetSTUGameInstance()->GetSelectedLevels();
    10    checkf(SelectedLevels.Num() != 0, TEXT("Levels data must not be empty!"));
    11    // if (!SelectedLevels.Num()) return;
    12
    13    LevelItemsBox->ClearChildren();
    14
    15    for (auto LevelData : SelectedLevels)
    16    {
    17        const auto LevelItemWidget = CreateWidget<USTULevelItemWidget>(GetWorld(), SelectedLevelWidgetClass);
    18        if (!LevelItemWidget) continue;
    19
    20        LevelItemWidget->SetLevelData(LevelData);
    21
    22        LevelItemsBox->AddChild(LevelItemWidget);
    23    }
    24}

点击关卡选项时, 更新STUGameInstance的关卡信息


修改STUGameInstance存放关卡的结构体

ShootThemUp: STUGameInstance.h

  1. 修改关卡信息类型


    1// protected
    2
    3UPROPERTY(EditDefaultsOnly)
    4FName StartupLevelName = NAME_None;

    改为

    1// private
    2FLevelData StartupLevel;
  2. 屏蔽获取关卡名接口, 添加获取关卡接口

    由调用处自行取出分量

    1// FName GetStartupLevelName() const { return StartupLevelName; }
    2FLevelData GetStartupLevel() const { return StartupLevel; }
  3. 修改GetStartupLevelName调用处

    ShootThemUp: Menu/UI/STUMenuWidget.cpp

     1// OnStartGame
     2
     3// if (STUGameInstance->GetStartupLevelName().IsNone())
     4if (STUGameInstance->GetStartupLevel().LevelName.IsNone())
     5{
     6    // ...
     7}
     8
     9// UGameplayStatics::OpenLevel(GetWorld(), STUGameInstance->GetStartupLevelName());
    10UGameplayStatics::OpenLevel(GetWorld(), STUGameInstance->GetStartupLevel().LevelName);
  4. 添加设置接口

    public

    1void SetStartupLevel(const FLevelData &LevelData) { StartupLevel = LevelData; }

定义委托类型

若关卡被选中, 广播选中关卡信息

ShootThemUp: STUCoreTypes.h

1DECLARE_MULTICAST_DELEGATE_OneParam(FOnLevelSelectedSignature, const FLevelData &);

为STULevelItemWidget实现按钮点击回调函数

  1. 添加属性: 保存关卡选项, 用以委托处理函数返回

    private

    ShootThemUp: Menu/UI/STULevelItemWidget.h
    1#include "STUCoreTypes.h"
    2
    3FLevelData LevelData;
  2. 修改SetLeverlData接口: 初始化LevelData成员

    ShootThemUp: Menu/UI/STULevelItemWidget.cpp
    1// SetLevelData
    2LevelData = Data;
  3. 添加数据成员

    public

    ShootThemUp: Menu/UI/STULevelItemWidget.h
    1FOnLevelSelectedSignature OnLevelItemSelected;
  4. 实现按钮回调函数

    private

    ShootThemUp: Menu/UI/STULevelItemWidget.h
    1UFUNCTION()
    2void OnLevelSelect(); /* OnLevelItemClicked */
    ShootThemUp: Menu/UI/STULevelItemWidget.cpp
    1void USTULevelItemWidget::OnLevelSelect()
    2{
    3    OnLevelItemSelected.Broadcast(LevelData);
    4}
  5. 覆写NativeOnInitialized, 注册按钮回调函数

    protected

    ShootThemUp: Menu/UI/STULevelItemWidget.h
    1virtual void NativeOnInitialized() override;
    ShootThemUp: Menu/UI/STULevelItemWidget.cpp
     1#include "Components/Button.h"
     2
     3void USTULevelItemWidget::NativeOnInitialized()
     4{
     5    Super::NativeOnInitialized();
     6
     7    if (LevelSelectButton)
     8    {
     9        LevelSelectButton->OnClicked.AddDynamic(this, &USTULevelItemWidget::OnLevelSelect);
    10    }
    11}

在STUMenuWidget中注册关卡选中事件


STULevelItemWidget提供设置高亮框可见性接口

public

ShootThemUp: Menu/UI/STULevelItemWidget.h

1void SetFrameImageVisibility(bool Visible); /* SetSelected */

ShootThemUp: Menu/UI/STULevelItemWidget.cpp

1void USTULevelItemWidget::SetFrameImageVisibility(bool Visible)
2{
3    if (!FrameImage) return;
4    FrameImage->SetVisibility(Visible ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
5}

STUMenuWidget添加数组, 存放已创建的关卡选项

private

ShootThemUp: Menu/UI/STUMenuWidget.h

1class USTULevelItemWidget;
2
3UPROPERTY()
4TArray<USTULevelItemWidget *> LevelItemWidgets;

ShootThemUp: Menu/UI/STUMenuWidget.cpp

1// InitLevelItemsBox
2LevelItemWidgets.Add(LevelItemWidget);

STULevelItemWidget添加接口, 获取LevelData

public

ShootThemUp: Menu/UI/STULevelItemWidget.h

1FLevelData GetLevelData() const { return LevelData; }

实现处理函数

private

ShootThemUp: Menu/UI/STUMenuWidget.h

1#include "STUCoreTypes.h"
2
3void OnLevelSelected(const FLevelData &LevelData);

ShootThemUp: Menu/UI/STUMenuWidget.cpp

 1void USTUMenuWidget::OnLevelSelected(const FLevelData &LevelData)
 2{
 3    if (!GetSTUGameInstance()) return;
 4    GetSTUGameInstance()->SetStartupLevel(LevelData);
 5
 6    for (auto LevelItem : LevelItemWidgets)
 7    {
 8        if (LevelItem)
 9        {
10            LevelItem->SetFrameImageVisibility(LevelItem->GetLevelData().LevelName == LevelData.LevelName);
11        }
12    }
13}

注册服务

ShootThemUp: Menu/UI/STUMenuWidget.cpp

1// InitLevelItemsBox
2LevelItemWidget->OnLevelItemSelected.AddUObject(this, &USTUMenuWidget::OnLevelSelected);

设置默认关卡, 标记上次打开关卡

每回去到主关卡, 都会调用NativeOnInitialized, 而游戏实例中存储的StartupLevel设置后一直有值

  1. 开始时, 设置默认关卡
  2. 之后, StartupLevel保存上次打开关卡

ShootThemUp: Menu/UI/STUMenuWidget.cpp

 1// InitLevelItemsBox
 2
 3if (GetSTUGameInstance()->GetStartupLevel().LevelName.IsNone())
 4{
 5    OnLevelSelected(GetSTUGameInstance()->GetSelectedLevels()[0]);
 6}
 7else
 8{
 9    OnLevelSelected(GetSTUGameInstance()->GetStartupLevel());
10}

查看

  1. 设置关卡选项类

    WBP_Menu

    -
    Selected Level Widget Class WBP_LevelItem


  2. 配置关卡选项

    BP_STUGameInstance


  3. 查看


如果关卡选项关卡名重复, 选择其中一个, 其他重复的也会高亮, 这是因为关卡使用关卡名标识, 没有标识ID的原因


配置时给出提示

ShootThemUp: STUGameInstance.h

1UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (ToolTip = "Level names must be unique!"))
2TArray<FLevelData> SelectedLevels;