102. UE5 GAS RPG 实现范围技能奥术伤害

在上一篇文章里,我们在技能蓝图里实现了通过技能实现技能指示,再次触发按键后,将通过定时器触发技能效果表现,最多支持11个奥术个体效果的播放。
在这一篇里,我们将实现技能播放时,对目标敌人应用技能伤害。

首先,我们将在GE里增加一些额外的参数,并且会设置序列化,可以同步到服务器,并在伤害技能类里创建配置项时增加对应参数,通过函数库应用时,将参数设置到GE实例,并在计算伤害的代码里,获取参数,并计算最终伤害。

添加范围伤害属性

首先,我们需要添加范围伤害相关的属性,需要在以下几个地方添加,由于之前制作技能时,也添加过,这里就不细说了,只列出对应的相关属性。
在RPGAbilityTypes.h中,伤害技能生成的配置项里,添加对应的参数

	//当前伤害类型是否为范围伤害UPROPERTY(BlueprintReadWrite)bool bIsRadialDamage = false;//内半径:在此半径内的所有目标都将受到完整的伤害UPROPERTY(BlueprintReadWrite)float RadialDamageInnerRadius = 0.f;//外半径:超过这个距离的目标受到最小伤害,最小伤害如果设置为0,则圈外不受到伤害UPROPERTY(BlueprintReadWrite)float RadialDamageOuterRadius = 0.f;//伤害源的中心点UPROPERTY(BlueprintReadWrite)FVector RadialDamageOrigin = FVector::ZeroVector;

在GE的实例上面设置对应的属性
在这里插入图片描述
添加对应的get和set函数
在这里插入图片描述
在这里插入图片描述
对其进行序列化,可以和服务器同步数据
在这里插入图片描述
在这里插入图片描述
在函数库里,增加对GE设置属性和获取,我们可以通过函数库的函数,传入GE实例对象进行获取和设置

	/*** 获取当前GE是否为范围伤害GE** @param EffectContextHandle 当前GE的上下文句柄** @return 如果是范围伤害 返回true** @note 此属性是RPGAbilityTypes.h内自定义属性,可实现复制。*/UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayEffects")static bool IsRadialDamage(const FGameplayEffectContextHandle& EffectContextHandle);/*** 获取当前GE 范围伤害内半径** @param EffectContextHandle 当前GE的上下文句柄** @return 返回负面效果触发间隔** @note 此属性是RPGAbilityTypes.h内自定义属性,可实现复制。*/UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayEffects")static float GetRadialDamageInnerRadius(const FGameplayEffectContextHandle& EffectContextHandle);/*** 获取当前GE 范围伤害外半径** @param EffectContextHandle 当前GE的上下文句柄** @return 返回负面效果触发间隔** @note 此属性是RPGAbilityTypes.h内自定义属性,可实现复制。*/UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayEffects")static float GetRadialDamageOuterRadius(const FGameplayEffectContextHandle& EffectContextHandle);/*** 获取当前GE 伤害中心点** @param EffectContextHandle 当前GE的上下文句柄** @return 攻击的击退会根据概率计算,如果有值,则为应用成功** @note 此属性是RPGAbilityTypes.h内自定义属性,可实现复制。*/UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayEffects")static FVector GetRadialDamageOrigin(const FGameplayEffectContextHandle& EffectContextHandle);
	/*** 设置GE是否为范围伤害** @param EffectContextHandle 当前GE的上下文句柄* @param bInIsRadialDamage true为设置为范围伤害** @note 此属性是RPGAbilityTypes.h内自定义属性,可实现复制。*/UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")static void SetIsRadialDamage(UPARAM(ref) FGameplayEffectContextHandle& EffectContextHandle, bool bInIsRadialDamage);/*** 设置GE 范围伤害 内半径距离** @param EffectContextHandle 当前GE的上下文句柄* @param InRadialDamageInnerRadius 内半径距离 内半径内受到完整伤害** @note 此属性是RPGAbilityTypes.h内自定义属性,可实现复制。*/UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")static void SetRadialDamageInnerRadius(UPARAM(ref) FGameplayEffectContextHandle& EffectContextHandle, float InRadialDamageInnerRadius);/*** 设置GE 范围伤害 外半径距离** @param EffectContextHandle 当前GE的上下文句柄* @param InRadialDamageOuterRadius 外半径距离,超出此距离外的敌人将无法受到伤害** @note 此属性是RPGAbilityTypes.h内自定义属性,可实现复制。*/UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")static void SetRadialDamageOuterRadius(UPARAM(ref) FGameplayEffectContextHandle& EffectContextHandle, float InRadialDamageOuterRadius);/*** 设置GE伤害源的中心点** @param EffectContextHandle 当前GE的上下文句柄* @param InRadialDamageOrigin 伤害源的中心点** @note 此属性是RPGAbilityTypes.h内自定义属性,可实现复制。*/UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")static void SetRadialDamageOrigin(UPARAM(ref) FGameplayEffectContextHandle& EffectContextHandle, const FVector& InRadialDamageOrigin);

接着在CPP文件里实现

bool URPGAbilitySystemLibrary::IsRadialDamage(const FGameplayEffectContextHandle& EffectContextHandle)
{if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get())){return RPGEffectContext->IsRadialDamage();}return false;
}float URPGAbilitySystemLibrary::GetRadialDamageInnerRadius(const FGameplayEffectContextHandle& EffectContextHandle)
{if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get())){return RPGEffectContext->GetRadialDamageInnerRadius();}return 0.f;
}float URPGAbilitySystemLibrary::GetRadialDamageOuterRadius(const FGameplayEffectContextHandle& EffectContextHandle)
{if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get())){return RPGEffectContext->GetRadialDamageOuterRadius();}return 0.f;
}FVector URPGAbilitySystemLibrary::GetRadialDamageOrigin(const FGameplayEffectContextHandle& EffectContextHandle)
{if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get())){return RPGEffectContext->GetRadialDamageOrigin();}return FVector::ZeroVector;
}
void URPGAbilitySystemLibrary::SetIsRadialDamage(FGameplayEffectContextHandle& EffectContextHandle, bool bInIsRadialDamage)
{FRPGGameplayEffectContext* RPGEffectContext = static_cast<FRPGGameplayEffectContext*>(EffectContextHandle.Get());RPGEffectContext->SetIsRadialDamage(bInIsRadialDamage);
}void URPGAbilitySystemLibrary::SetRadialDamageInnerRadius(FGameplayEffectContextHandle& EffectContextHandle, float InRadialDamageInnerRadius)
{FRPGGameplayEffectContext* RPGEffectContext = static_cast<FRPGGameplayEffectContext*>(EffectContextHandle.Get());RPGEffectContext->SetRadialDamageInnerRadius(InRadialDamageInnerRadius);
}void URPGAbilitySystemLibrary::SetRadialDamageOuterRadius(FGameplayEffectContextHandle& EffectContextHandle, float InRadialDamageOuterRadius)
{FRPGGameplayEffectContext* RPGEffectContext = static_cast<FRPGGameplayEffectContext*>(EffectContextHandle.Get());RPGEffectContext->SetRadialDamageOuterRadius(InRadialDamageOuterRadius);
}void URPGAbilitySystemLibrary::SetRadialDamageOrigin(FGameplayEffectContextHandle& EffectContextHandle, const FVector& InRadialDamageOrigin)
{FRPGGameplayEffectContext* RPGEffectContext = static_cast<FRPGGameplayEffectContext*>(EffectContextHandle.Get());RPGEffectContext->SetRadialDamageOrigin(InRadialDamageOrigin);
}

接着,我们在GE伤害类RPGDamageGameplayAbility.h,用于设置技能的相关配置

	//当前伤害类型是否为范围伤害UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")bool bIsRadialDamage = false;//内半径:在此半径内的所有目标都将受到完整的伤害UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")float RadialDamageInnerRadius = 0.f;//外半径:超过这个距离的目标受到最小伤害,最小伤害如果设置为0,则圈外不受到伤害UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")float RadialDamageOuterRadius = 0.f;//伤害源的中心点UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")FVector RadialDamageOrigin = FVector::ZeroVector;

然后在函数创建配置项时,添加将配置数值应用给生成的配置项上。

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;Params.DeathImpulseMagnitude = DeathImpulseMagnitude;Params.KnockbackForceMagnitude = KnockbackForceMagnitude;Params.KnockbackChance = KnockbackChance;//如果是范围伤害,将设置对应属性if(bIsRadialDamage){Params.bIsRadialDamage = bIsRadialDamage;Params.RadialDamageOrigin = RadialDamageOrigin;Params.RadialDamageInnerRadius = RadialDamageInnerRadius;Params.RadialDamageOuterRadius = RadialDamageOuterRadius;}return Params;
}

最后,就通过配置项,将配置项设置到GE实例上,这个我们是在函数库的函数实现的,我们增加对范围伤害属性的支持

FGameplayEffectContextHandle URPGAbilitySystemLibrary::ApplyDamageEffect(const FDamageEffectParams& DamageEffectParams)
{const FRPGGameplayTags& GameplayTags = FRPGGameplayTags::Get();const AActor* SourceAvatarActor = DamageEffectParams.SourceAbilitySystemComponent->GetAvatarActor();//创建GE的上下文句柄FGameplayEffectContextHandle EffectContextHandle = DamageEffectParams.SourceAbilitySystemComponent->MakeEffectContext();EffectContextHandle.AddSourceObject(SourceAvatarActor);//设置击退相关SetDeathImpulse(EffectContextHandle, DamageEffectParams.DeathImpulse);SetKnockbackForce(EffectContextHandle, DamageEffectParams.KnockbackForce);//设置范围伤害相关配置SetIsRadialDamage(EffectContextHandle, DamageEffectParams.bIsRadialDamage);SetRadialDamageInnerRadius(EffectContextHandle, DamageEffectParams.RadialDamageInnerRadius);SetRadialDamageOuterRadius(EffectContextHandle, DamageEffectParams.RadialDamageOuterRadius);SetRadialDamageOrigin(EffectContextHandle, DamageEffectParams.RadialDamageOrigin);//根据句柄和类创建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;
}

到这里,我们实现了在技能蓝图可以配置相关属性,然后生成到配置项里,然后通过函数库将其应用到GE实例上,GE实例会将其序列化,并同步到所有的客户段和服务器上。

实现伤害的应用

相关参数有了,我们还需要实现修改伤害,将范围伤害的功能应用上去。
实现范围伤害的计算,UE的内置里实现了对应的一套,我们可以通过调用内置的函数UGameplayStatics::ApplyRadialDamageWithFalloff去实现对应的伤害计算
在这里插入图片描述
函数计算完成后,会调用TakeDamage,去实现应用到角色身上,我们可以通过增加一个委托,然后覆写TakeDamage,实现委托的广播。
在这里插入图片描述
我们在战斗接口增加一个新的委托类型,用于广播受到的伤害

DECLARE_MULTICAST_DELEGATE_OneParam(FOnDamageSignature, float /*范围伤害造成的最终数值*/); //返回范围伤害能够对自身造成的伤害,在TakeDamage里广播

并增加一个获取伤害委托的函数

	/*** 获取角色受到伤害触发的委托,由于委托是创建在角色基类里的,这里可以通过添加struct来实现前向声明,不需要在头部声明一遍。* @return 委托*/virtual FOnDamageSignature& GetOnDamageDelegate() = 0; 

在角色基类里创建一个对应类型的变量

FOnDamageSignature OnDamageDelegate; //传入伤害后得到结果后的委托

覆写获取委托函数

virtual FOnDamageSignature& GetOnDamageDelegate() override;

在cpp里实现函数

FOnDamageSignature& ARPGCharacterBase::GetOnDamageDelegate()
{return OnDamageDelegate;
}

我们接着覆写范围伤害调用的TakeDamage函数

	/*** 覆写 应用伤害给自身* @see https://www.unrealengine.com/blog/damage-in-ue4* @param DamageAmount		要施加的伤害数值* @param DamageEvent		描述伤害细节的结构体,支持不同类型的伤害,如普通伤害、点伤害(FPointDamageEvent)、范围伤害(FRadialDamageEvent)等。* @param EventInstigator	负责造成伤害的 Controller,通常是玩家或 AI 的控制器。* @param DamageCauser		直接造成伤害的 Actor,例如爆炸物、子弹或掉落的石头。* @return					返回实际应用的伤害值。这允许目标修改或减少伤害,然后将最终的值返回。*/virtual float TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;

在从父函数获取的值通过委托返回

float ARPGCharacterBase::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{const float DamageTaken = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);OnDamageDelegate.Broadcast(DamageTaken);return DamageTaken;
}

接下来,我们在计算最终应用伤害的ExecCalc_Damage.cpp里,这个是自定义计算伤害的GE类,可以自己定义获取属性,和设置影响目标的属性值。
我们在里面首先绑定委托,在匿名函数里修改造成的伤害,然后通过调用内置函数计算范围伤害造成的最终伤害,如果超出外圈范围,将不受到伤害,所以,第二个伤害只我们传入了0,

		if(URPGAbilitySystemLibrary::IsRadialDamage(EffectContextHandle)){// 1. 覆写 TakeDamage 函数,通过函数获取范围技能能够造成的最终伤害// 2. 创建一个委托 OnDamageDelegate, 在TakeDamage里向外广播最终伤害数值// 3. 在战斗接口声明一个函数用于返回委托,并在角色基类实现,在计算伤害时通过战斗接口获取到委托,并绑定匿名函数// 4. 调用 UGameplayStatics::ApplyRadialDamageWithFalloff 函数应用伤害,函数内会调用角色身上的TakeDamage来广播委托。// 5. 在匿名函数中,修改实际造成的伤害。if(ICombatInterface* CombatInterface = Cast<ICombatInterface>(TargetAvatar)){CombatInterface->GetOnDamageDelegate().AddLambda([&](float DamageAmount){DamageTypeValue = DamageAmount;});}UGameplayStatics::ApplyRadialDamageWithFalloff(TargetAvatar,DamageTypeValue,0.f,URPGAbilitySystemLibrary::GetRadialDamageOrigin(EffectContextHandle),URPGAbilitySystemLibrary::GetRadialDamageInnerRadius(EffectContextHandle),URPGAbilitySystemLibrary::GetRadialDamageOuterRadius(EffectContextHandle),1.f,UDamageType::StaticClass(),TArray<AActor*>(),SourceAvatar,nullptr);}

到这里,我们实现范围伤害的应用,在计算伤害这里有点逻辑复杂,现绑定委托,然后调用函数触发委托,修改伤害值,这种相当于绕了一圈又回来了。
我比较推荐直来直去的逻辑,可以减少后期维护成本,希望有能力的同学可以实现对应的函数,直接返回值即可,没必要通过委托绕一圈。

在蓝图实现伤害的应用

我们在伤害数据资产里增加奥术爆发的伤害设置
在这里插入图片描述
然后应用给技能
在这里插入图片描述
这里,我们将不使用应用负面效果,但技能带有击飞效果,并将范围相关配置设置
在这里插入图片描述
设置完成,我们设置调试节点,来查看每次调用是否能够正确的显示内圈和外圈。
在这里插入图片描述
然后运行查看打印效果。
在这里插入图片描述
接着我们处理在应用伤害时的中心位置,在创建配置时,我们增加一个新的参数,用于可以设置目标位置

	//创建技能负面效果使用的结构体UFUNCTION(BlueprintPure)FDamageEffectParams MakeDamageEffectParamsFromClassDefaults(AActor* TargetActor = nullptr, FVector InRadialDamageOrigin = FVector::ZeroVector);

接着修改实现,我们将击退的相关数据也移动到了此函数内,用于计算技能的击退和正确的中心。

FDamageEffectParams URPGDamageGameplayAbility::MakeDamageEffectParamsFromClassDefaults(AActor* TargetActor, FVector InRadialDamageOrigin)
{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;Params.DeathImpulseMagnitude = DeathImpulseMagnitude;//击退相关Params.KnockbackForceMagnitude = KnockbackForceMagnitude;Params.KnockbackChance = KnockbackChance;if(IsValid(TargetActor)){//获取到攻击对象和目标的朝向,并转换成角度FRotator Rotation;//如果设置了伤害中心,则使用中心的设置,否则采用攻击造成的if(InRadialDamageOrigin.IsZero()){Rotation = (TargetActor->GetActorLocation() - GetAvatarActorFromActorInfo()->GetActorLocation()).Rotation();Rotation.Pitch = 45.f; //设置击退角度垂直45度}else{Rotation = (TargetActor->GetActorLocation() - InRadialDamageOrigin).Rotation();Rotation.Pitch = 90.f; //设置为击飞效果}const FVector ToTarget = Rotation.Vector();Params.DeathImpulse = ToTarget * DeathImpulseMagnitude;//判断攻击是否触发击退if(FMath::RandRange(1, 100) < Params.KnockbackChance){Params.KnockbackForce = ToTarget * KnockbackForceMagnitude;}}//如果是范围伤害,将设置对应属性if(bIsRadialDamage){Params.bIsRadialDamage = bIsRadialDamage;Params.RadialDamageOrigin = InRadialDamageOrigin.IsZero() ? RadialDamageOrigin : InRadialDamageOrigin;Params.RadialDamageInnerRadius = RadialDamageInnerRadius;Params.RadialDamageOuterRadius = RadialDamageOuterRadius;}return Params;
}

编译代码,我们在技能蓝图里,将获取到所有技能可命中的角色,然后将结果保存为变量,防止for循环多次调用前面的函数。
在这里插入图片描述
接着for循环遍历所有的目标,创建伤害配置,并应用给目标。
在这里插入图片描述
运行查看效果
在这里插入图片描述

修改计算伤害方式

之前,我们通过委托回调的方式修改,那种方式有些反人类,这里,我们可以将所需的计算封装为一个函数,并直接返回计算后的伤害。
这里,我在函数库里增加了一个新的函数,专门用于计算范围伤害,并且保留了距离减伤和障碍物阻挡功能。

	/** 此函数为计算范围性伤害,可以根据距离和障碍物进行精准控制最终造成的伤害* @param TargetActor - 需要计算攻击的目标* @param BaseDamage - 在伤害内半径(DamageInnerRadius)内应用的最大伤害值。* @param MinimumDamage - 在伤害外半径(DamageOuterRadius)处应用的最小伤害值。如果为0将不受伤害* @param Origin - 爆炸的原点(中心位置),即伤害的起点。* @param DamageInnerRadius - 全伤害半径:在该范围内的所有对象会受到最大伤害(BaseDamage)。* @param DamageOuterRadius - 最小伤害半径:在该范围之外的对象只会受到**MinimumDamage**。* @param DamageFalloff - 控制伤害递减的速率。值越高,伤害递减得越快。* @param DamageCauser - 伤害的直接来源,如爆炸的手雷或火箭弹。* @param InstigatedByController - 造成伤害的控制器,通常是执行该行为的玩家控制器。* @param DamagePreventionChannel - 阻挡伤害的通道。如果某个对象阻挡了该通道上的检测,则不会对目标应用伤害(如墙壁阻挡了视线)。* @return 返回对目标计算后的范围攻击应造成的伤害*/UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="RPGAbilitySystemLibrary|GameplayMechanics", meta=(WorldContext="WorldContextObject", AutoCreateRefTerm="IgnoreActors"))static float ApplyRadialDamageWithFalloff(AActor* TargetActor, float BaseDamage, float MinimumDamage, const FVector& Origin, float DamageInnerRadius, float DamageOuterRadius, float DamageFalloff, AActor* DamageCauser = NULL, AController* InstigatedByController = NULL, ECollisionChannel DamagePreventionChannel = ECC_Visibility);

这个函数是从内置函数修改而来,只对单个角色进行计算,获取目标的所有碰撞组件,然后计算是否技能和目标之间是否有阻挡物,然后通过调用角色身上的TakeDamage函数获取到最终伤害并返回。

float URPGAbilitySystemLibrary::ApplyRadialDamageWithFalloff(AActor* TargetActor, float BaseDamage, float MinimumDamage, const FVector& Origin, float DamageInnerRadius,float DamageOuterRadius, float DamageFalloff, AActor* DamageCauser, AController* InstigatedByController, ECollisionChannel DamagePreventionChannel)
{// 判断目标角色是否死亡bool bIsDead = true;if(TargetActor->Implements<UCombatInterface>()){bIsDead = ICombatInterface::Execute_IsDead(TargetActor);}if(bIsDead){return 0.f; //如果角色已经死亡,直接返回0}// 获取目标角色所有组件TArray<UActorComponent*> Components;TargetActor->GetComponents(Components);bool bIsDamageable = false; //判断攻击是能能够查看到目标TArray<FHitResult> HitList; //存储目标收到碰撞查询到的碰撞结果for (UActorComponent* Comp : Components){UPrimitiveComponent* PrimitiveComp = Cast<UPrimitiveComponent>(Comp);if (PrimitiveComp && PrimitiveComp->IsCollisionEnabled()){FHitResult Hit;bIsDamageable = ComponentIsDamageableFrom(PrimitiveComp, Origin, DamageCauser, {}, DamagePreventionChannel, Hit);HitList.Add(Hit);if(bIsDamageable) break;}}//应用目标的伤害值float AppliedDamage = 0.f;if (bIsDamageable){// 创建伤害事件FRadialDamageEvent DmgEvent;DmgEvent.DamageTypeClass = TSubclassOf<UDamageType>(UDamageType::StaticClass());DmgEvent.Origin = Origin;DmgEvent.Params = FRadialDamageParams(BaseDamage, MinimumDamage, DamageInnerRadius, DamageOuterRadius, DamageFalloff);DmgEvent.ComponentHits = HitList;// 应用伤害AppliedDamage = TargetActor->TakeDamage(BaseDamage, DmgEvent, InstigatedByController, DamageCauser);}return AppliedDamage;
}

ComponentIsDamageableFrom函数,是内置库里的函数,我这里直接复制出来,可以方便调用。

/** @RETURN 如果从 Origin 发出的武器射线击中了 VictimComp 组件,则返回 True。 OutHitResult 将包含击中的具体信息。 */
static bool ComponentIsDamageableFrom(UPrimitiveComponent* VictimComp, FVector const& Origin, AActor const* IgnoredActor, const TArray<AActor*>& IgnoreActors, ECollisionChannel TraceChannel, FHitResult& OutHitResult)
{// 配置碰撞查询参数,忽略指定的 ActorFCollisionQueryParams LineParams(SCENE_QUERY_STAT(ComponentIsVisibleFrom), true, IgnoredActor);LineParams.AddIgnoredActors( IgnoreActors );// 获取组件所在世界的指针UWorld* const World = VictimComp->GetWorld();check(World);// 使用组件的包围盒中心作为射线终点FVector const TraceEnd = VictimComp->Bounds.Origin;FVector TraceStart = Origin;// 如果起点和终点重合,微调起点以避免提前退出if (Origin == TraceEnd){// 微调 Z 轴TraceStart.Z += 0.01f;}// 只有当通道合法时才执行射线检测if (TraceChannel != ECollisionChannel::ECC_MAX){bool const bHadBlockingHit = World->LineTraceSingleByChannel(OutHitResult, TraceStart, TraceEnd, TraceChannel, LineParams);//::DrawDebugLine(World, TraceStart, TraceEnd, FLinearColor::Red, true);// 如果有阻挡物,检查是否为目标组件if (bHadBlockingHit){if (OutHitResult.Component == VictimComp){// 阻挡物是目标组件,返回 truereturn true;}else{// 击中其他阻挡物,记录日志并返回 falseUE_LOG(LogDamage, Log, TEXT("Radial Damage to %s blocked by %s (%s)"), *GetNameSafe(VictimComp), *OutHitResult.GetHitObjectHandle().GetName(), *GetNameSafe(OutHitResult.Component.Get()));return false;}}}else{// 如果通道无效,输出警告UE_LOG(LogDamage, Warning, TEXT("ECollisionChannel::ECC_MAX is not valid! No falloff is added to damage"));}// 未击中任何物体,构造一个伪造的 HitResult 假设击中组件中心FVector const FakeHitLoc = VictimComp->GetComponentLocation();FVector const FakeHitNorm = (Origin - FakeHitLoc).GetSafeNormal();		// 法线指向伤害源OutHitResult = FHitResult(VictimComp->GetOwner(), VictimComp, FakeHitLoc, FakeHitNorm);return true;
}

在计算伤害时,我们只需要调用一下函数,传入所需参数,即可返回值,简单方便。
在这里插入图片描述

解决范围指示光环指针问题

我们触发技能后,范围光环默认在地面,如果指针瞄准到角色,会出现突然闪现一段位置,这是因为鼠标拾取到角色身上的位置,然后拾取到地面,水平偏移会突然闪现一段位置,为了解决这个问题,我们需要创建一个新的通道,这个通道将不会拾取场景中的角色
在这里插入图片描述
将角色身上的对此通过忽略,以及一些不必要的碰撞体也需要设置。比如角色的胶囊体,模型,武器等,还有相机上的碰撞。
在这里插入图片描述
展示一下运行效果
在这里插入图片描述

最后贴一下完整的技能蓝图,可以放大查看
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/457843.html

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

相关文章

C++11新特性相关内容详细梳理

0. 引言 C11简介&#xff1a; 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff0c;使得C03这个名字已经取代了C98称为C11之前的最新C标准名称。不过由于C03(TC1)主要是对C98标准中的漏洞进行修复&#xff0c;语言的核心部分则没有改动&#xff0c;因此人们习惯性…

C#实现简单的文件夹对比程序

python版本的文件夹对比程序虽然简单&#xff0c;但可视化效果一般&#xff0c;不太好看。使用C#的Winform项目实现可视化对比文件夹内容&#xff0c;主要功能包括&#xff1a;   1&#xff09;采用Directory.GetDirectories获取子文件夹集合&#xff0c;Directory.GetFiles获…

C语言[求x的y次方]

C语言——求x的y次方 这段 C 代码的目的是从用户输入获取两个整数 x 和 y &#xff0c;然后计算 x 的 y 次幂&#xff08;不过这里有个小错误&#xff0c;实际计算的是 x 的 (y - 1) 次幂&#xff0c;后面会详细说&#xff09;&#xff0c;最后输出结果。 代码如下: #include…

8 个用于创建电商组件的 CSS 和 JS 代码片段

文章目录 前言正文1.自定义办公桌配置工具2.商品展示卡片3.Vue.js 支持的便捷购物体验4.简化的多步结账流程5.移动端优化的商品页面6.动态购物车效果7.React 支持的购物车页面8.尺码指南 总结 前言 优秀的电商网站&#xff0c;必须操作简便、注重细节&#xff0c;才能让用户留…

飞书文档解除复制限制

解除飞书文档没有编辑器权限限制复制功能方法 方法一&#xff1a;使用插件 方法二&#xff1a; 通过调试工具删除所有的copy事件 使用插件 缺点&#xff1a; 只有markdown格式&#xff0c;如果需要其他格式需要再通过Typora等markdown编辑器转pdf,word等格式 安装插件 Cloud Do…

OpenTelemetry 实际应用

介绍 OpenTelemetry“动手”指南适用于想要开始使用 OpenTelemetry 的人。 如果您是 OpenTelemetry 的新手&#xff0c;那么我建议您从OpenTelemetry 启动和运行帖子开始&#xff0c;我在其中详细介绍了 OpenTelemetry。 OpenTelemetry开始改变可观察性格局&#xff0c;它提供…

AAPL: Adding Attributes to Prompt Learning for Vision-Language Models

文章汇总 当前的问题 1.元标记未能捕获分类的关键语义特征 如下图(a)所示&#xff0c; π \pi π在类聚类方面没有显示出很大的差异&#xff0c;这表明元标记 π \pi π未能捕获分类的关键语义特征。我们进行简单的数据增强后&#xff0c;如图(b)所示&#xff0c;效果也是如…

RestHighLevelClient操作es查询文档

目录 利用RestHighLevelClient客户端操作es查询文档 查询match_all dsl语句&#xff1a; ​编辑 java代码 小结 match字段全文检索查询 dsl语句 java代码 multi_match多字段全文检索查询 dsl语句 java代码 term精确查询 dsl语句 java代码 range范围查询 dsl语句 j…

鸿蒙是必经之路

少了大嘴的发布会&#xff0c;老实讲有点让人昏昏入睡。关于技术本身的东西&#xff0c;放在后面。 我想想来加把油~ 鸿蒙发布后褒贬不一&#xff0c;其中很多人不太看好鸿蒙&#xff0c;一方面是开源性、一方面是南向北向的利益问题。 不说技术的领先点&#xff0c;我只扯扯…

破解API加密逆向接口分析,看这篇就够了

破解API加密逆向接口分析&#xff0c;看这篇就够了 每日一练&#xff1a;API接口数据逆向&#xff0c;看完这篇&#xff0c;就能学会找到逆向的入口函数、调试js代码、分析js代码、还原加解密算法&#xff01;为了能及时获取后续的爬虫及逆向的技术分享文章&#xff0c;请先关注…

qt EventFilter用途详解

一、概述 EventFilter是QObject类的一个事件过滤器&#xff0c;当使用installEventFilter方法为某个对象安装事件过滤器时&#xff0c;该对象的eventFilter函数就会被调用。通过重写eventFilter方法&#xff0c;开发者可以在事件处理过程中进行拦截和处理&#xff0c;实现对事…

代码随想录算法训练营第46期

class Solution { public: // 决定dp[i]的因素就是第i房间偷还是不偷。 // 偷第i房间&#xff0c;那么dp[i] dp[i - 2] nums[i] 即&#xff1a;第i-1房一定是不考虑的&#xff0c;找出 下标i-2&#xff08;包括i-2&#xff09;以内的房屋&#xff0c;最多可以偷窃的金额为dp[…

Unity插件-Intense TPS 讲解

目录 关于TPS 打开场景&#xff1a;WeaponTest.unity&#xff0c; 只要把这些枪点&#xff0c;打开&#xff08;默认隐藏&#xff0c;不知道为何), 一开始不能运行如何修复 总结 关于TPS 个人不是TPS&#xff0c;FPS的射击游戏爱好者&#xff0c; 不过感觉这个枪感&…

riscv uboot 启动流程分析 - SPL启动流程

分析uboot 启动流程硬件&#xff1a;启明智显推出M4核心板 &#xff08;https://gitee.com/qiming-zhixian/m4-openwrt&#xff09; 1.U-boot和SPL概述 U-Boot 分为 uboot-spl 和 uboot 两个组成部分。SPL 是 Secondary Program Loader 的简称&#xff0c;第二阶段程序加载器。…

springboot083基于springboot的个人理财系统--论文pf(论文+源码)_kaic

基于springboot的个人理财系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了个人理财系统的开发全过程。通过分析个人理财系统管理的不足&#xff0c;创建了一个计算机管理个人理财系统的方案。文章介绍了个…

JavaEE初阶---多线程(三)---内存可见性/单例模式/wait,notify的使用解决线程饿死问题

文章目录 1.volatile关键字1.1保证内存的可见性--引入1.2保证内存的可见性--分析1.3保证内存的可见性--解决1.4内存可见性-JMM内存模型 2.notify和wait介绍2.1作用一&#xff1a;控制调度顺序2.2作用二&#xff1a;避免线程饿死2.3notify和notifyAll区分 3.单例模式--经典设计模…

GoogleChrome的安装和使用

Google Chrome 文章目录 Google Chrome安装主页设置扩展程序 安装 chrome官网 正规的下载好之后logo是这样的 主页设置 说明 正常情况下, GoogleChrome是无法正常访问的, 因为chrome的搜索引擎默认使用的是谷歌搜索, 而在国内是无法正常访问谷歌搜索的, 所以需要更改一下主页…

【C语言】预处理(预编译)详解(上)(C语言最终篇)

文章目录 一、预定义符号二、#define定义常量三.、#define定义宏四、带有副作用的宏参数五、宏替换的规则六、宏和函数的对比1.宏的优势2.函数的优势3.宏和函数的命名约定 一、预定义符号 学习本篇文章的内容推荐先去看前面的编译和链接&#xff0c;才能更好地理解和吸收&#…

基于springboot+vue的高校就业管理系统,

基于springbootvue的高校就业管理系统, 分为管理员&#xff1a;测试账号:10086/123 学生&#xff1a;测试账号:10087/123 包含个人信息、查看企业岗位信息、简历信息管理、我的应聘企业&#xff1a;测试账号:10070/123 包含企业信息、岗位企业信息管理、查看学生简历信息…

颠覆级AI:10秒生成超清视频

颠覆级AI&#xff1a;10秒生成超清视频 Pyramid-Flow 是一款开源 AI 视频生成神器&#x1f4bb;&#xff0c;只需文字或图片即可极速生成高清视频&#x1f3a5;&#xff01;高效、高清、资源需求低&#xff0c;适合创作广告、教学视频等多种用途&#x1f680;&#xff0c;快来…