本篇日志中,我將會介紹如何實現一個有格子,每個格子有容量的物品庫存,如下圖:
一.庫存容器
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;
}
? ? ? ? 我們還有一個整理背包的函數,可以實現將庫存中同類物品盡可能放在一起:? ? ? ??
持續更新中。。。