独立游戏《星尘异变》UE5 C++程序开发日志3——实现一个存存组件

本篇日志中,我将会介绍如何实现一个有格子,每个格子有容量的物品库存,如下图:

一.库存容器

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;
}

        我们还有一个整理背包的函数,可以实现将库存中同类物品尽可能放在一起:        

持续更新中。。。

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timidcatt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值