UE4 基于GAS的插件ArcInventory拆解-3_物品生成器:UArcItemGenerator_Unique/Static/Perk/SimpleRandom

太长了做个目录

  • 一、`UArcItemGenerator`
    • 1.1、属性
    • 1.2、方法
      • 1.2.1、`GenerateItemStack`
    • 1.3、总结
      • 1.3.1、属性
      • 1.3.2、接口
  • 二、`UArcItemGenerator_Unique`
    • 2.1、属性
      • 2.1.1、`ItemDefinition`与`ItemRarity`
      • 2.1.2、`SubItemGenerators`
      • 2.1.3、`ItemDisplayName`
    • 2.2、接口
      • 2.2.1、`GenerateItemStack_Implementation`
    • 2.3、总结
  • 三、`UArcItemGenerator_Static`
    • 3.1、属性
    • 3.2、接口
    • 3.3、总结
  • 四、`ArcItemStack_Perk`
    • 4.1、属性
    • 4.2、接口
      • 4.2.1、 `CanAttachTo`
    • 4.3、总结
  • 五、`UArcItemGenerator_SimpleRandom`
    • 5.1、属性
      • 5.1.1、`ItemTagQuery`
      • 5.1.2、`RarityTable`
      • 5.1.3、`bRequireUniqueModifiers`
      • 5.1.4、`WeightedPossibleModifiers`
      • 5.1.5、`bRequireUniquePerks`
      • 5.1.6、`WeightedPossiblePerks`
      • 5.1.7、`DynamicPerkItemGenerator`
    • 5.2、接口
      • 5.2.1、`GenerateItemStack`
      • 5.2.2、`SelectWeightedItems`
      • 5.2.3、`PickWeightedItem`
    • 5.3、总结
  • 六、总结

一、UArcItemGenerator

使用生成器将ArcItemStack生成,其仅仅用于生成固定种类的Stack与UI数据,而动态变化的Definition和Rarity则作为函数参数传入

1.1、属性

栈类、Tag、UIData

UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Default")
TSubclassOf<UArcItemStack> ItemStackClass;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Default")
FGameplayTagContainer OwnedTags;
UPROPERTY(EditDefaultsOnly, Instanced, BlueprintReadOnly, Category = "UI", Meta = (AssetBundles = "UI"))
UArcInvUIData* UIData;

ItemStackClass:作为一个备选项与Definition的StackClass共同决定物品的Definition
OwnedTags:Generator自身的Tag,与生成的具体物品无关
UIData:没有引用

1.2、方法

virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override;
virtual FPrimaryAssetId GetPrimaryAssetId() const override;
UArcItemStack* GenerateItemStack(const FArcItemGeneratorContext& Context);

GetOwnedGameplayTags与GetPrimaryAssetId之前已经提过,不表

1.2.1、GenerateItemStack

GenerateItemStack为protected类型,只允许子类调用
作用:
传入Definition和Rarity,根据自身的DefaultStackClass和Definition的StackClass生成包含了Definition和 Rarity的新UArcItemStack并返回d

UArcItemStack* UArcItemGenerator::CreateNewItemStack(TSubclassOf<UArcItemDefinition_New> ItemDefinition, UArcItemRarity* ItemRarity)
{	
	//1、寻找ItemStackClass
	TSubclassOf<UArcItemStack> ISC = ItemStackClass;
	if (IsValid(ISC)){
		//检查我们的ItemStack类是否是item def想要的子类。 如果不是,就使用该项目定义的类
		if (IsValid(ItemDefinition.GetDefaultObject()->DefaultItemStackClass) && !ISC->IsChildOf(ItemDefinition.GetDefaultObject()->DefaultItemStackClass)){
			ISC = ItemDefinition.GetDefaultObject()->DefaultItemStackClass;}}
	if (!IsValid(ISC)){
		//如果没有指定ISC,则使用DefaultItemStackClass
		ISC = GetDefault<UArcInventoryDeveloperSettings>()->DefaultItemStackClass;
		if (!IsValid(ISC)){
			//如果依旧没有DefaultItemStackClass,则使用基类
			//但我就是不使用Definition中的ItemStackClass
			ISC = UArcItemStack::StaticClass();}}
	//2、生成并初始化ItemStack
	UArcItemStack* NewItemStack = NewObject<UArcItemStack>(GetTransientPackage(), ISC);
	NewItemStack->ItemDefinition = ItemDefinition;
	NewItemStack->Rarity = ItemRarity;
	return NewItemStack;
}

实现:
1、传入Definition与Rarity
2、获取到StackClass

  1. 查询自身有没有ItemStackClass(之后简称ISC)
    1.1. 有->是否是Definition的ISC的子类
    1.1.1. 是->使用自身ISC
    1.1.2. 否->使用Definition的ISC
    1.2. 否->项目设置是否有默认ISC
    1.2.1. 有->使用项目设置的默认ISC
    1.2.2. 没有->使用ISC基类

最大的问题在于没有ISC时不去查找ArcItemDefinition的基类而直接使用默认类,询问后作者会在之后的版本修复这个问题
修复后的优先级应是:

  • 如果当前ISC是Definition中ISC的子类,则使用当前ISC
  • 如果不是则依次考虑:ArcItemDefinition->UArcInventoryDeveloperSettings->UArcItemStack::StaticClass()

作者回复:
UE4 基于GAS的插件ArcInventory拆解-3_物品生成器:UArcItemGenerator_Unique/Static/Perk/SimpleRandom_第1张图片

1.3、总结

1.3.1、属性

ArcItemGenerator创建的是ArcItemStack而非ArcItemDefinition,Stack中的Definition可以为空,所以需要有默认的StackClass以及UIData

属性集与ArcItemDefinition高度重合,ItemStackClassOwnedTagsUIData三者在Definition中都有对应,且其中并没有定义Definition的变量,这是对Generator中变与不变的部分并进行合理划分。

UIDataStackClass可以在没有Definition的情况下也能正常进行物品操作与显示,而OwnedTags是对自身的描述。

1.3.2、接口

另一方面GenerateItemStack仅允许子类调用,证明需要用户自己创建子类生成自己的Generator类型

插件共提供了4个子类
在这里插入图片描述

二、UArcItemGenerator_Unique

作为UArcItemGenerator的子类,可以说是对UArcItemGenerator功能的补全,其能承担一个含有Definition的Stack生成任务。

2.1、属性

包含了Definition、Rarity、Name物品实体属性,同时通过SubItemGenerators增加拓展性

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Definition")
TSubclassOf<UArcItemDefinition_New> ItemDefinition;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Definition")
UArcItemRarity* ItemRarity;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Definition")
FText ItemDisplayName;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Instanced, Category = "Definition")
TArray<UArcItemGenerator*> SubItemGenerators;

2.1.1、ItemDefinitionItemRarity

对应ArcItemGeneratorCreateNewItemStack的两个变量输入

2.1.2、SubItemGenerators

SubItemStack异曲同工

2.1.3、ItemDisplayName

显示的名称,

2.2、接口

GenerateItemStack_Implementation

2.2.1、GenerateItemStack_Implementation

作用:
外界让Generator生成Stack的接口

UArcItemStack* UArcItemGenerator_Unique::GenerateItemStack_Implementation(const FArcItemGeneratorContext& Context)
{
	//1、调用父类函数生成并填充Stack
	UArcItemStack* ItemStack = CreateNewItemStack(ItemDefinition, ItemRarity);
	ItemStack->ItemName = ItemDisplayName; 
	//2、如果有子生成器,则一并生成并放入子Stack中
	for (UArcItemGenerator* SubItemGenerator : SubItemGenerators){
		if (IsValid(SubItemGenerator)){
			UArcItemStack* SubItem = SubItemGenerator->GenerateItemStack(Context);
			ItemStack->AddSubItemStack(SubItem);}}	
	return ItemStack;
}

实现:
继承自父类的GenerateItemStack的override
直接调用父类的CreateNewItemStack生成Stack并赋上Name,然后循环生成所有的Generator并放入子Stack中。

2.3、总结

其是Generator的完整实现,完成了一个Stack生成器的所有内容
比较奇怪的是为什么ItemName会放在Generator中而不是Definition中

三、UArcItemGenerator_Static

3.1、属性

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Generator")
	TSubclassOf<UArcItemGenerator> StaticGenerator;

一个指向UArcItemGenerator子类的指针StaticGenerator

3.2、接口

if (IsValid(StaticGenerator)){
		return StaticGenerator->GetDefaultObject<UArcItemGenerator>()->GenerateItemStack(Context);}
return nullptr;

调用StaticGenerator的GenerateItemStack

3.3、总结

迷惑

四、ArcItemStack_Perk

目前与UArcItemGenerator_SimpleRandom有比较强的互动

4.1、属性

UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Perk Settings", Replicated)
	TArray<FArcPerkModifierData> Modifiers;

简单直接,只有一个属性修改器数组

4.2、接口

virtual bool CanAttachTo_Implementation(UArcItemStack* OtherStack) override;

4.2.1、 CanAttachTo

作用:
重写了ArcItemStackCanAttachTo,判断是否能成为该Stack的子Stack

bool UArcItemStack_Perk::CanAttachTo_Implementation(UArcItemStack* OtherStack)
{
	if (OtherStack->IsA(UArcItemStack_Perk::StaticClass())){
		return false;}
	return true;
}

实现:
除了自身以外都可以成为其子Stack

4.3、总结

类似于一个属性修改器数组挂到其他Stack上,目前还不清楚一个Stack上挂一堆属性修改器是做什么。也许是武器给所有者提供的数值?

五、UArcItemGenerator_SimpleRandom

随机生成ItemStack

5.1、属性

	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Default")
	FGameplayTagQuery ItemTagQuery;
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Default")
	TArray< FArcItemGenerator_RaritySelector> RarityTable;
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Default")
	bool bRequireUniqueModifiers;
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Default")
	TArray<FArcItemGenerator_DynamicModifier> WeightedPossibleModifiers;
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Perks")
	bool bRequireUniquePerks;
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Perks")
	TArray<FArcItemGenerator_PerkSelector> WeightedPossiblePerks;
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Instanced, Category = "Perks")
	UArcItemGenerator* DynamicPerkItemGenerator;

5.1.1、ItemTagQuery

物品的Tag类型,根据Query找到对应物品集合

5.1.2、RarityTable

规定了物品中的Rarity类型、属性修改器数量、Perk数量

5.1.3、bRequireUniqueModifiers

在筛选多个属性修改器时是否每个修改器都是唯一的

5.1.4、WeightedPossibleModifiers

属性修改器的筛选范围

5.1.5、bRequireUniquePerks

在筛选多个ArcItemStack_Perk时是否每个ArcItemStack_Perk是唯一的

5.1.6、WeightedPossiblePerks

ArcItemStack_Perk的候选名单和对应权重

5.1.7、DynamicPerkItemGenerator

给属性修改器生成指定的ArcItemStack_Perk

5.2、接口

5.2.1、GenerateItemStack

作用:
随机生成物品和Rarity,以及根据RarityEntry生成修改器和Perk放入子Stack中

UArcItemStack* UArcItemGenerator_SimpleRandom::GenerateItemStack_Implementation(const FArcItemGeneratorContext& Context){
	//1、找一个符合条件的Definition
	//1.1、找到Query相关的所有Definitions
	TArray<TSubclassOf<UArcItemDefinition_New>> ItemDefs;
	if (!UArcItemBPFunctionLibrary::QueryMatchingItemDefinitions(ItemTagQuery, ItemDefs){
		return nullptr;}
	//1.2、从Definitions中随机一个Definition
	TSubclassOf<UArcItemDefinition_New> ItemDef = ItemDefs[FMath::RandRange(0, ItemDefs.Num() - 1)];
	
	//2、从RarityTable中随机一个RarityEntry
	FArcItemGenerator_RaritySelector RarityEntry = SelectSingleWeightedItem(RarityTable);
	
	//3、找一个符合条件的Rarity
	//3.1、根据RarityEntry中的Query查询符合条件的Rariety并放入Rarieties
	TArray<UArcItemRarity*> Rarieties;
	if (!UArcItemBPFunctionLibrary::QueryMatchingItemRarities(RarityEntry.RarityTagQuery, Rarieties)){}
	UArcItemRarity* Rarity = nullptr;
	//3.2、从Rarieties中随机一个Rariety
	if (Rarieties.Num() > 0){
		Rarity = Rarieties[FMath::RandRange(0, Rarieties.Num() - 1)];}

	//4、创建一个Stack并赋值Definition和Rariety
	UArcItemStack* ItemStack = CreateNewItemStack(ItemDef, Rarity);

	//5、创建动态修改器补偿
	if (RarityEntry.NumberOfModifiers > 0 && IsValid(DynamicPerkItemGenerator)){
		//5.1、选择出一组属性修改器
		TArray< FArcItemGenerator_DynamicModifier> SelectedMods;
		SelectWeightedItems(WeightedPossibleModifiers, RarityEntry.NumberOfModifiers, bRequireUniqueModifiers, SelectedMods);
		//5.2、创建UArcItemStack_Perk,将所有修改器移入新Perk中,将Perk放入Stack的子Stack中
		if (UArcItemStack_Perk* Perk = Cast<UArcItemStack_Perk>(DynamicPerkItemGenerator->GenerateItemStack(Context))){
			for (FArcItemGenerator_DynamicModifier& DynamicMod : SelectedMods){
				FArcPerkModifierData ModifierData;
					ModifierData.Attribute = DynamicMod.Attribute;
					ModifierData.SlotApplicationRequirement = DynamicMod.SlotApplicationRequirement;
					ModifierData.ModifierOp = DynamicMod.ModifierOp;
					ModifierData.Value = FMath::RandRange(DynamicMod.MinValue, DynamicMod.MaxValue);
					Perk->Modifiers.Add(ModifierData);}
			ItemStack->AddSubItemStack(Perk);}}
	
	//6、选择一组PerGenerator生成Perk并放入子Stack中
	if (RarityEntry.NumberOfPerks > 0){
		TArray<FArcItemGenerator_PerkSelector>;
		SelectWeightedItems(WeightedPossiblePerks, RarityEntry.NumberOfPerks, bRequireUniquePerks, SelectedPerks);
		for (FArcItemGenerator_PerkSelector& SelectedPerk : SelectedPerks){
			TArray<UArcItemGenerator*> PerkGenerators;
			if (!UArcItemBPFunctionLibrary::QueryMatchingItemGenerators(SelectedPerk.PerkTagQuery, PerkGenerators)){
				continue;}
			UArcItemGenerator* PerkGenerator = PerkGenerators[FMath::RandRange(0, PerkGenerators.Num() - 1)];
			if (!IsValid(PerkGenerator)){
				continue;}
			UArcItemStack* PerkInstance = PerkGenerator->GenerateItemStack(Context);
			ItemStack->AddSubItemStack(PerkInstance);}} 	
	return ItemStack;
}

方法:

  1. 根据ItemTagQuery随机生成Definition
  2. RarityTable中随机一个RarityEntry
  3. 根据RarityEntry中的QueryTag寻找Rarities并从中随机一个Rarity
  4. 根据Definition和Rarity生成一个Stack并初始化
  5. 生成一个UArcItemStack_Perk,从WeightedPossibleModifiers中获取到(RarityEntry中的NumberOfModifiers)个修改器放入Perk中,最后将该Perk放入主Stack中
  6. 根据

5.2.2、SelectWeightedItems

作用:
Protected函数
这是一个随机多选择器,可以在一组物品中根据物品权重和总物品数量随机出一组物品出来。但仍需指定确定数量。可以应用于打怪后爆出的随机物品生成中

template <typename WeightedStructType>
void SelectWeightedItems(const TArray<WeightedStructType>& WeightedRarities, int32 NumberToSelect, bool bSelectUnique, TArray< WeightedStructType>& OutSelections)
{
	//1、获取总权重
	int32 SumOfWeights = 0;
	for (const WeightedStructType& Entry : WeightedRarities){
		SumOfWeights += Entry.Weight;}

	 //2、选择一个候选列表(PossibleSelection)出来
	TArray<WeightedStructType*> PossibleSelection;
	for (int32 i = 0; i < NumberToSelect; i++){
		int32 Tries = 0;
		//2.1、根据权重随机出一个GrabbedStruct,重复则继续随机,随机10次后还找不到则跳过
		WeightedStructType* GrabbedStruct = nullptr;
		do{
			GrabbedStruct = PickWeightedItem(WeightedRarities, SumOfWeights);
			if (bSelectUnique && PossibleSelection.Contains(GrabbedStruct)){
				GrabbedStruct = nullptr;}
		} while (GrabbedStruct == nullptr || ++Tries > 10);
		//2.2、添加进候选数组
		if (GrabbedStruct != nullptr){
			PossibleSelection.Add(GrabbedStruct);}}
			
	//3、将候选列表转移至输出列表
	for (WeightedStructType* Selection : PossibleSelection){
		OutSelections.Add(*Selection);}
}

方法:
多次使用PickWeightedItem获取单个Rarity,最后输出选中的Rarity集合

5.2.3、PickWeightedItem

作用:
Protected函数
挑选加权项目。根据总权重和Rarities的各个分权重随机出一个Rarities出来

template <typename WeightedStructType>
WeightedStructType* PickWeightedItem(const TArray<WeightedStructType>& WeightedRarities, int32 SumOfWeights)
{
	int32 RandomRoll = FMath::RandRange(0, SumOfWeights);
	for (const WeightedStructType& Struct : WeightedRarities){
		if (RandomRoll < Struct.Weight){
			return const_cast<WeightedStructType*>(&Struct);}
		RandomRoll -= Struct.Weight;}
	return nullptr;
}

使用:
随机一个数,找到这个数对应的区间,返回该区间对应的Rarity

5.3、总结

  1. 对于Perk的使用比较懵逼,不清楚使用场景,是用于给拥有者增加属性吗(比如剑增加多少攻击力)
  2. 感觉这个类实现比较混乱,很多应该放在一起的东西并没有放在一起,许多东西不看函数实现也无法明白,再继续学习吧

六、总结

  1. 高内聚低耦合+数据驱动的恐怖如斯
  2. UArcItemGenerator提供所有物品生成器的核心并做成基类,然后根据基类衍生出不同的生成类型
  3. UArcItemGenerator_Unique用于根据Definition和Rarity生成特定物理种类和稀有度的物品
  4. UArcItemGenerator_Static不知道用来干啥
  5. ArcItemStack_PerkUArcItemGenerator_SimpleRandom共同实现:不同物品实体、不同品质对应不同属性加成效果的随机生成器

你可能感兴趣的:(ue4,c++)