当前位置: 首页 > news >正文

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。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【操作系统】同步互斥与Golang互斥锁实现
  • 【TomCat】安装部署
  • 实训day41(9.2)
  • Python读取Excel数据教程 - 详细版
  • HTTPS 通信时是对称加密还是非对称加密?
  • 2024年互联网公司时薪排行榜大曝光!看完我酸了,第一竟是他…
  • 云计算实训40——部署nmt、部署project_exam_system项目
  • 基于深度学习的稀疏感知器设计
  • 使用PyInstaller打包带图标的软件
  • kafak推送消息。
  • 将Nginx注册为Windows服务
  • uniapp布局
  • react 列表页面中管理接口请求的参数
  • 代码随想录冲冲冲 Day34 动态规划Part2
  • k8s集群环境搭建(一主二从--kubeadm安装)
  • [ JavaScript ] 数据结构与算法 —— 链表
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • Java 11 发布计划来了,已确定 3个 新特性!!
  • JAVA多线程机制解析-volatilesynchronized
  • Sublime text 3 3103 注册码
  • Vim 折腾记
  • Webpack入门之遇到的那些坑,系列示例Demo
  • 对超线程几个不同角度的解释
  • 高程读书笔记 第六章 面向对象程序设计
  • 给初学者:JavaScript 中数组操作注意点
  • 关于List、List?、ListObject的区别
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 免费小说阅读小程序
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 终端用户监控:真实用户监控还是模拟监控?
  • 机器人开始自主学习,是人类福祉,还是定时炸弹? ...
  • # 利刃出鞘_Tomcat 核心原理解析(八)-- Tomcat 集群
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • (2.2w字)前端单元测试之Jest详解篇
  • (21)起落架/可伸缩相机支架
  • (6)添加vue-cookie
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (三)终结任务
  • (十八)Flink CEP 详解
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • (原创)Stanford Machine Learning (by Andrew NG) --- (week 9) Anomaly DetectionRecommender Systems...
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转载)Google Chrome调试JS
  • *算法训练(leetcode)第四十七天 | 并查集理论基础、107. 寻找存在的路径
  • .net Application的目录
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter
  • .NET基础篇——反射的奥妙
  • .NET企业级应用架构设计系列之结尾篇
  • .NET中winform传递参数至Url并获得返回值或文件