UE4 基于GAS的插件ArcInventory拆解-4_库存组件子类:UArcInventoryComponent_Bag/Equipable/Active

一、UArcInventoryComponent_Bag

最朴素的背包

1.1、属性

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = Inventory)
	int32 BagSlotCount;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inventory", meta = (AllowPrivateAccess = "true"))
	FGameplayAttribute InventorySizeAttribute;

1.1.1、BagSlotCount

插槽数量

1.1.2、InventorySizeAttribute

插槽数量(由Attribute控制),当InventorySizeAttribute存在时使用InventorySizeAttribute

二、接口

virtual void UpdateBagInventorySlots();
UFUNCTION(BlueprintCallable, Category = "Inventory | Item Queries", meta = (ScriptName = "ItemQuery_GetAllBagSlots"))

1.2、UpdateBagInventorySlots

作用:
根据插槽数量动态更新插槽

void UArcInventoryComponent_Bag::UpdateBagInventorySlots()
{
	//1、获取到当前插槽数量
	FGameplayTag BagTag = GetDefault<UArcInventoryDeveloperSettings>()->BagItemSlotTag;
	TArray<FArcInventoryItemSlotReference> Slots;
	Query_GetAllBagSlots(Slots);
	int32 SlotCount = Slots.Num();

	//2、计算应有数量与当前数量的差值
	int32 BagSlotsToCreate = BagSlotCount - SlotCount;
	
	//3、多退少补
	if (BagSlotsToCreate > 0){
		for (int32 i = 0; i < BagSlotsToCreate; i++){
			CreateInventorySlot(BagTag.GetSingleTagContainer(), nullptr);}}
	else if(BagSlotsToCreate < 0){
		//remove the bottom slots
		for (int i = 0; i < FMath::Abs(BagSlotsToCreate); i++){
			int32 SlotToRemove = SlotCount - 1 - i;
			RemoveInventorySlot(Slots[SlotToRemove]);}}
}

实现:
计算所需插槽数-当前插槽数的差值,多了的话从最后一个插槽开始删除,少了的话添加新的

1.3、总结

只有一个功能:
根据插槽数量添加/删除背包插槽
由于大部分功能父类已经实现,UArcInventoryComponent_Bag仅仅需要确定背包插槽数量并调用父类的创建和删除就行

二、UArcInventoryComponent_Equippable

可以放置装备的背包组件

2.1、属性

UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "Abilities", Replicated, meta = (AllowPrivateAccess = "true"))
TArray< FArcInventoryItemInfoEntry> EquippedItemAbilityInfos;

2.1.1、EquippedItemAbilityInfos

作用:
一个储存了装备信息的数组

USTRUCT(BlueprintType)
struct FArcInventoryItemInfoEntry
{
	UPROPERTY(BlueprintReadOnly, Category = "Abilities")
		FArcInventoryItemSlotReference ItemSlotRef;
	UPROPERTY(BlueprintReadOnly, Category = "Abilities")
		FArcEquippedItemInfo EquippedItemInfo;
};

实现:
记录ItemRef以及其增加的GAGEAttributeSet留待装备被移出自己的插槽时回退。

2.1.2、FArcEquippedItemInfo

储存GAGEAttributeSet的集合

之后会以AbilityInfo描述GAGEAttributeSet的集合

//储存GA、GE、AttributeSet
USTRUCT(BlueprintType)
struct ARCINVENTORY_API FArcEquippedItemInfo
{
public:
	FArcEquippedItemInfo(){}
	UPROPERTY()
	TArray<FGameplayAbilitySpecHandle> AddedAbilities;
	UPROPERTY()
	TArray<UAttributeSet*> InstancedAttributeSets;
	UPROPERTY()
	TArray< FActiveGameplayEffectHandle> AddedGameplayEffects;
};

2.2、接口

UFUNCTION()
virtual void OnItemEquipped(class UArcInventoryComponent* Inventory, const FArcInventoryItemSlotReference& ItemSlotRef, UArcItemStack* ItemStack, UArcItemStack* PreviousItemStack);

2.2.1、当物品更换时:OnItemEquipped

作用:
装备放入/取出与交换的入口。所有ItemSlot都绑定了该函数
或许叫OnEquipItemChanged更合适

void UArcInventoryComponent_Equippable::OnItemEquipped(class UArcInventoryComponent* Inventory, const FArcInventoryItemSlotReference& ItemSlotRef, UArcItemStack* ItemStack, UArcItemStack* PreviousItemStack)
{
	if (IsValid(PreviousItemStack) && IsEquippedItemSlot(ItemSlotRef)){
		MakeItemUnequipped_Internal(ItemSlotRef, PreviousItemStack);}
	if (IsValid(ItemStack) && IsEquippedItemSlot(ItemSlotRef)){
		MakeItemEquipped_Internal(ItemSlotRef, ItemStack);}
}

实现:
只要物品槽有变动就会调用,如果是装备栏则根据取出/放入操作执行对应的内部函数

2.2.2、放入装备:MakeItemEquipped_Internal

作用:
给放入装备插槽的物品

bool UArcInventoryComponent_Equippable::MakeItemEquipped_Internal(const FArcInventoryItemSlotReference& ItemSlot, UArcItemStack* ItemStack)
{
	//1、获取Definition
	TSubclassOf<UArcItemDefinition_Equipment> ItemDefinition(ItemStack->GetItemDefinition());
	if (!IsValid(ItemDefinition)){
		return false;}
		
	//2、获取Entry
	//2.1、已经有了则获取
	FArcInventoryItemInfoEntry* Entry = EquippedItemAbilityInfos.FindByPredicate([ItemSlot](FArcInventoryItemInfoEntry& x) {
		return x.ItemSlotRef.SlotId == ItemSlot.SlotId;});
	//2.2、没有则创建
	if (Entry == nullptr){
		Entry = &EquippedItemAbilityInfos.Add_GetRef(FArcInventoryItemInfoEntry(ItemSlot));}
	
	//3、应用Definition中的Ability和AttributeSet
	bool bSuccess = ApplyAbilityInfo_Internal(ItemDefinition.GetDefaultObject()->EquippedItemAbilityInfo, (*Entry).EquippedItemInfo, ItemStack);
	if (bSuccess){
		//应用Attribute修改器
		ApplyPerks(ItemStack, ItemSlot);
		//广播修改
		OnEquippedItem.Broadcast(this, ItemSlot, ItemStack);}
	return 	bSuccess;
}

实现:
获取物品的AbilityInfo并应用,将Entry放入

2.2.2、取出装备:MakeItemUnequipped_Internal

bool UArcInventoryComponent_Equippable::MakeItemUnequipped_Internal(const FArcInventoryItemSlotReference& ItemSlot, UArcItemStack* ItemStack)
{
	//1、取出ItemDefinition和Entry
	TSubclassOf<UArcItemDefinition_Equipment> ItemDefinition(ItemStack->GetItemDefinition());
	FArcInventoryItemInfoEntry* Entry = EquippedItemAbilityInfos.FindByPredicate([ItemSlot](FArcInventoryItemInfoEntry& x) {
		return x.ItemSlotRef.SlotId == ItemSlot.SlotId;
	});
	
	//2、移除Perk以及清空AbilityInfo
	RemovePerks(ItemStack, ItemSlot);
	bool bSuccess = ClearAbilityInfo_Internal(ItemDefinition.GetDefaultObject()->EquippedItemAbilityInfo, (*Entry).EquippedItemInfo);

	//3、广播
	if (bSuccess){
		OnUnEquippedItem.Broadcast(this, ItemSlot, ItemStack);}
	return 	bSuccess;
}

2.3、总结

装备背包的实现非常简单。根据Tag查找可以装备的ItemSlot,当ItemSlot变换时将对应ItemStackAbilityInfo(GAGEAttributeSet的集合)进行应用或移除。
可以看到,无论变换的是什么背包,ItemSlot的类型始终是不变的,

三、UArcInventoryComponent_Active

激活物背包,只有一个物品能被激活
用于放置武器等互斥物品以及对应的切换操作
目前较为复杂的部分,也是深入理解ArcInventoryComponent的重要部分

3.1、属性

UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, ReplicatedUsing = OnRep_ActiveItemSlot, Category = "Inventory")
		int32 ActiveItemSlot;
UPROPERTY(VisibleInstanceOnly, BlueprintReadWrite, ReplicatedUsing = OnRep_PendingItemSlot, Category = "Inventory")
		int32 PendingItemSlot;
TArray<FArcInventoryItemSlotReference> CachedActiveItemSlots;

3.1.1、ActiveItemSlot

正在激活中的SlotID

3.1.2、PendingItemSlot

准备激活的SlotID

3.1.3、CachedActiveItemSlots

所有的可激活物品引用

3.2、接口

void UArcInventoryComponent_Active::UpdateActiveItemSlots(UArcInventoryComponent* InventoryComp);

3.2.1、UpdateActiveItemSlots

作用:
将满足ActiveSlotTagItemSlot放入CachedActiveItemSlots

void UArcInventoryComponent_Active::UpdateActiveItemSlots(UArcInventoryComponent* InventoryComp)
{
	FGameplayTag ActiveSlotTag = GetDefault<UArcInventoryDeveloperSettings>()->ActiveItemSlotTag;
	CachedActiveItemSlots.Empty(CachedActiveItemSlots.Num());
	Query_GetAllSlots(FArcInventoryQuery::QuerySlotMatchingTag(ActiveSlotTag), CachedActiveItemSlots);
}

实现:
调用父类的Query_GetAllSlots将查到的ItemSlot塞进CachedActiveItemSlots

3.2.2、BeginPlay

作用:
激活默认ActiveItem

void UArcInventoryComponent_Active::BeginPlay()
{
	//激活默认物品
	GetWorld()->GetTimerManager().SetTimerForNextTick([this]()
	{
		//检查第一个插槽是否是被Active,是的话激活(客户端方法)
		if (PendingItemSlot != INDEX_NONE){
			this->SwitchToPendingItemSlot();}
		//有时在客户端ActiveItemSlot已经有值但PendingItemSlot还没有被同步过来,则直接执行MakeItemActive
		else if (ActiveItemSlot != INDEX_NONE && GetOwnerRole() != ROLE_Authority){
			this->MakeItemActive(ActiveItemSlot);}});
	Super::BeginPlay();
}

3.2.3、SwitchToPendingItemSlot

作用:
切换激活物品为PendingItemSlot

void UArcInventoryComponent_Active::SwitchToPendingItemSlot()
{
	//1、尝试让ASC自行处理
	bool bActivatedAbility = false;
	if (UAbilitySystemComponent* ASC = UAbilitySystemGlobals::Get().GetAbilitySystemComponentFromActor(GetOwner())){
		FGameplayTag SwitchWeaponsAbilityTag = FGameplayTag::RequestGameplayTag(TEXT("Ability.ItemSwitch.Pending"), false);
		if (SwitchWeaponsAbilityTag.IsValid()){
			bActivatedAbility = ASC->TryActivateAbilitiesByTag(FGameplayTagContainer(SwitchWeaponsAbilityTag));}}
			
	//2、无法自行处理:先注销源ItemSlot,再激活PendingItemSlot
	if (!bActivatedAbility)//Otherwise just quickly switch the item{
		MakeItemInactive();
		MakeItemActive(PendingItemSlot);}
		
	//3、重置PendingItemSlot 
	PendingItemSlot = INDEX_NONE;
}

方法:
尝试让ASC自行处理,如果无法处理则先注销再激活

3.2.4、激活物品

作用:
根据插槽ID激活物品对应的AbilityInfo
MakeItemActive

void UArcInventoryComponent_Active::MakeItemActive(int32 NewActiveItemSlot)
{
	//1、根据SlotID获取SlotRef
	FArcInventoryItemSlotReference ItemSlotRef = CachedActiveItemSlots[NewActiveItemSlot];
	
	//2、执行内部激活函数
	if (!MakeItemActive_Internal(ItemSlotRef)){
		if (IsValid(GetItemInSlot(ItemSlotRef))){
			//激活失败但是Slot确实存在,则重置NewActiveItemSlot 
			NewActiveItemSlot = INDEX_NONE;}}

	//3、设置ActiveItemSlot 
	ActiveItemSlot = NewActiveItemSlot;
}

MakeItemActive_Internal(const FArcInventoryItemSlotReference& ItemSlot)

bool UArcInventoryComponent_Active::MakeItemActive_Internal(const FArcInventoryItemSlotReference& ItemSlot)
{
	UArcItemStack* ItemStack = GetItemInSlot(ItemSlot);
	return MakeItemActive_Internal(ItemSlot, ItemStack);
}

MakeItemActive_Internal(const FArcInventoryItemSlotReference& ItemSlot, UArcItemStack* ItemStack)

bool UArcInventoryComponent_Active::MakeItemActive_Internal(const FArcInventoryItemSlotReference& ItemSlot, UArcItemStack* ItemStack)
{
	//1、获取Definition
	TSubclassOf<UArcItemDefinition_Active> ItemDefinition(ItemStack->GetItemDefinition());
	
	//2、获取Entry,有的话直接拿,没有的话创建一个
	FArcInventoryItemInfoEntry* Entry = ActiveItemAbilityInfos.FindByPredicate([ItemSlot](const FArcInventoryItemInfoEntry& x) {
		return x.ItemSlotRef.SlotId == ItemSlot.SlotId;});
	if (Entry == nullptr){
		Entry = &ActiveItemAbilityInfos.Add_GetRef(FArcInventoryItemInfoEntry(ItemSlot));}

	//3、应用能力与Perk,调用Equipment的ApplyAbilityInfo_Internal
	bool bSuccess = ApplyAbilityInfo_Internal(ItemDefinition.GetDefaultObject()->ActiveItemAbilityInfo, (*Entry).EquippedItemInfo, ItemStack);
	if (bSuccess){
		ApplyPerks(ItemStack, ItemSlot);}
	
	//4、广播激活
	OnItemActive.Broadcast(this, ItemStack);
	return bSuccess;
}

实现:
根据ID查找到Stack内部的ItemInfo并应用

3.2.5、注销物品

作用:
将目前正在激活的物品注销

MakeItemInactive

void UArcInventoryComponent_Active::MakeItemInactive()
{
	//1、执行内部注销函数
	MakeItemInactive_Internal(GetActiveItemSlot());
	
	//2、没有新的武器切换,则重置ActiveItemSlot 
	if (/*GetOwnerRole() == ROLE_Authority &&*/ !bSwitchingWeapons){
		ActiveItemSlot = INDEX_NONE;}
}

MakeItemInactive_Internal(1参数)

bool UArcInventoryComponent_Active::MakeItemInactive_Internal(const FArcInventoryItemSlotReference& ItemSlot)
{
	UArcItemStack* ItemStack = GetItemInSlot(ItemSlot);
	return MakeItemInactive_Internal(ItemSlot, ItemStack);
}

MakeItemInactive_Internal(2参数)

bool UArcInventoryComponent_Active::MakeItemInactive_Internal(const FArcInventoryItemSlotReference& ItemSlot, UArcItemStack* ItemStack)
{
	//1、处理当服务器已经移除了当前ItemSlot但客户端还没有移除时,仅仅调用Inactive的广播
	if (!IsValid(ItemStack)){
		if (IsValid(ItemSlot)){
			OnItemInactive.Broadcast(this, ItemStack);}
		return false;}
	
	//2、获取ItemDefinition与缓存在ActiveItemAbilityInfos的Entry
	TSubclassOf<UArcItemDefinition_Active> ItemDefinition(ItemStack->GetItemDefinition());
	FArcInventoryItemInfoEntry* Entry = ActiveItemAbilityInfos.FindByPredicate([ItemSlot](const FArcInventoryItemInfoEntry& x) {
		return x.ItemSlotRef.SlotId == ItemSlot.SlotId;});
	
	//3、移除Perk和所有装备信息
	RemovePerks(ItemStack, ItemSlot);
	bool bSuccess = ClearAbilityInfo_Internal(ItemDefinition.GetDefaultObject()->ActiveItemAbilityInfo, (*Entry).EquippedItemInfo);
		
	//4、广播已经被移除
	OnItemInactive.Broadcast(this, ItemStack);
	return bSuccess;
}

实现:
直接找到当前激活Item并移除AbilityInfo

3.2.6、回调重写OnItemEquipped

作用:
当物品被装备/卸下时回调

void UArcInventoryComponent_Active::OnItemEquipped(class UArcInventoryComponent* Inventory, const FArcInventoryItemSlotReference& ItemSlotRef, UArcItemStack* ItemStack, UArcItemStack* PreviousItemStack)
{
	Super::OnItemEquipped(Inventory, ItemSlotRef, ItemStack, PreviousItemStack);
	
	//1、如果是放入,当是第一个ActiveItem时,激活该物品
	if (ActiveItemSlot == INDEX_NONE && IsActiveItemSlot(ItemSlotRef) && IsValid(ItemStack)){
		int32 ItemSlotIndex = GetActiveItemIndexBySlotRef(ItemSlotRef);
		PendingItemSlot = ItemSlotIndex;
		if (HasBegunPlay()){
			SwitchToPendingItemSlot();}}

	//2、当移除激活的物品,激活下一个可以激活的物品
	if (!IsValid(ItemStack)){
		int32 ItemSlotIndex = GetActiveItemIndexBySlotRef(ItemSlotRef);
		if (ItemSlotIndex == ActiveItemSlot){
			PendingItemSlot = GetNextValidActiveItemSlot();
			MakeItemInactive_Internal(ItemSlotRef, PreviousItemStack);
			SwitchToPendingItemSlot();}}}

实现:
当放入的是一个可激活物时,自动装备
当移除的是当前激活物时,自动装备下一件物品

3.2.7、交换

作用:
交换激活的物品

void UArcInventoryComponent_Active::SwapActiveItems(int32 NewItemSlot){
	bSwitchingWeapons = true;
	MakeItemInactive();
	MakeItemActive(NewItemSlot);
	bSwitchingWeapons = false;
}

实现:
移除原有激活物,激活新激活物

3.2.8、获取

函数作用
GetActiveItemStack获取当前激活的ItemStack
GetNextActiveItemSlot获取下一个ActiveItemSlot
GetPreviousActiveItemSlot获取上一个ActiveItemSlot
GetNextValidActiveItemSlot获取下一个存在ItemStackActiveItemSlot
GetPreviousValidActiveItemSlot获取上一个存在ItemStackActiveItemSlot

3.3、总结

提供了基础的物品激活功能
对于Equip进行限制的产物,仅允许一种激活物品存在
激活物最麻烦的是同步与联机问题

四、总结

UArcInventoryComponent_Bag:根据数量动态设置普通ItemSlot
UArcInventoryComponent_Equipable:接入GAS,能将装备放入ItemSlot中并应用AbilityInfo
UArcInventoryComponent_Active:仅允许一个AbilityInfo被激活
基本完成了ArcInventory的拼图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值