[UE4入门笔记(15)] 47.Widget获取位置和大小 48.背包系统 --梁迪老师UE4纯C++&Slate开发沙盒游戏

前言:

笔者目前在校本科大三,目标方向是人工智能、计算机视觉。上一个OpenCV学习笔记专栏已完结,在学习完OpenCV后,我继续学习C++,并用纯C++做UE4项目的方式继续提升自己的水平。

梁迪老师的水平非常高,他的课程本来也无需笔记:课程本身即为最好的笔记。但由于我天赋有限,还是边看边记,以防遗忘——知识点太多,步骤太繁杂了。在学习过程中,我也偶有思考,思索为什么某个方法老师要这样做。所以,一是为了记录,二是为了分享,才有了这个专栏。

内容方面,由于我在开启这个专栏时,此项目已经做完很多了。所以,前期的一些大篇幅叙述的知识,可能在后期应用中一带而过。以及,前期的一些知识,后期会重新剖析,并加上我的个人理解。

另外,若有学术交流/学业交流意愿,可以邮件联系1246210283@qq.com,希望一齐进步。


本篇学习内容:

47.Widget获取位置和大小
48.背包系统


47.Widget获取位置和大小

FGeometry类下的参数解释:

Position 获取相对于父类的位置

AbsolutePosition 获取在整个电脑屏幕中的位置

Size 获取这个Widget的大小

获取一个Widget A相对于某个Widget B的位置:(下面的A和B是对应的FGeometry)

B.AbsoluteToLocal(A.AbsolutePosition);

在鼠标事件中获取鼠标相对于Widget的位置:

MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());

获取DPI缩放:

UWidgetLayoutLibrary::GetViewportScale(GEngine->GameViewport);

获取鼠标在游戏窗口的位置:

#include "Engine/GameEngine.h"
#include "Engine/Engine.h"

//获取鼠标在游戏屏幕里的位置,这个位置没有经过DPI缩放
FVector2D MousePos;
if(GEngine) GEngine->GameViewport->GetMousePosition(MousePos);

//如果想要获取经过DPI缩放的位置,要获取DPIScaler

MousePos = MousePos/DPIScaler;

48.背包系统

背包系统有6个文件:ContainerBaseWidget、ContainerInputWidget、ContainerNormalWidget、ContainerOutputWidget、ContainerShortcutWidget、PackageWidget.其中,Input、Normal、Output、Shortcut类都继承自ContainerBaseWidget.

(1)枚举类准备

//背包容器类型
namespace EContainerType
{
	enum Type
	{
		Output,//合成表输出:背包合成台里的输出
		Input,//合成表输入:背包合成台里的输入
		Normal,//普通容器:背包中间那一堆,背包空间
		Shortcut,//快捷栏容器:背包下面的快捷栏容器
	};
}

(2)WorkIndex与子类传入给父类

父类Base中定义了一个参数SLATE_ATTRIBUTE(int,WorkIndex),并且有一个工厂方法static TSharedPtr<SSlAiContainerBaseWidget> CreateContainer(EContainerType::Type NeedType, int WorkID);如果想让子类创建时传入的这个WorkID传给父类,就需要在子类里创建一个一样的参数。随后在子类的构造函数中:

SSlAiContainerBaseWidget::Construct(
		SSlAiContainerBaseWidget::FArguments()
		.WorkIndex(InArgs._WorkIndex) //在子类的构造函数中,通过这种方式,把构建子类时传递的WorkIndex传递给了父类
	);

随后在工厂方法中:

TSharedPtr<SSlAiContainerBaseWidget> SSlAiContainerBaseWidget::CreateContainer(EContainerType::Type NeedType, int WorkID)
{
	TSharedPtr<SSlAiContainerBaseWidget> ResultContainer;
	switch (NeedType)
	{
	case EContainerType::Output:
		SAssignNew(ResultContainer, SSlAiContainerOutputWidget).WorkIndex(WorkID);
		break;
...
	}

}

这样,就可以把WorkIndex传给它的父类,并且对父类下的WorkIndex进行赋值。

(3)写PackageWidget结构
背包:根组件为SOverlay(因为背包是全屏幕填充的),根组件下创建SBox,SBox下创建SOverlay,再创建数个Slot,分别放背景图、底部快捷栏、背包主体、合成表输入框、合成表输出框、合成小箭头。

显然地,需要准备表格控件:快捷栏表格、背包表格、合成表表格和输出容器。

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

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

随后在cpp文件中写好构造函数。

(4)写初始化背包函数,这个函数作为委托给GameMode调用

(5)鼠标位置获取

在PackageWidget创建变量标定鼠标位置,再定义一个SLATE_ATTRIBUTE(float, UIScaler)来获取DPI缩放。由于背包UI是在GameHUD中实例化的,所以在GameHUD创建背包UI时将自己的UIScaler传给背包UI。

初始化鼠标位置后,重写Tick函数,每帧获取鼠标位置。

(6)鼠标选中背包容器

鼠标移动到背包容器上时,容器变色。

在PackageManager中准备方法:

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

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

这些方法写好之后,在PackageWidget的Tick函数中逐帧调用。

(7)背包拖拽

重写Onpaint函数和OnMouseButtonDown函数。

其中,Onpaint绘制物品和物品数量。OnMouseButtonDown调用PackageManager的LeftOption和RightOption。

随后在ContainerBaseWidget中准备:

	//物品序号
	int ObjectIndex;
	//物品数量
	int ObjectNum;

	//获取容器的物品ID和数量
	int GetIndex() const;
	int GetNum() const;

	//左键点击操作
	virtual void LeftOperate(int InputID, int InputNum, int&OutputID, int& OutputNum);
	
	//右键点击操作
	virtual void RightOperate(int InputID, int InputNum, int&OutputID, int& OutputNum);

	//获取物品是否可以叠加
	bool MultiplyAble(int ObjectID);

须知,我们需要实现哪些功能:(Normal容器下的规则)
Normal下的规则写在了虚函数LeftOperate、RightOperate下,其余的合成台等,可以对其进行重写规则。

鼠标左键选取背包某一格上物品,会拿起全部物品;右键选取背包上某一格物品,会拿起一半物品;
鼠标拿起物品时,左键或右键点击背包上某一格,如果此格上有物品,则会交换;如果此格上没有物品,左键会放下全部物品;如果此格上没有物品,右键会放下一个物品;

以此规则来写LeftOperate、RightOperate函数。

写完之后,可以写PackageManager的LeftOption和RightOption函数了。

(8)丢弃物品、绑定快捷栏

在ContainerBaseWidget中定义委托:

//合成输入委托
DECLARE_DELEGATE(FCompoundInput)
//合成提取委托,参数是物品序号,物品数量
DECLARE_DELEGATE_TwoParams(FCompoundOutput,int,int)
//丢弃物品委托,参数是物品序号,物品数量
DECLARE_DELEGATE_TwoParams(FThrowObject,int,int)
//背包快捷栏更新引起游戏变化委托,参数分别是快捷栏序号、更新的物品ID、更新物品数量
DECLARE_DELEGATE_ThreeParams(FPackShortChange,int,int,int)

然后重写ContainerOutputWidget的LeftOperateRightOperate函数

再重写ContainerInputWidget的ResetContainerPara函数

再重写ContainerShortcutWidget的ResetContainerPara函数

void SSlAiContainerShortcutWidget::ResetContainerPara(int ObjectID, int Num)
{
	bool IsChanged = false;
	if (ObjectIndex != ObjectID || ObjectNum != Num) IsChanged = true;

	//调用父类事件 放在中间是因为父类事件会对ObjectIndex和ObjectNum进行修改
	SSlAiContainerBaseWidget::ResetContainerPara(ObjectID, Num);

	//如果有改变,执行快捷栏修改更新委托,传出快捷栏序号以及新的物品ID
	if (IsChanged) PackShortChange.ExecuteIfBound(WorkIndex.Get(), ObjectID, Num);
}

这些重写的函数都会用到上面定义的委托。下面来绑定委托。

委托绑定的方法在PackageManager下,然后在SlAiPackageManager::InsertContainer函数下对委托进行绑定。

另外这四个委托绑定的方法中,ThrowObject和PackShortChange里面额外调用PackageManager创建的2个委托变量,绑定在背包初始化函数。在PackageManager的LeftOption中,丢弃物品直接为ThrowObject函数(即ContainerBase中委托绑定的函数),且这个函数中调用PackageManager创建的委托。

在PackageManager中另创建2个委托变量的好处:不用在PackageManager中引入PlayerState等文件。

(9)合成台实现

在JsonHandle中读取合成表数据,(自然要准备一个合成表结构体)

实现PackageManager的CompoundOutputCompoundInput函数

(10)添加物品到背包

快捷栏优先级最高,然后是背包主体。另外,如果有一个物品背包里有但是快捷栏没有,那么就添加到背包。

主要实现Private的SlAiPackageManager::SearchFreeSpace方法,然后创建一个SlAiPackageManager::SearchFreeSpace给外部访问,再创建一个AddObject给PlayerCharacter调用

在PlayerCharacter中写好相应调用函数后,在FlobObject、PlayerContainer下补全之前的代码。

(11)吃东西更新背包快捷栏

PackageManager下bool EatUpEvent(int ShortcutID);然后PlayerCharacter下void EatUpEvent();调用Package下的函数。然后PlayerAnim调用PlayerCharacter下函数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值