UE4开发C++沙盒游戏教程笔记(八)(对应教程 25 ~ 27)
24. 注册快捷栏到 PlayerState
这节课准备正式用一下在数据结构类写好的快捷栏容器结构体 ShortcutContainer。
首先在样式类声明 7 个笔刷,专门给物品的图片来使用。
SlAiGameStyle.h
USTRUCT()
struct SLAICOURSE_API FSlAiGameStyle : public FSlateWidgetStyle
{
// 物品的 Brush
UPROPERTY(EditAnywhere, Category = "Package")
FSlateBrush ObjectBrush_1;
UPROPERTY(EditAnywhere, Category = "Package")
FSlateBrush ObjectBrush_2;
UPROPERTY(EditAnywhere, Category = "Package")
FSlateBrush ObjectBrush_3;
UPROPERTY(EditAnywhere, Category = "Package")
FSlateBrush ObjectBrush_4;
UPROPERTY(EditAnywhere, Category = "Package")
FSlateBrush ObjectBrush_5;
UPROPERTY(EditAnywhere, Category = "Package")
FSlateBrush ObjectBrush_6;
UPROPERTY(EditAnywhere, Category = "Package")
FSlateBrush ObjectBrush_7;
}
然后通过数据处理类来获取这些笔刷,组合成一个笔刷数组。
SlAiDataHandle.h
class SLAICOURSE_API SlAiDataHandle
{
public:
// 物品贴图资源数组
TArray<const FSlateBrush*> ObjectBrushList;
private:
// 获取 GameStyle
const struct FSlAiGameStyle* GameStyle;
};
SlAiDataHandle.cpp
#include "SlAiGameWidgetStyle.h" // 添加头文件
void SlAiDataHandle::InitObjectAttr()
{
SlAiSingleton<SlAiJsonHandle>::Get()->ObjectAttrJsonRead(ObjectAttrMap);
// 获取 GameStyle
GameStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiGameStyle>("BPSlAiGameStyle");
// 填充笔刷数组
ObjectBrushList.Add(&GameStyle->EmptyBrush);
ObjectBrushList.Add(&GameStyle->ObjectBrush_1);
ObjectBrushList.Add(&GameStyle->ObjectBrush_2);
ObjectBrushList.Add(&GameStyle->ObjectBrush_3);
ObjectBrushList.Add(&GameStyle->ObjectBrush_4);
ObjectBrushList.Add(&GameStyle->ObjectBrush_5);
ObjectBrushList.Add(&GameStyle->ObjectBrush_6);
ObjectBrushList.Add(&GameStyle->ObjectBrush_7);
// 动态生成 Object 的图片 Brush,这段代码会引起崩溃,放在这里仅供参考
/*
for (int i = 1; i < ObjectAttrMap.Num(); ++i) {
// 测试函数,动态创建 FSlateBrush,一定要创建指针,否则会在函数结束时销毁资源
FSlateBrush* ObjectBrush = new FSlateBrush();
ObjectBrush->ImageSize = FVector2D(80.f, 80.f);
ObjectBrush->DrawAs = ESlateBrushDrawType::Image;
UTexture2D* ObjectTex = LoadObject<UTexture2D>(NULL, *(*ObjectAttrMap.Find(i))->TexPath);
ObjectBrushList.Add(ObjectBrush);
}
*/
}
SSlAiShortcutWidget.h
#include "SlAiTypes.h" // 引入头文件
把准备好的物品笔刷数组用在实例化物品容器结构体上。
SSlAiShortcutWidget.cpp
#include "SlAiDataHandle.h" // 引入头文件
void SSlAiShortcutWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
if (!IsInitializeContainer) {
InitializeContainer();
IsInitializeContainer = true;
}
}
void SSlAiShortcutWidget::InitializeContainer()
{
// 声明容器结构体的指针数组
TArray<TSharedPtr<ShortcutContainer>> ContainerList;
for (int i = 0; i < 9; ++i) {
//... 省略
// 实例化一个容器结构体
TSharedPtr<ShortcutContainer> Container = MakeShareable(new ShortcutContainer(ContainerBorder, ObjectImage,
ObjectNumText, &GameStyle->NormalContainerBrush, &GameStyle->ChoosedContainerBrush,
&SlAiDataHandle::Get()->ObjectBrushList));
// 如果是第一个容器,设置它为选中状态
if (i == 0) Container->SetChoosed(true);
ContainerList.Add(Container);
}
}
准备工作都做好了。后续我们想通过滑动滚轮切换选中的快捷栏,滑动滚轮的事件是要在玩家控制器上进行的。但我们不能直接让玩家控制器来持有一个快捷栏 UI 对象,往里面注入数据,这样做的耦合度太高了。
况且我们不能只把物品信息存储在快捷栏 UI 上,这样的话 UI 一但销毁就丢失快捷栏内物品信息了。按照标准来说,数据都是暂存在 UI 里供显示使用的,现在就只需要找一个对象来存放这个快捷栏里面有什么物品。
PlayerState 类就是一个很好的存储玩家数据的对象,PlayerState 与 UI 的数据交互我们通过委托来实现。控制器的滑动滚轮事件留到下节课再讲。
首先在快捷栏界面类里声明委托。
SSlAiShortcutWidget.h
// 注册容器到 PlayerState 类的委托(为了统一,委托名我把 cut 的 c 换成小写了)
DECLARE_DELEGATE_TwoParams(FRegisterShortcutContainer, TArray<TSharedPtr<ShortcutContainer>>*, TSharedPtr<STextBlock>)
class SLAICOURSE_API SSlAiShortcutWidget : public SCompoundWidget
{
public:
// 声明委托的变量
FRegisterShortcutContainer RegisterShortcutContainer;
}
随后在 .cpp 里执行这个委托
SSlAiShortcutWidget.cpp
void SSlAiShortcutWidget::InitializeContainer()
{
// 将实例化的结构体注册进 PlayerState 的容器数组
RegisterShortcutContainer.ExecuteIfBound(&ContainerList, ShortcutInfoTextBlock);
}
在 PlayerState 里添加存储快捷栏内物品信息结构体的数组容器、物品名字信息的文本变量;以及添加两个方法,一个是给快捷栏界面提供所有格子内物品结构体的(用来给快捷栏界面的委托绑定用),一个是获取物品名字信息的。
SlAiPlayerState.h
#include "SlAiTypes.h" // 引入头文件
#include "SlAiPlayerState.generated.h"
class STextBlock; // 提前声明
UCLASS()
class SLAICOURSE_API ASlAiPlayerState : public APlayerState
{
GENERATED_BODY()
public:
ASlAiPlayerState();
// 提供给 ShortcutWidget 的添加快捷栏容器委托
void RegisterShortcutContainer(TArray<TSharedPtr<ShortcutContainer>>* ContainerList,
TSharedPtr<STextBlock> ShortcutInfoTextBlock);
private:
// 获取快捷栏物品信息
FText GetShortcutInfoText() const;
private:
// 快捷栏序列
TArray<TSharedPtr<ShortcutContainer>> ShortcutContainerList;
// 快捷栏信息参数
TAttribute<FText> ShortcutInfoTextAttr;
};
SlAiPlayerState.cpp
#include "STextBlock.h" // 引入头文件
ASlAiPlayerState::ASlAiPlayerState()
{
}
void ASlAiPlayerState::RegisterShortcutContainer(TArray<TSharedPtr<ShortcutContainer>>* ContainerList,
TSharedPtr<STextBlock> ShortcutInfoTextBlock)
{
for (TArray<TSharedPtr<ShortcutContainer>>::TIterator It(*ContainerList); It; ++It) {
ShortcutContainerList.Add(*It);
}
ShortcutInfoTextAttr.BindUObject(this, &ASlAiPlayerState::GetShortcutInfoText);
// 绑定快捷栏信息 TextBlock
ShortcutInfoTextBlock->SetText(ShortcutInfoTextAttr);
// 临时测试代码,设置快捷栏的物品
ShortcutContainerList[1]->SetObject(1)->SetObjectNum(5);
ShortcutContainerList[2]->SetObject(2)->SetObjectNum(15);
ShortcutContainerList[3]->SetObject(3)->SetObjectNum(1);
ShortcutContainerList[4]->SetObject(4)->SetObjectNum(35);
ShortcutContainerList[5]->SetObject(5)->SetObjectNum(45);
ShortcutContainerList[6]->SetObject(6)->SetObjectNum(55);
ShortcutContainerList[7]->SetObject(7)->SetObjectNum(64);
}
FText ASlAiPlayerState::GetShortcutInfoText() const
{
// 供测试使用
return FText::FromString("hahaha");
}
HUD 类可以作为控制器与 UI 界面沟通的一个桥梁。它可以获得快捷栏界面,但是又怎么让它获取到 PlayerState,让快捷栏界面的委托能够绑定到它里面的方法呢?这时候就要借助下 GameMode 了。
在 GameMode 里初始化 PlayState,顺便也把角色和控制器类也初始化了。
SlAiGameMode.h
public:
// 组件赋值,给 GameHUD 调用,避免空引用引起崩溃
void InitGamePlayModule();
public:
class ASlAiPlayerController* SPController;
class ASlAiPlayerCharacter* SPCharacter;
class ASlAiPlayerState* SPState;
}
SlAiGameMode.cpp
void ASlAiGameMode::Tick(float DeltaSeconds)
{
}
void ASlAiGameMode::InitGamePlayModule()
{
// 添加引用
SPController = Cast<ASlAiPlayerController>(UGameplayStatics::GetPlayerController(GetWorld(), 0));
SPCharacter = Cast<ASlAiPlayerCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
SPState = Cast<ASlAiPlayerState>(SPController->PlayerState);
}
void ASlAiGameMode::BeginPlay()
{
SlAiDataHandle::Get()->InitializeGameData();
// 如果控制器存在则初始化组件
if (!SPController) InitGamePlayModule();
}
然后 HUD 再通过 GameMode 获取 PlayerState,让快捷栏界面的委托能顺利绑定到 PlayerState 的方法。
SlAiGameHUD.h
{
public:
// 保存 GameMode 指针
class ASlAiGameMode* GM;
protected:
// 重写 BeginPlay()
virtual void BeginPlay() override;
}
SlAiGameHUD.cpp
#include "UI/HUD/SlAiGameHUD.h"
#include "Engine/Engine.h"
#include "SlateBasics.h"
#include "Kismet/GameplayStatics.h" // 引入头文件
#include "SSlAiGameHUDWidget.h"
// 引入头文件
#include "SSlAiShortcutWidget.h"
// 引入头文件
#include "SlAiPlayerController.h"
#include "SlAiPlayerState.h"
#include "SlAiGameMode.h"
void ASlAiGameHUD::BeginPlay()
{
Super::BeginPlay();
GM = Cast<ASlAiGameMode>(UGameplayStatics::GetGameMode(GetWorld()));
if (!GM) return;
// 先确保要调用的组件都已经实现
GM->InitGamePlayModule();
// 绑定注册快捷栏容器
GameHUDWidget->ShortcutWidget->RegisterShortcutContainer.BindUObject(GM->SPState, &ASlAiPlayerState::RegisterShortcutContainer);
}
最后在游戏样式蓝图类配置物品笔刷。
运行可见下方快捷栏的提示文字和物品。(这里物品名字的 Font_Outline_40 的描边是黑色的)
25. 快捷栏切换与右手插槽
实现切换选中的快捷栏
接下来写一下鼠标滑轮滚动切换快捷栏选中的功能。
添加两个动作映射的按键绑定,这里就用文字表述了:
ScrollUp -> Mouse Wheel Up
ScrollDown -> Mouse Wheel Down
切换快捷栏所用到的数据和方法也是在 PlayerState 里面来实现。
SlAiPlayerState.h
#include "SlAiTypes.h"
// 引入头文件
#include "SlAiDataHandle.h"
class STextBlock;
UCLASS()
class SLAICOURSE_API ASlAiPlayerState : public APlayerState
{
GENERATED_BODY()
public:
// 切换快捷栏
void ChooseShortcut(bool IsPre);
// 获取选中容器内的物品的 Index
int GetCurrentHandObjectIndex() const;
public:
// 当前被选中的快捷栏序号
int CurrentShortcutIndex;
}
SlAiPlayerState.cpp
#include "STextBlock.h"
ASlAiPlayerState::ASlAiPlayerState()
{
// 当前选中的快捷栏序号
CurrentShortcutIndex = 0;
}
void ASlAiPlayerState::ChooseShortcut(bool IsPre)
{
// 下一个被选择的容器的下标
int NextIndex = 0;
if (IsPre) {
NextIndex = CurrentShortcutIndex - 1 < 0 ? 8 : CurrentShortcutIndex - 1;
}
else {
NextIndex = CurrentShortcutIndex + 1 > 8 ? 0 : CurrentShortcutIndex + 1;
}
ShortcutContainerList[CurrentShortcutIndex]->SetChoosed(false);
ShortcutContainerList[NextIndex]->SetChoosed(true);
// 更新当前选中的容器 Index
CurrentShortcutIndex = NextIndex;
}
int ASlAiPlayerState::GetCurrentHandObjectIndex() const
{
if (ShortcutContainerList.Num() == 0) return 0;
return ShortcutContainerList[CurrentShortcutIndex]->ObjectIndex;
}
FText ASlAiPlayerState::GetShortcutInfoText() const
{
// 把之前的测试代码去掉
TSharedPtr<ObjectAttribute> ObjectAttr;
ObjectAttr = *SlAiDataHandle::Get()->ObjectAttrMap.Find(GetCurrentHandObjectIndex());
switch (SlAiDataHandle::Get()->CurrentCulture)
{
case ECultureTeam::EN:
return ObjectAttr->EN;
case ECultureTeam::ZH:
return ObjectAttr->ZH;
}
return ObjectAttr->ZH;
}
接下来在玩家控制器类绑定滑轮滚动的事件。此外我们在按住鼠标做动作的时候不允许切换选中的快捷栏,否则会有 bug(比如吃东西的途中又切换剑,此时又要播放劈砍动作)
SlAiPlayerController.h
#include "SlAiTypes.h"
{
public:
// 获取玩家状态
class ASlAiPlayerState* SPState;
private:
// 滑轮上下滚动事件
void ScrollUpEvent();
void ScrollDownEvent();
private:
// 是否按着鼠标左右键
bool IsLeftButtonDown;
bool IsRightButtonDown;
}
SlAiPlayerController.cpp
// 引入头文件
#include "SlAiPlayerState.h"
void ASlAiPlayerController::BeginPlay()
{
Super::BeginPlay();
if (!SPCharacter) SPCharacter = Cast<ASlAiPlayerCharacter>(GetCharacter());
// 获取状态
if(!SPState) SPState = Cast<ASlAiPlayerState>(PlayerState);
bShowMouseCursor = false;
FInputModeGameOnly InputMode;
InputMode.SetConsumeCaptureMouseDown(true);
SetInputMode(InputMode);
LeftUpperType = EUpperBody::Punch;
RightUpperType = EUpperBody::PickUp;
// 初始化是否按住鼠标键
IsLeftButtonDown = false;
IsRightButtonDown = false;
}
void ASlAiPlayerController::SetupInputComponent()
{
// ... 省略
InputComponent->BindAction("RightEvent", IE_Released, this, &ASlAiPlayerController::RightEventStop);
// 绑定鼠标滑轮事件
InputComponent->BindAction("ScrollUp", IE_Pressed, this, &ASlAiPlayerController::ScrollUpEvent);
InputComponent->BindAction("ScrollDown", IE_Pressed, this, &ASlAiPlayerController::ScrollDownEvent);
}
// 补充鼠标左右键是否按下的变量更改
void ASlAiPlayerController::LeftEventStart()
{
IsLeftButtonDown = true;
SPCharacter->UpperType = LeftUpperType;
}
void ASlAiPlayerController::LeftEventStop()
{
IsLeftButtonDown = false;
SPCharacter->UpperType = EUpperBody::None;
}
void ASlAiPlayerController::RightEventStart()
{
IsRightButtonDown = true;
SPCharacter->UpperType = RightUpperType;
}
void ASlAiPlayerController::RightEventStop()
{
IsRightButtonDown = false;
SPCharacter->UpperType = EUpperBody::None;
}
void ASlAiPlayerController::ScrollUpEvent()
{
// 如果不允许切换,直接返回
if (!SPCharacter->IsAllowSwitch) return;
// 如果鼠标正在按下则不允许跳转
if (IsLeftButtonDown || IsRightButtonDown) return;
// 告诉状态类切换快捷栏容器
SPState->ChooseShortcut(true);
}
void ASlAiPlayerController::ScrollDownEvent()
{
// 如果不允许切换,直接返回
if (!SPCharacter->IsAllowSwitch) return;
// 如果鼠标正在按下则不允许跳转
if (IsLeftButtonDown || IsRightButtonDown) return;
// 告诉状态类切换快捷栏容器
SPState->ChooseShortcut(false);
}
此时运行游戏,滚动鼠标滚轮可以切换快捷栏里选中的格子,并且左右键按下后播放动作的途中不允许切换。
初步实现手上出现选中的快捷栏内物品模型
接下来实现 “角色切换到对应物品时,角色手上也出现对应物品的模型” 这个功能。
首先创建一个 C++ 的 Actor 类,路径为 /Public/Hand,命名为 SlAiHandObject,作为手上物品的基类。
随后基于这个类新建子类 SlAiHandNone、SlAiHandApple、SlAiHandAxe、SlAiHandHammer、SlAiHandMeat、SlAiHandStone、SlAiHandSword、SlAiHandWood 这八个类。
目前先简单完善下手持物品的基类,方便看效果。
SlAiHandObject.h
protected:
UPROPERTY(EditAnywhere, Category = "SlAi")
class UStaticMeshComponent* BaseMesh;
SlAiHandObject.cpp
// 引入头文件
#include "Components/StaticMeshComponent.h"
#include "ConstructorHelpers.h"
ASlAiHandObject::ASlAiHandObject()
{
PrimaryActorTick.bCanEverTick = true;
// 创建静态模型组件
BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BaseMesh"));
RootComponent = BaseMesh;
// 给模型组件添加上模型(临时代码,用于看效果)
static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticBaseMesh(TEXT("StaticMesh'/Game/Res/PolygonAdventure/Meshes/SM_Wep_Axe_01.SM_Wep_Axe_01'"));
BaseMesh->SetStaticMesh(StaticBaseMesh.Object);
}
在角色类里添加一个手部的 UChildActorComponent,用来当手持物品。
SlAiPlayerCharacter.h
private:
UPROPERTY(VisibleDefaultsOnly, Category = "SlAi")
USkeletalMeshComponent* MeshFirst;
// 手上物品
UPROPERTY(VisibleDefaultsOnly, Category = "SlAi")
class UChildActorComponent* HandObject;
老师提供的模型已经给角色的骨骼添加了对应的插槽,所以我们直接给这个 HandObject 绑定到插槽上就好了
SlAiPlayerCharacter.cpp
// 引入头文件
#include "Components/ChildActorComponent.h"
#include "SlAiHandObject.h"
ASlAiPlayerCharacter::ASlAiPlayerCharacter()
{
MeshFirst->SetOwnerNoSee(true);
// 实例化手上物品
HandObject = CreateDefaultSubobject<UChildActorComponent>(TEXT("HandObject"));
BaseTurnRate = 45.f;
}
void ASlAiPlayerCharacter::BeginPlay()
{
Super::BeginPlay();
// 把手持物品组件绑定到第三人称模型右手插槽上
HandObject->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, FName("RHSocket"));
// 添加 Actor 到 HandObject
HandObject->SetChildActorClass(ASlAiHandObject::StaticClass());
}
现在运行游戏应该可以看到角色手上有一个斧头,但它的碰撞影响到角色移动,效果不太完美,后续继续再改进。
26. 物品类与碰撞事件绑定
添加一个新的物体通道 Tool,默认回应为 Overlap。
再添加一个碰撞配置
然后把 PlayerProfile 的碰撞预设,对 Tool 的追踪类型改成 Ignore。
给手持物品类添加一个根组件,否则没办法对手持物体的网格体进行旋转等设置。
添加一个碰撞箱用于交互检测,再补充交互检测的两个方法。
添加手持物品 ID,用于标记这个物品。
SlAiHandObject.h
UCLASS()
class SLAICOURSE_API ASlAiHandObject : public AActor
{
GENERATED_BODY()
public:
ASlAiHandObject();
virtual void Tick(float DeltaTime) override;
public:
// 物品 ID
int ObjectIndex;
protected:
virtual void BeginPlay() override;
// 重写碰撞交互检测的方法
UFUNCTION()
virtual void OnOverlayBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
virtual void OnOverlayEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
protected:
// 根组件
class USceneComponent* RootScene;
// 静态模型
UPROPERTY(EditAnywhere, Category = "SlAi")
class UStaticMeshComponent* BaseMesh;
// 盒子碰撞体
UPROPERTY(EditAnywhere, Category = "SlAi")
class UBoxComponent* AffectCollision;
};
SlAiHandObject.cpp
#include "Components/StaticMeshComponent.h"
#include "Components/BoxComponent.h" // 引入头文件
#include "ConstructorHelpers.h"
ASlAiHandObject::ASlAiHandObject()
{
PrimaryActorTick.bCanEverTick = true;
// 实例化根组件
RootScene = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));
RootComponent = RootScene;
// 创建静态模型组件
BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BaseMesh"));
BaseMesh->SetupAttachment(RootComponent);
BaseMesh->SetCollisionProfileName(FName("NoCollision"));
// 实现碰撞组件
AffectCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("AffectCollision"));
AffectCollision->SetupAttachment(RootComponent);
AffectCollision->SetCollisionProfileName(FName("ToolProfile"));
// 初始时关闭 Overlay 检测
// 4.26.2 已经不允许这样用了,所以这里有更改
//AffectCollision->bGenerateOverlapEvents = false;
AffectCollision->SetGenerateOverlapEvents(false);
// 绑定检测方法到碰撞体
FScriptDelegate OverlayBegin;
OverlayBegin.BindUFunction(this, "OnOverlayBegin");
AffectCollision->OnComponentBeginOverlap.Add(OverlayBegin);
FScriptDelegate OverlayEnd;
OverlayEnd.BindUFunction(this, "OnOverlayEnd");
AffectCollision->OnComponentEndOverlap.Add(OverlayEnd);
// 去掉之前写的用于预览的代码
}
virtual void OnOverlayBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}
virtual void OnOverlayEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
关于盒体触发器的事件,如果读者不太明白的话建议看下虚幻引擎官方的一些触发器教程,这里就不作过多讲解了。
角色类这里也要去掉先前测试用的代码
SlAiPlayerCharacter.cpp
void ASlAiPlayerCharacter::BeginPlay()
{
Super::BeginPlay();
HandObject->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, FName("RHSocket"));
// 添加 Actor 到 HandObject (先把这个代码注释掉)
//HandObject->SetChildActorClass(ASlAiHandObject::StaticClass());
}
先把斧头类的内容完善下,便于观察运行效果。
SlAiHandAxe.h
public:
ASlAiHandAxe();
protected:
virtual void BeginPlay() override;
每个手持物品的网格体都要调整至贴合角色握持姿势的状态,同时碰撞盒也要调整至恰当,不过老师给我们准备好了数值。(实际上这一步操作也可以放到蓝图去做,因为通过代码调整实在太麻烦)
SlAiHandAxe.cpp
// 添加头文件
#include "Components/StaticMeshComponent.h"
#include "ConstructorHelpers.h"
#include "Components/BoxComponent.h"
ASlAiHandAxe::ASlAiHandAxe()
{
// 给模型组件添加模型
static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticBaseMesh(TEXT("StaticMesh'/Game/Res/PolygonAdventure/Meshes/SM_Wep_Axe_01.SM_Wep_Axe_01'"));
BaseMesh->SetStaticMesh(StaticBaseMesh.Object);
BaseMesh->SetRelativeLocation(FVector(-28.f, -4.9f, 3.23f));
BaseMesh->SetRelativeRotation(FRotator(0.f, -90.f, 90.f));
BaseMesh->SetRelativeScale3D(FVector(1.f, 1.f, 1.f));
// 设置碰撞盒属性
AffectCollision->SetRelativeLocation(FVector(32.f, -5.f, 3.f));
AffectCollision->SetRelativeScale3D(FVector(0.375f, 0.5f, 0.125f));
}
void ASlAiHandAxe::BeginPlay()
{
Super::BeginPlay();
// 定义物品序号
ObjectIndex = 5;
}
在 Blueprint/Temp 路径下创建 SlAiHandAxe 的蓝图,取名为 AxeBP,此时在蓝图里应该可以看到位置调整好了的、带碰撞盒的斧子。
随后将另外 7 个物品也写好跟 SlAiHandAxe 一样的代码配置。此处就不贴它们的头文件了
SlAiHandApple.cpp
// 添加头文件
#include "Components/StaticMeshComponent.h"
#include "ConstructorHelpers.h"
#include "Components/BoxComponent.h"
ASlAiHandApple::ASlAiHandApple()
{
// 给模型组件添加模型
static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticBaseMesh(TEXT("StaticMesh'/Game/Res/PolygonAdventure/Meshes/SM_Item_Fruit_02.SM_Item_Fruit_02'"));
BaseMesh->SetStaticMesh(StaticBaseMesh.Object);
BaseMesh->SetRelativeLocation(FVector(-8.f, -3.f, 7.f));
BaseMesh->SetRelativeRotation(FRotator(-90.f, 0.f, 0.f));
BaseMesh->SetRelativeScale3D(FVector(1.f, 1.f, 1.f));
// 设置碰撞盒属性
AffectCollision->SetBoxExtent(FVector(10.f, 10.f, 10.f));
AffectCollision->SetRelativeLocation(FVector(0.f, 0.f, 10.f));
}
void ASlAiHandApple::BeginPlay()
{
Super::BeginPlay();
// 定义物品序号
ObjectIndex = 3;
}
SlAiHandMeat.cpp
// 添加头文件
#include "Components/StaticMeshComponent.h"
#include "ConstructorHelpers.h"
#include "Components/BoxComponent.h"
ASlAiHandMeat::ASlAiHandMeat()
{
// 给模型组件添加模型
static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticBaseMesh(TEXT("StaticMesh'/Game/Res/PolygonAdventure/Meshes/SM_Prop_Meat_02.SM_Prop_Meat_02'"));
BaseMesh->SetStaticMesh(StaticBaseMesh.Object);
BaseMesh->SetRelativeLocation(FVector(6.f, -7.044f, 2.62f));
BaseMesh->SetRelativeRotation(FRotator(-50.f, 90.f, 0.f));
BaseMesh->SetRelativeScale3D(FVector(0.75f, 0.75f, 0.75f));
// 设置碰撞盒属性
AffectCollision->SetBoxExtent(FVector(10.f, 10.f, 10.f));
AffectCollision->SetRelativeLocation(FVector(0.f, 0.f, 10.f));
}
void ASlAiHandMeat::BeginPlay()
{
Super::BeginPlay();
// 定义物品序号
ObjectIndex = 4;
}
SlAiHandStone.cpp
// 添加头文件
#include "Components/StaticMeshComponent.h"
#include "ConstructorHelpers.h"
#include "Components/BoxComponent.h"
ASlAiHandStone::ASlAiHandStone()
{
// 给模型组件添加模型
static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticBaseMesh(TEXT("StaticMesh'/Game/Res/PolygonAdventure/Meshes/SM_Prop_StoneBlock_01.SM_Prop_StoneBlock_01'"));
BaseMesh->SetStaticMesh(StaticBaseMesh.Object);
BaseMesh->SetRelativeLocation(FVector(1.f, -1.414f, 7.071f));
BaseMesh->SetRelativeRotation(FRotator(0.f, 0.f, -135.f));
BaseMesh->SetRelativeScale3D(FVector(0.25f, 0.25f, 0.25f));
// 设置碰撞盒属性
AffectCollision->SetBoxExtent(FVector(10.f, 10.f, 10.f));
AffectCollision->SetRelativeLocation(FVector(0.f, 0.f, 10.f));
}
void ASlAiHandStone::BeginPlay()
{
Super::BeginPlay();
// 定义物品序号
ObjectIndex = 2;
}
SlAiHandSword.cpp
// 添加头文件
#include "Components/StaticMeshComponent.h"
#include "ConstructorHelpers.h"
#include "Components/BoxComponent.h"
ASlAiHandSword::ASlAiHandSword()
{
// 给模型组件添加模型
static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticBaseMesh(TEXT("StaticMesh'/Game/Res/PolygonAdventure/Meshes/SM_Wep_Sword_01.SM_Wep_Sword_01'"));
BaseMesh->SetStaticMesh(StaticBaseMesh.Object);
BaseMesh->SetRelativeLocation(FVector(-15.f, 1.f, 2.f));
BaseMesh->SetRelativeRotation(FRotator(-20.f, 90.f, -90.f));
BaseMesh->SetRelativeScale3D(FVector(0.8f, 0.8f, 1.f));
// 设置碰撞盒属性
AffectCollision->SetRelativeLocation(FVector(62.f, 1.f, 2.f));
AffectCollision->SetRelativeRotation(FRotator(0.f, 0.f, 20.f));
AffectCollision->SetRelativeScale3D(FVector(1.5f, 0.19f, 0.1f));
}
void ASlAiHandSword::BeginPlay()
{
Super::BeginPlay();
// 定义物品序号
ObjectIndex = 7;
}
SlAiHandWood.cpp
// 添加头文件
#include "Components/StaticMeshComponent.h"
#include "ConstructorHelpers.h"
#include "Components/BoxComponent.h"
ASlAiHandWood::ASlAiHandWood()
{
// 给模型组件添加模型
static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticBaseMesh(TEXT("StaticMesh'/Game/Res/PolygonAdventure/Meshes/SM_Env_TreeLog_01.SM_Env_TreeLog_01'"));
BaseMesh->SetStaticMesh(StaticBaseMesh.Object);
BaseMesh->SetRelativeScale3D(FVector(0.1f, 0.1f, 0.1f));
BaseMesh->SetRelativeRotation(FRotator(0.f, -20.f, 0.f));
// 设置碰撞盒属性
AffectCollision->SetBoxExtent(FVector(10.f, 10.f, 10.f));
AffectCollision->SetRelativeLocation(FVector(0.f, 0.f, 10.f));
}
void ASlAiHandWood::BeginPlay()
{
Super::BeginPlay();
// 定义物品序号
ObjectIndex = 1;
}
由于没有锤子的独立模型,所以锤子类的代码稍有不同。
SlAiHandHammer.h
public:
ASlAiHandHammer();
protected:
virtual void BeginPlay() override;
protected:
// 作为锤头
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh")
UStaticMeshComponent* ExtendMesh;
SlAiHandHammer.cpp
// 添加头文件
#include "Components/StaticMeshComponent.h"
#include "ConstructorHelpers.h"
#include "Components/BoxComponent.h"
ASlAiHandHammer::ASlAiHandHammer()
{
// 给模型组件添加模型
static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticBaseMesh(TEXT("StaticMesh'/Game/Res/PolygonAdventure/Meshes/SM_Bld_FencePost_01.SM_Bld_FencePost_01'"));
// 绑定模型到 Mesh 组件
BaseMesh->SetStaticMesh(StaticBaseMesh.Object);
// 添加扩展模型组件
ExtendMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ExtendMesh"));
ExtendMesh->SetupAttachment(RootComponent);
ExtendMesh->SetCollisionProfileName(FName("NoCollision"));
// 绑定模型到扩展模型组件
static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticExtendMesh(TEXT("StaticMesh'/Game/Res/PolygonAdventure/Meshes/SM_Prop_StoneBlock_01.SM_Prop_StoneBlock_01'"));
// 绑定模型到扩 Mesh 组件
ExtendMesh->SetStaticMesh(StaticExtendMesh.Object);
BaseMesh->SetRelativeLocation(FVector(35.f, 0.f, 3.f));
BaseMesh->SetRelativeRotation(FRotator(0.f, -90.f, -90.f));
BaseMesh->SetRelativeScale3D(FVector(0.4f, 0.4f, 0.4f));
ExtendMesh->SetRelativeLocation(FVector(33.f, 1.f, 3.f));
ExtendMesh->SetRelativeRotation(FRotator(0.f, -90.f, -90.f));
ExtendMesh->SetRelativeScale3D(FVector(0.4f, 0.4f, 0.4f));
// 设置碰撞盒属性
AffectCollision->SetRelativeLocation(FVector(26.f, 1.f, 3.f));
AffectCollision->SetRelativeScale3D(FVector(0.22f, 0.44f, 0.31f));
}
void ASlAiHandHammer::BeginPlay()
{
Super::BeginPlay();
// 定义物品序号
ObjectIndex = 6;
}
None 因为不需要有网格体,所以也有些区别
SlAiHandNone.cpp
// 添加头文件
#include "Components/BoxComponent.h"
ASlAiHandNone::ASlAiHandNone()
{
// 不用绑定模型
// 设置碰撞盒属性
AffectCollision->SetBoxExtent(FVector(10.f, 10.f, 10.f));
AffectCollision->SetRelativeLocation(FVector(0.f, 0.f, 10.f));
}
void ASlAiHandNone::BeginPlay()
{
Super::BeginPlay();
// 定义物品序号
ObjectIndex = 0;
}
编译后可以在 Temp 目录下新建这另外 7 个类的蓝图(但是 Temp 里面的蓝图后续都用不到,现在只是通过蓝图看看效果,看完可以删除),命名格式与 AxeBP 一致。
此时可以看见所有的物品都有了自己的蓝图和模型。