BlueRoseNote/03-UnrealEngine/Gameplay/Lyra/UE5 Lyra学习笔记(4)—Inventory.md
2023-06-29 11:55:02 +08:00

11 KiB
Raw Permalink Blame History

title, date, tags, rating
title date tags rating
UE5 Lyra学习笔记(4)—Inventory 2022-08-10 13:55:20 Lyra Gameplay

前言

ULyraInventoryManagerComponent通过GameFeature功能在模块激活时挂载Controller上具体可以查看ShooterCore的GameFeature配置文件。 Lyra中的物品系统采用 Entry - Instance - Definition - Fragment的结构。但该系统有没有完成的痕迹,所以个人不建议直接拿来用,但里面有考虑到网络同步的设计值得学习。 与ActionRPG项目相比它没有实现SaveGame/LoadGame部分也没有使用FPrimaryAssetId来减少网络同步以及存档大小。所以也挺可惜的。

Inventory的主要的资产为ULyraInventoryItemDefinition前缀名为ID_Equipment的主要资产为ULyraEquipmentDefinition前缀名为WID_ 。这些资产位于:

  • Plugins\GameFeatures\ShooterCore\Content\Items\
  • Plugins\GameFeatures\ShooterCore\Content\Weapons\

Inventory

Class

  • ULyraInventoryManagerComponent整个背包系统的管理组件。使用FLyraInventoryList来管理物品数据。
    • FLyraInventoryList类型为FFastArraySerializer(为了解决网络传输而实现的类)。内部使用TArray<FLyraInventoryEntry> Entries来存储物品数据。
    • FLyraInventoryEntry类型为FFastArraySerializerItem(为了解决网络传输而实现的类)。内部使用ULyraInventoryItemInstance来存储物品数据内部还有StackCount与LastObservedCount变量该变量不同步
      • ULyraInventoryItemInstance物品实例。内部变量有TSubclassOf<ULyraInventoryItemDefinition> ItemDef以及一个用于存储物品状态的FGameplayTagStackContainer(Lyra定义的Tag堆栈)
      • ULyraInventoryItemDefinition物品定义UCLASS(Blueprintable, Const, Abstract)。内部变量有TArray<TObjectPtr<ULyraInventoryItemFragment>> Fragments也就是说一个ItemDefinition会有多个ItemFragment。另外就是用于显示名称的FText DisplayName
      • ULyraInventoryItemFragment物品片段UCLASS(DefaultToInstanced, EditInlineNew, Abstract)。可以理解为物品信息碎片,一个物品定义由多个碎片组成。

相关UClassMeta的官方文档解释

  • Blueprintable将此类公开为用于创建蓝图的可接受基类。默认为NotBlueprintable。子类会继承该标签。
  • Const此类中的所有属性和函数都是const并且导出为const。子类会继承该标签。
  • AbstractAbstract说明符会将类声明为"抽象基类"。阻止用户向关卡中添加此类的Actor。
  • DefaultToInstanced此类的所有实例都被认为是"实例化的"。实例化的类(组件)将在构造时被复制。子类会继承该标签。
  • EditInlineNew指示可以从虚幻编辑器"属性Property"窗口创建此类的对象,而非从现有资源引用。默认行为是仅可通过"属性Property"窗口指定对现有对象的引用。子类会继承该标签;子类可通过 NotEditInlineNew 来覆盖它。

ItemInstance

该类为物品的实例也是物品的操作类。包含了ItemDefinition之外还记录了一些额外的Runtime状态数据FGameplayTagStackContainer也是一个FFastArraySerializer类还定义各种操作用函数。

ItemDefinition

该类为定义物品各种属性的资产主要通过ItemFragment进行碎片化描述。通过蓝图继承的方式来创建一个个的物品定义。

PS.个人很好奇为什么不把这这个类做成一个DataAsset因为这个类也就是个静态数据类。至少ULyraPickupDefinition继承自UDataAsset。

ItemFragment

该类为物品碎片化的属性描述类同时附带OnInstanceCreated()虚函数,以实现一些属性变化外的特殊效果。

在Lyra中实现了这一些

  • UInventoryFragment_EquippableItem用于指定ULyraEquipmentDefinition定义于Equipment类。
  • UInventoryFragment_SetStats用于给ItemInstance添加状态标签。
  • UInventoryFragment_QuickBarIcon用于指定快捷栏SlateBrush资源以及UI显示名称。
  • UInventoryFragment_PickupIcon用于指定骨骼物体枪械或者血包、显示名称以及pad颜色。

Pickup

定义了Pickup相关类

  • FInventoryPickup可以理解为Entry。
    • FPickupInstanceInstance。
      • FPickupTemplate可以理解为Definition。
  • UPickupable与IPickupable接口类。

AddEntry

给FLyraInventoryList添加Entry的逻辑大致为

  1. 检查相关变量
  2. TArray<FLyraInventoryEntry> Entries添加Entry。
    1. 使用NewObject()填充Instance指针。
    2. 使用Instance->SetItemDef()设置ItemDefinition类。
    3. 通过ItemDefinition取得ItemFragment并且调用接口函数OnInstanceCreated()部分ItemDefinition会执行一些特殊逻辑。
  3. 设置Entry的StackCount并设置Entry为Dirty触发更新逻辑。
  4. 返回Instance指针。

代码如下:

ULyraInventoryItemInstance* FLyraInventoryList::AddEntry(TSubclassOf<ULyraInventoryItemDefinition> ItemDef, int32 StackCount)  
{  
   ULyraInventoryItemInstance* Result = nullptr;  
  
   check(ItemDef != nullptr);  
   check(OwnerComponent);  
  
   AActor* OwningActor = OwnerComponent->GetOwner();  
   check(OwningActor->HasAuthority());  
  
  
   FLyraInventoryEntry& NewEntry = Entries.AddDefaulted_GetRef();  
   NewEntry.Instance = NewObject<ULyraInventoryItemInstance>(OwnerComponent->GetOwner());  //@TODO: Using the actor instead of component as the outer due to UE-127172  
   NewEntry.Instance->SetItemDef(ItemDef);  
   for (ULyraInventoryItemFragment* Fragment : GetDefault<ULyraInventoryItemDefinition>(ItemDef)->Fragments)  
   {      
		if (Fragment != nullptr)  
	    {
		   Fragment->OnInstanceCreated(NewEntry.Instance);  
	    } 
    }   
    NewEntry.StackCount = StackCount;  
	Result = NewEntry.Instance;  
  
   //const ULyraInventoryItemDefinition* ItemCDO = GetDefault<ULyraInventoryItemDefinition>(ItemDef);  
   MarkItemDirty(NewEntry);  
  
   return Result;  
}

Equipment

Lyra的装备系统只使用了Entry - Instance - Definition结构。

Class

  • ULyraEquipmentManagerComponent类似ULyraInventoryManagerComponent的装备管理类。
    • FLyraEquipmentList
    • FLyraAppliedEquipmentEntry
      • ULyraEquipmentInstance装备实例。
        • ULyraEquipmentDefinition装备定义。
        • FLyraEquipmentActorToSpawnSpawn用的相关数据。
  • ULyraPickupDefinitionUDataAsset。主要是有ULyraInventoryItemDefinition以及其他美术资产指针。
    • ULyraWeaponPickupDefinitionULyraPickupDefinition的子类储存Spawn用的LocationOffset与Scale数据。
  • ULyraGameplayAbility_FromEquipmentEquipment类型GA的父类实现2个ULyraEquipmentInstance/ULyraInventoryItemInstance Get函数。子类有2个蓝图与1个c++类。
  • ULyraQuickBarComponent

ULyraEquipmentInstance

OnEquipped ()/OnUnequipped()会调用对用的K2函数BlueprintImplementableEvent具体逻辑在蓝图实现。主要的逻辑就是播放对应的Montage动画。

ULyraEquipmentDefinition

  • TSubclassOf<ULyraEquipmentInstance> InstanceType装备类型通过Class类型来判断装备类型。
  • TArray<TObjectPtr<const ULyraAbilitySet>> AbilitySetsToGrant:装备的附带能力集。
  • TArray<FLyraEquipmentActorToSpawn> ActorsToSpawn 装备Actor。

FGameplayTagStack

Lyra里定义了FGameplayTagStackContainer 与FGameplayTagStack这2个类来解决GameplayTagStack的同步问题。 其中TagToCountMap只是本地/服务器用于快速查找GameplayTag的Stack数而维护的Map它会在Add/RemoveStack()之外还会在三个网络同步函数进行对应修改。

  • AddStack()
  • RemoveStack()
  • 网络同步函数
    • PreReplicatedRemove()
    • PostReplicatedAdd()
    • PostReplicatedChange()

ItemDefintion是否可以做成DataAsset

蓝图类以及非蓝图类资产,例如关卡和数据资产(UDataAsset 类的资产实例)。

非蓝图资产

参考官方文档AssetManagement 假如主要资产类型不需要存储蓝图数据,你可以使用非蓝图资产。非蓝图资产在代码中的访问更简单,而且更节省内存。 如需在编辑器中新建一个非蓝图主资产,请在"高级"内容浏览器窗口中新建一个数据资产,或使用自定义用户界面来创建新关卡等。以这种方式创建资产与创建蓝图类不一样;你创建的资产是类的实例,而非类本身。要访问类,请用 GetPrimaryAssetObject 这类C++函数加载它们或者用蓝图函数名称中没有Class。一旦加载后你就可以直接访问它们并读取数据。

因为这些资产是实例而不是类,所以你无法从它们继承类或其他资产。如果你要这样做,例如,如果你想创建一个子资产,继承其父类的值(除了那些显式覆盖的值),你应该使用蓝图类来代替。

UI

Lyra没有制作背包部分的逻辑但预留了相关的实现。采用在

  • void FLyraInventoryList::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
  • void FLyraInventoryList::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
  • void FLyraInventoryList::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize) 调用BroadcastChangeMessage()来实现传入数据到其他组件的目的。
/** A message when an item is added to the inventory */  
USTRUCT(BlueprintType)  
struct FLyraInventoryChangeMessage  
{  
   GENERATED_BODY()  
  
   //@TODO: Tag based names+owning actors for inventories instead of directly exposing the component?  
   UPROPERTY(BlueprintReadOnly, Category=Inventory)  
   UActorComponent* InventoryOwner = nullptr;  
  
   UPROPERTY(BlueprintReadOnly, Category = Inventory)  
   ULyraInventoryItemInstance* Instance = nullptr;  
  
   UPROPERTY(BlueprintReadOnly, Category=Inventory)  
   int32 NewCount = 0;  
  
   UPROPERTY(BlueprintReadOnly, Category=Inventory)  
   int32 Delta = 0;  
};

void FLyraInventoryList::BroadcastChangeMessage(FLyraInventoryEntry& Entry, int32 OldCount, int32 NewCount)  
{  
   FLyraInventoryChangeMessage Message;  
   Message.InventoryOwner = OwnerComponent;  
   Message.Instance = Entry.Instance;  
   Message.NewCount = NewCount;  
   Message.Delta = NewCount - OldCount;  
  
   UGameplayMessageSubsystem& MessageSystem = UGameplayMessageSubsystem::Get(OwnerComponent->GetWorld());  
   MessageSystem.BroadcastMessage(TAG_Lyra_Inventory_Message_StackChanged, Message);  
}

StatTags的作用

物品系统的ULyraInventoryItemInstance中的来记录某个Tag对应的Int32数量在蓝图中调用GetStatTagCount来获取对应的数量。

UPROPERTY(Replicated)
FGameplayTagStackContainer StatTags;

AddStatTagCount在Fragmentstat里被调用用于设置初始的话数量RemoveStatTagCount则在 GameplayEffect的cost中被调用。

PS.可以用来记录 血药的使用量重量32000 用一次=》30000