UE4开发C++沙盒游戏教程笔记(十二)(对应教程集数 38 ~ 39)

UE4开发C++沙盒游戏教程笔记(十二)(对应教程 38 ~ 39)

37. 绘制背包 UI

背包里面有不同功能的格子,如果玩过 Minecraft 的读者应该应该能理解。所以我们在数据结构类添加一个背包格子的枚举类型。

SlAiTypes.h

// 背包容器类型
namespace EContainerType
{
	enum Type
	{
		Output,	// 合成表输出
		Input,	// 合成表输入
		Normal,	// 普通容器
		Shortcut,	// 快捷栏容器
	};
}

格子基类照例需要获取游玩样式类。

添加一个 TAttribute<int> 类型的变量,同时声明它为 SLATE_ATTRIBUTE,因此这个值会由外界传入。它用于标记格子在背包中的位置作用。

添加背包格子基类用到的一些控件的指针。最后在添加一个静态工厂方法来获取这个类的实例。

SSlAiContainerBaseWidget.h

#include "CoreMinimal.h"
#include "SlAiTypes.h"	// 添加头文件

class SLAICOURSE_API SSlAiContainerBaseWidget : public SCompoundWidget
{
public:

	SLATE_BEGIN_ARGS(SSlAiContainerBaseWidget)
	{}

	// 声明 Slate 属性,它会在添加界面时,从外部传入
	SLATE_ATTRIBUTE(int, WorkIndex)

	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs);

	// 获取实例
	static TSharedPtr<SSlAiContainerBaseWidget> CreateContainer(EContainerType::Type NeedType, int WorkID);

protected:

	// 格子的各个控件
	TSharedPtr<class SBorder> ContainerBorder;
	TSharedPtr<SBorder> ObjectImage;
	TSharedPtr<class STextBlock> ObjectNumText;
	
	// 获取 GameStyle
	const struct FSlAiGameStyle* GameStyle;	

	// 工作序号,用于标记格子在背包中的位置作用
	TAttribute<int> WorkIndex;
};

同时也给四个子类的头文件全部加上这句代码,因为父类界面的这个 SLATE 属性是不会被子类继承的,只有子类也声明一个同样的属性才能让这个属性传到父类的这个属性。

	SLATE_BEGIN_ARGS(SSlAiContainerBaseWidget)
	{}

	SLATE_ATTRIBUTE(int, WorkIndex)		// 给四个子类全部加上这句

	SLATE_END_ARGS()

然后给四个子类的 .cpp 文件添加下面这段代码,即把子类的属性传递给父类。此处以 SSlAiContainerShortcutWidget 为例。

SSlAiContainerShortcutWidget.cpp

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiContainerBaseWidget::Construct(const FArguments& InArgs)
{
	// 添加这里的代码
	SSlAiContainerBaseWidget::Construct(
		SSlAiContainerBaseWidget::FArguments()
		.WorkIndex(InArgs._WorkIndex)
	);
	
	
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

然后就是完善格子基类的头文件中声明的静态工厂方法,以及完善布局。

SSlAiContainerBaseWidget.cpp

// 引入头文件
#include "SlAiStyle.h"
#include "SlAiGameWidgetStyle.h"

#include "SBorder.h"
#include "STextBlock.h"

#include "SSlAiContainerNormalWidget.h"
#include "SSlAiContainerShortcutWidget.h"
#include "SSlAiContainerInputWidget.h"
#include "SSlAiContainerOutputWidget.h"


BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiContainerBaseWidget::Construct(const FArguments& InArgs)
{
	// 获取 GameStyle
	GameStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiGameStyle>("BPSlAiGameStyle");
	// 给工作序号属性赋值,从外界传入
	WorkIndex = InArgs._WorkIndex;

	ChildSlot
	[
		SAssignNew(ContainerBorder, SBorder)
		.BorderImage(&GameStyle->NormalContainerBrush)
		.Padding(FMargin(8.f))
		[
			SAssignNew(ObjectImage, SBorder)
			.BorderImage(&GameStyle->EmptyBrush)
			.HAlign(HAlign_Right)
			.VAlign(VAlign_Bottom)
			.Padding(FMargin(0.f, 0.f, 4.f, 0.f))
			[
				SAssignNew(ObjectNumText, STextBlock)
				.Font(GameStyle->Font_Outline_16)
				.ColorAndOpacity(GameStyle->FontColor_Black)
			]
		]
	];	
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

TSharedPtr<SSlAiContainerBaseWidget> SSlAiContainerBaseWidget::CreateContainer(EContainerType::Type NeedType, int WorkID)
{
	TSharedPtr<SSlAiContainerBaseWidget> ResultContainer;
	switch (NeedType)
	{
	case EContainerType::Output:
		// 经过前面的准备,此处就可以让子类界面注册的时候传递 WorkIndex 给父类了
		SAssignNew(ResultContainer, SSlAiContainerOutputWidget).WorkIndex(WorkID);
		break;
	case EContainerType::Input:
		SAssignNew(ResultContainer, SSlAiContainerInputWidget).WorkIndex(WorkID);
		break;
	case EContainerType::Normal:
		SAssignNew(ResultContainer, SSlAiContainerNormalWidget).WorkIndex(WorkID);
		break;
	case EContainerType::Shortcut:
		SAssignNew(ResultContainer, SSlAiContainerShortcutWidget).WorkIndex(WorkID);
		break;
	}
	return ResultContainer;
}

此时格子基类已经写好了,接下来就是完善背包界面,将不同类型的格子安排到界面上。

在游玩样式类给背包界面添加笔刷。

SlAiGameStyle.h

USTRUCT()
struct SLAICOURSE_API FSlAiGameStyle : public FSlateWidgetStyle
{


	UPROPERTY(EditAnywhere, Category = "Package")
	FSlateBrush EmptyBrush;

	// 背包的背景框
	UPROPERTY(EditAnywhere, Category = "Package")
	FSlateBrush PackageBGBrush;

	// 合成表箭头
	UPROPERTY(EditAnywhere, Category = "Package")
	FSlateBrush CompoundArrowBrush;
}
//... 后面省略

首先声明一个游玩样式类的常量指针,其次给背包界面的 4 种格子所处的不同区域各自准备一个对应类型的指针。最后添加一个初始化背包管理器的方法,这个方法供 GameMode 的委托使用。

SSlAiPackageWidget.h

#include "CoreMinimal.h"
// 引入头文件
#include "SlAiTypes.h"

class SLAICOURSE_API SSlAiPackageWidget : public SCompoundWidget
{
public:

	SLATE_BEGIN_ARGS(SSlAiPackageWidget)
	{}
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs);

	// 注册背包管理器事件,由 GameMode 的 InitPackageManager 委托进行调用
	void InitPackageManager();

protected:

	// 获取 GameStyle
	const struct FSlAiGameStyle* GameStyle;	

	// 快捷栏表格
	TSharedPtr<class SUniformGridPanel> ShortcutGrid;
	
	// 背包表格
	TSharedPtr<SUniformGridPanel> PackageGrid;
	
	// 合成表表格
	TSharedPtr<SUniformGridPanel> CompoundGrid;

	// 输出容器
	TSharedPtr<class SBorder> OutputBorder;
};

把背包界面旧的用来测试效果的布局和引入的头文件删掉,换成新的正式布局。

SSlAiPackageWidget.cpp

// 去掉之前添加的旧头文件,引入新头文件
#include "SlAiStyle.h"
#include "SlAiGameWidgetStyle.h"

#include "SSlAiContainerBaseWidget.h"
#include "SOverlay.h"
#include "SBox.h"
#include "SImage.h"
#include "SUniformGridPanel.h"

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiPackageWidget::Construct(const FArguments& InArgs)
{
	// 获取 GameStyle
	GameStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiGameStyle>("BPSlAiGameStyle");

	ChildSlot
	[
		SNew(SOverlay)

		// 背包
		+SOverlay::Slot()
		.HAlign(HAlign_Right)
		.VAlign(VAlign_Center)
		.Padding(FMargin(0.f, 0.f, 50.f, 0.f))
		[
			SNew(SBox)
			.WidthOverride(800.f)
			.HeightOverride(800.f)
			[
				SNew(SOverlay)

				// 背景图
				+SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Fill)
				[
					SNew(SImage)
					.Image(&GameStyle->PackageBGBrush)
				]
				
				// 底部快捷栏
				+SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Fill)
				.Padding(FMargin(40.f, 680.f, 40.f, 40.f))
				[
					SAssignNew(ShortcutGrid, SUniformGridPanel)
				]
				
				// 背包主体
				+SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Fill)
				.Padding(FMargin(40.f, 320.f, 40.f, 160.f))
				[
					SAssignNew(PackageGrid, SUniformGridPanel)
				]
				
				// 合成表输入框
				+SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Fill)
				.Padding(FMargin(80.f, 40.f, 480.f, 520.f))
				[
					SAssignNew(CompoundGrid, SUniformGridPanel)
				]
				
				// 合成表输出框
				+SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Fill)
				.Padding(FMargin(560.f, 120.f, 160.f, 600.f))
				[
					SAssignNew(OutputBorder, SBorder)
				]
				
				// 合成小箭头
				+SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Fill)
				.Padding(FMargin(400.f, 120.f, 320.f, 600.f))
				[
					SNew(SImage)
					.Image(&GameStyle->CompoundArrowBrush)
				]
			]
		]
	];	
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

// 目前先写添加格子到界面格子区域的逻辑
void SSlAiPackageWidget::InitPackageManager()
{
	// 初始化快捷栏
	for (int i = 0; i < 9; ++i) {
		// 创建容器实例
		TSharedPtr<SSlAiContainerBaseWidget> NewContainer = SSlAiContainerBaseWidget::CreateContainer(EContainerType::Shortcut, i);
		// 将容器添加到 UI
		ShortcutGrid->AddSlot(i, 0)[NewContainer->AsShared()];
	}
	
	// 初始化背包主体
	for (int i = 0; i < 36; ++i) {
		TSharedPtr<SSlAiContainerBaseWidget> NewContainer = SSlAiContainerBaseWidget::CreateContainer(EContainerType::Normal, i);
		PackageGrid->AddSlot(i % 9, i / 9)[NewContainer->AsShared()];
	}
	
	// 初始化合成台
	for (int i = 0; i < 9; ++i) {
		TSharedPtr<SSlAiContainerBaseWidget> NewContainer = SSlAiContainerBaseWidget::CreateContainer(EContainerType::Input, i);
		CompoundGrid->AddSlot(i % 3, i / 3)[NewContainer->AsShared()];
	}
	
	// 初始化输出容器
	TSharedPtr<SSlAiContainerBaseWidget> NewContainer = SSlAiContainerBaseWidget::CreateContainer(EContainerType::Output, 1);
	OutputBorder->SetContent(NewContainer->AsShared());
}

在 GameMode 处声明委托,以及声明一个方法用于初始化背包管理器,还要声明一个 bool 变量用于判断是否已经初始化背包。

SlAiGameMode.h

// 初始化背包管理类委托
DECLARE_DELEGATE(FInitPackageManager)

public:

	// 初始化背包管理委托,绑定的方法是 PackageWidget 的 InitPackageManager 方法
	FInitPackageManager InitPackageManager;

protected:

	// 初始化背包管理类
	void InitializePackage();

private:

	// 是否已经初始化背包
	bool IsInitPackage;

SlAiGameMode.cpp

ASlAiGameMode::ASlAiGameMode()
{

	// 开始没有初始化背包
	IsInitPackage = false;
}

void ASlAiGameMode::Tick(float DeltaSeconds)
{
	InitializePackage();
}

void ASlAiGameMode::InitializePackage()
{
	if (IsInitPackage) return;

	// 叫 PackageWidget 初始化背包管理器
	InitPackageManager.ExecuteIfBound();

	IsInitPackage = true;
}

由 HUD 来绑定委托。

SlAiGameHUD.cpp

#include "SSlAiPlayerStateWidget.h"
// 引入头文件
#include "SSlAiPackageWidget.h"

void ASlAiGameHUD::BeginPlay()
{

	// 初始化背包管理器到背包组件
	GM->InitPackageManager.BindRaw(GameHUDWidget->PackageWidget.Get(), &SSlAiPackageWidget::InitPackageManager);
}

在样式类蓝图配置下笔刷:

配置背包用的笔刷

运行游戏,可看见物品栏界面显示在屏幕右边。

背包界面

38. 鼠标选中背包容器

要让背包格子能够与鼠标交互,则需要获取鼠标在屏幕中的位置以及界面在屏幕中的位置,并获取这两者之间的相对位置。直接说可能比较难以理解,接下来编写相关代码来逐步理清逻辑。

目前准备实现的是:鼠标移到背包界面的格子上时,格子会变成选中态(跟快捷栏那个选中格子一样)。

那么先在格子基类添加一个 bool 变量保存格子是否在 Hover 状态,再添加一个方法来更改它处于不同状态时的图片笔刷。

SSlAiContainerBaseWidget.h

class SLAICOURSE_API SSlAiContainerBaseWidget : public SCompoundWidget
{
public:

	// 更新鼠标移动到上面的状态
	void UpdateHovered(bool IsHovered);

protected:

	// 是否在 Hover 状态
	bool IsHover;
};

SSlAiContainerBaseWidget.cpp

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiContainerBaseWidget::Construct(const FArguments& InArgs)
{
	
	// 初始化是否悬停为 false
	IsHover = false;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION


void SSlAiContainerBaseWidget::UpdateHovered(bool IsHovered)
{
	// 如果鼠标移动到上面了
	if (IsHovered) {
		if (!IsHover) ContainerBorder->SetBorderImage(&GameStyle->ChoosedContainerBrush);
	}
	else {
		if (IsHover) ContainerBorder->SetBorderImage(&GameStyle->NormalContainerBrush);
	}
	// 更新当前状态
	IsHover = IsHovered;
}

背包管理器可以看作是 PlayerController 和 UI 之间数据交互的桥梁。因为可能在很多地方都要用到,所以要使用单例模式。之前已经用过单例模式,这里就不多说了。

由于要在背包管理器类里管理背包 UI 的格子,所以需要获取 UI 里不同种类格子组的指针。声明 3 个指针数组(指向输入格子组、普通格子组、快捷栏格子组)和 1 个指针(指向输出格子)。

再添加一个初始化这些格子的方法,也就是 InsertContainer()。让背包界面在它初始化格子布局的同时传入参数。

添加一个方法来让处于 Normal 或 Hover 态的格子改变笔刷,它会调用格子基类的 UpdateHovered()。
再声明一个方法用于获取当前鼠标悬浮于其上的格子。
并且声明一个变量用来获取上一个鼠标悬浮过的格子,让它能够在鼠标被移开时变回原色。

SlAiPackageManager.h

#pragma once

#include "CoreMinimal.h"
// 引入头文件
#include "SlAiTypes.h"
#include "SSlAiContainerBaseWidget.h"

class SLAICOURSE_API SlAiPackageManager
{
public:

	SlAiPackageManager();

	static void Initialize();

	static TSharedPtr<SlAiPackageManager> Get();

	// 添加容器
	void InsertContainer(TSharedPtr<class SSlAiContainerBaseWidget> Container, EContainerType::Type InsertType);

	// 更新悬停的容器颜色
	void UpdateHovered(FVector2D MousePos, FGeometry PackGeo);

private:

	// 创建实例方法
	static TSharedRef<SlAiPackageManager> Create();

	// 获取鼠标指向的容器
	TSharedPtr<SSlAiContainerBaseWidget> LocateContainer(FVector2D MousePos, FGeometry PackGeo);

private:

	// 单例指针
	static TSharedPtr<SlAiPackageManager> PackageInstance;

	// 容器列表
	TArray<TSharedPtr<SSlAiContainerBaseWidget>> InputContainerList;
	TArray<TSharedPtr<SSlAiContainerBaseWidget>> NormalContainerList;
	TArray<TSharedPtr<SSlAiContainerBaseWidget>> ShortcutContainerList;
	// 输出容器只有一个
	TSharedPtr<SSlAiContainerBaseWidget> OutputContainer;

	// 上一个悬停的容器
	TSharedPtr<SSlAiContainerBaseWidget> LastHoveredCon;
}

SlAiPackageManager.cpp

// 开始时对单例指针赋值
TSharedPtr<SlAiPackageManager> SlAiPackageManager::PackageInstance = NULL;

void SlAiPackageManager::Initialize()
{
	if (!PackageInstance.IsValid())
		PackageInstance = Create();
}

TSharedPtr<SlAiPackageManager> SlAiPackageManager::Get()
{
	Initialize();
	return PackageInstance;
}

TSharedRef<SlAiPackageManager> SlAiPackageManager::Create()
{
	TSharedRef<SlAiPackageManager> PackageRef = MakeShareable(new SlAiPackageManager());
	return PackageRef;
}

SlAiPackageManager::SlAiPackageManager()
{
}

void SlAiPackageManager::InsertContainer(TSharedPtr<class SSlAiContainerBaseWidget> Container, EContainerType::Type InsertType)
{
	switch (InsertType)
	{
	case EContainerType::Output:
		OutputContainer = Container;
		break;
	case EContainerType::Input:
		InputContainerList.Add(Container);
		break;
	case EContainerType::Normal:
		NormalContainerList.Add(Container);
		break;
	case EContainerType::Shortcut:
		ShortcutContainerList.Add(Container);
		break;
	}
}

void SlAiPackageManager::UpdateHovered(FVector2D MousePos, FGeometry PackGeo)
{
	// 先获取悬停的容器
	TSharedPtr<SSlAiContainerBaseWidget> CurrHoveredCon = LocateContainer(MousePos, PackGeo);
	// 如果容器存在
	if (CurrHoveredCon.IsValid())
	{
		// 设置当前容器悬停显示
		CurrHoveredCon->UpdateHovered(true);
		// 如果上一容器存在,并且与当前容器不相同
		if (LastHoveredCon.IsValid() && LastHoveredCon.Get() != CurrHoveredCon.Get()) {
			// 更新悬停显示
			LastHoveredCon->UpdateHovered(false);
		}
	}
	else {
		// 当前容器不存在且上一容器存在,取消上一容器的悬停显示
		if (LastHoveredCon.IsValid()) {
			LastHoveredCon->UpdateHovered(false);
		}
	}

	// 更新上一悬停容器
	LastHoveredCon = CurrHoveredCon;
}

TSharedPtr<SSlAiContainerBaseWidget> SlAiPackageManager::LocateContainer(FVector2D MousePos, FGeometry PackGeo)
{
	// 迭代找到指向的容器
	for (TArray<TSharedPtr<SSlAiContainerBaseWidget>>::TIterator It(ShortcutContainerList); It; ++It) {
		// 获取区域
		FVector2D StartPos = PackGeo.AbsoluteToLocal((*It)->GetCachedGeometry().AbsolutePosition);
		FVector2D EndPos = StartPos + FVector2D(80.f, 80.f);
		// 判断鼠标位置是否在区域内,在的话直接返回这个容器
		if (MousePos.X >= StartPos.X && MousePos.X <= EndPos.X && MousePos.Y >= StartPos.Y && MousePos.Y <= EndPos.Y)
		{
			return *It;
		}
	}
	
	for (TArray<TSharedPtr<SSlAiContainerBaseWidget>>::TIterator It(NormalContainerList); It; ++It) {
		// 获取区域
		FVector2D StartPos = PackGeo.AbsoluteToLocal((*It)->GetCachedGeometry().AbsolutePosition);
		FVector2D EndPos = StartPos + FVector2D(80.f, 80.f);
		// 判断鼠标位置是否在区域内,在的话直接返回这个容器
		if (MousePos.X >= StartPos.X && MousePos.X <= EndPos.X && MousePos.Y >= StartPos.Y && MousePos.Y <= EndPos.Y)
		{
			return *It;
		}
	}
	
	for (TArray<TSharedPtr<SSlAiContainerBaseWidget>>::TIterator It(InputContainerList); It; ++It) {
		// 获取区域
		FVector2D StartPos = PackGeo.AbsoluteToLocal((*It)->GetCachedGeometry().AbsolutePosition);
		FVector2D EndPos = StartPos + FVector2D(80.f, 80.f);
		// 判断鼠标位置是否在区域内,在的话直接返回这个容器
		if (MousePos.X >= StartPos.X && MousePos.X <= EndPos.X && MousePos.Y >= StartPos.Y && MousePos.Y <= EndPos.Y)
		{
			return *It;
		}
	}
	
	// 这里处理输出容器
	// 获取区域
	FVector2D StartPos = PackGeo.AbsoluteToLocal(OutputContainer->GetCachedGeometry().AbsolutePosition);
	FVector2D EndPos = StartPos + FVector2D(80.f, 80.f);
	// 判断鼠标位置是否在区域内,在的话直接返回这个容器
	if (MousePos.X >= StartPos.X && MousePos.X <= EndPos.X && MousePos.Y >= StartPos.Y && MousePos.Y <= EndPos.Y)
	{
		return OutputContainer;
	}
	// 鼠标没有在任何一个容器的时候则返回空
	return nullptr;
}

在背包界面的头文件做如下操作:

声明一个二维向量,存储鼠标在屏幕中的位置。并重写一下 Tick() 方法来更新鼠标位置。
由于界面是经过缩放的,所以 DPI 缩放的值也会影响到鼠标位置的数值,要获取位于 SiAiGameHUDWidget 的 UIScaler,我们就要利用 Slate 的属性。 所以声明一个 Slate 属性类型的变量来获取 UIScaler。

SSlAiPackageWidget.h

class SLAICOURSE_API SSlAiPackageWidget : public SCompoundWidget
{
public:

	SLATE_BEGIN_ARGS(SSlAiPackageWidget)
	{}
	
	// 添加 DPI 缩放的属性
	SLATE_ATTRIBUTE(float, UIScaler)
	
	SLATE_END_ARGS()
	
	void Construct(const FArguments& InArgs);

	// 重写 Tick 函数,用于实时获取鼠标位置,并检测鼠标是否悬浮在格子上
	virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;

	void InitPackageManager();

protected:

	
	// 鼠标位置标定
	FVector2D MousePosition;

	// DPI 的缩放
	TAttribute<float> UIScaler;

private:

	// 是否已经初始化背包管理器
	bool IsInitPackageMana;
};

在游玩界面的 .cpp 里把 UIScaler 传入。

SSlAiGameHUDWidget.cpp

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiGameHUDWidget::Construct(const FArguments& InArgs)
{


	ChildSlot
	[
		SNew(SDPIScaler)
		.DPIScaler(UIScaler)
		[
			SNew(SOverlay)

			// ... 省略
			
			// Package
			+SOverlay::Slot()
			.HAlign(HAlign_Fill)
			.VAlign(VAlign_Fill)
			[
				SAssignNew(PackageWidget, SSlAiPackageWidget)
				// 设置 DPI
				.UIScaler(UIScaler)
				.Visibility(EVisibility::Hidden)
			]
		]
	];	

	InitUIMap();
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

Tick 函数里面添加 Debug 逻辑来显示下鼠标的位置,测试完可以去掉 Debug 代码。

SSlAiPackageWidget.cpp

// 添加头文件
#include "Engine/GameEngine.h"
#include "Engine/Engine.h"
#include "SlAiHelper.h"

#include "SlAiPackageManager.h"

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiPackageWidget::Construct(const FArguments& InArgs)
{

	GameStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiGameStyle>("BPSlAiGameStyle");
	// 获取 UIScaler
	UIScaler = InArgs._UIScaler;

	ChildSlot
	[
		// ... 省略
	];	

	// 初始化鼠标位置
	MousePosition = FVector2D(0.f, 0.f);
	// 初始化是否初始化背包管理器的 bool 变量
	IsInitPackageMana = false;
}

void SSlAiPackageWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
	// 如果背包显示并且世界存在,实时更新鼠标位置
	if (GetVisibility() == EVisibility::Visible && GEngine)
	{
		GEngine->GameViewport->GetMousePosition(MousePosition);
		// Debug 用,以此显示鼠标的绝对坐标和经过 DPI 值适配后的相对坐标,测试完可以删掉
		SlAiHelper::Debug(FString("AbsoMousePos : ") + MousePosition.ToString(), 0.f);
		MousePosition = MousePosition / UIScaler.Get();
		// Debug 用
		SlAiHelper::Debug(FString("RelaMousePos : ") + MousePosition.ToString(), 0.f);
	}

	// 如果背包管理器已经初始化
	if (IsInitPackageMana) {
		// 实时更新容器悬停显示
		SlAiPackageManager::Get()->UpdateHovered(MousePosition, AllottedGeometry);
	}
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

void SSlAiPackageWidget::InitPackageManager()
{
	for (int i = 0; i < 9; ++i) {
		TSharePtr<SSlAiContainerBaseWidget> NewContainer = SSlAiContainerBaseWidget::CreateContainer(EContainerType::Shortcut, i);
		ShortcutGrid->AddSlot(i, 0)[NewContainer->AsShared()];
		// 注册容器到背包管理器
		SlAiPackageManager::Get()->InsertContainer(NewContainer, EContainerType::Shortcut);
	}
	
	for (int i = 0; i < 36; ++i) {
		TSharePtr<SSlAiContainerBaseWidget> NewContainer = SSlAiContainerBaseWidget::CreateContainer(EContainerType::Normal, i);
		PackageGrid->AddSlot(i % 9, i / 9)[NewContainer->AsShared()];
		// 注册容器到背包管理器
		SlAiPackageManager::Get()->InsertContainer(NewContainer, EContainerType::Normal);
	}
	
	for (int i = 0; i < 9; ++i) {
		TSharePtr<SSlAiContainerBaseWidget> NewContainer = SSlAiContainerBaseWidget::CreateContainer(EContainerType::Input, i);
		CompoundGrid->AddSlot(i % 3, i / 3)[NewContainer->AsShared()];
		// 注册容器到背包管理器
		SlAiPackageManager::Get()->InsertContainer(NewContainer, EContainerType::Input);
	}
	
	TSharedPtr<SSlAiContainerBaseWidget> NewContainer = SSlAiContainerBaseWidget::CreateContainer(EContainerType::Output, 1);
	OutputBorder->SetContent(NewContainer->AsShared());
	// 注册容器到背包管理器
	SlAiPackageManager::Get()->InsertContainer(NewContainer, EContainerType::Output);

	// 设置已经初始化背包管理器
	IsInitPackageMana = true;
}

此时运行游戏,打开背包,将鼠标放到背包的格子上,可看见格子会因鼠标悬停而切换成选中样式的格子图片。(在 Standalone 以外的运行模式可能会因为屏幕尺寸问题,格子不能正常响应)

格子响应

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值