UE4开发C++沙盒游戏教程笔记(十三)(对应教程 40 ~ 42)
39. 小扩展_蓝图背包拖拽
本集老师主要演示了蓝图实现背包拖拽的功能。
其实大致思路就是拖动的时候会创建一个 “拖动格子” 的 Widget(供视觉表现,不携带数据) ,同时也会有一个对象(Operation)携带被拖动格子所包含的数据(比如物品 ID、数量等)。然后传递到鼠标松开时所对准的另一个格子(即拖动到目标格子)。此时 “拖动格子” 消失,目标格子接收数据,这样就完成了一次拖拽。
由于蓝图逻辑不是本篇笔记的重点,就不多解释了。下一节课梁迪老师会用 C++ 实现背包格子拖拽。
40. OnPaint 实现背包拖拽
上一节老师用蓝图节点来演示背包拖拽操作,结果里面的 Operation 在本集用不到 = = 💧,大概只是为了演示大致思路吧。
格子基类添加格子内存放的物品种类序号和数量的两个 int 变量。
添加一个虚方法 ResetContainerPara(),用于根据上面这两个变量改变格子显示的物品笔刷和数字。
添加一个方法 MultiplyAble(),根据物品种类 ID 判断这个物品能否叠加。
由于上面两个变量的访问权限是 protected,顺便再声明两个 public 的 Get 方法。
最后添加两个方法,用于执行鼠标拖拽物品时按左键和右键的相应逻辑。
SSlAiContainerBaseWidget.h
class SLAICOURSE_API SSlAiContainerBaseWidget : public SCompoundWidget
{
public:
// 重置自身属性
virtual void ResetContainerPara(int ObjectID, int Num);
// 获取容器的物品 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);
protected:
// 获取是否可以叠加
bool MultiplyAble(int ObjectID);
protected:
bool IsHover;
// 物品序号
int ObjectIndex;
// 物品数量
int ObjectNum;
};
SSlAiContainerBaseWidget.cpp
// 引入头文件
#include "SlAiDataHandle.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiContainerBaseWidget::Construct(const FArguments& InArgs)
{
IsHover = false;
// 初始化时物品数量和序号都为 0
ObjectIndex = ObjectNum = 0;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlAiContainerBaseWidget::ResetContainerPara(int ObjectID, int Num)
{
// 如果输入 ID 不相同,更新贴图
if (ObjectIndex != ObjectID) ObjectImage->SetBorderImage(SlAiDataHandle::Get()->ObjectBrushList[ObjectID]);
ObjectIndex = ObjectID;
ObjectNum = Num;
// 如果物品 ID 为 0
if (ObjectIndex == 0){
ObjectNumText->SetText(FText::FromString(""));
}
else {
// 判断物品是否可以叠加,是的话显示数量
if (MultiplyAble(ObjectIndex)) {
// 显示数量
ObjectNumText->SetText(FText::FromString(FString::FromInt(ObjectNum)));
}
else {
// 不可叠加的话不显示数量
ObjectNumText->SetText(FText::FromString(""));
}
}
}
int SSlAiContainerBaseWidget::GetIndex() const
{
return ObjectIndex;
}
int SSlAiContainerBaseWidget::GetNum() const
{
return ObjectNum;
}
void SSlAiContainerBaseWidget::LeftOperate(int InputID, int InputNum, int& OutputID, int& OutputNum)
{
// 如果输入物品与本地物品相同并且可以叠加
if (InputID == ObjectIndex && MultiplyAble(ObjectIndex)) {
// 根据数量判断返回的 ID
OutputID = (ObjectNum + InputNum <= 64) ? 0 : InputID;
// 如果小于 64,返回 0,大于则返回差值
OutputNum = (ObjectNum + InputNum <= 64) ? 0 : (ObjectNum + InputNum - 64);
// 设置本地数量,上限为 64
ObjectNum = (ObjectNum + InputNum <= 64) ? (ObjectNum + InputNum) : 64;
// 更新属性
ResetContainerPara(ObjectIndex, ObjectNum);
// 直接返回
return;
}
// 直接更换数据
OutputID = ObjectIndex;
OutputNum = ObjectNum;
// 更新属性
ResetContainerPara(InputID, InputNum);
}
void SSlAiContainerBaseWidget::RightOperate(int InputID, int InputNum, int& OutputID, int& OutputNum)
{
// 如果输入为空,直接把本地的一半给出去,使用进一法
if (InputID == 0)
{
OutputID = ObjectIndex;
// 区分单数双数
OutputNum = (ObjectNum % 2 == 1) ? (ObjectNum / 2 + 1) : (ObjectNum / 2);
// 更新属性
ResetContainerPara(ObjectNum - OutputNum == 0 ? 0 : ObjectIndex, ObjectNum - OutputNum);
// 直接返回
return;
}
// 如果物品相同并且可以合并,或者本地物品为空,添加一个到本地
if (ObjectIndex == 0 || (InputID == ObjectIndex && MultiplyAble(InputID)))
{
// 根据本地数量是否超出范围绑定输出数量
OutputNum = (ObjectNum + 1 <= 64) ? (InputNum - 1) : InputNum;
// 根据数量是否为 0 定义输出 ID
OutputID = (OutputNum == 0) ? 0 : InputID;
// 更新属性
ResetContainerPara(InputID, (ObjectNum + 1 <= 64) ? (ObjectNum + 1) : ObjectNum);
// 直接返回
return;
}
// 如果物品不相同或者相同但是不能合并,直接交换
OutputID = ObjectIndex;
OutputNum = ObjectNum;
// 更新属性
ResetContainerPara(InputID, InputNum);
}
bool SSlAiContainerBaseWidget::MultiplyAble(int ObjectID)
{
// 获取物品属性
TSharedPtr<ObjectAttribute> ObjectAttr = *SlAiDataHandle::Get()->ObjectAttrMap.Find(ObjectID);
// 返回是否是武器或者工具
return (ObjectAttr->ObjectType != EObjectType::Tool && ObjectAttr->ObjectType != EObjectType::Weapon);
}
在背包管理器类的头文件作如下操作:
添加两个变量,记录鼠标正在拖拽的物品的物品种类序号和物品数量。
添加拖拽物品时,按下鼠标左键和右键所执行的方法,它们会调用格子基类里对应的 Left / RightOperate()。
SlAiPackageManager.h
class SLAICOURSE_API SlAiPackageManager
{
public:
// 左键事件,参数是鼠标位置和 PackageWidget 的 Geometry
void LeftOption(FVector2D MousePos, FGeometry PackGeo);
// 右键事件,参数是鼠标位置和 PackageWidget 的Geometry
void RightOption(FVector2D MousePos, FGeometry PackGeo);
public:
// 鼠标物品 ID
int ObjectIndex;
// 鼠标物品数量
int ObjectNum;
}
SlAiPackageManager.cpp
SlAiPackageManager::SlAiPackageManager()
{
// 初始化物品和数量为 0(数据为临时测试用)
ObjectIndex = 3;
ObjectNum = 35;
}
void SlAiPackageManager::LeftOption(FVector2D MousePos, FGeometry PackGeo)
{
// 先获取点击的容器
TSharedPtr<SSlAiContainerBaseWidget> ClickedContainer = LocateContainer(MousePos, PackGeo);
// 如果容器存在,执行容器事件
if (ClickedContainer.IsValid()) {
ClickedContainer->LeftOperate(ObjectIndex, ObjectNum, ObjectIndex, ObjectNum);
}
// 如果容器不存在并且手上物品不为空
if (!ClickedContainer.IsValid() && ObjectIndex != 0) {
// 把物品丢掉,待填写
// 重置物品
ObjectIndex = ObjectNum = 0;
}
}
void SlAiPackageManager::RightOption(FVector2D MousePos, FGeometry PackGeo)
{
// 先获取点击的容器
TSharedPtr<SSlAiContainerBaseWidget> ClickedContainer = LocateContainer(MousePos, PackGeo);
// 如果容器存在,执行容器事件
if (ClickedContainer.IsValid()) {
ClickedContainer->RightOperate(ObjectIndex, ObjectNum, ObjectIndex, ObjectNum);
}
}
重写一下绘制函数,用于绘制拖拽途中显示的拖拽物品 Widget。
重写鼠标点击事件,判断按下的是哪一边的鼠标按键,然后调用背包管理器类里相应的方法。
SSlAiPackageWidget.h
class SLAICOURSE_API SSlAiPackageWidget : public SCompoundWidget
{
public:
// 重写绘制函数
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
// 重写鼠标点击事件
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
void InitPackageManager();
};
SSlAiPackageWidget.cpp
int32 SSlAiPackageWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
// 先调用一下父类函数
SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
// 如果背包管理器还没有初始化
if (!IsInitPackageMana) return LayerId;
// 如果背包管理器的手上物品不为 0,就进行渲染
if (GetVisibility() == EVisibility::Visible && SlAiPackageManager::Get()->ObjectIndex != 0 && SlAiPackageManager::Get()->ObjectNum != 0)
{
// 渲染物品图标
FSlateDrawElement::MakeBox(
OutDrawElements,
LayerId + 30,
AllottedGeometry.ToPaintGeometry(MousePosition - FVector2D(32.f, 32.f), FVector2D(64.f, 64.f)),
SlAiDataHandle::Get()->ObjectBrushList[SlAiPackageManager::Get()->ObjectIndex],
ESlateDrawEffect::None,
FLinearColor(1.f, 1.f, 1.f, 1.f)
);
// 获取物品属性
TSharedPtr<ObjectAttribute> ObjectAttr = *SlAiDataHandle::Get()->ObjectAttrMap.Find(SlAiPackageManager::Get()->ObjectIndex);
// 渲染数量,如果是不可叠加物品就不渲染
if (ObjectAttr->ObjectType != EObjectType::Tool && ObjectAttr->ObjectType != EObjectType::Weapon) {
// 渲染数量文字
FSlateDrawElement::MakeText(
OutDrawElements,
LayerId + 30,
AllottedGeometry.ToPaintGeometry(MousePosition - FVector2D(12.f, 16.f), FVector2D(16.f, 16.f)),
FString::FromInt(SlAiPackageManager::Get()->ObjectNum),
GameStyle->Font_Outline_16,
ESlateDrawEffect::None,
GameStyle->FontColor_Black
);
}
}
return LayerId;
}
FReply SSlAiPackageWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
// 如果背包管理器还没有初始化
if (!IsInitPackageMana) return FReply::Handled();
// 如果是左键点击
if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) {
SlAiPackageManager::Get()->LeftOption(MousePosition, MyGeometry);
}
// 如果是右键
if (MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton)) {
SlAiPackageManager::Get()->RightOption(MousePosition, MyGeometry);
}
// 另一种获取鼠标位置的方式:在鼠标事件中获取鼠标相对于 Widget 的位置
// 目前的获取鼠标位置逻辑写在 Tick() 里
//MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
return FReply::Handled();
}
运行后,打开背包可看见鼠标正在拖拽一个物品。通过鼠标左右键可以将物品放置到背包格子上,拖拽物品时点到其他地方的话物品会消失,可以检查一下放置和拿取的逻辑。但输出的格子在设计上是不允许物品被放进去的,后面会进行调整。
实际上笔者在测试的时候发现鼠标正在拖拽的物品的渲染层级在输入格子、箭头和输出格子之下。经过一段时间的调整后发现只要将 SSlAiPackageWidget.cpp 的 OnPaint() 方法中的 LayerId + 30 改成 LayerId + 77 就好了(其实就是提高拖拽物品 Widget 的渲染层级)。此处仅个人修改得出,读者如果遇到这个情况可以根据自己的情况来提高渲染层级。
41. 丢弃物品与绑定快捷栏
在格子基类添加 4 个委托。前两个负责背包界面的合成输入和输出,但是完整的合成逻辑暂时先不写;第三个负责丢弃物品;第四个负责快捷栏的更新。
SSlAiContainerBaseWidget.h
// 合成输入委托
DECLARE_DELEGATE(FCompoundInput)
// 合成提取委托,参数是物品序号,物品数量
DECLARE_DELEGATE_TwoParams(FCompoundOutput, int, int)
// 丢弃物品委托,参数是物品序号,物品数量
DECLARE_DELEGATE_TwoParams(FThrowObject, int, int)
// 背包快捷栏更新状态引起游戏变化委托,参数分别是快捷栏序号,更新的物品 ID,更新物品数量
DECLARE_DELEGATE_ThreeParams(FPackShortChange, int, int, int)
class SLAICOURSE_API SSlAiContainerBaseWidget : public SCompoundWidget
{
public:
// 合成输入委托
FCompoundInput CompoundInput;
// 合成输出委托
FCompoundOutput CompoundOutput;
// 丢弃物品委托
FThrowObject ThrowObject;
// 快捷栏更新委托
FPackShortChange PackShortChange;
};
在输出格子类重写左键和右键操作。因为这个格子与其他格子在鼠标悬浮时按下左键右键的效果有差异。并且在左右键操作里添加可以取出多少件物品的判断,这个取物品数量的值在后面会传入输出委托,按照这个值消耗对应数量的输入物品。
在其中执行合成输出委托和丢弃委托。
SSlAiContainerOutputWidget.h
class SLAICOURSE_API SSlAiContainerOutputWidget : public SCompoundWidget
{
public:
// 重写左键操作
virtual void LeftOperate(int InputID, int InputNum, int& OutputID, int& OutputNum) override;
// 重写右键操作
virtual void RightOperate(int InputID, int InputNum, int& OutputID, int& OutputNum) override;
};
SSlAiContainerOutputWidget.cpp
void SSlAiContainerOutputWidget::LeftOperate(int InputID, int InputNum, int& OutputID, int& OutputNum)
{
// 如果本地物品为空,输入什么就返回什么,直接 return
if (ObjectIndex == 0) {
OutputID = InputID;
OutputNum = InputNum;
return;
}
// 以下都是输入物品不为空的状态
// 如果输入物品与本地物品相同并且可以叠加
if (InputID == ObjectIndex && MultiplyAble(InputID))
{
OutputID = ObjectIndex;
OutputNum = (InputNum + ObjectNum <= 64) ? (InputNum + ObjectNum) : 64;
ObjectNum = (InputNum + ObjectNum <= 64) ? 0 : (InputNum + ObjectNum - 64);
// 执行合成输出委托
CompoundOutput.ExecuteIfBound(ObjectIndex, OutputNum - InputNum);
// 直接 return
return;
}
// 如果物品不相同或者物品相同但是不能叠加,输出物品,丢弃输入物品
OutputID = ObjectIndex;
OutputNum = ObjectNum;
// 执行合成输出委托
CompoundOutput.ExecuteIfBound(ObjectIndex, ObjectNum);
// 执行丢弃物品委托
ThrowObject.ExecuteIfBound(InputID, InputNum);
}
void SSlAiContainerOutputWidget::RightOperate(int InputID, int InputNum, int& OutputID, int& OutputNum)
{
// 如果本地物品为 0
if (ObjectIndex == 0) {
OutputID = InputID;
OutputNum = InputNum;
return;
}
// 以下都是本地物品不为 0 的情况
// 如果输入为空,直接给出本地的一半
if (InputID == 0) {
OutputID = ObjectIndex;
// 区别单数双数
OutputNum = (ObjectNum % 2 == 1) ? (ObjectNum / 2 + 1) : (ObjectNum / 2);
// 执行合成输出委托
CompoundOutput.ExecuteIfBound(ObjectIndex, OutputNum);
// 更新属性
//ResetContainerPara(ObjectNum - OutputNum == 0 ? 0 : ObjectIndex, ObjectNum - OutputNum);
// 直接返回
return;
}
// 以下都是输入不为空的情况
// 如果物品相同并且可以合并,给出去一半
if (InputID == ObjectIndex && MultiplyAble(InputID)) {
OutputID = ObjectIndex;
// 预备输出的数量
int PreOutputNum = (ObjectNum % 2 == 1) ? (ObjectNum / 2 + 1) : (ObjectNum / 2);
// 实际输出的数量
OutputNum = (PreOutputNum + InputNum <= 64) ? (PreOutputNum + InputNum) : 64;
// 更新本地数量
ObjectNum = ObjectNum - (OutputNum - InputNum);
// 执行合成输出委托
CompoundOutput.ExecuteIfBound(ObjectIndex, OutputNum - InputNum);
// 更新属性
//ResetContainerPara(ObjectNum == 0 ? 0 : ObjectIndex, ObjectNum);
// 直接返回
return;
}
// 如果物品不相同或者相同但是不能合并,把输入物品丢弃,输出本地物品的一半
OutputID = ObjectIndex;
// 区别单数双数
OutputNum = (ObjectNum % 2 == 1) ? (ObjectNum / 2 + 1) : (ObjectNum / 2);
// 执行合成输出委托
CompoundOutput.ExecuteIfBound(ObjectIndex, OutputNum);
// 执行丢弃物品委托
ThrowObject.ExecuteIfBound(InputID, InputNum);
}
输入格子类添加一个更新格子的方法,方便在对输出格子进行操作的时候改变输入格子内的物品状态。
在其中执行合成输入委托。
SSlAiContainerInputWidget.h
class SLAICOURSE_API SSlAiContainerInputWidget : public SCompoundWidget
{
public:
virtual void ResetContainerPara(int ObjectID, int Num) override;
};
下方的代码,老师添加的 bool 值 IsChanged,个人感觉是没有用的,并且会导致以下 Bug:比如用木头合成苹果,当输入九宫格内有 9 个木头,还差 1 个木头就可以合成 2 个苹果时,用左键把 1 个木头放到那个只有 1 个木头的格子,输出格子内的苹果不会变成 2 个。
解决这个 Bug 的方法就是把与局部变量 IsChange 相关的判断逻辑去掉,直接执行调用父类事件和执行合成输入委托。
SSlAiContainerInputWidget.cpp
void SSlAiContainerInputWidget::ResetContainerPara(int ObjectID, int Num)
{
// 定义是否改变
bool IsChanged = false;
if (ObjectIndex != ObjectID || ObjectNum != Num) IsChanged = true;
// 调用父类事件
SSlAiContainerBaseWidget::ResetContainerPara(ObjectID, Num);
// 如果有改变,就执行合成输入委托
if (IsChanged) CompoundInput.ExecuteIfBound();
}
因为快捷栏选中的格子会影响到角色手持物品,所以快捷栏格子也要添加对应的更新逻辑。
在其中执行快捷栏修改更新委托。
SSlAiContainerShortcutWidget.h
class SLAICOURSE_API SSlAiContainerShortcutWidget : public SCompoundWidget
{
public:
virtual void ResetContainerPara(int ObjectID, int Num) override;
};
下方的代码与上面输入格子存在同样的问题,会导致以下 Bug:往快捷栏格子中已经存在可叠加物品的时候再通过左键放入同类物品时,游戏主界面中的快捷栏格子的物品数量不变。
解决方法同上。
SSlAiContainerShortcutWidget.cpp
void SSlAiContainerShortcutWidget::ResetContainerPara(int ObjectID, int Num)
{
// 定义是否改变
bool IsChanged = false;
if (ObjectIndex != ObjectID || ObjectNum != Num) IsChanged = true;
// 调用父类事件
SSlAiContainerBaseWidget::ResetContainerPara(ObjectID, Num);
// 如果有改变,执行快捷栏修改更新委托,传出快捷栏序号以及新的物品 ID
if (IsChanged) PackShortChange.ExecuteIfBound(WorkIndex.Get(), ObjectID, Num);
}
在背包管理器类添加 4 个方法,供格子基类的 4 个委托绑定。
然后声明 2 个委托,分别用来绑定丢弃物品方法和修改快捷栏信息的方法。
SlAiPackageManager.h
class SLAICOURSE_API SlAiPackageManager
{
public:
// 丢弃物品委托,绑定的方法是 PlayerCharacter 的 PlayerThrowObject()
FThrowObject PlayerThrowObject;
// 修改快捷栏信息委托,绑定到 PlayerState 的 ChangeHandObject()
FPackShortChange ChangeHandObject;
private:
// 丢弃物品事件
void ThrowObject(int ObjectID, int Num);
// 合成提取事件
void CompoundOutput(int ObjectID, int Num);
// 合成输入事件
void CompoundInput();
// 快捷栏变换事件
void PackShortChange(int ShortcutID, int ObjectID, int ObjectNum);
}
在背包格子初始化的时候绑定格子基类的委托到背包管理器的方法,顺便把之前空着的丢弃逻辑部分补上。
SlAiPackageManager.cpp
void SlAiPackageManager::InsertContainer(TSharedPtr<class SSlAiContainerBaseWidget> Container, EContainerType::Type InsertType)
{
switch (InsertType)
{
case EContainer::Output:
OutputContainer = Container;
// 绑定合成输出容器的委托
OutputContainer->CompoundOutput.BindRaw(this, &SlAiPackageManager::CompoundOutput);
OutputContainer->ThrowObject.BindRaw(this, &SlAiPackageManager::ThrowObject);
break;
case EContainer::Input:
// 绑定合成输入委托
Container->CompoundInput.BindRaw(this, &SlAiPackageManager::CompoundInput);
InputContainerList.Add(Container);
break;
case EContainer::Normal:
NormalContainerList.Add(Container);
break;
case EContainer::Shortcut:
// 绑定快捷栏修改的委托
Container->PackShortChange.BindRaw(this, &SlAiPackageManager::PackShortChange);
ShortcutContainerList.Add(Container);
break;
}
}
void SlAiPackageManager::LeftOption(FVector2D MousePos, FGeometry PackGeo)
{
if (!ClickedContainer.IsValid() && ObjectIndex != 0) {
// 把物品丢掉
ThrowObject(ObjectIndex, ObjectNum);
ObjectIndex = ObjectNum = 0;
}
}
void SlAiPackageManager::ThrowObject(int ObjectID, int Num)
{
PlayerThrowObject.ExecuteIfBound(ObjectID, Num);
}
void SlAiPackageManager::PackShortChange(int ShortcutID, int ObjectID, int ObjectNum)
{
// 执行委托,绑定的方法是 PlayerState 的 ChangeHandObject,在 PlayerCharacter 下进行绑定
ChangeHandObject.ExecuteIfBound(ShortcutID, ObjectID, ObjectNum);
}
void SlAiPackageManager::CompoundOutput(int ObjectID, int Num)
{
}
void SlAiPackageManager::CompoundInput()
{
}
在掉落物类添加一个丢弃的方法,用于执行被丢弃时的逻辑(其实跟采集资源时生成掉落物的逻辑差不多)。
SlAiFlobObject.h
public:
// 丢弃物品初始化,丢弃的物品 ID 和丢弃方向
void ThrowFlobObject(int ObjectID, float DirYaw);
SlAiFlobObject.cpp
void ASlAiFlobObject::CreateFlobObject(int ObjectID)
{
// ... 省略
}
void ASlAiFlobObject::ThrowFlobObject(int ObjectID, float DirYaw)
{
// 指定 ID
ObjectIndex = ObjectID;
// 渲染贴图
RenderTexture();
// 随机方向添加力
FRandomStream Stream;
// 产生新的随机种子
Stream.GenerateNewSeed();
// 添加偏移方向
DirYaw += Stream.RandRange(-30, 30);
FRotator ForceRot = FRotator(0.f, DirYaw, 0.f);
// 添加力
BoxCollision->AddForce((FVector(0.f, 0.f, 2.f) + ForceRot.Vector()) * 120000.f);
}
再让角色类来调用掉落物这个丢弃的方法,作为角色丢弃物品逻辑的一部分。
SlAiPlayerCharacter.h
public:
// 丢弃物品
void PlayerThrowObject(int ObjectID, int Num);
SlAiPlayerCharacter.cpp
void ASlAiPlayerCharacter::PlayerThrowObject(int ObjectID, int Num)
{
if (GetWorld()) {
for (int i = 0; i < Num; ++i) {
// 生成掉落资源
ASlAiFlobObject* FlobObject = GetWorld()->SpawnActor<ASlAiFlobObject>(GetActorLocation() + FVector(0.f, 0.f, 50.f), FRotator::ZeroRotator);
// 以丢弃的方式生成掉落物
FlobObject->ThrowFlobObject(ObjectID, GetActorRotation().Yaw);
}
}
}
在 PlayerState 添加一个更新快捷栏物品的方法,并且方法里还需要让游玩控制器更改角色手持物品。所以再声明一下游玩控制器的指针变量和重写 BeginPlay(),游玩控制器的指针在这里面赋值。
SlAiPlayerState.h
public:
// 更改快捷栏物品信息
void ChangeHandObject(int ShortcutID, int ObjectID, int ObjectNum);
public:
// 获取控制器指针
class ASlAiPlayerController* SPController;
protected:
// 重写 BeginPlay()
virtual void BeginPlay() override;
SlAiPlayerState.cpp
// 添加头文件
#include "Kismet/GameplayStatics.h"
#include "SlAiPlayerController.h"
void ASlAiPlayerState::BeginPlay()
{
Super::BeginPlay();
// 如果控制器指针为空,添加引用
SPController = Cast<ASlAiPlayerController>(UGameplayStatics::GetPlayerController(GetWorld(), 0));
}
void ASlAiPlayerState::ChangeHandObject(int ShortcutID, int ObjectID, int ObjectNum)
{
// 更改快捷栏信息
ShortcutContainerList[ShortcutID]->SetObject(ObjectID)->SetObjectNum(ObjectNum);
// 告诉 Controller 更新一次手持物品
SPController->ChangeHandObject();
}
最后在 GameMode 绑定背包管理器的两个委托变量到角色类和 PlayerState 的方法。
SlAiGameMode.cpp
// 引入头文件
#include "SlAiPackageManager.h"
void ASlAiGameMode::InitializePackage()
{
if (IsInitPackage) return;
InitPackageManager.ExecuteIfBound();
// 绑定丢弃物品委托
SlAiPackageManager::Get()->PlayerThrowObject.BindUObject(SPCharacter, &ASlAiPlayerCharacter::PlayerThrowObject);
// 绑定修改快捷栏信息委托
SlAiPackageManager::Get()->ChangeHandObject.BindUObject(SPState, &ASlAiPlayerState::ChangeHandObject);
IsInitPackage = true;
}
此时运行游戏,在拖拽状态下,背包界面格子以外的地方按左键可以丢弃物品,能看到有掉落物飞出。快捷栏拿着剑,用背包里面的苹果替换当前选中的快捷栏格子,角色手上的物品也会随之变成苹果。并且此时输出格子也放不进物品了。