这一篇文章我们再实现一个技能火焰爆炸,由于我们之前已经实现了三个玩家技能,这一个技能有一些总结的味道,对于创建技能相同的部分,长话短说,我们过一遍。
准备工作
我们需要一个技能类,继承于伤害技能基类
主要在里面覆写一下技能描述
接下来主要是实现
FString URPGFireBlast::GetDescriptionAtLevel(const int32 Level, const FString& Title)
{const int32 Damage = GetDamageByDamageType(Level, FRPGGameplayTags::Get().Damage_Fire);const float ManaCost = GetManaCost(Level);const float Cooldown = GetCooldown(Level);return FString::Printf(TEXT(// 标题"<Title>%s</>\n"// 细节"<Small>等级:</> <Level>%i</>\n""<Small>技能冷却:</> <Cooldown>%.1f</>\n""<Small>蓝量消耗:</> <ManaCost>%.1f</>\n\n"//%.1f会四舍五入到小数点后一位// 技能描述"<Default>向四面八方发射 %i 颗火球,每颗火球会在返回时发生爆炸,并造成</> <Damage>%i</> <Default>点径向火焰伤害,并有一定几率触发燃烧。</>"),// 动态修改值*Title,Level,Cooldown,-ManaCost,NumFireBalls,Damage);
}
然后,创建一个蓝图类,用于实现技能逻辑,技能逻辑在下面再实现,我们将装配,伤害,等相关的实现。
技能需要创建两个标签,一个是技能的标识标签,通过标签可以获取到此技能,另一个就是技能对应的冷却标签,我们直接通过标签管理器创建即可。
技能的标识标签需要在代码中获取,我们需要在代码中创建
有了技能蓝图和标签,我们再技能数据资产将数据添加,后续可以通过此来获取数据,显示到ui,等。
在技能面板的技能按钮,将需要显示此技能的按钮的技能tag设置为对应技能tag
接下来,就是实现技能的伤害,冷却和消耗,对于伤害和消耗,我们需要在对应的图表CT_Damage和CT_SpellCost里增加对应的表格内容。
在技能蓝图里,设置对应的技能伤害GE,这个我们是通用的,然后引用伤害
并将标签设置
接着创建两个GE,实现冷却和消耗
冷却还是之前思路,添加标签实现
消耗去修改属性,这种也不是必须的,我们在技能里调用CommitAbility之前可以设置,或者实现一种自动计算的方式都可以。
然后设置给技能
接下来就是运行测试效果是否符合预期
为了方便后续测试,我们将技能直接放置到技能栏,首先设置技能的输入标签
然后在初始技能数组里添加对应的技能
添加火球
接下来,我们实现技能的表现效果,技能会平均角度,向角色的四面八方移动,然后移动到一定距离,自动返回并发生爆炸。实现这个效果,我们无法通过使用内置的方法,需要使用时间轴来实现效果。
基于之前的火球术的实现,我们创建一个对应的火球的子类
命名一下
在类里,我们覆写beginPlay函数,并添加一个蓝图实现
/*** 火焰爆发使用的火球类*/
UCLASS()
class RPG_API ARPGFireBall : public AProjectile
{GENERATED_BODY()public://执行蓝图时间轴事件,需要在蓝图中实现此事件UFUNCTION(BlueprintImplementableEvent)void StartOutgoingTimeline();UPROPERTY(BlueprintReadOnly) //当前火球返回的目标角色,默认是技能的释放者,在创建火球是创建TObjectPtr<AActor> ReturnToActor;
protected:virtual void BeginPlay() override;
在游戏开始事件里调用时间轴事件
void ARPGFireBall::BeginPlay()
{Super::BeginPlay();StartOutgoingTimeline(); //调用开始时间线修改
}
编译打开UE创建一个对应的蓝图类
添加一个Niagara组件,用于显示粒子效果
设置爆炸效果和音效配置
因为我们需要使用时间轴实现,内置的自动移动的相关可以关闭
关闭帧更新
我们也不需要自动激活
在火球蓝图里,我们实现时间轴事件,首先获取到初始位置和火球移动的最远目标位置
在后面,我们首先实现火球从初始位置移动到终点位置,通过时间轴实现
时间轴里面设置了一个值,这个值采用现快后慢的方式,这样火球有一种减速的效果。值是0-1,我们可以通过插值的方式实现位置从起点到终点。
在上一个时间轴执行完成之后,我们再运行一个时间轴,这个事件轴是实现角色返回到角色的当前位置,所以,我们修改为了从终点位置向玩家角色位置插值,这里将当前玩家的位置进行了保存,方便后续使用
这个时间轴的线是先慢后快
最后,我们在返回更新时,如果火球和角色的距离小于某个阈值,那么将自我销毁,防止火球攻击自身
对于这种相乘的我们可以转换类型
这是转换成浮点类型的效果
修改技能类实现表现效果
接着,我们修改技能实现效果,我们在技能里设置一个参数,用于设置火球类
private:UPROPERTY(EditDefaultsOnly, Category="FireBlast") //生成火球使用的类TSubclassOf<ARPGFireBall> FireBallClass;
编译,在技能蓝图类里设置我们创建的火球蓝图类
接着,我们在技能类里创建一个生成火球的函数,这个函数里,可以生成技能所需的火球
/*** 生成技能所需的火球* @return NumFireBalls个数火球的数组*/UFUNCTION(BlueprintCallable)TArray<ARPGFireBall*> SpawnFireBall();
在代码实现里,我们获取到角色的位置和朝向,通过之前创建的函数,获取到每个火球的旋转值,并通过旋转值创建火球,生成所需的参数。
TArray<ARPGFireBall*> URPGFireBlast::SpawnFireBall()
{//获取到角色朝向和位置const FVector Forward = GetAvatarActorFromActorInfo()->GetActorForwardVector();const FVector Location = GetAvatarActorFromActorInfo()->GetActorLocation();//通过函数获取到每个需要生成的火球的旋转TArray<FRotator> Rotators = URPGAbilitySystemLibrary::EvenlySpacedRotators(Forward, FVector::UpVector, 360.f, NumFireBalls);TArray<ARPGFireBall*> FireBalls; //生成所需的火球数组for(const FRotator& Rotator : Rotators){//创建变换FTransform SpawnTransform;SpawnTransform.SetLocation(Location);SpawnTransform.SetRotation(Rotator.Quaternion());//创建火球 使用 SpawnActorDeferred 来生成对象时,UE 会延迟实际的对象生成过程,这样你有机会在完全初始化对象之前进行自定义配置。ARPGFireBall* FireBall = GetWorld()->SpawnActorDeferred<ARPGFireBall>(FireBallClass,SpawnTransform,GetOwningActorFromActorInfo(),CurrentActorInfo->PlayerController->GetPawn(),ESpawnActorCollisionHandlingMethod::AlwaysSpawn);//设置火球的伤害配置FireBall->DamageEffectParams = MakeDamageEffectParamsFromClassDefaults();FireBall->ReturnToActor = GetAvatarActorFromActorInfo();FireBalls.Add(FireBall);//在配置完成火球配置后,调用FinishSpawning将火球正式添加到场景中FireBall->FinishSpawning(SpawnTransform);}return FireBalls;
}
然后在技能蓝图里调用函数
接下来就是运行查看效果
实现伤害
接下来,我们实现技能的伤害,伤害分为两种,一种是火球在行进当中与敌人触碰造成的伤害,另一种是在火球靠近释放者一定范围后,销毁时产生的爆炸。
我们首先实现行进中与敌人产生碰撞造成的伤害。
首先,我们配置技能的蓝图的配置,设置对应的伤害以及伤害类型。
接着,我们要在c++中,覆写OnSphereOverlap函数,实现在移动过程中造成的伤害。这里,我们基于ARPGFireBlot重写,去掉了与敌人产生重叠后的销毁火球的事件,而是应用一个伤害GE,由于火球没有伤害击退效果,这里我们去掉了相关代码。
void ARPGFireBall::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{if(GetInstigator() == OtherActor) return; //判断和碰撞体接触的目标是否为自身//如果不是敌人,将不执行后续if(!URPGAbilitySystemLibrary::IsNotFriend(GetInstigator(), OtherActor)) return;//目标未继承战斗接口,返回if(!OtherActor->Implements<UCombatInterface>()) return;//播放击中特效 当前服务器端未调用销毁时,直接播放if(!bHit) PlayImpact();//在重叠后,销毁自身if(HasAuthority()) //是否为服务器,如果服务器端,将执行应用GE,并通过GE的自动同步到客户端{//为目标应用GEif(UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(OtherActor)){//死亡冲击的力度和方向DamageEffectParams.DeathImpulse = GetActorForwardVector() * DamageEffectParams.DeathImpulseMagnitude;//通过配置项应用给目标ASCDamageEffectParams.TargetAbilitySystemComponent = TargetASC;URPGAbilitySystemLibrary::ApplyDamageEffect(DamageEffectParams);}}
}
接下来则是实现火球移动结束后的爆炸效果,这个效果实现,我们需要修改伤害配置项,将其修改为范围伤害,这里采用的是增加一些修改对应配置的函数,然后在蓝图调用实现配置项的修改,然后应用给目标。
这里,我们在函数库里增加了四个函数,用于修改伤害配置项的范围伤害参数,击退,死亡时的击退以及攻击敌人的ASC的函数
/*** 修改伤害配置项,将其设置为具有范围伤害的配置项* @param DamageEffectParams 需要修改的配置项* @param bIsRadial 设置是否为范围伤害* @param InnerRadius 内半径* @param OutRadius 外半径* @param Origin 伤害中心*/UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")static void SetIsRadialDamageEffectParams(UPARAM(ref) FDamageEffectParams& DamageEffectParams, bool bIsRadial, float InnerRadius, float OutRadius, FVector Origin);/*** 修改伤害的冲击力的方向* @param DamageEffectParams 需要修改的伤害配置项* @param KnockbackDirection 攻击时触发击退的方向*/UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")static void SetKnockbackDirection(UPARAM(ref) FDamageEffectParams& DamageEffectParams, FVector KnockbackDirection, float Magnitude = 0.f);/*** 修改伤害的冲击力的方向* @param DamageEffectParams 需要修改的伤害配置项* @param ImpulseDirection 死亡时触发击退的方向*/UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")static void SetDeathImpulseDirection(UPARAM(ref) FDamageEffectParams& DamageEffectParams, FVector ImpulseDirection, float Magnitude = 0.f);/*** 设置伤害配置应用目标ASC* @param DamageEffectParams 需要修改的伤害配置 * @param InASC 应用目标ASC*/UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")static void SetEffectParamsTargetASC(UPARAM(ref) FDamageEffectParams& DamageEffectParams, UAbilitySystemComponent* InASC);
接着,我们实现对配置项的修改
void URPGAbilitySystemLibrary::SetIsRadialDamageEffectParams(FDamageEffectParams& DamageEffectParams, bool bIsRadial, float InnerRadius, float OutRadius, FVector Origin)
{DamageEffectParams.bIsRadialDamage = bIsRadial;DamageEffectParams.RadialDamageInnerRadius = InnerRadius;DamageEffectParams.RadialDamageOuterRadius = OutRadius;DamageEffectParams.RadialDamageOrigin = Origin;
}void URPGAbilitySystemLibrary::SetKnockbackDirection(FDamageEffectParams& DamageEffectParams, FVector KnockbackDirection, float Magnitude)
{KnockbackDirection.Normalize();if(Magnitude == 0.f){DamageEffectParams.KnockbackForce = KnockbackDirection * DamageEffectParams.KnockbackForceMagnitude;}else{DamageEffectParams.KnockbackForce = KnockbackDirection * Magnitude;}
}void URPGAbilitySystemLibrary::SetDeathImpulseDirection(FDamageEffectParams& DamageEffectParams, FVector ImpulseDirection, float Magnitude)
{ImpulseDirection.Normalize();if(Magnitude == 0.f){DamageEffectParams.DeathImpulse = ImpulseDirection * DamageEffectParams.DeathImpulseMagnitude;}else{DamageEffectParams.DeathImpulse = ImpulseDirection * Magnitude;}
}void URPGAbilitySystemLibrary::SetEffectParamsTargetASC(FDamageEffectParams& DamageEffectParams, UAbilitySystemComponent* InASC)
{DamageEffectParams.TargetAbilitySystemComponent = InASC;
}
接着,我们编译代码,打开火球蓝图,在触发火球爆炸后,实现对应逻辑
我们将后面的逻辑封装为了一个函数,这个函数内主要逻辑是设置技能造成的范围伤害相关参数,以及获取到需要应用伤害的目标,然后调用函数应用,销毁自身。
应用伤害的函数看似很长,其实主要就是遍历伤害数组,然后修改应用目标的ASC,以及击退相关参数,这里对每个目标的值都不一样。最后调用函数库函数应用即可。
修改爆炸使用GameplayCue实现
我们实现了伤害,这里再修改爆炸时的爆炸效果,我们采用通过c++调用,然后使用GameplayCue实现。
我们在c++里增加一个新的标签,这个火焰爆炸的GameplayCue可以通过此标签调用
FGameplayTag GameplayCue_FireBlast; //火焰爆炸火球爆炸时的表现效果
GameplayTags.GameplayCue_FireBlast = UGameplayTagsManager::Get().AddNativeGameplayTag(FName("GameplayCue.FireBlast"),FString("火焰爆炸表现效果标签"));
我们需要覆写RPGFireBall的PlayImpact函数
virtual void PlayImpact() override;
实现这里,我们将通过UGameplayCueManager去调用GameplayCue,而不是之前的方式。
void ARPGFireBall::PlayImpact()
{if(GetOwner()){//设置GameplayCue播放位置FGameplayCueParameters Parameters;Parameters.Location = GetActorLocation();UGameplayCueManager::ExecuteGameplayCue_NonReplicated(GetOwner(), FRPGGameplayTags::Get().GameplayCue_FireBlast, Parameters);}//将音乐停止后会自动销毁if(LoopingSoundComponent){//循环组件暂停并销毁LoopingSoundComponent->Stop();LoopingSoundComponent->DestroyComponent();}bHit = true;
}
接着编译打开蓝图,创建一个对应的GameplayCue
设置应用标签
然后设置粒子系统和爆炸音效。