创建数据资产
添加一个数据资产PDA_ShopItem
InventoryComponent
InventoryItem
// 幻雨喜欢小猫咪
#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;
};
// 幻雨喜欢小猫咪
#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
使用资源管理来管理资产,使得商品资产不用绑定到商品界面中去
#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;
};
#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
用于充当这些物品的基类
#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();
};
#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
// 幻雨喜欢小猫咪
#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;
};
// 幻雨喜欢小猫咪
#include "ShopItemWidget.h"
void UShopItemWidget::NativeOnListItemObjectSet(UObject* ListItemObject)
{
IUserObjectListEntry::NativeOnListItemObjectSet(ListItemObject);
ShopItem = Cast<UPDA_ShopItem>(ListItemObject);
if (!ShopItem) return;
SetIcon(ShopItem->GetIcon());
}
ShopWidget
// 幻雨喜欢小猫咪
#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;
};
// 幻雨喜欢小猫咪
#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
创建两个蓝图分别继承ShopWidget
和ShopItemWidget
继承ShopWidget
的蓝图中添加一个瓦片视图
设置一下控件类为商品的蓝图
此处可以修改间距
GameplayWidget
中添加商店
// 商店
UPROPERTY(meta=(BindWidget))
TObjectPtr<UShopWidget> ShopWidget;
再到蓝图中把商店拖进来
商品都会加载进来