104. UE5 GAS RPG 实现技能火焰爆炸

发布于:2024-11-03 ⋅ 阅读:(57) ⋅ 点赞:(0)

这一篇文章我们再实现一个技能火焰爆炸,由于我们之前已经实现了三个玩家技能,这一个技能有一些总结的味道,对于创建技能相同的部分,长话短说,我们过一遍。

准备工作

我们需要一个技能类,继承于伤害技能基类
在这里插入图片描述
主要在里面覆写一下技能描述
在这里插入图片描述
接下来主要是实现

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的自动同步到客户端
	{
		//为目标应用GE
		if(UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(OtherActor))
		{
			//死亡冲击的力度和方向
			DamageEffectParams.DeathImpulse = GetActorForwardVector() * DamageEffectParams.DeathImpulseMagnitude;
			
			//通过配置项应用给目标ASC
			DamageEffectParams.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
在这里插入图片描述
设置应用标签
在这里插入图片描述
然后设置粒子系统和爆炸音效。
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到