上一集的写的播放拳击动画只是测试下上半身和下半身的蒙太奇动画混合,这集实现只有玩家通过鼠标操控才播放对应蒙太奇动画。
在数据结构类添加上半身所有状态的枚举。就是比如当前拿着剑的话,左键就是挥砍动作;空手时左键则是拳击动作。
SlAiTypes.h
// 上半身动画的状态
namespace EUpperBody
{
enum Type
{
None,
Punch,
Hit,
Fight,
PickUp,
Eat
};
}
角色类存储上半身状态的枚举,并添加一个变量限制视角的切换。
限制视角切换的原因是,以某一个人称进行动作时,不应该让另一个人称的蒙太奇动画跟当前人称的蒙太奇动画同时播放,因为动画中会添加 Notify,如果不处理这种情况会导致 Notify 触发多次。
SlAiPlayerCharacter.h
public:
// 上半身动画状态
EUpperBody::Type UpperType;
// 是否允许切换视角
bool IsAllowSwitch;
SlAiPlayerCharacter.cpp
ASlAiPlayerCharacter::ASlAiPlayerCharacter()
{
GameView = EGameViewMode::Third;
// 上半身动作初始化为无动作
UpperType = EUpperBody::None;
// 一开始允许切换视角
IsAllowSwitch = true;
}
动画类声明一个方法,负责改变角色的 IsAllowSwitch;并且声明变量记录当前正在播放的蒙太奇动画以及当前是第几人称视角。
SlAiPlayerAnim.h
#include "Animation/AnimInstance.h"
// 添加头文件
#include "SlAiTypes.h"
class UAnimMontage;
{
protected:
// 修改是否允许切换视角
void AllowViewChange(bool IsAllow);
protected:
// 保存当前播放的 Montage
UAnimMontage* CurrentMontage;
// 指定自己的运行人称
EGameViewMode::Type GameView;
}
根据当前上半身的状态决定播放哪个蒙太奇动画。
SlAiPlayerAnim.cpp
void USlAiPlayerAnim::UpdateMontage()
{
if (!SPCharacter) return;
// 如果当前的人称状态和这个动作的不一致,直接返回
if (SPCharacter->GameView != GameView) return;
// 如果当前的动作没有停止,不更新动作
if (!Montage_GetIsStopped(CurrentMontage)) return;
switch (SPCharacter->UpperType)
{
case EUpperBody::None:
// 如果有哪个动作在播放
if (CurrentMontage != nullptr) {
Montage_Stop(0);
CurrentMontage = nullptr;
// 允许切换视角
AllowViewChange(true);
}
break;
case EUpperBody::Punch:
if (!Montage_IsPlaying(PlayerPunchMontage)) {
Montage_Play(PlayerPunchMontage);
CurrentMontage = PlayerPunchMontage;
// 不允许切换视角
AllowViewChange(false);
}
break;
case EUpperBody::Hit:
if (!Montage_IsPlaying(PlayerHitMontage)) {
Montage_Play(PlayerHitMontage);
CurrentMontage = PlayerHitMontage;
AllowViewChange(false);
}
break;
case EUpperBody::Fight:
if (!Montage_IsPlaying(PlayerFightMontage)) {
Montage_Play(PlayerFightMontage);
CurrentMontage = PlayerFightMontage;
AllowViewChange(false);
}
break;
case EUpperBody::PickUp:
if (!Montage_IsPlaying(PlayerPickUpMontage)) {
Montage_Play(PlayerPickUpMontage);
CurrentMontage = PlayerPickUpMontage;
AllowViewChange(false);
}
break;
case EUpperBody::Eat:
if (!Montage_IsPlaying(PlayerEatMontage)) {
Montage_Play(PlayerEatMontage);
CurrentMontage = PlayerEatMontage;
AllowViewChange(false);
}
break;
}
}
void USlAiPlayerAnim::AllowViewChange(bool IsAllow)
{
if (!SPCharacter) return;
SPCharacter->IsAllowSwitch = IsAllow;
}
在游玩控制器类里添加左右键的预动作,用于赋给角色类的 UpperType。并且添加下左右键绑定的方法里面运行的逻辑。
SlAiPlayerController.h
#include "SlAiTypes.h" // 引入头文件
#include "SlAiPlayerController.generated.h"
UCLASS()
class SLAICOURSE_API ASlAiPlayerController : public APlayerController
{
GENERATED_BODY()
private:
// 左键预动作
EUpperBody::Type LeftUpperType;
// 右键预动作
EUpperBody::Type RightUpperType;
}
SlAiPlayerController.cpp
#include "SlAiPlayerCharacter.h"
void ASlAiPlayerController::BeginPlay()
{
SetInputMode(InputMode);
// 设置预动作
LeftUpperType = EUpperBody::Punch;
RightUpperType = EUpperBody::PickUp;
}
void ASlAiPlayerController::ChangeView()
{
// 如果不允许切换视角,直接返回
if (!SPCharacter->IsAllowSwitch) return;
switch (SPCharacter->GameView)
{
case EGameViewMode::First:
SPCharacter->ChangeView(EGameViewMode::Third);
break;
case EGameViewMode::Third:
SPCharacter->ChangeView(EGameViewMode::First);
break;
}
}
// 鼠标左右事件,目前只改变角色上半身的状态枚举
void ASlAiPlayerController::LeftEventStart()
{
SPCharacter->UpperType = LeftUpperType;
}
void ASlAiPlayerController::LeftEventStop()
{
SPCharacter->UpperType = EUpperBody::None;
}
void ASlAiPlayerController::RightEventStart()
{
SPCharacter->UpperType = RightUpperType;
}
void ASlAiPlayerController::RightEventStop()
{
SPCharacter->UpperType = EUpperBody::None;
}
随后在第一人称和第三人称的动画类更改各自的 GameView 变量。
SlAiFirstPlayerAnim.cpp
USlAiFirstPlayerAnim::USlAiFirstPlayerAnim()
{
// ... 省略
// 设置自己的运行人称是第一人称
GameView = EGameViewMode::First;
}
SlAiThirdPlayerAnim.cpp
USlAiThirdPlayerAnim::USlAiThirdPlayerAnim()
{
// ... 省略
// 设置自己的运行人称是第三人称
GameView = EGameViewMode::Third;
Direction = 0.f;
IsInAir = false;
}
现在角色已经可以根据鼠标左右键来播放对应动画了,并且在做动作时无法切换人称。不过目前人物上半身的拳击动画看起来偏向一边,接下来做一下调整。
在第三人称动画蓝图里面作如图更改,同时也把这套更改复刻到第一人称动画蓝图。
然后在动画类修改一下之前声明的 SpineRotator,配合刚刚在动画蓝图添加的节点来调整上半身的朝向。
SlAiPlayerAnim.cpp
void USlAiPlayerAnim::UpdateParameter()
{
if (!SPCharacter) return;
Speed = SPCharacter->GetVelocity().Size();
// 定义上半身的旋转
float SpineDir = SPCharacter->GetActorRotation().Yaw - 90.f;
if (SpineDir > 180.f) SpineDir -= 360.f;
if (SpineDir < -180.f) SpineDir += 360.f;
SpineRotator = FRotator(0.f, SpineDir, 90.f);
}
运行后可看到,此时角色在做动作时,上半身正确地朝着正前方。(虽然笔者感觉第三人称的拳击还是有些歪)
新建一个 C++ 的 Slate Widget Style 类,取名 SlAiGameStyle,路径是 /Public/UI/Style。作为游玩时的 UI 样式类。(要记得它文件名必然会是 xxxWidgetStyle)
随后在 /Public/UI/Widget 路径下创建两个 Widget。
新建一个 C++ 的 Slate Widget 类,取名 SlAiGameHUDWidget。作为游玩时所有 UI 的根 Widget。
再新建一个 C++ 的 Slate Widget 类,取名 SlAiShortcutWidget。作为底部快捷栏界面。
首先将根 Widget 添加到游戏视口中。
SlAiGameHUD.h
UCLASS()
class SLAICOURSE_API ASlAiGameHUD : public AHUD
{
GENERATED_BODY()
public:
ASlAiGameHUD();
private:
TSharedPtr<class SSlAiGameHUDWidget> GameHUDWidget;
};
SlAiGameHUD.cpp
#include "UI/HUD/SlAiGameHUD.h"
// 引入头文件
#include "SSlAiGameHUDWidget.h"
#include "Engine/Engine.h"
#include "SlateBasics.h"
ASlAiGameHUD::ASlAiGameHUD()
{
if (GEngine && GEngine->GameViewport) {
SAssignNew(GameHUDWidget, SSlAiGameHUDWidget);
GEngine->GameViewport->AddViewportWidgetContent(
SNew(SWeakWidget).PossiblyNullContent(GameHUDWidget.ToSharedRef()));
}
}
然后给根 Widget 添加上之前菜单界面用过的自定义 DPI 相关代码逻辑。还有添加快捷栏的 Widget 的指针。
SSlAiGameHUDWidget.h
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
class SLAICOURSE_API SSlAiGameHUDWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SSlAiGameHUDWidget)
{}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
// 绑定到 UIScaler 的方法
float GetUIScaler() const;
public:
// 快捷栏指针
TSharedPtr<class SSlAiShortcutWidget> ShortcutWidget;
private:
// 获取屏幕 Size
FVector2D GetViewportSize() const;
private:
// DPI 缩放
TAttribute<float> UIScaler;
};
将快捷栏 Widget 添加到根 Widget 里面。
SSlAiGameHUDWidget.cpp
#include "UI/Widget/SSlAiGameHUDWidget.h"
#include "SlateOptMacros.h"
// 添加头文件
#include "Engine/Engine.h"
#include "SDPIScaler.h"
#include "SOverlay.h"
#include "SSlAiShortcutWidget.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiGameHUDWidget::Construct(const FArguments& InArgs)
{
UIScaler.Bind(this, &SSlAiGameHUDWidget::GetUIScaler);
ChildSlot
[
SNew(SDPIScaler)
.DPIScale(UIScaler)
[
SNew(SOverlay)
+SOverlay::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Bottom)
[
SAssignNew(ShortcutWidget, SSlAiShortcutWidget)
]
]
];
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
float SSlAiGameHUDWidget::GetUIScaler() const
{
return GetViewportSize().Y / 1080.f;
}
FVector2D SSlAiGameHUDWidget::GetViewportSize() const
{
FVector2D Result(1920.f, 1080.f);
if (GEngine && GEngine->GameViewport) {
GEngine->GameViewport->GetViewportSize(Result);
}
return Result;
}
给快捷栏 Widget 准备一些笔刷和字体之类的样式。
SlAiGameStyle.h
#pragma once
#include "CoreMinimal.h"
#include "Styling/SlateWidgetStyle.h"
#include "Styling/SlateWidgetStyleContainerBase.h"
#include "SlateBrush.h" // 引入头文件
#include "SlAiGameWidgetStyle.generated.h"
USTRUCT()
struct SLAICOURSE_API FSlAiGameStyle : public FSlateWidgetStyle
{
GENERATED_USTRUCT_BODY()
FSlAiGameStyle();
virtual ~FSlAiGameStyle();
virtual void GetResources(TArray<const FSlateBrush*>& OutBrushes) const override;
static const FName TypeName;
virtual const FName GetTypeName() const override { return TypeName; };
static const FSlAiGameStyle& GetDefault();
// 指定快捷栏的容器图标,把快捷栏的 Style 指定到 Package
UPROPERTY(EditAnywhere, Category = "Package")
FSlateBrush NormalContainerBrush;
// 指定被选择的容器的 Brush
UPROPERTY(EditAnywhere, Category = "Package")
FSlateBrush ChoosedContainerBrush;
// 指定没有颜色的 Brush
UPROPERTY(EditAnywhere, Category = "Package")
FSlateBrush EmptyBrush;
// 60 号字体
UPROPERTY(EditAnywhere, Category = "Common")
FSlateFontInfo Font_60;
// 50 号字体有轮廓
UPROPERTY(EditAnywhere, Category = "Common")
FSlateFontInfo Font_Outline_50;
// 40 号字体
UPROPERTY(EditAnywhere, Category = "Common")
FSlateFontInfo Font_40;
// 40 号字体有轮廓
UPROPERTY(EditAnywhere, Category = "Common")
FSlateFontInfo Font_Outline_40;
// 30 号字体
UPROPERTY(EditAnywhere, Category = "Common")
FSlateFontInfo Font_30;
// 20 号字体
UPROPERTY(EditAnywhere, Category = "Common")
FSlateFontInfo Font_20;
// 20 号字体有轮廓
UPROPERTY(EditAnywhere, Category = "Common")
FSlateFontInfo Font_Outline_20;
// 16 号字体有轮廓
UPROPERTY(EditAnywhere, Category = "Common")
FSlateFontInfo Font_Outline_16;
// 16 号字体有轮廓
UPROPERTY(EditAnywhere, Category = "Common")
FSlateFontInfo Font_16;
// 白色颜色
UPROPERTY(EditAnywhere, Category = "Common")
FLinearColor FontColor_White;
// 黑色颜色
UPROPERTY(EditAnywhere, Category = "Common")
FLinearColor FontColor_Black;
}
//... 后面省略
初步完善一下快捷栏 Widget 的存储容器逻辑、界面获取样式、页面布局、初始化等代码。
SSlAiShortcutWidget.h
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
class SLAICOURSE_API SSlAiShortcutWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SSlAiShortcutWidget)
{}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;
private:
// 初始化所有容器
void InitializeContainer();
private:
// 获取 GameStyle
const struct FSlAiGameStyle* GameStyle;
// 物品信息文本的指针
TSharedPtr<class STextBlock> ShortcutInfoTextBlock;
// 网格指针(所谓网格可以理解为一个有横列和竖列之分的容器控件)
TSharedPtr<class SUniformGridPanel> GridPanel;
// 是否初始化容器
bool IsInitializeContainer;
};
SSlAiShortcutWidget.cpp
#include "UI/Widget/SSlAiShortcutWidget.h"
#include "SlateOptMacros.h"
// 添加头文件
#include "SlAiStyle.h"
#include "SlAiGameWidgetStyle.h"
#include "SBox.h"
#include "SOverlay.h"
#include "STextBlock.h"
#include "SUniformGridPanel.h"
#include "SBorder.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiShortcutWidget::Construct(const FArguments& InArgs)
{
// 获取 GameStyle
GameStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiGameStyle>("BPSlAiGameStyle");
ChildSlot
[
SNew(SBox)
.WidthOverride(900.f)
.HeightOverride(160.f)
[
SNew(SOverlay)
+SOverlay::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Top)
[
SAssignNew(ShortcutInfoTextBlock, STextBlock)
.Font(GameStyle->Font_Outline_40)
.ColorAndOpacity(GameStyle->FontColor_White)
]
+SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.Padding(FMargin(0.f, 60.f, 0.f, 0.f))
[
SAssignNew(GridPanel, SUniformGridPanel)
]
]
];
// 设置没有初始化容器
IsInitializeContainer = false;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiShortcutWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
if (!IsInitializeContainer) {
InitializeContainer();
IsInitializeContainer = true;
}
}
void SSlAiShortcutWidget::InitializeContainer()
{
for (int i = 0; i < 9; ++i) {
// 创建容器
TSharedPtr<SBorder> ContainerBorder;
TSharedPtr<SBorder> ObjectImage;
TSharedPtr<STextBlock> ObjectNumText;
SAssignNew(ContainerBorder, SBorder)
.Padding(FMargin(10.f))
// 供测试用,待删除
.BorderImage(&GameStyle->NormalContainerBrush)
[
SAssignNew(ObjectImage, SBorder)
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
.Padding(FMargin(0.f, 0.f, 5.f, 0.f))
// 供测试用,待删除
.BorderImage(&GameStyle->EmptyBrush)
[
SAssignNew(ObjectNumText, STextBlock)
.Font(GameStyle->Font_Outline_20)
.ColorAndOpacity(GameStyle->FontColor_Black)
// 供测试用,待删除
.Text(FText::FromString("12"))
]
];
GridPanel->AddSlot(i, 0)
[
ContainerBorder->AsShared()
];
}
}
在 UI/Style/ 目录下以 SlAiGameWidgetStyle 类为父类,新建样式类蓝图 BPSlAiGameStyle,配置相关样式。(老师没有展示样式类的全部配置信息,读者根据自己的理解或喜好填吧,我也不打算下载跟老师一个版本的引擎再打开参考项目了,视觉上的东西不必过于苛求,运行时发现不合适再改就是了。)
此时运行后可以看到游戏界面下方的 9 个快捷栏格子以及数字。
游戏的每个物品都有它自己的数据,比如名字、物品类型、使用的图片等等,这些数据放在 Json 里面的话方便我们写代码进行读取,然后可以在快捷栏上显示出来。所以接下来要将物品的属性从老师准备好的 Json 文件中读取出来,其路径是 \Content\Res\ConfigData\ObjectAttribute.json 。
首先把上一节课结尾在 SSlAiShortcutWidget.cpp 里添加的测试代码删掉。
紧接着在数据结构类里面添加物品类型的枚举,以及物品属性和快捷栏容器的结构体。
SlAiTypes.h
// 物品类型
namespace EObjectType
{
enum Type
{
Normal = 0, // 普通物品,木头,石头
Food, // 食物,苹果,肉
Tool, // 工具,锤子,斧头
Weapon // 武器,剑
};
}
// 物品属性结构体
struct ObjectAttribute
{
FText EN; // 英文名
FText ZH; // 中文名
EObjectType::Type ObjectType; // 物品类型
int PlantAttack; // 对植物的攻击力
int MetalAttack; // 对金属资源的攻击力
int AnimalAttack; // 对动物的攻击力
int AffectRange; // 攻击距离
FString TexPath; // 图片路径
// 构造函数
ObjectAttribute(const FText ENName, const FText ZHName, const EObjectType::Type OT, const int PA, const int MA,
const int AA, const int AR, const FString TP) {
EN = ENName;
ZH = ZHName;
ObjectType = OT;
PlantAttack = PA;
MetalAttack = MA;
AnimalAttack = AA;
AffectRange = AR;
TexPath = TP;
}
// Debug 语句用于测试,测完可以删除
FString ToString() {
return EN.ToString() + FString("--") + ZH.ToString() + FString("--") + FString::FromInt((int)ObjectType) +
FString("--") + FString::FromInt(PlantAttack) + FString("--") + FString::FromInt(MetalAttack) +
FString("--") + FString::FromInt(AnimalAttack) + FString("--") + FString::FromInt(AffectRange) +
FString("--") + TexPath;
}
};
Json 数据处理类里面补充读取物品属性 Json 数据的逻辑。
SlAiJsonHandle.h
{
public:
// 解析物品属性
void ObjectAttrJsonRead(TMap<int, TSharedPtr<ObjectAttribute>>& ObjectAttrMap);
private:
// 定义一个从 FString 转换到 ObjectType 的方法
EObjectType::Type StringToObjectType(const FString ArgStr);
private:
FString RecordDataFileName;
// 物品属性文件名
FString ObjectAttrFileName;
FString RelativePath;
};
SlAiJsonHandle.cpp
SlAiJsonHandle::SlAiJsonHandle()
{
RecordDataFileName = FString("RecordData.json");
// 添加物品属性的 Json 文件路径
ObjectAttrFileName = FString("ObjectAttribute.json");
RelativePath = FString("Res/ConfigData/");
}
// 读取物品的 Json 信息
void SlAiJsonHandle::ObjectAttrJsonRead(TMap<int, TSharedPtr<ObjectAttribute>>& ObjectAttrMap)
{
FString JsonValue;
LoadStringFromFile(ObjectAttrFileName, RelativePath, JsonValue);
TArray<TSharedPtr<FJsonValue>> JsonParsed;
TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(JsonValue);
if (FJsonSerializer::Deserialize(JsonReader, JsonParsed)) {
for (int i = 0; i < JsonParsed.Num(); ++i) {
TArray<TSharedPtr<FJsonValue>> ObjectAttr = JsonParsed[i]->AsObject()->GetArrayField(FString::FromInt(i));
FText EN = FText::FromString(ObjectAttr[0]->AsObject()->GetStringField("EN"));
FText ZH = FText::FromString(ObjectAttr[1]->AsObject()->GetStringField("ZH"));
FString ObjectTypeStr = ObjectAttr[2]->AsObject()->GetStringField("ObjectType");
int PlantAttack = ObjectAttr[3]->AsObject()->GetIntegerField("PlantAttack");
// 老师的变量名打错了,如果像我这样把 MetalAttcck 改成 MetalAttack 的话,Json 文件
// 里面的对象名记得改一下
int MetalAttack = ObjectAttr[4]->AsObject()->GetIntegerField("MetalAttack");
int AnimalAttack = ObjectAttr[5]->AsObject()->GetIntegerField("AnimalAttack");
int AffectRange = ObjectAttr[6]->AsObject()->GetIntegerField("AffectRange");
FString TexPath = ObjectAttr[7]->AsObject()->GetStringField("TexPath");
EObjectType::Type ObjectType = StringToObjectType(ObjectTypeStr);
TSharedPtr<ObjectAttribute> ObjectAttrPtr = MakeShareable(new ObjectAttribute(EN, ZH, ObjectType, PlantAttack, MetalAttack, AnimalAttack, AffectRange, TexPath));
ObjectAttrMap.Add(i, ObjectAttrPtr);
}
}
else {
SlAiHelper::Debug(FString("Deserialize Failed"));
}
}
// 这里硬编码是因为数据结构类里面,这个枚举类型没有添加反射宏,没办法用 SlAiDataHandle 里的类似代码
// 如果读者想写得通用些的话参考 SlAiDataHandle 的类似代码
EObjectType::Type StringToObjectType(const FString ArgStr)
{
if (ArgStr.Equals(FString("Normal"))) return EObjectType::Normal;
if (ArgStr.Equals(FString("Food"))) return EObjectType::Food;
if (ArgStr.Equals(FString("Tool"))) return EObjectType::Tool;
if (ArgStr.Equals(FString("Weapon"))) return EObjectType::Weapon;
return EObjectType::Normal;
}
读取完 Json 文件的数据后,依旧是回到数据处理类来初始化这些物品信息数据。用一个 Map 来存储这些物品数据结构体。
SlAiDataHandle.h
class SLAICOURSE_API SlAiDataHandle
{
public:
// 游戏数据初始化
void InitializeGameData();
public:
// 物品属性图
TMap<int, TSharedPtr<ObjectAttribute>> ObjectAttrMap;
private:
// 初始化物品属性图
void InitObjectAttr();
};
SlAiDataHandle.cpp
void SlAiDataHandle::InitializeGameData()
{
// 初始化物品属性图
InitObjectAttr();
}
void SlAiDataHandle::InitObjectAttr()
{
SlAiSingleton<SlAiJsonHandle>::Get()->ObjectAttrJsonRead(ObjectAttrMap);
for (TMap<int, TSharedPtr<ObjectAttribute>>::TIterator It(ObjectAttrMap); It; ++It) {
// Debug 模块用于测试,测完可以删除
SlAiHelper::Debug((It->Value)->ToString(), 120.f);
}
}
最后让 GameMode 在游戏开始的时候调用数据处理类的这个初始化方法。
SlAiGameMode.cpp
void ASlAiGameMode::BeginPlay()
{
// 初始化游戏数据
SlAiDataHandle::Get()->InitializeGameData();
}
此时运行游戏,可以看到左上角输出了物品属性 Json 文件里的所有数据,并且都分开成一个个结构体实例。
我们的快捷栏也需要创建一个结构体来保存它里面的数据,所以还是在数据结构类里添加它的结构体。
SlAiTypes.h
// 引入四个头文件
#include "SBorder.h"
#include "SImage.h"
#include "STextBlock.h"
#include "SlateBasics.h"
// 快捷栏容器结构体
struct ShortcutContainer
{
// 物品 ID
int ObjectIndex;
int ObjectNum;
TSharedPtr<SBorder> ContainerBorder;
TSharedPtr<SBorder> ObjectImage;
TSharedPtr<STextBlock> ObjectNumText;
const FSlateBrush* NormalContainerBrush;
const FSlateBrush* ChoosedContainerBrush; // 此处英文单词拼写有误..将错就错吧,无所谓了
TArray<const FSlateBrush*>* ObjectBrushList;
ShortcutContainer(TSharedPtr<SBorder> CB, TSharedPtr<SBorder> OI, TSharedPtr<STextBlock> ONT, const FSlateBrush* NCB, const FSlateBrush* CCB, TArray<const FSlateBrush*>* OBL)
{
ContainerBorder = CB;
ObjectImage = OI;
ObjectNumText = ONT;
NormalContainerBrush = NCB;
ChoosedContainerBrush = CCB;
ObjectBrushList = OBL;
// 初始化显示设置(实际上本集开头删掉的快捷栏界面测试代码就是在这里替代掉的)
ObjectIndex = 0;
ObjectNum = 0;
ContainerBorder->SetBorderImage(NormalContainerBrush);
ObjectImage->SetBorderImage((*ObjectBrushList)[0]);
}
// 设置是否选中当前的物品,true 就是选中,返回物品类型
int SetChoosed(bool Option) {
if (Option)
{
ContainerBorder->SetBorderImage(ChoosedContainerBrush);
}
else {
ContainerBorder->SetBorderImage(NormalContainerBrush);
}
return ObjectIndex;
}
// 设置Index
ShortcutContainer* SetObject(int NewIndex) {
ObjectIndex = NewIndex;
ObjectImage->SetBorderImage((*ObjectBrushList)[ObjectIndex]);
return this;
}
// 设置数量
ShortcutContainer* SetObjectNum(int Num = 0) {
ObjectNum = Num;
// 如果数量为 0 或者 1,不显示数字
if (ObjectNum == 0 || ObjectNum == 1) {
// 4.26.2 版本提示不再支持 STextBlock 的 SetText 方法接收 FString 类型的参数,所以我改了
//ObjectNumText->SetText(FString(""));
ObjectNumText->SetText(FText::GetEmpty());
}
else {
//ObjectNumText->SetText(FString::FromInt(ObjectNum));
ObjectNumText->SetText(FText::AsNumber(ObjectNum));
}
return this;
}
};
目前先写到这里,下一节课会让这个结构体的数据应用到快捷栏界面上。