本篇日志中,我将会介绍如何实现一个有格子,每个格子有容量的物品库存,如下图:
一.库存容器
1.储存数据的容器
库存容器最重要的目的就是存储每一种类的物品拥有的数量,这里我用的是哈希表:
std::unordered_map<std::string, int>StardustCount;//从星尘ID到存储的数量的映射
哈希表的优点就是查询速度极快,我们的的库存在每次发生“反应”,进口等过程时都要进行数量的查询,所以要尽可能降低查询的复杂度,这也就是为什么我们不用TMap,因为TMap在每次使用"[]"运算符前,要检查其是否含有要查询的元素。
而他的优点就是不便于展示,因为我们要实现的库存是有有格子,每个格子有存储上限的容器,所以我们要再定义一个数组,数组中的每一个索引对应的就是展示的一个格子:
2.显示数据的容器
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="Storage")TArray<UStardustItemClass*>Storage;//输入仓库
数组中的数据类型是一个UObject指针,该UObject内除了上一篇日志中展示的数据外,多了一个该槽位物品数量的变量"Quantity":
USTRUCT(BlueprintType)
struct FStardustItem
{GENERATED_BODY();UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StardustBase")FName StardustName{"Empty"};//名称UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StardustBase")FText Description;//描述UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StardustBase")FName StardustId{ "Empty" };//编号UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StardustBase")FStardustStatisticsForReaction ReactionStatistics{FStardustStatisticsForReaction()};//反应数据UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StardustBase")FStardustStatisticsForInventory InventoryStatistics{FStardustStatisticsForInventory()};//库存数据UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StardustBase", meta = (UIMin = 1))int Quantity{0};//数量FStardustItem() = default;explicit FStardustItem(const FStardustDataTable& Stardust){StardustId = Stardust.StardustId;StardustName = Stardust.StardustName;Description = Stardust.Description;Quantity = 0;ReactionStatistics = Stardust.ReactionStatistics;InventoryStatistics = Stardust.InventoryStatistics;}void SetQuantity(int Num)//设置该槽位内的星尘数量{if (Num > 0){Quantity = Num;}else{//数量为0就将其替换成默认空的物品*this = FStardustItem();};}};UCLASS(BlueprintType)
class ASTROMUTATE_2_API UStardustItemClass : public UObject
{GENERATED_BODY()
public://物品信息UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="StardustItem")FStardustItem ItemData;};
3.库存的其他数据
我们游戏中的库存不是无限大的,所有有一个最大的槽位数,该变量可能受游戏中的因素影响,上面的数组大小始终等于该变量大小
然后我们还需要加载全局单例,后面需要用到上一篇日志中提到过的“星尘”数据表
//仓库槽位数
UPROPERTY(EditAnywhere, BlueprintReadWrite, category = "StardustInventory")
int SlotsCapacity;
//全局单例,用于查询数据表中的物品数据
UPROPERTY()
class UAstromutateGameInstance* Instance;
//在.cpp的BeginPlay中实例化Instance
//Instance = Cast<UAstromutateGameInstance>(GetWorld()->GetGameInstance());
二.容器的查询
因为我们之前实现过哈希表,所以可以直接O(1)查询某物品在库存中的容量
UFUNCTION(BlueprintCallable, Category = "StardustInventory")
FORCEINLINE int CheckStardust(FName StardustType) {return StardustCount[TCHAR_TO_UTF8(*StardustType.ToString())];};//从映射中O(1)查询其在库存中的数量
我们还有一个查询某物品在库存中还能添加多少的函数,也是利用哈希表O(1)实现
//.h中的声明
//检查星尘在库存中还能添加多少
UFUNCTION(BlueprintCallable, Category = "StardustInventory")
int CheckAddable(const FName& StardustId);//.cpp中的实现
nt UStarInventoryComponent::CheckAddable(const FName& StardustId)
{std::string StardustIdString{ TCHAR_TO_UTF8(*StardustId.ToString()) };//将FName转换成std::stringint StackLimit{ Instance->StardustMap[StardustIdString]->InventoryStatistics.StardustStackLimit };//该类物品的堆叠上限int AvailableInPartial{ StackLimit - StardustCount[StardustIdString] % StackLimit };//该类物品在非空槽位中还能装多少if (StardustCount[StardustIdString] % StackLimit == 0)//所有物品的堆叠上限不能为0{AvailableInPartial = 0;}int AvailableInEmptySlots = StardustCount["Empty"] * StackLimit;//在空槽位中可放的数量return AvailableInEmptySlots + AvailableInPartial;
}
三.库存的修改
我们库存中的增加和删除操作都是基于对单个槽位的修改实现的,传入参数是期望的“星尘”,用的是完整的槽位中物品的结构,返回值为是否修改成功,修改时需要同时维护数组和哈希表:
bool UStarInventoryComponent::SetSlotElement(const FName StardustId,const int Amount, int index)
{if (index < 0 || index >= Storage.Num()){//检查索引是否合法UE_LOG(LogTemp, Error, TEXT("se slot at %d failed,invalid index"), index);return false;}int OriginalAmount = Storage[index]->ItemData.GetQuantity(); FName OriginalId = Storage[index]->ItemData.StardustId;StardustCount[TCHAR_TO_UTF8(*Storage[index]->ItemData.StardustId.ToString())] -= OriginalAmount;//先将这一格清空//如果星尘是新加进来的,就要将表格中的数据赋给新星尘std::string NewStardustId{ TCHAR_TO_UTF8(*StardustId.ToString()) };FStardustTable StardustInfo= *Instance->StardustMap[NewStardustId];int StackLimit = StardustInfo.InventoryStatistics.StardustStackLimit;//将新星辰的数据覆盖原星辰Storage[index]->ItemData = FStardustItem(StardustInfo);if (Amount > StackLimit){//超出堆叠上限的部分直接抛弃if (OriginalId == "Empty" && Storage[index]->ItemData.StardustId != "Empty"){StardustCount["Empty"]--;}if (OriginalId != "Empty" && Storage[index]->ItemData.StardustId == "Empty"){StardustCount["Empty"]++;}StardustCount[NewStardustId] += StackLimit;Storage[index]->ItemData.SetQuantity(StackLimit);return true;}if (Amount <= 0){//将该槽位的星尘替换成空星尘StardustCount["Empty"]++;Storage[index]->ItemData.SetQuantity(Amount);return true;}if (OriginalId == "Empty" && Storage[index]->ItemData.StardustId != "Empty"){StardustCount["Empty"]--;}if (OriginalId != "Empty" && Storage[index]->ItemData.StardustId == "Empty"){StardustCount["Empty"]++;}//正常更改数量Storage[index]->ItemData.SetQuantity(Amount);StardustCount[NewStardustId] += Amount;return true;
}
我们还有一个整理背包的函数,可以实现将库存中同类物品尽可能放在一起:
持续更新中。。。