UE5多人MOBA+GAS 32、制作商店系统(一)

文章目录


创建数据资产

添加一个数据资产PDA_ShopItem

InventoryComponent

InventoryItem

cpp 复制代码
// 幻雨喜欢小猫咪

#pragma once

#include "CoreMinimal.h"
#include "GameplayEffect.h"
#include "Engine/DataAsset.h"
#include "PDA_ShopItem.generated.h"

class UPDA_ShopItem;
/**
 * 表示商店物品集合的结构体,用于管理一组UPDA_ShopItem引用
 */
USTRUCT(BlueprintType)
struct FItemCollection
{
	GENERATED_BODY()
public:
	FItemCollection();

	// 使用现有物品列表初始化集合
	FItemCollection(const TArray<const UPDA_ShopItem*>& InItems);

	/**
	 * 向集合中添加新物品
	 * @param NewItem  要添加的物品
	 * @param bAddUnique  是否确保唯一性(默认false)
	 */
	void AddItem(const UPDA_ShopItem* NewItem, bool bAddUnique = false);

	/**
	 * 检查集合中是否包含指定物品
	 * @param Item 要检擦的物品
	 * @return 存在返回true,否则false
	 */
	bool Contains(const UPDA_ShopItem* Item) const;

	/**
	 * 获取集合中所有物品的引用
	 * @return 所有物品的引用 
	 */
	const TArray<const UPDA_ShopItem*>& GetItems() const;

private:
	TArray<TObjectPtr<const UPDA_ShopItem>> Items;
};

/**
 * 商店物品基础数据资产类,定义可在商店中交易的物品属性
 */
UCLASS()
class CRUNCH_API UPDA_ShopItem : public UPrimaryDataAsset
{
	GENERATED_BODY()
public:
	// 获取物品ID
	virtual FPrimaryAssetId GetPrimaryAssetId() const override;

	// 获取商店物品的资产类型标识符
	static FPrimaryAssetType GetShopItemAssetType();

	// 获取物品图标
	UTexture2D* GetIcon() const;

	// 获取物品的名称
	FText GetItemName() const { return ItemName; }

	// 获取物品的描述
	FText GetItemDescription() const { return ItemDescription; }

	// 获取物品的购买价格
	float GetPrice() const { return Price; }

	// 获取物品的出售价格(设为购入价格的一半)
	float GetSellPrice() const { return Price / 2.0f; }

	// 获取装备时触发的GE
	TSubclassOf<UGameplayEffect> GetEquippedEffect() const { return EquippedEffect; }
	
	// 获取使用时触发的GE
	TSubclassOf<UGameplayEffect> GetConsumeEffect() const { return ConsumeEffect; }
	
	// 获取物品授予的GA
	TSubclassOf<UGameplayAbility> GetGrantedAbility() const { return GrantedAbility; }

	// 获取物品授予的GA的默认对象
	UGameplayAbility* GetGrantedAbilityCDO() const;

	// 检查物品是否可堆叠
	bool GetIsStackable() const { return bIsStackable; }

	// 检查物品是否可消耗
	bool GetIsConsumable() const { return bIsConsumable; }

	// 获取最大堆叠数量
	int32 GetMaxStackCount() const { return MaxStackCount; }

	// 获取合成所需的材料物品列表
	const TArray<TSoftObjectPtr<UPDA_ShopItem>>& GetIngredients() const { return IngredientItems; }

private:
	/** 物品图标资源引用 */
	UPROPERTY(EditDefaultsOnly, Category = "ShopItem", meta = (DisplayName = "图标"))
	TSoftObjectPtr<UTexture2D> Icon;

	/** 物品基础购买价格 */
	UPROPERTY(EditDefaultsOnly, Category = "ShopItem", meta = (DisplayName = "价格"))
	float Price;

	/** 物品显示名称 */
	UPROPERTY(EditDefaultsOnly, Category = "ShopItem", meta = (DisplayName = "物品名称"))
	FText ItemName;

	/** 物品详细描述 */
	UPROPERTY(EditDefaultsOnly, Category = "ShopItem", meta = (DisplayName = "物品描述"))
	FText ItemDescription;

	/** 标识物品是否为消耗品 */
	UPROPERTY(EditDefaultsOnly, Category = "ShopItem", meta = (DisplayName = "是否为消耗品"))
	bool bIsConsumable;

	/** 装备时应用的GameplayEffect */
	UPROPERTY(EditDefaultsOnly, Category = "ShopItem", meta = (DisplayName = "装备效果"))
	TSubclassOf<UGameplayEffect> EquippedEffect;

	/** 使用时应用的GameplayEffect */
	UPROPERTY(EditDefaultsOnly, Category = "ShopItem", meta = (DisplayName = "使用效果"))
	TSubclassOf<UGameplayEffect> ConsumeEffect;

	/** 物品授予的GameplayAbility */
	UPROPERTY(EditDefaultsOnly, Category = "ShopItem", meta = (DisplayName = "授予能力"))
	TSubclassOf<UGameplayAbility> GrantedAbility;

	/** 标识物品是否可堆叠 */
	UPROPERTY(EditDefaultsOnly, Category = "ShopItem", meta = (DisplayName = "是否可堆叠"))
	bool bIsStackable = false;

	/** 最大堆叠数量(仅在可堆叠时有效) */
	UPROPERTY(EditDefaultsOnly, Category = "ShopItem", meta = (DisplayName = "最大堆叠数量"))
	int MaxStackCount = 5;

	/** 合成/制作所需的材料物品列表 */
	UPROPERTY(EditDefaultsOnly, Category = "ShopItem", meta = (DisplayName = "所需材料"))
	TArray<TSoftObjectPtr<UPDA_ShopItem>> IngredientItems;
};
cpp 复制代码
// 幻雨喜欢小猫咪


#include "Inventory/PDA_ShopItem.h"
#include "Abilities/GameplayAbility.h"

FPrimaryAssetId UPDA_ShopItem::GetPrimaryAssetId() const
{
	return FPrimaryAssetId(GetShopItemAssetType(), GetFName());
}

FPrimaryAssetType UPDA_ShopItem::GetShopItemAssetType()
{
	return FPrimaryAssetType("ShopItem");
}

UTexture2D* UPDA_ShopItem::GetIcon() const
{
	return Icon.LoadSynchronous();
}

UGameplayAbility* UPDA_ShopItem::GetGrantedAbilityCDO() const
{
	if (GrantedAbility)
	{
		return Cast<UGameplayAbility>(GrantedAbility->GetDefaultObject());
	}
	return nullptr;
}

FItemCollection::FItemCollection()
	:Items{}
{
}

FItemCollection::FItemCollection(const TArray<const UPDA_ShopItem*>& InItems)
	: Items{InItems}
{
}

void FItemCollection::AddItem(const UPDA_ShopItem* NewItem, bool bAddUnique)
{
	if (bAddUnique && Contains(NewItem))
		return;

	Items.Add(NewItem);
}

bool FItemCollection::Contains(const UPDA_ShopItem* Item) const
{
	return Items.Contains(Item);
}

const TArray<const UPDA_ShopItem*>& FItemCollection::GetItems() const
{
	return Items;
}

创建资源管理器

CAssetManager使用资源管理来管理资产,使得商品资产不用绑定到商品界面中去

cpp 复制代码
#pragma once

#include "CoreMinimal.h"
#include "Engine/AssetManager.h"
#include "Inventory/PDA_ShopItem.h"
#include "CAssetManager.generated.h"

/**
 * 自定义资产管理器,负责游戏核心资产的加载和管理
 * 处理角色定义、商店物品加载,并提供物品合成系统的数据支持
 */
UCLASS()
class UCAssetManager : public UAssetManager
{
	GENERATED_BODY()
public:
	// 获取资产管理器单例
	static UCAssetManager& Get();

	/**
	 * 异步加载所有商店物品资产
	 * @param LoadFinishedCallback - 加载完成时执行的回调
	 */
	void LoadShopItems(const FStreamableDelegate& LoadFinishedCallback);

	/**
	 * 获取已加载的商店物品
	 * @param OutItems - 输出加载的商店物品数组
	 * @return 是否成功获取
	 */
	bool GetLoadedShopItems(TArray<const UPDA_ShopItem*>& OutItems) const;

private:
	// 商店物品加载完成后的处理
	void ShopItemLoadFinished(FStreamableDelegate Callback);
	
	// 构建物品关系映射(合成配方系统)
	void BuildItemMaps();
	
	/**
	 * 添加物品关系到合成映射
	 * @param Ingredient - 材料物品
	 * @param CombinationItem - 能合成的目标物品
	 */
	void AddToCombinationMap(const UPDA_ShopItem* Ingredient, const UPDA_ShopItem* CombinationItem);

	/** 合成配方映射:材料物品 -> 能合成的物品集合 */
	UPROPERTY()
	TMap<const UPDA_ShopItem*, FItemCollection> CombinationMap;

	/** 材料需求映射:目标物品 -> 所需材料集合 */
	UPROPERTY()
	TMap<const UPDA_ShopItem*, FItemCollection> IngredientMap;
};
cpp 复制代码
#include "Framework/CAssetManager.h"

UCAssetManager& UCAssetManager::Get()
{
	// 尝试从引擎获取当前资产管理器实例
	UCAssetManager* Singleton = Cast<UCAssetManager>(GEngine->AssetManager.Get());
	if (Singleton)
	{
		return *Singleton;
	}

	// 如果获取失败,记录致命错误并创建新实例(安全后备)
	UE_LOG(LogLoad, Fatal, TEXT("资源管理器 必须是 CAssetManager 类型的实例"));
	return (*NewObject<UCAssetManager>());
}

// 加载所有商店物品类型的主资产,并在加载完成后触发回调
void UCAssetManager::LoadShopItems(const FStreamableDelegate& LoadFinishedCallback)
{
	// 加载指定类型的主资产(商店物品)
	LoadPrimaryAssetsWithType(
		UPDA_ShopItem::GetShopItemAssetType(),		// 商店物品资产类型
		TArray<FName>(),							// 资产名称列表:空数组表示加载该类型所有资产
		FStreamableDelegate::CreateUObject(			// 创建绑定到当前对象的委托
			this,
			&UCAssetManager::ShopItemLoadFinished,  // 资产加载完成时触发的成员函数
			LoadFinishedCallback                    // 透传外部传入的回调委托
		)
	);
}

bool UCAssetManager::GetLoadedShopItems(TArray<const UPDA_ShopItem*>& OutItems) const
{
	TArray<UObject*> LoadedObjects;
	// 获取上商店物品主资产列表
	bool bLoaded = GetPrimaryAssetObjectList(
		UPDA_ShopItem::GetShopItemAssetType(),  // 商店物品资产类型
		LoadedObjects						// 存储加载的商店物品
		);

	if (bLoaded)
	{
		for (UObject* LoadedObject : LoadedObjects)
		{
			OutItems.Add(Cast<UPDA_ShopItem>(LoadedObject));
		}
	}

	return bLoaded;
}

// 商店物品加载完成后的处理
void UCAssetManager::ShopItemLoadFinished(FStreamableDelegate Callback)
{
	// 执行回调(通知外部加载完成)
	Callback.ExecuteIfBound();

	// 构建物品映射(合成系统)
	BuildItemMaps();
}

// 构建物品合成关系映射表
void UCAssetManager::BuildItemMaps()
{
	TArray<const UPDA_ShopItem*> LoadedItems;
	// 获取所有已加载的商店物品
	if (GetLoadedShopItems(LoadedItems))
	{
		// 遍历每一个物品
		for (const UPDA_ShopItem* Item : LoadedItems)
		{
			// 合成清单为空,则跳过
			if (Item->GetIngredients().Num() == 0)
			{
				continue;
			}

			TArray<const UPDA_ShopItem*> Items;
			// 处理每个合成材料
			for (const TSoftObjectPtr<UPDA_ShopItem>& Ingredient : Item->GetIngredients())
			{
				// 同步加载
				UPDA_ShopItem* IngredientItem = Ingredient.LoadSynchronous();
				Items.Add(IngredientItem);
				
				// 添加到合成映射表(材料->可合成的物品)
				AddToCombinationMap(IngredientItem, Item);
			}

			// 添加到材料映射表(物品->所需材料)
			IngredientMap.Add(Item, FItemCollection{Items});
		}
	}
}

void UCAssetManager::AddToCombinationMap(const UPDA_ShopItem* Ingredient, const UPDA_ShopItem* CombinationItem)
{
	// 检查是否已存在该材料的记录
	FItemCollection* Combinations = CombinationMap.Find(Ingredient);
	if (Combinations)
	{
		// 确保不重复添加相同合成结果
		if (!Combinations->Contains(CombinationItem))
			Combinations->AddItem(CombinationItem);
	}
	else
	{
		// 创建新条目(材料->合成结果集合)
		CombinationMap.Add(Ingredient, FItemCollection{TArray<const UPDA_ShopItem*>{CombinationItem}});
	}
}

添加物品UI 以及 商店

ItemWidget用于充当这些物品的基类

cpp 复制代码
#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/Image.h"
#include "ItemWidget.generated.h"

/**
 * 
 */
UCLASS()
class CRUNCH_API UItemWidget : public UUserWidget
{
	GENERATED_BODY()
public:
	// 初始化控件
	virtual void NativeConstruct() override;

	// 设置物品图标
	virtual void SetIcon(UTexture2D* IconTexture);

protected:
	// 创建并设置ToolTip控件
	// UItemToolTip* SetToolTipWidget(const UPA_ShopItem* Item);
	
	// 获取图标控件(子类可访问)
	UImage* GetItemIcon() const { return ItemIcon; }

private:
	// 物品图标显示控件(与蓝图中的Image组件绑定)
	UPROPERTY(meta=(BindWidget))
	TObjectPtr<UImage> ItemIcon;

	// ToolTip控件类(在编辑器中设置默认类型)
	// UPROPERTY(EditDefaultsOnly, Category = "ToolTip")
	// TSubclassOf<UItemToolTip> ItemToolTipClass;

	// 鼠标按下事件处理
	virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
	
	// 鼠标释放事件处理
	virtual FReply NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;

	// 右键点击响应(子类可重写实现具体逻辑)
	virtual void RightButtonClicked();
	
	// 左键点击响应(子类可重写实现具体逻辑)
	virtual void LeftButtonClicked();

};
cpp 复制代码
#include "ItemWidget.h"

void UItemWidget::NativeConstruct()
{
	Super::NativeConstruct();
	// 允许控件获得焦点
	SetIsFocusable(true);
	
}

void UItemWidget::SetIcon(UTexture2D* IconTexture)
{
	if (ItemIcon)
	{
		ItemIcon->SetBrushFromTexture(IconTexture);
	}
}

FReply UItemWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
	// 父类的按下处理
	FReply SuperReply = Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);

	// 按下的是右键
	if (InMouseEvent.IsMouseButtonDown(EKeys::RightMouseButton))
	{
		// 设置控件焦点
		return FReply::Handled().SetUserFocus(TakeWidget());
	}

	// 左按按下
	if (InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
	{
		return FReply::Handled()
		.SetUserFocus(TakeWidget())
		.DetectDrag(TakeWidget(),EKeys::LeftMouseButton); // 拖拽
	}
	return SuperReply; // 返回父类处理
}

FReply UItemWidget::NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
	FReply SuperReply = Super::NativeOnMouseButtonUp(InGeometry, InMouseEvent);

	// 仅当控件当前有焦点时处理点击事件(避免误触其它控件)
	if (HasAnyUserFocus())
	{
		// 右键释放:触发右键点击事件
		if (InMouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
		{
			RightButtonClicked();	// 执行右键的逻辑
			return FReply::Handled();
		}

		// 左键释放:触发左键点击事件
		if (InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
		{
			LeftButtonClicked(); // 执行左键逻辑
			return FReply::Handled(); // 标记事件已处理
		}
	}
	return SuperReply;
}

void UItemWidget::RightButtonClicked()
{
	UE_LOG(LogTemp, Warning, TEXT("按下右键"));
}

void UItemWidget::LeftButtonClicked()
{
	UE_LOG(LogTemp, Warning, TEXT("按下左键"));
}

继承该UI创建商店的物品控件以及商店界面
ShopItemWidget

cpp 复制代码
// 幻雨喜欢小猫咪

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/IUserObjectListEntry.h"
#include "Inventory/PDA_ShopItem.h"
#include "UI/Common/ItemWidget.h"
#include "ShopItemWidget.generated.h"

/**
 * 
 */
UCLASS()
class UShopItemWidget : public UItemWidget, 
						public IUserObjectListEntry
{
	GENERATED_BODY()
public:
	
	//~ Begin IUserObjectListEntry 接口实现
	// 当列表项绑定数据对象时调用(通常为UPA_ShopItem实例)
	virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;
	//~ End IUserObjectListEntry 接口实现

	// 获取当前绑定的商店物品数据
	FORCEINLINE const UPDA_ShopItem* GetShopItem() const { return ShopItem; }
private:
	
	// 当前绑定的商店物品数据资产
	UPROPERTY()
	TObjectPtr<const UPDA_ShopItem> ShopItem;
};
cpp 复制代码
// 幻雨喜欢小猫咪


#include "ShopItemWidget.h"

void UShopItemWidget::NativeOnListItemObjectSet(UObject* ListItemObject)
{
	IUserObjectListEntry::NativeOnListItemObjectSet(ListItemObject);

	ShopItem = Cast<UPDA_ShopItem>(ListItemObject);
	if (!ShopItem) return;
	
	SetIcon(ShopItem->GetIcon());
}

ShopWidget

cpp 复制代码
// 幻雨喜欢小猫咪

#pragma once

#include "CoreMinimal.h"
#include "ShopItemWidget.h"
#include "Blueprint/UserWidget.h"
#include "Components/TileView.h"
#include "Inventory/PDA_ShopItem.h"
#include "ShopWidget.generated.h"

/**
 * 
 */
UCLASS()
class CRUNCH_API UShopWidget : public UUserWidget
{
	GENERATED_BODY()
public:
	virtual void NativeConstruct() override;

private:
	// 商店物品列表
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UTileView> ShopItemList;

	// 加载商店物品
	void LoadShopItems();

	// 商店物品加载完成
	void ShopItemLoadFinished();

	// 商店物品生成
	void ShopItemWidgetGenerated(UUserWidget& NewWidget);
	
	// 商店物品到控件的映射表
	// 用途:快速查找物品对应的控件实例
	UPROPERTY()
	TMap<const UPDA_ShopItem*, const UShopItemWidget*> ItemsMap;
};
cpp 复制代码
// 幻雨喜欢小猫咪


#include "ShopWidget.h"

#include "Framework/CAssetManager.h"

void UShopWidget::NativeConstruct()
{
	Super::NativeConstruct();
	// 设置可聚焦
	SetIsFocusable(true);
	// 加载物品
	LoadShopItems();

	// 绑定列表项生成事件
	ShopItemList->OnEntryWidgetGenerated().AddUObject(this, &UShopWidget::ShopItemWidgetGenerated);
	
}

void UShopWidget::LoadShopItems()
{
	// 调用资产管理器的异步加载方法
	// 加载完成后触发 ShopItemLoadFinished 回调
	UCAssetManager::Get().LoadShopItems(
		FStreamableDelegate::CreateUObject(this, &UShopWidget::ShopItemLoadFinished)
		);
}

void UShopWidget::ShopItemLoadFinished()
{
	// 获取所有已加载的商店物品
	TArray<const UPDA_ShopItem*> ShopItems;
	if (UCAssetManager::Get().GetLoadedShopItems(ShopItems))
	{
		// 添加商店物品
		for (const UPDA_ShopItem* ShopItem : ShopItems)
		{
			ShopItemList->AddItem(const_cast<UPDA_ShopItem*>(ShopItem));
		}
	}
}

void UShopWidget::ShopItemWidgetGenerated(UUserWidget& NewWidget)
{
	// 转换为商店物品控件
	UShopItemWidget* ItemWidget = Cast<UShopItemWidget>(&NewWidget);
	if (ItemWidget)
	{
		// 添加到物品映射表
		ItemsMap.Add(ItemWidget->GetShopItem(), ItemWidget);;
	}
}

创建商品资产

修改项目设置

到一般设置中设置一下资源管理类

到资源管理器中点击加号添加一个新的扫描类型ShopItem

该类型定义于此处,类型是由自己定义的一样,否则无法加载

需要再设置一下资产的基类以及资产所在的目录

目录为各种这个资产所在的目录

然后要在规则中把烘焙规则改为固定烘焙以下是最终状态

蓝图中创建UI

创建两个蓝图分别继承ShopWidgetShopItemWidget

继承ShopWidget的蓝图中添加一个瓦片视图

设置一下控件类为商品的蓝图

此处可以修改间距

GameplayWidget中添加商店

cpp 复制代码
	// 商店
	UPROPERTY(meta=(BindWidget))
	TObjectPtr<UShopWidget> ShopWidget;

再到蓝图中把商店拖进来

商品都会加载进来

相关推荐
幻雨様17 小时前
UE5多人MOBA+GAS 番外篇:将冷却缩减属性应用到技能冷却中
ue5
吴梓穆2 天前
UE5 UI自适应 DPI缩放
ui·ue5
幻雨様3 天前
UE5多人MOBA+GAS 30、技能升级机制
运维·服务器·ue5
吴梓穆8 天前
UE5 UI WarpBox 包裹框
ue5
脑壳疼___8 天前
vue3与ue5通信-工具类
前端·javascript·ue5
CG_MAGIC8 天前
虚幻 5 与 3D 软件的协作:实时渲染,所见所得
3d·ue5·游戏引擎·图形渲染·虚幻·游戏美术·渲云渲染
零一数创8 天前
数字孪生赋能智慧能源电力传输管理新模式
ue5·能源·数字孪生·ue·智慧能源·零一数创
零一数创8 天前
智慧能源驱动数字孪生重介选煤新模式探索
人工智能·ue5·能源·数字孪生·ue·零一数创
不爱说话的采儿9 天前
UE5保姆级基础教程(第五章)
经验分享·其他·ue5·游戏引擎·课程设计