UE4 基于GAS的插件ArcInventory拆解-1_库存核心:ArcInventoryComponent、FArcInventoryItemSlot

零、资料

插件地址:
https://www.unrealengine.com/marketplace/zh-CN/product/arc-inventory
文档地址:
https://redirect.epicgames.com/?redirectTo=https://punyhumangames.github.io/ArcPluginsDocumentation/arcinventory/

一、UArcInventoryComponent

1.1 主要属性

	FArcInventoryItemSlotArray BagInventory;
	TArray<FArcInventoryItemSlotReference> AllReferences;

确定了InventoryComp的核心:背包

1.1.1 BagInventory

这是存储所有库存的地方,只具有储存的同步功能

结构体定义:

USTRUCT()
struct FArcInventoryItemSlotArray : public FFastArraySerializer
{
	GENERATED_USTRUCT_BODY()
public:

	UPROPERTY()
	TArray< FArcInventoryItemSlot> Slots;

	UPROPERTY()
	UArcInventoryComponent* Owner;

	bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
	{
		return FFastArraySerializer::FastArrayDeltaSerialize<FArcInventoryItemSlot, FArcInventoryItemSlotArray>(Slots, DeltaParms, *this);
	}
};

主要负责存储插槽数组,并使用了FFastArraySerializer来优化结构体数组的同步问题

FFastArraySerializer资料:
官方文档
https://docs.unrealengine.com/4.27/en-US/API/Runtime/Engine/Engine/FFastArraySerializer/
UE4Guide
https://ikrima.dev/ue4guide/networking/network-replication/fast-tarray-replication/

1.1.2、AllReferences

作用:
是外部获取Slot的Handle,包含了所有BagInventory里Slot的引用

//仅仅是主要结构
USTRUCT(BlueprintType)
struct ARCINVENTORY_API FArcInventoryItemSlotReference
{
	GENERATED_USTRUCT_BODY()
public:
	//...不同类型的构造函数

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Inventory)
		int32 SlotId;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Inventory)
	FGameplayTagContainer SlotTags;

	UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = Inventory)
	class UArcInventoryComponent* ParentInventory;

	bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
	
	bool operator==(const FArcInventoryItemSlotReference& Other) const;
	bool operator==(const FArcInventoryItemSlot& Other) const;
};

实现
通过记录Inventory指针+索引Id来记录Slot引用,方便网络复制以及查找
相应地,提供==重载简化操作

1.2、主要接口

核心函数望文生义:背包基本的增删改查操作

//增
	UFUNCTION(BlueprintCallable, Category="Arc|Inventory")
	virtual bool LootItem(UArcItemStack* Item);
	UFUNCTION(BlueprintCallable, Category = "Arc|Inventory")
	virtual bool PlaceItemIntoSlot(UArcItemStack* Item, const FArcInventoryItemSlotReference& ItemSlot);

//删
	UFUNCTION(BlueprintCallable, Category = "Arc|Inventory")
	virtual bool RemoveItemFromInventory(const FArcInventoryItemSlotReference& ItemSlot);
	UFUNCTION(BlueprintCallable, Category = "Arc|Inventory")
	virtual bool RemoveAllItemsFromInventory(TArray<UArcItemStack*>& OutItemsRemoved);

//改(交换)
	UFUNCTION(BlueprintCallable, Category = "Arc|Inventory")
	virtual bool SwapItemSlots(const FArcInventoryItemSlotReference& FromSlot, const FArcInventoryItemSlotReference& ToSlot);

//查
	UFUNCTION(BlueprintCallable, Category = "Arc|Inventory")
	virtual bool IsValidItemSlot(const FArcInventoryItemSlotReference& Slot);

	UFUNCTION(BlueprintPure, Category = "Arc|Inventory")
	virtual TArray<FArcInventoryItemSlotReference> GetAllSlotReferences();
	virtual FArcInventoryItemSlot& GetItemSlot(const FArcInventoryItemSlotReference& RefSlot);
	virtual UArcItemStack* GetItemInSlot(const FArcInventoryItemSlotReference& Reference);
	
	UFUNCTION(BlueprintPure, Category = "Arc|Inventory")
	virtual int32 GetInventorySize();	    											  


1.2.1、增

作用
将物品放入背包中,目前是两种方式:放置到固定位置+自动放置到任意符合的位置

bool LootItem(UArcItemStack* Item){
	for (auto Slot : BagInventory.Slots){
		FArcInventoryItemSlotReference SlotRef(Slot, this);
		if (AcceptsItem(Item, SlotRef) && PlaceItemIntoSlot(Item, SlotRef)){
			return true;}}
	return false;}
	
bool PlaceItemIntoSlot(UArcItemStack* Item, const FArcInventoryItemSlotReference& ItemSlot){
	//1.检查是否能塞进去
	if (!AcceptsItem(Item, ItemSlot)){
		return false;}
	if (IsValid(Item) && Item->GetOwner() != GetOwner()){
		UArcItemStack::TransferStackOwnership(Item, GetOwner());}
	//2.赋值
	FArcInventoryItemSlot& Slot = GetItemSlot(ItemSlot);
	UArcItemStack* PreviousItem = Slot.ItemStack;
	Slot.ItemStack = Item;
	//3.广播和网络更新
	BagInventory.MarkItemDirty(Slot);
	OnInventoryUpdate.Broadcast(this);
	OnItemSlotChange.Broadcast(this, ItemSlot, Item, PreviousItem);
	GetOwner()->ForceNetUpdate();
	return true;
}

实现:
加入库存的是ItemStack类型,加入方式简单直接。
ItemStack是ItemSlot的内部储存实现,可以被重载生成自己的插槽类型

1.2.2、删

作用
根据Reference删除特定Slot内的元素,或者删除所有元素

bool UArcInventoryComponent::RemoveItemFromInventory(const FArcInventoryItemSlotReference& ItemSlot)
{
	//1.检查
	if (!IsValid(ItemSlot)){
		return false;}
	FArcInventoryItemSlot& Item = GetItemSlot(ItemSlot);
	UArcItemStack* PreviousItem = Item.ItemStack;
	if (!IsValid(Item.ItemStack)){
		return false;}

	//2.删除
	Item.ItemStack = nullptr;

	//3.广播
	BagInventory.MarkItemDirty(Item);
	BagInventory.MarkArrayDirty();
	OnInventoryUpdate.Broadcast(this);
	OnItemSlotChange.Broadcast(this, ItemSlot, Item.ItemStack, PreviousItem);
	GetOwner()->ForceNetUpdate();
	return true;
}

bool UArcInventoryComponent::RemoveAllItemsFromInventory(TArray<UArcItemStack*>& OutItemsRemoved){
	for (FArcInventoryItemSlot& ItemSlot : BagInventory.Slots){
		if (!IsValid(ItemSlot.ItemStack)){
			continue;}
		OutItemsRemoved.Add(ItemSlot.ItemStack);
		RemoveItemFromInventory(FArcInventoryItemSlotReference(ItemSlot, this));}
	return true;}

实现:
利用Reference进行简单的查询与删除

1.2.3、改

作用:
主要是交换逻辑

bool UArcInventoryComponent::SwapItemSlots(const FArcInventoryItemSlotReference& SourceSlot, const FArcInventoryItemSlotReference& DestSlot){
	//1、大量检查
	if (!IsValid(SourceSlot) || !IsValid(DestSlot)){
		return false;}	
	//1.1、最少有一个插槽在本库存
	UArcInventoryComponent* SourceInventory = SourceSlot.ParentInventory;
	UArcInventoryComponent* DestinationInventory = DestSlot.ParentInventory;
	if (SourceInventory != this && DestinationInventory != this){
		return false;}
	//1.2、根据标签确定两个插槽可以Swap
	UArcItemStack* SourceItem = SourceInventory->GetItemInSlot(SourceSlot);
	UArcItemStack* DestItem = DestinationInventory->GetItemInSlot(DestSlot);
	if (!DestinationInventory->AcceptsItem_AssumeEmptySlot(SourceItem, DestSlot) || !SourceInventory->AcceptsItem_AssumeEmptySlot(DestItem, SourceSlot)){
		return false;}
		
	//2、先删后增
	SourceInventory->RemoveItemFromInventory(SourceSlot);
	DestinationInventory->RemoveItemFromInventory(DestSlot);
	SourceInventory->PlaceItemIntoSlot(DestItem, SourceSlot);
	DestinationInventory->PlaceItemIntoSlot(SourceItem, DestSlot);
	return true;
}

实现:
检查插槽,然后交换插槽

1.2.4、查

作用:
进行状态查询以及通过Reference找到对应插槽
共三个作用:
查询插槽是否存在
获取到特定插槽(物品),目前仅是单个插槽
获取到所有库存/引用
获取库存大小

FArcInventoryItemSlot& UArcInventoryComponent::GetItemSlot(const FArcInventoryItemSlotReference& RefSlot){
	for (FArcInventoryItemSlot& SlotSlot : BagInventory.Slots){		
		if (RefSlot == SlotSlot){
			BagInventory.MarkItemDirty(SlotSlot);
			return SlotSlot;}}
	return BagInventory.Slots[0];} 

实现:
单个查询使用遍历+==,其他查询基本是直接输出变量

1.3、ArcInventoryComponent总结

1.3.1 背包组件:

ArcInventoryComponent实现了基础的增删改查,满足一个背包基础的功能搭建

1.3.2 ArcInventory的核心

ArcInventoryComponent为成为了系统的中心与桥梁。其主要处理的问题:

1.3.2.1、数据同步

贯穿增删改查的是数据同步。
ArcInventoryComponent采用了针对结构体优化的FFastArraySerializer来储存背包数据。同时,采用更利于同步的FArcInventoryItemSlotReference作为Handle进行外部通讯以及RPC的工具,减轻了同步负担的同时也增加了数据安全性

1.3.2.2、灵活度

将背包-插槽-物品-Handle全部拆分,带来了高度的灵活性。
背包:ArcInventoryComponent作为库存操作的整体与内外系统交互
插槽:‘ArcItemSlot’:库存保存的插槽实例,并不保存实际数据,而是建立物品放入以及使用的规则
物品:’ArcItemStack‘真正保存了插槽的数据,与’ArcItemSlot’通过指针连接,记录所有真正的数据,包括最大堆栈数量、物品信息等
Handle:ArcItemReference用于作为Handle供外部调用,是外部系统获取插槽的重要途经,通过使用轻量化的Handle传输而不是直接Slot或Stack传输可以大大减轻网络的系统压力,并使外部系统更难去介入Inventory内部实现

以上所有类都可以通过继承实现更丰富的功能

二、FArcInventoryItemSlot

继承自FFastArraySerializerItem,优化网络传输

2.1、基础属性

主要是:物品/上一个物品、筛选/被筛选Tag、自身信息

	UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = Inventory)
	UArcItemStack* ItemStack;
	TWeakObjectPtr<UArcItemStack> OldItemStack;
	
	UPROPERTY()
	FArcInventoryItemSlotFilterHandle ItemSlotFilter;
	UPROPERTY(BlueprintReadWrite, VisibleInstanceOnly, Category = Inventory)
	FGameplayTagContainer SlotTags;
	
	UPROPERTY(BlueprintReadOnly, VisibleInstanceOnly, Category = Inventory)
	int32 SlotId;

	UArcInventoryComponent* Owner;

2.1.1、ItemStackOldItemStack

存储物品的现有信息和上一个信息,详情请看第二篇

2.1.2、ItemSlotFilterSlotTags

作为筛选器以及被筛选器使用,双向筛选确定物品是否能放入当前Slot

2.1.3、OwnerSlotId

记录自身信息,方便Component获取

2.2、接口

并没有外部需要调用的接口,仅仅用于数据检查以及网络复制

bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
void PreReplicatedRemove(const struct FArcInventoryItemSlotArray& InArraySerializer);
void PostReplicatedAdd(const struct FArcInventoryItemSlotArray& InArraySerializer);
void PostReplicatedChange(const struct FArcInventoryItemSlotArray& InArraySerializer);
bool operator==(FArcInventoryItemSlot const& Other) const

2.3、FArcInventoryItemSlot总结

需要判断确定什么东西能进该插槽,以及确定插槽类型,将数据交给了Stack储存,实现插槽数据与控制的解耦

三、总结

UArcInventoryComponentFArcInventoryItemSlot以及FArcInventoryItemSlotReference共同构成了库存的核心基础操作

3.1、UArcInventoryComponent

整个库存的中枢
专心处理背包插槽的逻辑:增删改查,所有的操作又基于背包与物品的中间层FArcInventoryItemSlot,完全不需要知道物品的具体实现细节。

3.2、FArcInventoryItemSlot

库存本身与物品实体之间的桥梁,库存的最小单位
作为插槽的具体实现,依赖于UArcInventoryComponent而不是实际物品,同时又与物品(‘ItemStack’)进行交互,获取自己想要的信息。

3.3、FArcInventoryItemSlotReference

库存与外界实体交互的桥梁
其可以看作是一个插槽的Handle,记录插槽的引用信息供内外部调用。方便使用、将内部实现与外部接口进行了隔离,方便数据传输以及RPC调用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值