跳到主要内容C++
UE5 C++ 背包系统开发教程
基于 UE5 C++ 的背包系统实现方案。涵盖 GameInstance 全局存储设计、物品 Actor 类与重叠事件、增强输入映射配置、背包组件逻辑封装以及 UMG UI 界面开发。实现了物品的拾取、丢弃、堆叠及拖拽交换功能,展示了 C++ 在游戏逻辑处理中的性能优势与结构化管理方式。
静心35 浏览 前言
本教程适合对 UE C++ 以及 Gameplay 框架有一定了解的同学。通过实现基础功能,展示 C++ 在逻辑处理上的性能优势与代码清晰度。
创建所有需要用到的类
一、GameInstance 类
GameInstance 用于存储全局变量,背包数组在此保存可跨地图持久化。直接在 GameInstance 中创建物品结构体方便调用。
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "Engine/DataTable.h"
#include "MyBaseGameInstance.generated.h"
UENUM(BlueprintType)
enum class EItem_Type : uint8 {
Weapon UMETA(DisplayName = "Weapon"),
Prop UMETA(DisplayName = "Prop"),
Food UMETA(DisplayName = "Food")
};
USTRUCT(BlueprintType)
struct FItem_Struct : public FTableRowBase {
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite) FString Name;
UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 Index;
UPROPERTY(EditAnywhere, BlueprintReadWrite) UTexture2D* Icon;
UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 Count;
UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 Max_Count;
UPROPERTY(EditAnywhere, BlueprintReadWrite) bool Can_Stack;
(EditAnywhere, BlueprintReadWrite) EItem_Type Item_Type;
};
()
UMyBaseGameInstance : UGameInstance {
()
:
(EditAnywhere, BlueprintReadWrite) TArray<FItem_Struct> BackPack_Array;
};
UPROPERTY
UCLASS
class
BACKPACK_CPP_API
public
GENERATED_BODY
public
UPROPERTY
编译后在项目设置中将游戏实例改为 MyBaseGameInstance 类。
二、Item 类
创建物品基类 Actor,命名为 ItemBase。包含物品信息结构体及重叠事件组件。
#pragma once
#include "CoreMinimal.h"
#include "Components/SphereComponent.h"
#include "GameFramework/Actor.h"
#include "Engine/DataTable.h"
#include "Gameplay/MyBaseGameInstance.h"
#include "ItemBase.generated.h"
UCLASS()
class BACKPACK_CPP_API AItemBase : public AActor {
GENERATED_BODY()
public:
AItemBase();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="BackPackSystem") FItem_Struct CurrentItemState;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Comp") USphereComponent* SphereComp;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Comp") UStaticMeshComponent* MeshComp;
protected:
UFUNCTION()
void OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
void OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
};
AItemBase::AItemBase() {
SphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere"));
RootComponent = SphereComp;
MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
MeshComp->SetupAttachment(SphereComp);
SphereComp->OnComponentBeginOverlap.AddDynamic(this, &AItemBase::OnSphereBeginOverlap);
}
void AItemBase::OnSphereBeginOverlap(...) {
if (OtherActor && OtherActor != this) {
if (OtherActor->ActorHasTag("Player")) {
ABackPack_CppCharacter* Player = Cast<ABackPack_CppCharacter>(OtherActor);
if (Player && IsValid(Player->BackPackComponent)) {
Player->FocusedItem = this;
}
}
}
}
三、绑定输入映射
在角色类中设置增强输入,绑定 Tab 键打开背包,E 键交互拾取。
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputAction* OpenBPAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputAction* InteractAction;
bool bIsOpenBackPack = false;
UPROPERTY()
UBackPackUI* BackPackUI;
UFUNCTION(BlueprintCallable, Category="BackPackSystem")
void BackPackUIController();
UPROPERTY()
AItemBase* FocusedItem;
void Interact();
void ABackPack_CppCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) {
EnhancedInputComponent->BindAction(OpenBPAction, ETriggerEvent::Triggered, this, &ABackPack_CppCharacter::BackPackUIController);
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Triggered, this, &ABackPack_CppCharacter::Interact);
}
四、BackPackComponent 类
将背包逻辑封装为组件,便于管理添加/丢弃物品及 UI 控制。
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="BackPackSystem")
UBackPackComponent* BackPackComponent;
五、UI 类
1. BackPackSlot
UCLASS()
class BACKPACK_CPP_API UBackPackSlot : public UUserWidget {
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BackPackSystem", meta = (BindWidget))
TObjectPtr<UImage> SlotImage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BackPackSystem", meta = (BindWidget))
TObjectPtr<UTextBlock> Number_Text;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BackPackSystem")
int32 Index = -1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BackPackSystem")
int32 Number = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BackPackSystem")
TObjectPtr<UTexture2D> Image;
void SetSlot();
};
void UBackPackSlot::SetSlot() {
if (Number > 1) {
Number_Text->SetText(FText::FromString(FString(std::to_string(Number).c_str())));
Number_Text->SetOpacity(1.0f);
} else {
Number_Text->SetOpacity(0.0f);
}
FSlateBrush InBrush;
InBrush.SetResourceObject(Image);
SlotImage->SetBrush(InBrush);
}
2. BackPackUI
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BackPackSystem", meta = (BindWidget))
TObjectPtr<UUniformGridPanel> BackPackPanel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BackPackSystem", meta = (BindWidget))
TObjectPtr<UButton> CloseButton;
int32 SlotCount = 16;
3. OperateUI
UCLASS()
class BACKPACK_CPP_API UOperateUI : public UUserWidget {
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BackPackSystem", meta = (BindWidget))
TObjectPtr<UButton> UseButton;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BackPackSystem", meta = (BindWidget))
TObjectPtr<UButton> ThrowButton;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BackPackSystem")
int32 SlotIndex;
UFUNCTION(BlueprintCallable, Category="BackPackSystem")
void OnUseButtonClick();
UFUNCTION(BlueprintCallable, Category="BackPackSystem")
void OnThrowButtonClick();
};
实现具体功能
1. 打开 & 关闭背包
Tab 键触发 BackPackUIController,创建 UI 控件并添加到视口,设置鼠标光标模式。
void UBackPackComponent::BackPackUIController() {
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
if (!bIsOpenBackPack) {
if (!IsValid(GetWorld())) return;
APlayerController* OwningPlayer = GetWorld()->GetFirstPlayerController();
TSubclassOf<UUserWidget> WidgetClass = LoadClass<UBackPackUI>(nullptr, TEXT("/Game/BackPackSystem/UMG/UMG_BackPack.UMG_BackPack_C"));
BackPackUI = CreateWidget<UBackPackUI>(OwningPlayer, WidgetClass);
if (BackPackUI) {
BackPackUI->BackPackTransfer = Cast<UMyBaseGameInstance>(GetWorld()->GetGameInstance())->BackPack_Array;
BackPackUI->AddToViewport();
bIsOpenBackPack = true;
PlayerController->SetShowMouseCursor(true);
PlayerController->SetInputMode(FInputModeUIOnly());
}
} else {
if (BackPackUI) BackPackUI->RemoveFromParent();
PlayerController->SetInputMode(FInputModeGameOnly());
PlayerController->SetShowMouseCursor(false);
bIsOpenBackPack = false;
if (OperateUI) OperateUI->RemoveFromParent();
}
}
void UBackPackUI::NativeConstruct() {
Super::NativeConstruct();
UMyBaseGameInstance* MyGameInstance = Cast<UMyBaseGameInstance>(GetWorld()->GetGameInstance());
BackPackTransfer = MyGameInstance->BackPack_Array;
RefreshBackPack();
CloseButton->OnClicked.AddDynamic(this, &UBackPackUI::OnCloseButtonClick);
}
void UBackPackUI::RefreshBackPack() {
if (!IsValid(GetWorld())) return;
APlayerController* OwningPlayer = GetWorld()->GetFirstPlayerController();
TSubclassOf<UBackPackSlot> WidgetClass = LoadClass<UBackPackSlot>(nullptr, TEXT("/Game/BackPackSystem/UMG/UMG_BackPackSlot.UMG_BackPackSlot_C"));
BackPackPanel->ClearChildren();
for (int32 i = 0; i < SlotCount; i++) {
BackPackSlot = CreateWidget<UBackPackSlot>(OwningPlayer, WidgetClass);
if (BackPackTransfer.IsValidIndex(i)) {
BackPackSlot->Image = BackPackTransfer[i].Icon;
BackPackSlot->Index = i;
BackPackSlot->Number = BackPackTransfer[i].Count;
BackPackSlot->SetSlot();
}
BackPackPanel->AddChildToUniformGrid(BackPackSlot, (i / 4), (i % 4));
}
}
2. 拾取物品
按下 E 键调用 AddItem,检查背包是否有同名可堆叠物品,更新数量或新增条目。
void UBackPackComponent::AddItem(FItem_Struct NewItem) {
UWorld* world = GetWorld();
if (!world) return;
UMyBaseGameInstance* MyGameInstance = Cast<UMyBaseGameInstance>(GetWorld()->GetGameInstance());
if (!MyGameInstance) return;
TArray<FItem_Struct>& BackpackArray = MyGameInstance->BackPack_Array;
for (auto& Item : BackpackArray) {
if (Item.Name == NewItem.Name && Item.Can_Stack && Item.Count < Item.Max_Count) {
Item.Count += NewItem.Count;
return;
}
}
NewItem.Index = BackpackArray.Num();
BackpackArray.Add(NewItem);
}
3. 丢弃物品
右键点击格子生成 OperateUI,点击丢弃按钮调用 ThrowItem,根据索引移除物品并在原地生成 Actor。
void UBackPackComponent::ThrowItem(int32 SlotIndex) {
if (!IsValid(GetWorld())) return;
UMyBaseGameInstance* MyGameInstance = Cast<UMyBaseGameInstance>(GetWorld()->GetGameInstance());
TArray<FItem_Struct>& BackPackArray = MyGameInstance->BackPack_Array;
if (SlotIndex < 0 || SlotIndex >= BackPackArray.Num()) return;
if (BackPackArray.Num() > 0) {
FString Name = BackPackArray[SlotIndex].Name;
AActor* SpawnActor = SpawnActorClass(Name);
if (SpawnActor) {
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
if (PlayerController) {
APawn* PlayerPawn = PlayerController->GetPawn();
if (PlayerPawn) {
FVector Location = PlayerPawn->GetActorLocation();
FVector SpawnLocation = Location + FVector(0.f, 0.f, -80.f);
SpawnActor->SetActorLocation(SpawnLocation);
}
BackPackArray[SlotIndex].Count--;
if (BackPackArray[SlotIndex].Count <= 0) BackPackArray.RemoveAt(SlotIndex);
}
if (BackPackUI) {
BackPackUI->BackPackTransfer = BackPackArray;
BackPackUI->RefreshBackPack();
}
}
}
}
AActor* UBackPackComponent::SpawnActorClass(const FString& Name) {
if (!IsValid(GetWorld())) return nullptr;
FString BlueprintPath = FString::Printf(TEXT("/Game/BackPackSystem/Item/%s.%s_C"), *Name, *Name);
UClass* ActorClass = LoadClass<AItemBase>(nullptr, *BlueprintPath);
if (ActorClass) {
AActor* SpawnedActor = GetWorld()->SpawnActor<AActor>(ActorClass);
return SpawnedActor;
}
return nullptr;
}
4. 交换物品
拖动 Slot 到另一个 Slot,交换数组中的两个物体位置并刷新 UI。
void UBackPackComponent::SwapItem(int32 DragIndex, int32 DropIndex) {
if (!IsValid(GetWorld())) return;
UMyBaseGameInstance* MyGameInstance = Cast<UMyBaseGameInstance>(GetWorld()->GetGameInstance());
TArray<FItem_Struct> BackPackArray = MyGameInstance->BackPack_Array;
FItem_Struct TempItem = BackPackArray[DragIndex];
BackPackArray[DragIndex] = BackPackArray[DropIndex];
BackPackArray[DropIndex] = TempItem;
MyGameInstance->BackPack_Array = BackPackArray;
if (BackPackUI) {
BackPackUI->BackPackTransfer = BackPackArray;
BackPackUI->RefreshBackPack();
}
}
总结
本文展示了基于 UE5 C++ 的背包系统核心逻辑实现,包括数据存储、Actor 交互、输入绑定、组件封装及 UI 动态生成。通过 C++ 实现可有效提升复杂逻辑的性能表现与可维护性。
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online