UE4纯C++实现游戏快捷栏之将快捷栏注册到玩家状态

我们有了UI有了物品信息,接下来我们便需要给每一个玩家绑定一个快捷栏了,我们分以下几部分来注册我们玩家的快捷栏。

  1.Types.h:定于ShortcutContainer类,定义快捷栏的单个容器结构体,其内部存储玩家所引用的快捷栏的单个格子的信息数据

·基础的,我们将在结构体中保存{单个物品ID、物品数量、容器笔刷、物品笔刷、物品名称、容器未被选中笔刷、容器被选中笔刷、物品笔刷列表}

·此外,我们定义其初始化构造函数来完成信息填充、定义是否选中该格子来反馈正确的笔刷、定义设置新物品ID和Num的函数

 1 // 快捷栏容器结构体
 2 struct ShortcutContainer
 3 {
 4     int ObjectIndex;    // 物品ID
 5     int ObjectNum;        // 物品数量
 6     TSharedPtr<SBorder> ContainerBorder;
 7     TSharedPtr<SBorder> ObjectImage;
 8     TSharedPtr<STextBlock> ObjectNumText;
 9     const FSlateBrush* NormalContainerBrush;
10     const FSlateBrush* ChoosedContainerBrush;
11     TArray<const FSlateBrush*>* ObjectBrushList;
12 
13     // 初始化构造函数
14     ShortcutContainer(TSharedPtr<SBorder> CB, TSharedPtr<SBorder> OI, TSharedPtr<STextBlock> ONT, 
15         const FSlateBrush* NCB, const FSlateBrush* CCB, TArray<const FSlateBrush*>* OBL) : 
16         ContainerBorder(CB), ObjectImage(OI), ObjectNumText(ONT), NormalContainerBrush(NCB),
17         ChoosedContainerBrush(CCB), ObjectBrushList(OBL){
18         // 初始化显示设置
19         ObjectIndex = 0;
20         ObjectNum = 0;
21         ContainerBorder->SetBorderImage(NormalContainerBrush);
22         ObjectImage->SetBorderImage((*ObjectBrushList)[0]);
23     }
24 
25     // 设置是否选中当前物品
26     int SetChoosed(bool Option) {
27         if (Option) {
28             ContainerBorder->SetBorderImage(ChoosedContainerBrush);
29         }
30         else {
31             ContainerBorder->SetBorderImage(NormalContainerBrush);
32         }
33         return ObjectIndex;
34     }
35 
36     // 物品栏设置摆放的物品,传入物品id
37     ShortcutContainer* SetObject(int NewIndex) {
38         ObjectIndex = NewIndex;
39         ObjectImage->SetBorderImage((*ObjectBrushList)[ObjectIndex]);
40         return this;
41     }
42 
43     // 设置数量及数字显示
44     ShortcutContainer* SetObjectNum(int Num = 0) {
45         ObjectNum = Num;
46         // 如果物品数量为0or1时,不显示数字
47         if (ObjectNum == 0 || ObjectNum == 1) {
48             ObjectNumText->SetText(FString(""));
49         }
50         else {
51             ObjectNumText->SetText(FString::FromInt(ObjectNum));
52         }
53         return this;
54     }
55 
56 };
View Code

   2.GameWidgetStyle.h:填充我们需要使用的物品笔刷,并在蓝图中配置

 1 USTRUCT()
 2 struct SLAICOURSE_API FSDGameStyle : public FSlateWidgetStyle
 3 {
 4     // 物品的 Brush
 5     UPROPERTY(EditAnywhere, Category = "Package")
 6     FSlateBrush ObjectBrush_1;
 7     UPROPERTY(EditAnywhere, Category = "Package")
 8     FSlateBrush ObjectBrush_2;
 9     UPROPERTY(EditAnywhere, Category = "Package")
10     FSlateBrush ObjectBrush_3;
11     UPROPERTY(EditAnywhere, Category = "Package")
12     FSlateBrush ObjectBrush_4;
13     UPROPERTY(EditAnywhere, Category = "Package")
14     FSlateBrush ObjectBrush_5;
15     UPROPERTY(EditAnywhere, Category = "Package")
16     FSlateBrush ObjectBrush_6;
17     UPROPERTY(EditAnywhere, Category = "Package")
18     FSlateBrush ObjectBrush_7;
19     
20 }
View Code

 

  3.DataHandle:获取物品列表的笔刷数组,以便在游戏中的其他地方使用笔刷绘制出物品图标

DataHandle.h:添加物品笔刷数组、GameStyle指针

 1 class SLAICOURSE_API SDDataHandle
 2 {
 3 public:
 4     // 物品贴图资源数组
 5     TArray<const FSlateBrush*> ObjectBrushList;
 6 
 7 private:
 8     // 获取 GameStyle 
 9     const struct SDGameStyle* GameStyle;
10 };
View Code

DataHandle.cpp:在初始物品数据函数中先获取GameStyle在填充笔刷数组

 1 void SDDataHandle::InitObjectAttr()
 2 {
 3     SDSingleton<SDJsonHandle>::Get()->ObjectAttrJsonRead(ObjectAttrMap);
 4 
 5     // 获取GameStyle
 6     GameStyle = &SDStyle::Get().GetWidgetStyle<FSDGameStyle>("BPSDGameStyle");
 7 
 8     // 填充笔刷数组
 9     ObjectBrushList.Add(&GameStyle->EmptyBrush);
10     ObjectBrushList.Add(&GameStyle->ObjectBrush_1);
11     ObjectBrushList.Add(&GameStyle->ObjectBrush_2);
12     ObjectBrushList.Add(&GameStyle->ObjectBrush_3);
13     ObjectBrushList.Add(&GameStyle->ObjectBrush_4);
14     ObjectBrushList.Add(&GameStyle->ObjectBrush_5);
15     ObjectBrushList.Add(&GameStyle->ObjectBrush_6);
16     ObjectBrushList.Add(&GameStyle->ObjectBrush_7);
17     
18 }
View Code

  4.ShortcutWidget:

·在InitializeContainer()函数中,先创建快捷栏中多个容器组成的数组,并循环初始化九个(在未读表之前,我们先全部初始化为空数据)

 1 void SSDShortcutWidget::InitContainer()
 2 {
 3     TArray<TSharedPtr<ShortcutContainer>> ContainerList;    // 由单个容器组成的列表
 4 
 5     // 容器列表初始化
 6     for (int i = 0; i < 9; i++) {
 7         // 创建容器
 8         TSharedPtr<SBorder> ContainerBorder;
 9 
10         // 构建UI框架
11         TSharedPtr<SBorder> ObjectImage;        // 物品图片
12         TSharedPtr<STextBlock> ObjectNumText;    // 物品数量
13         SAssignNew(ContainerBorder, SBorder)
14             .Padding(FMargin(10.f))
15             [
16                 SAssignNew(ObjectImage, SBorder)
17                     .HAlign(HAlign_Right)
18                     .VAlign(VAlign_Bottom)
19                     .Padding(FMargin(0.f, 0.f, 5.f, 0.f))
20                     [
21                         SAssignNew(ObjectNumText, STextBlock)
22                             .Font(GameStyle->Font_Outline_20)
23                             .ColorAndOpacity(GameStyle->FontColor_Black)
24                     ]
25             ];
26 
27         // 每初始完一个组件ContainerBorder就将其添加到GridPanel
28         GridPanel->AddSlot(i, 0)
29         [
30             ContainerBorder->AsShared()
31         ] ;
32 
33         // 给快捷栏单个物品框赋值(实例化一个容器结构体)
34         TSharedPtr<ShortcutContainer> Container = MakeShareable(new ShortcutContainer(ContainerBorder
35             ,ObjectImage , ObjectNumText, &GameStyle->NormalContainerBrush, &GameStyle->ChoosedContainerBrush, 
36             &SDDataHandle::Get()->ObjectBrushList));
37 
38         // 快捷栏第一个为选中
39         if (i == 0) Container->SetChoosed(true);
40 
41         // 添加到数组
42         ContainerList.Add(Container);
43     }
44 
45 }
View Code

 

经过上面的步骤,我们已经有了一个完整的滑动窗口,每个窗口还可以自定义其包含的物品数据,接下来我们将该滑动窗口绑定到玩家,并设置滑轮切换快捷栏选中的事件。

为了降低耦合度,我们不让玩家控制器来持有快捷栏UI对象,而是让PlayerState来持有存储该快捷栏对象,同时PlayerState也是存储游戏用户数据的对象,适合同时持有快捷栏里数据和操控快捷栏。

快捷栏UI和PlayerState之间我们通过委托来实现数据交互。

  1.ShortcutWidget.h:声明委托、创建委托变量

1 // 注册容器到PlayerState类的委托
2 DECLARE_DELEGATE_TwoParams(FRegisterShortcutContainer, TArray<TSharedPtr<ShortcutContainer>>*, TSharedPtr<STextBlock>)
3 
4 class SANDBOXGAME_API SSDShortcutWidget : public SCompoundWidget
5 {
6 public:
7     // 创建委托变量
8     FRegisterShortcutContainer RegisterShortcutContainer;
9 }
View Code

  2.ShortcutWidget.cpp:执行委托

1 void SSDShortcutWidget::InitContainer()
2 {
3 
4     // 将实例化的结构体注册进PlayerState的容器数组
5 RegisterShortcutContainer.ExecuteIfBound(&ContainerList, shortcutInfoTextBlock);
6 }
View Code

  3.PlayerState.h:添加与委托变量(这里的TAttribute也是委托,使用的是TAttribute委托函数Get,具体是移步这篇文字:TAttribute原理)同名的委托函数

 1 UCLASS()
 2 class SANDBOXGAME_API ASDPlayerState : public APlayerState
 3 {
 4     GENERATED_BODY()
 5     
 6 public:
 7 
 8     ASDPlayerState();
 9 
10     // 提供给ShortcutWidget的添加快捷栏容器委托
11     void RegisterShortcutContainer(TArray<TSharedPtr<ShortcutContainer>>* ContainerList, TSharedPtr<STextBlock> ShortcutInfoTextBlock);
12 
13 private:
14     // 获取快捷栏物品信息
15     FText GetShortcutInfoText() const;
16 
17 private:
18     // 快捷栏序列
19     TArray<TSharedPtr<ShortcutContainer>> ShortcutContainerList;
20 
21     // 快捷栏信息参数(TAttribute为保存了值和获取该值委托的结构)
22     TAttribute<FText> ShortcutInfoTextAttr;
23 
24 };
View Code

  4.PlayerState.cpp:实现RegisterShortcutContainer函数、GetShortcutInfoText()函数(test版)

将传入的ContainerList初始化PlayerState持有的ShortcutContainerList变量

将PlayerState持有的ShortcutInfoTextAttr委托函数绑定为GetShortcutInfoText()

将ShortcutInfoTextBlock快捷栏文本指针所指内容绑定为ShortcutInfoTextAttr

 1 void ASDPlayerState::RegisterShortcutContainer(TArray<TSharedPtr<ShortcutContainer>>* ContainerList, TSharedPtr<STextBlock> ShortcutInfoTextBlock)
 2 {
 3     for (TArray<TSharedPtr<ShortcutContainer>>::TIterator It(*ContainerList); It; ++It) {
 4         ShortcutContainerList.Add(*It);
 5     }
 6 
 7     // 绑定函数
 8     ShortcutInfoTextAttr.BindUObject(this, &ASDPlayerState::GetShortcutInfoText);
 9 
10     // 绑定快捷栏信息TextBlokc
11     ShortcutInfoTextBlock->SetText(ShortcutInfoTextAttr);
12 
13 
14     // 物品栏测试
15     ShortcutContainerList[1]->SetObject(1)->SetObjectNum(5);
16     ShortcutContainerList[2]->SetObject(2)->SetObjectNum(15);
17     ShortcutContainerList[3]->SetObject(3)->SetObjectNum(1);
18     ShortcutContainerList[4]->SetObject(4)->SetObjectNum(35);
19     ShortcutContainerList[5]->SetObject(5)->SetObjectNum(45);
20     ShortcutContainerList[6]->SetObject(6)->SetObjectNum(55);
21     ShortcutContainerList[7]->SetObject(7)->SetObjectNum(65);
22     
23 }
24 
25 FText ASDPlayerState::GetShortcutInfoText() const
26 {
27     return FText::FromString("ha123");
28 }
View Code

 

最后我们通过GameMode来持有PlayerState,在UIWidget中通过获取GameMode来将快捷栏容器绑定注册

  1.GameMode.h:持有PlayerState指针并定义初始化函数(避免调用崩溃)

 1 class SANDBOXGAME_API ASDGameMode : public AGameModeBase
 2 {
 3     GENERATED_BODY()
 4     
 5 public:
 6         //...
 7     // 组件赋值,给GameHUD调用,避免空引用
 8     void InitGamePlayModule();
 9 
10 public:
11     // 单人游戏中由GameMode掌控这些指针
12     ASDPlayerController* SPController;
13     
14     ASDPlayerCharacter* SPCharacter;
15 
16     ASDPlayerState* SPState;
17     
18 };
View Code

  2.GameMode.cpp:初始化PlayerState指针函数实现

 1 void ASDGameMode::InitGamePlayModule()
 2 {
 3     // 添加引用,初始化指针
 4     SPController = Cast<ASDPlayerController>(UGameplayStatics::GetPlayerController(GetWorld(), 0));
 5     SPCharacter = Cast<ASDPlayerCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
 6     SPState = Cast<ASDPlayerState>(SPController->PlayerState);
 7 
 8 }
 9 
10 void ASDGameMode::BeginPlay()
11 {
12     // 游戏数据初始化
13     SDDataHandle::Get()->InitGameData();
14 
15     if (!SPController) InitGamePlayModule();
16 
17 }
View Code

  3.GameHUD.h:获取GM指针,重写BeginPlay()函数完成快捷栏UI绑定PlayerState(若PlayerState中数据改变则UI收通知改变)

 1 class SANDBOXGAME_API ASDGameHUD : public AHUD
 2 {
 3 
 4 public:
 5     // 保存GameMode指针
 6     ASDGameMode* GM;
 7 
 8 protected:
 9     virtual void BeginPlay() override;
10 
11 private:
12     TSharedPtr<SSDGameHUDWidget> GameHUDWidget;
13 };
View Code

  4.GameHUD.cpp:实现BeginPlay()完成

 1 void ASDGameHUD::BeginPlay()
 2 {
 3     // 先调用父类BeginPlay()绑定Controller和Character
 4     Super::BeginPlay();
 5 
 6     GM = Cast<ASDGameMode>(UGameplayStatics::GetGameMode(GetWorld()));
 7     if (!GM) return;
 8     // 确保所需组件都初始化
 9     GM->InitGamePlayModule();
10 
11     // 绑定注册快捷栏容器
12     GameHUDWidget->ShortcutWidget->RegisterShortcutContainer.BindUObject(GM->SPState,
13         &ASDPlayerState::RegisterShortcutContainer);
14 }
View Code

  

注:这里如果要在GameHUD()中成功调用GM->InitGamePlayModule();初始化数据,需将游戏改为独立运行,而非以客户端运行,客户端上是没有GM的,故一直为空

效果图:

 

posted @ 2024-04-26 00:09  七星易  阅读(16)  评论(0编辑  收藏  举报