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 };
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 }
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 };
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 }
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 }
经过上面的步骤,我们已经有了一个完整的滑动窗口,每个窗口还可以自定义其包含的物品数据,接下来我们将该滑动窗口绑定到玩家,并设置滑轮切换快捷栏选中的事件。
为了降低耦合度,我们不让玩家控制器来持有快捷栏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 }
2.ShortcutWidget.cpp:执行委托
1 void SSDShortcutWidget::InitContainer() 2 { 3 4 // 将实例化的结构体注册进PlayerState的容器数组 5 RegisterShortcutContainer.ExecuteIfBound(&ContainerList, shortcutInfoTextBlock); 6 }
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 };
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 }
最后我们通过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 };
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 }
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 };
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 }
注:这里如果要在GameHUD()中成功调用GM->InitGamePlayModule();初始化数据,需将游戏改为独立运行,而非以客户端运行,客户端上是没有GM的,故一直为空
效果图: