本篇日志中,我将会介绍如何实现一个有格子,每个格子有容量的物品库存,如下图:
一.库存容器
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::string
int 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;
}
我们还有一个整理背包的函数,可以实现将库存中同类物品尽可能放在一起:
持续更新中。。。