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;

再到蓝图中把商店拖进来

商品都会加载进来

相关推荐
哎呦哥哥和巨炮叔叔3 小时前
虚幻引擎 5.5 能否取代 V-Ray?现代建筑可视化渲染技术对比解析
ue5·实时渲染·虚幻引擎5·建筑可视化·渲染101云渲染·v-ray渲染·建筑效果图
zhangzhangkeji5 小时前
UE5 多线程(4):资源竞争与原子变量。UE 建议使用 STL版本的原子量,不用自己版本的原子量 TAtomic<T> 的实现了
ue5
AI视觉网奇6 小时前
ue slot 插槽用法笔记
笔记·学习·ue5
lllljz6 小时前
Blender导出模型到Unity或UE5引擎材质丢失模型出错
unity·ue5·游戏引擎·blender·材质
AI视觉网奇6 小时前
blender fbx 比例不对 比例调整
笔记·学习·ue5
哎呦哥哥和巨炮叔叔6 小时前
Unreal Engine 是否支持光线追踪?UE5 光线追踪原理与性能解析
ue5·unreal engine·光线追踪·lumen·实时渲染·渲染101云渲染·ue云渲染
zhangzhangkeji6 小时前
UE5 多线程(3):线程退出与单例线程
ue5
AI视觉网奇7 小时前
static mesh 转skeleton mesh
笔记·学习·ue5
AI视觉网奇1 天前
metahuman 购买安装记录
笔记·学习·ue5
速冻鱼Kiel1 天前
虚幻状态树解析
ue5·游戏引擎·虚幻