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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

地平线SuperDrive首秀:千人研发投入,出场即「比肩第一梯队」

作者 |德新 编辑 |王博 8月底&#xff0c;地平线在北京开放了第一批面向媒体的高阶智驾方案SuperDrive体验。 预计到明年第三季度&#xff0c;SuperDrive将伴随主机厂客户的第一款量产车交付。 目前在国内&#xff0c;仅有英伟达和华为两家的平台基础上&#xff0c;有车企向…

webm转换mp4怎么转?分享6种简单好用的转换方法

在日常的视频处理中&#xff0c;将WebM视频转换为MP4格式是一个常见的需求。无论是为了兼容性、分享还是编辑&#xff0c;MP4格式都因其广泛的支持和良好的性能而备受欢迎。本文将为大家介绍6种高效方法&#xff0c;有需要的小伙伴快来学习下吧。 方法一&#xff1a;口袋视频转…

暴力数据结构之优先级队列的解析及其模拟实现(C++)

1.认识优先级队列 如果我们给每个元素都分配一个数字来标记其优先级&#xff0c;不妨设较小的数字具有较高的优先级&#xff0c;这样我们就可以在一个集合中访问优先级最高的元素并对其进行查找和删除操作了。 优先级队列&#xff08;priority queue&#xff09; 是0个或多个元…

Spring-容器:IOC-基于注解管理Bean

目录 一、基于注解管理Bean&#xff08;重点&#xff09;1.1、概述1.2、开启组件扫描1.2.1、指定要排除的组件1.2.2、仅扫描指定组件 1.3、使用注解定义Bean1.4、使用Autowired注入1.4.1、属性注入1.4.2、set注入1.4.3、构造方法注入1.4.4、形参注入1.4.5、无注解注入1.4.6、联…

第十周:机器学习笔记

第十周机器学习周报 摘要Abstract机器学习——self-attention&#xff08;注意力机制&#xff09;1. 为什么要用self-attention2. self-attention 工作原理2.1 求α的两种方式2.2 attention-score&#xff08;关联程度&#xff09; Pytorch学习1. 损失函数代码实战1.1 L1loss&a…

传统CV算法——边缘算子与图像金字塔算法介绍

边缘算子 图像梯度算子 - Sobel Sobel算子是一种用于边缘检测的图像梯度算子&#xff0c;它通过计算图像亮度的空间梯度来突出显示图像中的边缘。Sobel算子主要识别图像中亮度变化快的区域&#xff0c;这些区域通常对应于边缘。它是通过对图像进行水平和垂直方向的差分运算来…

Robotics: computational motion planning 部分笔记—— week 1 graph-based

grassfire algorithm 四周扩散性&#xff1b;从终点开始按照相邻最小距离格子移动 Dijkstra’s Algorithm 标明从起点开始的所有点的最短距离&#xff08;从上一节点继承&#xff09;&#xff0c;直到终点 A* Algorithm 带有启发性的&#xff0c;给出距离估计&#xff0c…

小杨的H字矩阵小杨的日字矩阵 c++

小杨的H字矩阵 题目描述 小杨想要构造一个NxN的H字矩阵(N为奇数)&#xff0c;具体来说&#xff0c;这个矩阵共有N行&#xff0c;每行N个字符&#xff0c;其中最左列、最右列都是 | &#xff08;键盘右侧删除键下回车键上&#xff0c;shift\&#xff09;&#xff0c;而中间一行…

国内领先线上运动平台:如何借助AI技术实现业务腾飞与用户体验升级

“ 从智能训练到身体分析&#xff0c;再到辅助判决&#xff0c;AI技术正以惊人的速度渗透进体育和健身领域&#xff0c;为运动员和健身爱好者提供了前所未有的个性化体验。 ” AI&#xff0c;运动的智能伴侣 在巴黎奥运会上&#xff0c;AI技术的运用成为了焦点。它不仅为运动…

Java并发编程实战 03 | Java线程状态

在本文中&#xff0c;我们将深入探讨 Java 线程的六种状态以及它们之间的转换过程。其实线程状态之间的转换就如同生物生命从诞生、成长到最终死亡的过程一样。也是一个完整的生命周期。 首先我们来看看操作系统中线程的生命周期是如何转换的。 操作系统中的线程状态转换 线…

STM32F4按键状态机--单击、双击、长按

STM32F4按键状态机--单击、双击、长按 一、状态机的三要素二、使用状态机原因2.1资源占用方面2.2 执行效率方面&#xff1a;2.3 按键抖动方面&#xff1a; 三、状态机实现3.1 状态机分析3.1 程序实现 百度解析的状态机概念如下 状态机由状态寄存器和组合逻辑电路构成&#xff0…

深度学习 --- VGG16能让某个指定的feature map激活值最大化图片的可视化(JupyterNotebook实战)

VGG16能让某个指定的feature map激活值最大化图片的可视化 在前面的文章中&#xff0c;我用jupyter notebook分别实现了&#xff0c;预训练好的VGG16模型各层filter权重的可视化和给VGG16输入了一张图像&#xff0c;可视化VGG16各层的feature map。深度学习 --- VGG16卷积核的可…

Python 优雅编程:会报恩的代码(五)

文章目录 引言从文本搜索指定单词&#xff0c;不区分单词的大小写使用 str.lower()使用 re 模块 从文本搜索多个单词&#xff0c;依旧不区分单词的大小写使用 str.lower() 和循环使用 re 模块 反复执行 re.compile&#xff0c;re 是否会缓存编译结果&#xff1f;结语 引言 在 …

day47——面向对象特征之继承

一、继承&#xff08;inhert&#xff09; 面向对象三大特征&#xff1a;封装、继承、多态 继承&#xff1a;所谓继承&#xff0c;是类与类之间的关系。就是基于一个已有的类&#xff0c;来创建出一个新类的过程叫做继承。主要提高代码的复用性。 1.1 继承的作用 1> 实现…

【一嗨租车-注册安全分析报告-滑动验证加载不正常导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

UE4_后期处理_后期处理材质及后期处理体积三—遮挡物体描边显示

一、效果&#xff1a; 在很多游戏中为了玩家能看到墙面背后是否有敌人&#xff0c;会给被遮挡的敌人增加描边显示&#xff0c;效果如下&#xff1a; 参考&#xff1a; https://zhuanlan.zhihu.com/p/81310476 https://zhuanlan.zhihu.com/p/358140547 二、所需知识 知识点…

Java笔试面试题AI答之JDBC(3)

文章目录 13. 编写JDBC连Oracle的程序?14. 简述JDBC的主要组件有哪些 &#xff1f;15. JDBC中如何防止SQL注入攻击&#xff1f;1. 使用预处理语句&#xff08;PreparedStatement&#xff09;2. 避免在SQL查询中直接拼接用户输入的数据总结 16. JDBC的脏读是什么&#xff1f;哪…

Spring01——Spring简介、Spring Framework架构、Spring核心概念、IOC入门案例、DI入门案例

为什么要学 spring技术是JavaEE开发必备技能&#xff0c;企业开发技术选型命中率>90%专业角度 简化开发&#xff1a;降低企业开发的复杂度框架整合&#xff1a;高效整合其他技术&#xff0c;提高开发与运行效率 学什么 简化开发 IOCAOP 事务处理 框架整合 MyBatis 怎…

深度学习的基础_多层感知机的手动实现

多层感知机&#xff08;Multilayer Perceptron&#xff0c;简称MLP&#xff09;是一种前馈人工神经网络。它包含至少三层节点&#xff1a;一个输入层、一个或多个隐藏层以及一个输出层。除输入节点外&#xff0c;每个节点都是一个带有非线性激活函数的神经元&#xff08;或称为…

Word快速重复上一步操作的三种高效方法

在日常工作、学习和生活中&#xff0c;我们经常需要执行一系列重复性的操作。这些操作可能简单如复制粘贴、调整图片大小&#xff0c;也可能复杂如编辑文档、处理数据等。为了提高效率&#xff0c;掌握快速重复上一步操作的方法显得尤为重要。本文将介绍三种高效的方法&#xf…