[UE4入门笔记(15)] 47.Widget获取位置和大小 48.背包系统 --梁迪老师UE4纯C++Slate开发沙盒游戏
目录
- 前言:
- 本篇学习内容:
- 47.Widget获取位置和大小
- 48.背包系统
前言:
笔者目前在校本科大三,目标方向是人工智能、计算机视觉。上一个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如果想让子类创建时传入的这个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的LeftOperate、RightOperate函数
再重写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);//如果有改变,执行快捷栏修改更新委托,传出快捷栏序号以及新的物品IDif (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的CompoundOutput和CompoundInput函数
(10)添加物品到背包
快捷栏优先级最高,然后是背包主体。另外,如果有一个物品背包里有但是快捷栏没有,那么就添加到背包。
主要实现Private的SlAiPackageManager::SearchFreeSpace方法,然后创建一个SlAiPackageManager::SearchFreeSpace给外部访问,再创建一个AddObject给PlayerCharacter调用
在PlayerCharacter中写好相应调用函数后,在FlobObject、PlayerContainer下补全之前的代码。
(11)吃东西更新背包快捷栏
PackageManager下bool EatUpEvent(int ShortcutID);然后PlayerCharacter下void EatUpEvent();调用Package下的函数。然后PlayerAnim调用PlayerCharacter下函数。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
