92. UE5 GAS RPG 使用C++创建GE实现灼烧的负面效果

news/2024/9/19 0:48:35/ 标签: ue5, c++, java

在正常游戏里,有些伤害技能会携带一些负面效果,比如火焰伤害的技能会携带燃烧效果,敌人在受到伤害后,会接受一个燃烧的效果,燃烧效果会在敌人身上持续一段时间,并且持续受到火焰灼烧。
我们将在这一篇文章里,实现伤害技能附带负面效果,并可以设置负面效果的参数,来实现对敌人添加负面buff

添加负面效果标签

首先我们添加对应的伤害类型的负面标签

	FGameplayTag DeBuff_Burn; //火属性负面效果 燃烧FGameplayTag DeBuff_Stun; //雷属性负面效果 眩晕FGameplayTag DeBuff_Arcane; //魔法伤害负面效果FGameplayTag DeBuff_Physical; //物理伤害负面效果 流血

并且添加一个Map,用于负面效果标签和属性抵抗表情对应,抵抗可以用于降低负面效用的成功率

TMap<FGameplayTag, FGameplayTag> DeBuffsToResistance; //属性伤害标签对应负面标签

将标签注册的标签管理器

	/** 负面标签注册*/GameplayTags.DeBuff_Burn = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("DeBuff.Burn"),FString("火属性燃烧负面标签"));GameplayTags.DeBuff_Stun = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("DeBuff.Stun"),FString("雷属性眩晕负面标签"));GameplayTags.DeBuff_Arcane = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("DeBuff.Arcane"),FString("魔法属性负面标签"));GameplayTags.DeBuff_Physical = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("DeBuff.Physical"),FString("物理属性流血负面标签"));

我们在应用负面效果时,目标角色可以通过自身的对应类型的抵抗来降低负面效果应用的成功率,所以,我们需要一个对应的Map

	/** 负面标签和属性抵抗标签对于对应*/GameplayTags.DeBuffsToResistance.Add(GameplayTags.DeBuff_Burn, GameplayTags.Attributes_Resistance_Fire);GameplayTags.DeBuffsToResistance.Add(GameplayTags.DeBuff_Stun, GameplayTags.Attributes_Resistance_Lightning);GameplayTags.DeBuffsToResistance.Add(GameplayTags.DeBuff_Arcane, GameplayTags.Attributes_Resistance_Arcane);GameplayTags.DeBuffsToResistance.Add(GameplayTags.DeBuff_Physical, GameplayTags.Attributes_Resistance_Physical);

添加负面效果配置项

如果我们需要添加一些负面效果相关的配置项,然后使用Set By Caller 的方式去设置,我们先在RPGDamageGameplayAbility.h里添加对负面效果的配置项

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")FGameplayTag DeBuffDamageType = FGameplayTag(); //负面效果伤害类型UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")float DeBuffChance = 20.f; //触发负面的机率UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")float DeBuffDamage = 5.f; //负面伤害UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")float DeBuffFrequency = 1.f; //负面伤害触发间隔时间UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")float DeBuffDuration = 5.f; //负面效果持续时间

我们要通过Set ByCaller设置负面效果GE的属性,那么,我们选择使用标签,这样不会出错
接着,我们创建四个对应的标签

	FGameplayTag DeBuff_Chance; //负面效果触发几率标签FGameplayTag DeBuff_Damage; //负面效果伤害标签FGameplayTag DeBuff_Duration; //负面效果持续时间标签FGameplayTag DeBuff_Frequency; //负面效果触发间隔标签

然后注册

	/** 负面效果配置标签*/GameplayTags.DeBuff_Chance = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("DeBuff.Chance"),FString("负面效果 触发几率"));GameplayTags.DeBuff_Damage = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("DeBuff.Damage"),FString("负面效果 伤害"));GameplayTags.DeBuff_Duration = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("DeBuff.Duration"),FString("负面效果 持续时间"));GameplayTags.DeBuff_Frequency = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("DeBuff.Frequency"),FString("负面效果 触发间隔"));

创建负面效果使用的结构体

接下来,我们创建一个结构体,用于在给目标应用负面效果时使用,由于这个结构体的数据需要序列化以后,传输到服务器端进行处理,所以,我们将其设置到RPGAbilityTypes.h文件内,之前我们创建它是为了在代码内创建GE句柄时,能够使用我们自定义的结构体,增加了暴击和格挡的数据。
我们在里面创建一个结构体,用来配置应用一个负面效果时,所需要的所有数据

USTRUCT(BlueprintType)
struct FDamageEffectParams
{GENERATED_BODY()FDamageEffectParams(){}UPROPERTY()TObjectPtr<UObject> WorldContextObject = nullptr; //当前场景上下文对象UPROPERTY()TSubclassOf<UGameplayEffect> DamageGameplayEffectClass = nullptr; //需要应用的GE的类UPROPERTY()TObjectPtr<UAbilitySystemComponent> SourceAbilitySystemComponent; //源ASCUPROPERTY()TObjectPtr<UAbilitySystemComponent> TargetAbilitySystemComponent; //目标ASCUPROPERTY()TMap<FGameplayTag, float> DamageTypes; //技能造成的多种伤害和伤害类型UPROPERTY()float AbilityLevel = 1.f; //技能等级UPROPERTY()FGameplayTag DeBuffDamageType = FGameplayTag(); //负面效果伤害类型UPROPERTY()float DeBuffChance = 0.f; //触发负面效果概率UPROPERTY()float DeBuffDamage = 0.f; //负面效果伤害UPROPERTY()float DeBuffDuration = 0.f; //负面效果持续时间UPROPERTY()float DeBuffFrequency = 0.f; //负面效果触发频率
};

在伤害技能基础类增加一个创建配置项的函数

有了配置项的结构体,我们需要实现一个函数,在伤害技能的基类上,通过伤害技能的配置生成结构体

	//创建技能负面效果使用的结构体FDamageEffectParams MakeDamageEffectParamsFromClassDefaults(AActor* TargetActor = nullptr);

然后实现通过技能上的配置生成配置项

FDamageEffectParams URPGDamageGameplayAbility::MakeDamageEffectParamsFromClassDefaults(AActor* TargetActor)
{FDamageEffectParams Params;Params.WorldContextObject = GetAvatarActorFromActorInfo();Params.DamageGameplayEffectClass = DamageEffectClass;Params.SourceAbilitySystemComponent = GetAbilitySystemComponentFromActorInfo();Params.TargetAbilitySystemComponent = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetActor);for(auto& Pair : DamageTypes){const float ScaledDamage = Pair.Value.GetValueAtLevel(GetAbilityLevel()); //根据等级获取技能伤害Params.DamageTypes.Add(Pair.Key, ScaledDamage);}Params.AbilityLevel = GetAbilityLevel();Params.DeBuffDamageType = DeBuffDamageType;Params.DeBuffChance = DeBuffChance;Params.DeBuffDamage = DeBuffDamage;Params.DeBuffDuration = DeBuffDuration;Params.DeBuffFrequency = DeBuffFrequency;return Params;
}

在函数库添加一个通过配置项实现GE的应用

我们有了配置项,可以将应用设置为通用的函数,所以我们在函数库里增加一个静态函数,只要结构体配置完全,我们可以直接调用此函数完成内部逻辑
我们添加一个函数,传入参数就是配置项

	//通过技能生成的负面配置项应用技能负面效果UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")static FGameplayEffectContextHandle ApplyDamageEffect(const FDamageEffectParams& DamageEffectParams);

然后实现此函数,在函数内存创建GE的上下文和实例,并通过标签的SetByCaller设置GE所使用的值,最后应用到目标ASC上,并返回GE的上下文句柄。
注意,这里我们设置伤害的标签是属性伤害类型标签,在应用时,我们就可以负面效果类型获取当前GE是否设置了对应的负面效果

FGameplayEffectContextHandle URPGAbilitySystemBlueprintLibrary::ApplyDamageEffect(const FDamageEffectParams& DamageEffectParams)
{const FRPGGameplayTags& GameplayTags = FRPGGameplayTags::Get();const AActor* SourceAvatarActor = DamageEffectParams.SourceAbilitySystemComponent->GetAvatarActor();//创建GE的上下文句柄FGameplayEffectContextHandle EffectContextHandle = DamageEffectParams.SourceAbilitySystemComponent->MakeEffectContext();EffectContextHandle.AddSourceObject(SourceAvatarActor);//根据句柄和类创建GE实例const FGameplayEffectSpecHandle SpecHandle = DamageEffectParams.SourceAbilitySystemComponent->MakeOutgoingSpec(DamageEffectParams.DamageGameplayEffectClass, DamageEffectParams.AbilityLevel, EffectContextHandle);//通过标签设置GE使用的配置for(auto& Pair : DamageEffectParams.DamageTypes){UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, Pair.Key, Pair.Value);}UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, GameplayTags.DeBuff_Chance, DamageEffectParams.DeBuffChance);UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, DamageEffectParams.DeBuffDamageType, DamageEffectParams.DeBuffDamage);UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, GameplayTags.DeBuff_Duration, DamageEffectParams.DeBuffDuration);UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, GameplayTags.DeBuff_Frequency, DamageEffectParams.DeBuffFrequency);//将GE应用给目标ASCDamageEffectParams.TargetAbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());return EffectContextHandle;
}

修改生成发射物的函数

我们在ProjectileSpell.cpp文件里,由于修改了增加的负面效果增加的部分,所以,我们需要对发射物生成进行修改,不再创建发射物时创建GE,而是修改为生成一个配置结构体,供后续使用。

void UProjectileSpell::SpawnProjectile(const FVector& ProjectileTargetLocation, const FGameplayTag& SocketTag, const FName SocketName, const bool bOverridePitch, const float PitchOverride)
{const bool bIsServer = GetAvatarActorFromActorInfo()->HasAuthority(); //判断此函数是否在服务器运行if (!bIsServer) return;if (GetAvatarActorFromActorInfo()->Implements<UCombatInterface>()){const FVector SocketLocation = ICombatInterface::Execute_GetCombatSocketLocationByTag(GetAvatarActorFromActorInfo(), SocketTag, SocketName);FRotator Rotation = (ProjectileTargetLocation - SocketLocation).Rotation(); //将方向转为旋转if(bOverridePitch){Rotation.Pitch = PitchOverride; //覆写发射角度}FTransform SpawnTransform;SpawnTransform.SetLocation(SocketLocation);SpawnTransform.SetRotation(Rotation.Quaternion());//SpawnActorDeferred将异步创建实例,在实例创建完成时,相应的数据已经应用到了实例身上AProjectile* Projectile = GetWorld()->SpawnActorDeferred<AProjectile>(ProjectileClass,SpawnTransform,GetOwningActorFromActorInfo(),Cast<APawn>(GetAvatarActorFromActorInfo()),ESpawnActorCollisionHandlingMethod::AlwaysSpawn);Projectile->DamageEffectParams = MakeDamageEffectParamsFromClassDefaults();//确保变换设置被正确应用Projectile->FinishSpawning(SpawnTransform);}
}

在AbilityTypes.h里增加参数

在之前的文章里 51. UE5 RPG 自定义FGameplayEffectContext,我们实现自定义GE上下文的属性,增加了格挡和暴击两个属性是否触发,并实现了对其的序列化,可以在客户端将内容复制到服务器端,因为属性的处理都是在服务器端进行的,这里有个交互的过程,需要序列化。
我们给GE上下文增加多个属性,这样,生成的实例里可以设置这些属性
注意,这里的标签没有设置反射,需要我们自己实现类型,在序列化时也有所体现。

protected:UPROPERTY()bool bIsBlockedHit = false; //格挡UPROPERTY()bool bIsCriticalHit = false; //暴击UPROPERTY()bool bIsSuccessfulDeBuff = false; //成功应用负面效果UPROPERTY()float DeBuffDamage = 0.f; //负面效果每次造成的伤害UPROPERTY()float DeBuffDuration = 0.f; //负面效果持续时间UPROPERTY()float DeBuffFrequency = 0.f; //负面效果触发频率间隔TSharedPtr<FGameplayTag> DamageType; //负面效果的伤害类型

并增加其对应的设置方法

public:bool IsBlockedHit() const { return bIsBlockedHit; } //获取 格挡bool IsCriticalHit() const { return bIsCriticalHit; } //获取 暴击bool IsSuccessfulDeBuff() const { return bIsSuccessfulDeBuff; } //获取 应用负面效果float GetDeBuffDamage() const { return DeBuffDamage; } //获取 负面效果伤害float GetDeBuffDuration() const { return DeBuffDuration; } //获取 负面效果持续时间float GetDeBuffFrequency() const { return DeBuffFrequency; } //获取 负面效果伤害触发间隔TSharedPtr<FGameplayTag> GetDeBuffDamageType() const { return DamageType; } //获取 负面效果伤害类型void SetIsBlockedHit(const bool bInIsBlockedHit) { bIsBlockedHit = bInIsBlockedHit; } // 设置 格挡void SetIsCriticalHit(const bool bInIsCriticalHit) { bIsCriticalHit = bInIsCriticalHit; } // 设置 暴击void SetIsSuccessfulDeBuff(const bool bInIsSuccessfulDeBuff) { bIsSuccessfulDeBuff = bInIsSuccessfulDeBuff; } //设置 应用负面效果void SetDeBuffDamage(const float InDamage) { DeBuffDamage = InDamage; } //设置 负面效果伤害void SetDeBuffDuration(const float InDuration) { DeBuffDuration = InDuration; } //设置 负面效果伤害void SetDeBuffFrequency(const float InFrequency) { DeBuffFrequency = InFrequency; } //设置 负面效果伤害void SetDeBuffDamageType(const TSharedPtr<FGameplayTag>& InDamageType) { DamageType = InDamageType; } //设置 负面效果伤害类型

我们还要修改它的序列化的方法,让其能够复制到服务器端去处理。
首先是增加序列化内容

		//自定义内容,增加暴击和格挡触发存储if(bIsBlockedHit){RepBits |= 1 << 7;}if(bIsCriticalHit){RepBits |= 1 << 8;}if(bIsSuccessfulDeBuff){RepBits |= 1 << 9;}if(DeBuffDamage > 0.f){RepBits |= 1 << 10;}if(DeBuffDuration > 0.f){RepBits |= 1 << 11;}if(DeBuffFrequency > 0.f){RepBits |= 1 << 12;}if(DamageType.IsValid()){RepBits |= 1 << 13;}

接着就是设置序列内容的长度

	//使用了多少长度,就将长度设置为多少Ar.SerializeBits(&RepBits, 14);

接着就是在服务器端的反序列化

	//新增对暴击格挡的序列化或反序列化处理if (RepBits & (1 << 7)){Ar << bIsBlockedHit;}if (RepBits & (1 << 8)){Ar << bIsCriticalHit;}if (RepBits & (1 << 9)){Ar << bIsSuccessfulDeBuff;}if (RepBits & (1 << 10)){Ar << DeBuffDamage;}if (RepBits & (1 << 11)){Ar << DeBuffDuration;}if (RepBits & (1 << 12)){Ar << DeBuffFrequency;}if (RepBits & (1 << 13)){if (Ar.IsLoading()) //判断是否在加载资源{if (!DamageType.IsValid()){DamageType = TSharedPtr<FGameplayTag>(new FGameplayTag());}}DamageType->NetSerialize(Ar, Map, bOutSuccess);}

这就完成了对自定义属性的添加,并且能够实现服务器获取,在AttributeSet里对属性进行处理。

接着,我们在函数库里增加一些函数,可以直接调用函数库内的函数实现对属性设置和获取

	//获取当前GE是否成功应用负面效果UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayEffects")static bool IsSuccessfulDeBuff(const FGameplayEffectContextHandle& EffectContextHandle);//获取当前GE负面效果伤害UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayEffects")static float GetDeBuffDamage(const FGameplayEffectContextHandle& EffectContextHandle);//获取当前GE负面效果持续时间UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayEffects")static float GetDeBuffDuration(const FGameplayEffectContextHandle& EffectContextHandle);//获取当前GE负面效果触发间隔UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayEffects")static float GetDeBuffFrequency(const FGameplayEffectContextHandle& EffectContextHandle);//获取当前GE负面效果伤害类型UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayEffects")static FGameplayTag GetDeBuffDamageType(const FGameplayEffectContextHandle& EffectContextHandle);
bool URPGAbilitySystemBlueprintLibrary::IsSuccessfulDeBuff(const FGameplayEffectContextHandle& EffectContextHandle)
{if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get())){return RPGEffectContext->IsSuccessfulDeBuff();}return false;
}float URPGAbilitySystemBlueprintLibrary::GetDeBuffDamage(const FGameplayEffectContextHandle& EffectContextHandle)
{if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get())){return RPGEffectContext->GetDeBuffDamage();}return 0.f;
}float URPGAbilitySystemBlueprintLibrary::GetDeBuffDuration(const FGameplayEffectContextHandle& EffectContextHandle)
{if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get())){return RPGEffectContext->GetDeBuffDuration();}return 0.f;
}float URPGAbilitySystemBlueprintLibrary::GetDeBuffFrequency(const FGameplayEffectContextHandle& EffectContextHandle)
{if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get())){return RPGEffectContext->GetDeBuffFrequency();}return 0.f;
}FGameplayTag URPGAbilitySystemBlueprintLibrary::GetDeBuffDamageType(const FGameplayEffectContextHandle& EffectContextHandle)
{if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get())){//如果当前存在设置了伤害类型if(RPGEffectContext->GetDeBuffDamageType().IsValid()){//取消指针return *RPGEffectContext->GetDeBuffDamageType();}}return FGameplayTag();
}

在设置这里,因为是需要在同一地方使用,所以,我们直接将其合并为了一个函数设置

	//设置GE是否应用负面效果UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")static void SetIsSuccessfulDeBuff(UPARAM(ref) FGameplayEffectContextHandle& EffectContextHandle, bool bInIsSuccessfulDeBuff);//设置GE负面效果相关数值 负面效果伤害类型 负面效果伤害 负面效果持续时间 负面效果触发间隔时间UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")static void SetDeBuff(UPARAM(ref) FGameplayEffectContextHandle& EffectContextHandle, FGameplayTag& InDamageType, float InDamage, float InDuration, float InFrequency);
void URPGAbilitySystemBlueprintLibrary::SetIsSuccessfulDeBuff(FGameplayEffectContextHandle& EffectContextHandle, const bool bInIsSuccessfulDeBuff)
{FRPGGameplayEffectContext* RPGEffectContext = static_cast<FRPGGameplayEffectContext*>(EffectContextHandle.Get());RPGEffectContext->SetIsSuccessfulDeBuff(bInIsSuccessfulDeBuff);
}void URPGAbilitySystemBlueprintLibrary::SetDeBuff(FGameplayEffectContextHandle& EffectContextHandle, FGameplayTag& InDamageType, const float InDamage, const float InDuration, const float InFrequency)
{FRPGGameplayEffectContext* RPGEffectContext = static_cast<FRPGGameplayEffectContext*>(EffectContextHandle.Get());//通过标签创建一个共享指针const TSharedPtr<FGameplayTag> DamageType = MakeShared<FGameplayTag>(InDamageType);RPGEffectContext->SetDeBuffDamageType(DamageType);RPGEffectContext->SetDeBuffDamage(InDamage);RPGEffectContext->SetDeBuffDuration(InDuration);RPGEffectContext->SetDeBuffFrequency(InFrequency);
}

实现负面效果应用

实现了相应的函数后,我们需要接着实现负面效果的应用,我们在ExecCalc_Damage.cpp里,专门实现了对目标进行造成的最终伤害的计算,我们在内部实现负面效果的是否应用成功的计算,首先获取到负面效果命中率,然后通过目标抵抗降低命中率,接着通过随机数判断当前是否需要应用负面效果。

void UExecCalc_Damage::DetermineDeBuff(const FGameplayEffectCustomExecutionParameters& ExecutionParams, const FGameplayEffectSpec& Spec, const FAggregatorEvaluateParameters& EvaluationParameters, TMap<FGameplayTag, FGameplayEffectAttributeCaptureDefinition> TagsToCaptureDefs)
{const FRPGGameplayTags& GameplayTags = FRPGGameplayTags::Get();//遍历所有的负面效果伤害类型,根据伤害类型是否赋值来判断是否需要应用负面效果for(const TTuple<FGameplayTag, FGameplayTag>& Pair : GameplayTags.DeBuffsToResistance){FGameplayTag DeBuffDamageType = Pair.Key; //获取到负面效果伤害类型const FGameplayTag ResistanceType = Pair.Value; //获取到负面效果抵抗类型const float TypeDamage = Spec.GetSetByCallerMagnitude(DeBuffDamageType, false, -1.f);//如果负面效果设置了伤害,即使为0,也需要应用负面效果if(TypeDamage > -.5f){//获取负面效果命中率const float SourceDeBuffChance = Spec.GetSetByCallerMagnitude(GameplayTags.DeBuff_Chance, false, -1.f);//----------------获取负面效果抵抗------------float TargetDeBuffResistance = 0.f; //计算目标对收到的负面效果类型的抵抗//检查对应的属性快照是否设置,防止报错checkf(TagsToCaptureDefs.Contains(ResistanceType), TEXT("在ExecCalc_Damage中,无法获取到Tag[%s]对应的属性快照"), *ResistanceType.ToString());//通过抗性标签获取到属性快照的值const FGameplayEffectAttributeCaptureDefinition CaptureDef = TagsToCaptureDefs[ResistanceType];ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(CaptureDef, EvaluationParameters, TargetDeBuffResistance);TargetDeBuffResistance = FMath::Clamp(TargetDeBuffResistance, 0.f, 100.f); //将抗住限制在0到100//----------------计算负面效果是否应用------------const float EffectiveDeBuffChance = SourceDeBuffChance * (100 - TargetDeBuffResistance) / 100.f; //计算出负面效果的实际命中率const bool bDeBuff = FMath::RandRange(1, 100) < EffectiveDeBuffChance; //判断此次效果是否实现命中if(bDeBuff){//获取GE上下文设置负面效果相关配置FGameplayEffectContextHandle ContextHandle = Spec.GetContext();//设置当前应用负面效果成功URPGAbilitySystemBlueprintLibrary::SetIsSuccessfulDeBuff(ContextHandle, true);const float SourceDeBuffDuration = Spec.GetSetByCallerMagnitude(GameplayTags.DeBuff_Duration, false, 0.f);const float SourceDeBuffFrequency = Spec.GetSetByCallerMagnitude(GameplayTags.DeBuff_Frequency, false, 0.f);//设置负面效果 伤害类型 伤害 持续时间 触发频率URPGAbilitySystemBlueprintLibrary::SetDeBuff(ContextHandle, DeBuffDamageType, TypeDamage, SourceDeBuffDuration, SourceDeBuffFrequency);}}}
}

将一部分代码转换为函数

有个小技巧,我们还可以将其内容提取为单独函数
在这里插入图片描述
设置命名和参数
在这里插入图片描述
可以去除不需要的参数。
在这里插入图片描述

整理AttributeSet内的代码

代码写的越来越多,会变成一坨,所以我们需要将代码分离成函数,这样方便后期的维护。
我们在自定义的AttributeSet里增加三个函数,分别用于处理接收实际受到的伤害属性,获取到的经验值,以及需要处理应用负面效果的函数。

	//处理传入的参数为伤害属性时,处理的逻辑void HandleIncomingDamage(const FEffectProperties& Props);//处理传入的参数为经验属性时,处理的逻辑void HandleIncomingXP(const FEffectProperties& Props);//如果当前伤害触发了负面效果,处理的逻辑void HandleDeBuff(const FEffectProperties& Props);

有了这几个函数,我们的代码就清晰了很多,比如接收到属性的函数代码,这里防止敌人在死亡后还会受到伤害,我们在进行属性处理前判断角色是否处于死亡状态。

void URPGAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{Super::PostGameplayEffectExecute(Data);FEffectProperties Props;SetEffectProperties(Data, Props);//判断当前目标是否已经死亡,如果死亡,将不再进行处理if(Props.TargetCharacter->Implements<UCombatInterface>() && ICombatInterface::Execute_IsDead(Props.TargetCharacter)) return;if(Data.EvaluatedData.Attribute == GetHealthAttribute()){SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));// UE_LOG(LogTemp, Warning, TEXT("%s 的生命值发生了修改,当前生命值:%f"), *Props.TargetAvatarActor->GetName(), GetHealth());}if(Data.EvaluatedData.Attribute == GetManaAttribute()){SetMana(FMath::Clamp(GetMana(), 0.f, GetMaxMana()));}if(Data.EvaluatedData.Attribute == GetIncomingDamageAttribute()){HandleIncomingDamage(Props);}if(Data.EvaluatedData.Attribute == GetIncomingXPAttribute()){HandleIncomingXP(Props);}}

由于它们都只需要在Props里拿值进行运算,我们只需要一个Props属性即可。

void URPGAttributeSet::HandleIncomingXP(const FEffectProperties& Props)
{const float LocalIncomingXP = GetIncomingXP();SetIncomingXP(0);// UE_LOG(LogRPG, Log, TEXT("获取传入经验值:%f"), LocalIncomingXP);if(Props.SourceCharacter->Implements<UPlayerInterface>() && Props.SourceCharacter->Implements<UCombatInterface>()){//获取角色当前等级和经验const int32 CurrentLevel = ICombatInterface::Execute_GetPlayerLevel(Props.SourceCharacter);const int32 CurrentXP = IPlayerInterface::Execute_GetXP(Props.SourceCharacter);//获取获得经验后的新等级const int32 NewLevel = IPlayerInterface::Execute_FindLevelForXP(Props.SourceCharacter, CurrentXP + LocalIncomingXP);const int32 NumLevelUps = NewLevel - CurrentLevel; //查看等级是否有变化if(NumLevelUps > 0){//如果连升多级,我们通过for循环获取每个等级的奖励for(int32 i = CurrentLevel; i < NewLevel; i++){//获取升级提供的技能点和属性点const int32 AttributePointsReward = IPlayerInterface::Execute_GetAttributePointsReward(Props.SourceCharacter, i);const int32 SpellPointsReward = IPlayerInterface::Execute_GetSpellPointsReward(Props.SourceCharacter, i);//增加角色技能点和属性点IPlayerInterface::Execute_AddToAttributePoints(Props.SourceCharacter, AttributePointsReward);IPlayerInterface::Execute_AddToSpellPoints(Props.SourceCharacter, SpellPointsReward);}//提升等级IPlayerInterface::Execute_AddToPlayerLevel(Props.SourceCharacter, NumLevelUps);//播放升级效果IPlayerInterface::Execute_LevelUp(Props.SourceCharacter);//将血量和蓝量填充满, 我们将设置变量SetHealth(GetMaxHealth());SetMana(GetMaxMana());}//将经验应用给自身,通过事件传递,在玩家角色被动技能GA_ListenForEvents里接收IPlayerInterface::Execute_AddToXP(Props.SourceCharacter, LocalIncomingXP);}
}

在接收伤害的函数里,我们增加对负面效果应用的判断,如果当前应用的负面效果,我们将调用负面效果函数处理逻辑。

void URPGAttributeSet::HandleIncomingDamage(const FEffectProperties& Props)
{const float LocalIncomingDamage = GetIncomingDamage();SetIncomingDamage(0.f);if(LocalIncomingDamage > 0.f){const float NewHealth = GetHealth() - LocalIncomingDamage;SetHealth(FMath::Clamp(NewHealth, 0.f, GetMaxHealth()));const bool bFatal = NewHealth <= 0.f; //血量小于等于0时,角色将会死亡if(bFatal){//调用死亡函数ICombatInterface* CombatInterface = Cast<ICombatInterface>(Props.TargetAvatarActor);if(CombatInterface){CombatInterface->Die();}//死亡时,发送经验事件SendXPEvent(Props);}else{//激活受击技能FGameplayTagContainer TagContainer;TagContainer.AddTag(FRPGGameplayTags::Get().Effects_HitReact);// Props.TargetASC->CancelAbilities(&TagContainer); //先取消之前的受击Props.TargetASC->TryActivateAbilitiesByTag(TagContainer); //根据tag标签激活技能}//获取格挡和暴击const bool IsBlockedHit = URPGAbilitySystemBlueprintLibrary::IsBlockedHit(Props.EffectContextHandle);const bool IsCriticalHit = URPGAbilitySystemBlueprintLibrary::IsCriticalHit(Props.EffectContextHandle);//显示伤害数字ShowFloatingText(Props, LocalIncomingDamage, IsBlockedHit, IsCriticalHit);//判断当前是否应用负面效果if(URPGAbilitySystemBlueprintLibrary::IsSuccessfulDeBuff(Props.EffectContextHandle)){HandleDeBuff(Props);}}
}

通过代码创建GameplayEffect类

接下来,我们要实现设置GE,并将其应用到角色身上,这里,采用c++编写的方式实现,我会将代码和在UE里编辑的配置进行比对,方便大家更清晰的熟悉每个配置项。
我们将在之前添加的HandleDeBuff函数中增加处理,这个函数将在可以对目标应用负面效果时调用。
我们首先通过库函数获取到创建GE类的相关参数

	//获取负面效果相关参数const FGameplayTag DeBuffType = URPGAbilitySystemBlueprintLibrary::GetDeBuffDamageType(Props.EffectContextHandle);const float DeBuffDamage = URPGAbilitySystemBlueprintLibrary::GetDeBuffDamage(Props.EffectContextHandle);const float DeBuffDuration = URPGAbilitySystemBlueprintLibrary::GetDeBuffDuration(Props.EffectContextHandle);const float DeBuffFrequency = URPGAbilitySystemBlueprintLibrary::GetDeBuffFrequency(Props.EffectContextHandle);

然后我们设置一个名称并创建一个GE类

	//创建GE所使用的名称,并创建一个可实例化的GEFString DeBuffName = FString::Printf(TEXT("DynamicDeBuff_%s"), *DeBuffType.ToString());UGameplayEffect* Effect = NewObject<UGameplayEffect>(GetTransientPackage(), FName(DeBuffName));

接下来就是修改GE类的配置项,它们将在我们应用GE实例时,产生效果。
首先我们设置GE为有时间限制的,并设置对应时间

	//设置动态创建GE的属性Effect->DurationPolicy = EGameplayEffectDurationType::HasDuration; //设置GE为有时间限制的效果Effect->DurationMagnitude = FScalableFloat(DeBuffDuration); //设置GE的持续时间

在这里插入图片描述
然后就是设置Period

	Effect->Period = FScalableFloat(DeBuffFrequency); //设置GE的触发策略,间隔时间Effect->bExecutePeriodicEffectOnApplication = false; //在应用后不会立即触发,而是在经过了Period后才会触发Effect->PeriodicInhibitionPolicy = EGameplayEffectPeriodInhibitionRemovedPolicy::NeverReset; //设置每次应用后不会重置触发时间

在这里插入图片描述
接着就是叠加层数相关设置,这里和我们所需的不同,但是为了显示所有设置,我全添加了

	//设置可叠加层数Effect->StackingType = EGameplayEffectStackingType::AggregateBySource; //设置GE应用基于释放者查看Effect->StackLimitCount = 1; //设置叠加层数Effect->StackDurationRefreshPolicy = EGameplayEffectStackingDurationPolicy::RefreshOnSuccessfulApplication; //在应用后重置时,重置持续时间Effect->StackPeriodResetPolicy = EGameplayEffectStackingPeriodPolicy::ResetOnSuccessfulApplication; //在应用时,触发并重置Period时间Effect->StackExpirationPolicy = EGameplayEffectStackingExpirationPolicy::ClearEntireStack; //GE时间到了默认清除所有层数,还有可以清除单层的设置//Effect->OverflowEffects.Add() //在叠加层数超出时,将触发此数组内的GE应用到角色Effect->bDenyOverflowApplication = true; //设置为true时,叠加层数超出时,将不会刷新GE实例Effect->bClearStackOnOverflow = true; //设置为true时,叠加层数超出时,将清除GE

在这里插入图片描述
在5.3版本修改为了通过GEComponent来设置Actor身上的标签,在老版是可以直接通过InheritableOwnedTagsContainer获取容器去修改

	//在5.3版本修改为通过GEComponent来设置GE应用的标签,向目标Actor增加对应的标签UTargetTagsGameplayEffectComponent& TargetTagsGameplayEffectComponent = Effect->AddComponent<UTargetTagsGameplayEffectComponent>();FInheritedTagContainer InheritableOwnedTagsContainer = TargetTagsGameplayEffectComponent.GetConfiguredTargetTagChanges(); //获取到标签容器InheritableOwnedTagsContainer.AddTag(DeBuffType); //添加标签TargetTagsGameplayEffectComponent.SetAndApplyTargetTagChanges(InheritableOwnedTagsContainer); //应用并更新

在这里插入图片描述
接着就是设置属性的修改

	//设置属性修改const int32 Index = Effect->Modifiers.Num(); //获取当前修改属性的Modifiers的长度,也就是下一个添加的modify的下标索引Effect->Modifiers.Add(FGameplayModifierInfo()); //添加一个新的ModifyFGameplayModifierInfo& ModifierInfo = Effect->Modifiers[Index]; //通过下标索引获取ModifyModifierInfo.ModifierMagnitude = FScalableFloat(DeBuffDamage); //设置应用的属性值ModifierInfo.ModifierOp = EGameplayModOp::Additive; //设置属性运算符号ModifierInfo.Attribute = URPGAttributeSet::GetIncomingDamageAttribute(); //设置修改的属性

在这里插入图片描述
然后就通过GE创建GE实例,并应用到目标身上

	//创建GE实例,并添加伤害类型标签,应用GEFGameplayEffectContextHandle EffectContextHandle = Props.SourceASC->MakeEffectContext();EffectContextHandle.AddSourceObject(Props.SourceCharacter);if(const FGameplayEffectSpec* MutableSpec = new FGameplayEffectSpec(Effect, EffectContextHandle, 1.f)){FRPGGameplayEffectContext* RPGContext = static_cast<FRPGGameplayEffectContext*>(MutableSpec->GetContext().Get());const TSharedPtr<FGameplayTag> DeBuffDamageType = MakeShareable(new FGameplayTag(DeBuffType));RPGContext->SetDeBuffDamageType(DeBuffDamageType);Props.TargetASC->ApplyGameplayEffectSpecToSelf(*MutableSpec);}

最后,粘贴一下完整代码

void URPGAttributeSet::HandleDeBuff(const FEffectProperties& Props)
{//获取负面效果相关参数const FGameplayTag DeBuffType = URPGAbilitySystemBlueprintLibrary::GetDeBuffDamageType(Props.EffectContextHandle);const float DeBuffDamage = URPGAbilitySystemBlueprintLibrary::GetDeBuffDamage(Props.EffectContextHandle);const float DeBuffDuration = URPGAbilitySystemBlueprintLibrary::GetDeBuffDuration(Props.EffectContextHandle);const float DeBuffFrequency = URPGAbilitySystemBlueprintLibrary::GetDeBuffFrequency(Props.EffectContextHandle);//创建GE所使用的名称,并创建一个可实例化的GEFString DeBuffName = FString::Printf(TEXT("DynamicDeBuff_%s"), *DeBuffType.ToString());UGameplayEffect* Effect = NewObject<UGameplayEffect>(GetTransientPackage(), FName(DeBuffName));//设置动态创建GE的属性Effect->DurationPolicy = EGameplayEffectDurationType::HasDuration; //设置GE为有时间限制的效果Effect->DurationMagnitude = FScalableFloat(DeBuffDuration); //设置GE的持续时间Effect->Period = FScalableFloat(DeBuffFrequency); //设置GE的触发策略,间隔时间Effect->bExecutePeriodicEffectOnApplication = false; //在应用后不会立即触发,而是在经过了Period后才会触发Effect->PeriodicInhibitionPolicy = EGameplayEffectPeriodInhibitionRemovedPolicy::NeverReset; //设置每次应用后不会重置触发时间//设置可叠加层数Effect->StackingType = EGameplayEffectStackingType::AggregateBySource; //设置GE应用基于释放者查看Effect->StackLimitCount = 1; //设置叠加层数Effect->StackDurationRefreshPolicy = EGameplayEffectStackingDurationPolicy::RefreshOnSuccessfulApplication; //在应用后重置时,重置持续时间Effect->StackPeriodResetPolicy = EGameplayEffectStackingPeriodPolicy::ResetOnSuccessfulApplication; //在应用时,触发并重置Period时间Effect->StackExpirationPolicy = EGameplayEffectStackingExpirationPolicy::ClearEntireStack; //GE时间到了默认清除所有层数,还有可以清除单层的设置//Effect->OverflowEffects.Add() //在叠加层数超出时,将触发此数组内的GE应用到角色// Effect->bDenyOverflowApplication = true; //设置为true时,叠加层数超出时,将不会刷新GE实例// Effect->bClearStackOnOverflow = true; //设置为true时,叠加层数超出时,将清除GE//在5.3版本修改为通过GEComponent来设置GE应用的标签,向目标Actor增加对应的标签UTargetTagsGameplayEffectComponent& TargetTagsGameplayEffectComponent = Effect->AddComponent<UTargetTagsGameplayEffectComponent>();FInheritedTagContainer InheritableOwnedTagsContainer = TargetTagsGameplayEffectComponent.GetConfiguredTargetTagChanges(); //获取到标签容器InheritableOwnedTagsContainer.AddTag(DeBuffType); //添加标签TargetTagsGameplayEffectComponent.SetAndApplyTargetTagChanges(InheritableOwnedTagsContainer); //应用并更新//设置属性修改const int32 Index = Effect->Modifiers.Num(); //获取当前修改属性的Modifiers的长度,也就是下一个添加的modify的下标索引Effect->Modifiers.Add(FGameplayModifierInfo()); //添加一个新的ModifyFGameplayModifierInfo& ModifierInfo = Effect->Modifiers[Index]; //通过下标索引获取ModifyModifierInfo.ModifierMagnitude = FScalableFloat(DeBuffDamage); //设置应用的属性值ModifierInfo.ModifierOp = EGameplayModOp::Additive; //设置属性运算符号ModifierInfo.Attribute = URPGAttributeSet::GetIncomingDamageAttribute(); //设置修改的属性//创建GE实例,并添加伤害类型标签,应用GEFGameplayEffectContextHandle EffectContextHandle = Props.SourceASC->MakeEffectContext();EffectContextHandle.AddSourceObject(Props.SourceCharacter);if(const FGameplayEffectSpec* MutableSpec = new FGameplayEffectSpec(Effect, EffectContextHandle, 1.f)){FRPGGameplayEffectContext* RPGContext = static_cast<FRPGGameplayEffectContext*>(MutableSpec->GetContext().Get());const TSharedPtr<FGameplayTag> DeBuffDamageType = MakeShareable(new FGameplayTag(DeBuffType));RPGContext->SetDeBuffDamageType(DeBuffDamageType);Props.TargetASC->ApplyGameplayEffectSpecToSelf(*MutableSpec);}}

我们就可以去项目中测试是否有bug。


http://www.ppmy.cn/news/1519754.html

相关文章

[知识分享]华为铁三角工作法

在通信技术领域&#xff0c;尤其是无线通信和物联网领域&#xff0c;“华为铁三角”是华为公司内部的一种销售、交付和服务一体化的运作模式。这种模式强调的是以客户为中心&#xff0c;通过市场、销售、交付和服务三个关键环节的紧密协作&#xff0c;快速响应客户需求&#xf…

upload-labs通关攻略

Pass-1 这里上传php文件说不允许上传 然后咱们开启抓包将png文件改为php文件 放包回去成功上传 Pass-2 进来查看提示说对mime进行检查 抓包把这里改为image/jpg; 放包回去就上传成功了 Pass-3 这里上传php文件它说不允许上传这些后缀的文件 那咱们就可以改它的后缀名来绕过…

HarmonyOS开发实战( Beta5版)AOT编译使用指南

AOT编译使用指南 AOT(Ahead Of Time)即预先编译&#xff0c;在程序运行前&#xff0c;预先编译成高性能机器码&#xff0c;让程序在首次运行就能通过执行高性能机器码获得性能收益 方舟AOT编译器实现了PGO (Profile-Guided-Optimization&#xff09;编译优化&#xff0c;即通过…

Spring Boot 中 `@Transactional` 注解使用示例

Transactional 注解在 Spring Boot 中用于管理事务。它确保在方法执行过程中&#xff0c;所有数据库操作要么全部成功&#xff0c;要么全部回滚&#xff0c;以维护数据的一致性。下面是一些使用 Transactional 的示例&#xff1a; 1. 在服务层使用 Transactional Service pub…

【React】Redux-toolkit 处理异步操作

安装 npm install reduxjs/toolkit react-redux创建 store src\store\index.js import { configureStore } from reduxjs/toolkit; import homeReducer from ./modules/home;const store configureStore({reducer: {home: homeReducer,}, });export default store;创建 Red…

UE4 BuildCookRun中的Archive的含义

在UE4中&#xff0c;Archive、Cook、Stage、Package、Build的次序是怎么样的&#xff1f; 整体打包过程如下: Build -> Cook-> Stage -> Package -> Archive。其中&#xff0c;Archive 的含义是从Staged目录中拷贝文件到一个额外的目录即Archive目录。被称为“归档…

四十五、【人工智能】【机器学习】- Robust Regression(稳健回归)

系列文章目录 第一章 【机器学习】初识机器学习 第二章 【机器学习】【监督学习】- 逻辑回归算法 (Logistic Regression) 第三章 【机器学习】【监督学习】- 支持向量机 (SVM) 第四章【机器学习】【监督学习】- K-近邻算法 (K-NN) 第五章【机器学习】【监督学习】- 决策树…

【Android】repositories和sourceSets指定了 `libs` 目录的区别

repositories { flatDir { dirs libs } } 这段代码的作用是告诉 Gradle 在指定的目录&#xff08;这里是 libs 目录&#xff09;中查找 JAR 文件或 AAR 文件。flatDir 是一种简单的文件目录结构&#xff0c;它不会解析子目录&#xff0c;只会查找指定目录中的文件。 reposito…

Arduino 串口打印小知识点

String str[]{"abc","defg","hijk","lm","n"}; int num; void setup() {Serial.begin(115200);numsizeof(str) /sizeof(str[2]);Serial.print("该数组 str[]的长度&#xff1a;");Serial.print(num); }void loop(…

Python编码系列—Python中的HTTPS与加密技术:构建安全的网络通信

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

p2p、分布式,区块链笔记:基于IPFS实现的数据库orbitdb笔记

orbitdb orbitdb &#xff1a;Peer-to-Peer Databases for the Decentralized Web 特性说明特点无服务器、分布式、p2p编程语言JavaScript对其他语言的支持A python client for the Orbitdb HTTP API&#xff0c;go-orbit-db&#xff0c; 让我们了解一下谁在使用 js-ipfs&…

jmeter 响应乱码

Jmeter在做接口测试的时候的&#xff0c;如果接口响应的内容中有中文&#xff0c;jmeter的响应内容很可能显示乱码&#xff0c;为了规避这种出现乱码的问题&#xff0c;就要对jmeter的响应结果进行编码处理。 打开jmeter进行接口、压力、性能等测试&#xff0c;出现以下乱码问…

4. MyBatis如何与Spring集成?有哪些常见的配置方式?

MyBatis 可以通过多种方式与 Spring 集成&#xff0c;通常通过配置 Spring 来管理 MyBatis 的 SqlSessionFactory 和 Mapper&#xff0c;并使用 Spring 的事务管理功能来管理数据库事务。以下是 MyBatis 与 Spring 集成的常见配置方式&#xff1a; 1. 基于 XML 配置的集成 这是…

交叉编译 gmp

文章目录 交叉编译 gmp1 概述2 源码下载2.1 官网下载2.2 使用 apt source 下载 3 交叉编译4 关于 DESTDIR 的说明 交叉编译 gmp 1 概述 GMP (GNU Multiple Precision Arithmetic Library) 是一个用于任意精度计算设计的数学库&#xff0c;它的主要目标应用是密码学应用和研究…

PHP:构建高效动态网页的基石

PHP:构建高效动态网页的基石 在当今的互联网世界中,PHP作为一种服务器端脚本语言,依然占据着不可替代的地位。自1995年诞生以来,PHP凭借其简洁的语法、丰富的功能和广泛的数据库支持,成为了开发动态网页和Web应用的首选语言之一。本文将深入探讨PHP的核心优势、最新发展动…

内存管理篇-20 Linux虚拟内存管理

1.虚拟地址的经典布局 这里的内容比较少。只要就是内核用户空间的划分。内核空间又有自己的划分。也需要注意一下每个区域的性能。理论上线性映射是最简单的&#xff0c;所以性能最高。同时&#xff0c;注意内核空间是可以配置的&#xff0c;并不是都3:1。 2.ARM32下的内存…

android 离线的方式使用下载到本地的gradle

1、android studio在下载gradle的时候&#xff0c;特别慢&#xff0c;有的时候会下载不完的情况&#xff0c;这样我们就要离线使用了。 2、下载Gradle Gradle | Releases 或者 Releases gradle/gradle GitHub Gradle | Releases 这里我们下载8.10 complete版本&#xff0c…

数据库(MySQL)的基本操作

1.简介 &#xff08;1&#xff09;数据库 1.数据库&#xff08;Data Base&#xff0c;简称DB&#xff09;&#xff1a;长期保存在计算机的存储设备上&#xff0c;数据是按照一定的规则组织起来的&#xff0c;能被用户、应用平台共享的数据集合。&#xff08;存储、维护和管理…

C++使用日志库经验总结

1、log4cpp日志源文件路径设置 在 Visual Studio 中&#xff0c;C 项目的日志格式可以通过设置项目的属性来调整。如果你想要使用完整路径来显示诊断消息&#xff0c;可以在项目属性中的“C/C”选项卡下的“高级”属性页中找到“使用完整路径”&#xff08;/FC&#xff09;选项…

【设计模式】详细解释工厂模式和策略模式的区别,并给出形象生动的示例说明

工厂模式&#xff08;Factory Pattern&#xff09;和策略模式&#xff08;Strategy Pattern&#xff09;都是设计模式中的重要概念&#xff0c;它们用于解决不同的设计问题。下面我会详细解释它们的区别&#xff0c;并给出示例。 工厂模式 定义&#xff1a; 工厂模式是一种创…