在上一篇文章里,我们在技能蓝图里实现了通过技能实现技能指示,再次触发按键后,将通过定时器触发技能效果表现,最多支持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;
}
在计算伤害时,我们只需要调用一下函数,传入所需参数,即可返回值,简单方便。
解决范围指示光环指针问题
我们触发技能后,范围光环默认在地面,如果指针瞄准到角色,会出现突然闪现一段位置,这是因为鼠标拾取到角色身上的位置,然后拾取到地面,水平偏移会突然闪现一段位置,为了解决这个问题,我们需要创建一个新的通道,这个通道将不会拾取场景中的角色
将角色身上的对此通过忽略,以及一些不必要的碰撞体也需要设置。比如角色的胶囊体,模型,武器等,还有相机上的碰撞。
展示一下运行效果
最后贴一下完整的技能蓝图,可以放大查看