Init
This commit is contained in:
191
03-UnrealEngine/Gameplay/Lyra/UE5 Lyra学习笔记(4)—Inventory.md
Normal file
191
03-UnrealEngine/Gameplay/Lyra/UE5 Lyra学习笔记(4)—Inventory.md
Normal file
@@ -0,0 +1,191 @@
|
||||
---
|
||||
title: UE5 Lyra学习笔记(4)—Inventory
|
||||
date: 2022-08-10 13:55:20
|
||||
tags: Lyra Gameplay
|
||||
rating: ⭐️⭐️
|
||||
---
|
||||
# 前言
|
||||
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`。子类会继承该标签。
|
||||
- Abstract:`Abstract`说明符会将类声明为"抽象基类"。阻止用户向关卡中添加此类的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。
|
||||
- FPickupInstance:Instance。
|
||||
- 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指针。
|
||||
|
||||
代码如下:
|
||||
```c++
|
||||
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:装备定义。
|
||||
- FLyraEquipmentActorToSpawn:Spawn用的相关数据。
|
||||
- ULyraPickupDefinition:UDataAsset。主要是有ULyraInventoryItemDefinition以及其他美术资产指针。
|
||||
- ULyraWeaponPickupDefinition:ULyraPickupDefinition的子类,储存Spawn用的LocationOffset与Scale数据。
|
||||
- ULyraGameplayAbility_FromEquipment:Equipment类型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](https://docs.unrealengine.com/4.27/zh-CN/ProductionPipelines/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()来实现传入数据到其他组件的目的。
|
||||
```c++
|
||||
/** 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来获取对应的数量。
|
||||
```c++
|
||||
UPROPERTY(Replicated)
|
||||
FGameplayTagStackContainer StatTags;
|
||||
```
|
||||
|
||||
AddStatTagCount在Fragment(stat)里被调用,用于设置初始的话数量,RemoveStatTagCount则在 GameplayEffect的cost中被调用。
|
||||
|
||||
PS.可以用来记录 血药的使用量,重量32000 用一次=》30000
|
Reference in New Issue
Block a user