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

95. UE5 GAS RPG 实现创建多段飞弹攻击敌人

从这篇开始,我们将实现一些技能,比如多段火球术,闪电链等等。
在这一篇里,我们先实现多段火球术,技能可以通过配置发射出多个火球术进行攻击。

创建多段火球函数

首先在我们之前创建的RPGFireBolt.h类里面增加一个生成多段火球的函数,使用之前的配置。
然后可以设置最大火球数量以及最大攻击角度

	UFUNCTION(BlueprintCallable, Category="Projectile")
	void SpawnProjectiles(const FVector& ProjectileTargetLocation, const FGameplayTag& SocketTag, const FName SocketName, const bool bOverridePitch = false, const float PitchOverride = 0.f, AActor* HomingTarget = nullptr);

protected:

	UPROPERTY(EditDefaultsOnly, Category="FireBolt")
	float ProjectileSpread = 90.f; //攻击角度

	UPROPERTY(EditDefaultsOnly, Category="FireBolt")
	int32 MaxNumProjectiles = 5; //最大生成火球数量

然后在实现里,我们通过等级和最大火球数量取最小值,如果是1级,就只能发射一个火球。那么,还是按之前默认的发射单个技能的函数去实现。
如果数量大于1,那么,我们需要计算多段,然后在这一段角度里,获取到中间角度,生成一段火球。
具体逻辑,就是获取到每一段的角度,然后,获取到角色最左侧的角度,根据最左侧开始递归,生成每一个火球。

void URPGFireBolt::SpawnProjectiles(const FVector& ProjectileTargetLocation, const FGameplayTag& SocketTag, const FName SocketName, const bool bOverridePitch, const float PitchOverride, AActor* HomingTarget)
{
	const bool bIsServer = GetAvatarActorFromActorInfo()->HasAuthority(); //判断此函数是否在服务器运行
	if (!bIsServer) return;

	if (GetAvatarActorFromActorInfo()->Implements<UCombatInterface>())
	{
		//限制产生火球的最大数量
		NumProjectiles = FMath::Min(MaxNumProjectiles, GetAbilityLevel()); 

		//根据可生成数量进行逻辑判断
		if(NumProjectiles > 1)
		{
			//获取释放位置
			const FVector SocketLocation = ICombatInterface::Execute_GetCombatSocketLocationByTag(GetAvatarActorFromActorInfo(), SocketTag, SocketName);
			FRotator Rotation = (ProjectileTargetLocation - SocketLocation).Rotation(); //将方向转为旋转
			if(bOverridePitch) Rotation.Pitch = PitchOverride; //覆写发射角度
			
			const float DeltaSpread = ProjectileSpread / NumProjectiles; //技能分的段数
			const FVector LeftOfSpread = Rotation.Vector().RotateAngleAxis(-ProjectileSpread / 2.f, FVector::UpVector); //获取到最左侧的角度
			
			for(int32 i = 0; i<NumProjectiles; i++)
			{
				const FVector Direction = LeftOfSpread.RotateAngleAxis(DeltaSpread * (i + 0.5f), FVector::UpVector); //获取当前分段的角度
				FTransform SpawnTransform;
				SpawnTransform.SetLocation(SocketLocation);
				SpawnTransform.SetRotation(Direction.Rotation().Quaternion());
				
				//SpawnActorDeferred将异步创建实例,在实例创建完成时,相应的数据已经应用到了实例身上
				AProjectile* Projectile = GetWorld()->SpawnActorDeferred<AProjectile>(
					ProjectileClass,
					SpawnTransform,
					GetOwningActorFromActorInfo(),
					Cast<APawn>(GetAvatarActorFromActorInfo()),
					ESpawnActorCollisionHandlingMethod::AlwaysSpawn);

				Projectile->DamageEffectParams = MakeDamageEffectParamsFromClassDefaults();

				//确保变换设置被正确应用
				Projectile->FinishSpawning(SpawnTransform);
				
				UKismetSystemLibrary::DrawDebugArrow(GetAvatarActorFromActorInfo(), SocketLocation, SocketLocation + Direction * 100.f, 5, FLinearColor::Green, 120, 5);
			}
		}
		else
		{
			SpawnProjectile(ProjectileTargetLocation, SocketTag, SocketName, bOverridePitch, PitchOverride);
		}
		
	}
}

编译打开蓝图,在蓝图里,我们使用新创建的函数来实现火球术的生成。
在这里插入图片描述
将技能生成5级,查看效果
在这里插入图片描述
然后我们修改角度,查看不一样的效果。
在这里插入图片描述
在这里插入图片描述

实现分段函数

由于在一定角度范围内,平均角度,获取多个角度的函数通用性比较高,所以我们将在函数库里增加两个函数,用于生成多段角度和多段向量。
所以,我们创建两个函数,用于获取相应内容,这里我将函数库里所有的函数都添加了对应的注释,方便查看,如果有需要的理解的朋友可以在文章底部加群里了解更多。

	/**
	 * 这个函数根据传入的值计算均匀分布的多段角度,
	 *
	 * @param Forward 正前方向
	 * @param Axis 基于旋转的轴
	 * @param Spread 角度范围
	 * @param NumRotators 分段数
	 *
	 * @return TArray<FRotator&> 返回每段角度的中间角度的数组
	 *
	 * @note 这个函数用于在技能生成投掷物的函数逻辑中。
	 */
	UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayMechanics")
	static TArray<FRotator> EvenlySpacedRotators(const FVector& Forward, const FVector & Axis, float Spread, int32 NumRotators);

	/**
	 * 这个函数根据传入的值计算均匀分布的多段朝向
	 *
	 * @param Forward 正前方向
	 * @param Axis 基于旋转的轴
	 * @param Spread 角度范围
	 * @param NumVectors 分段数
	 *
	 * @return TArray<FVector&> 返回每段角度的中间角度的朝向数组
	 *
	 * @note 这个函数用于在技能生成投掷物的函数逻辑中。
	 */
	UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayMechanics")
	static TArray<FVector> EvenlyRotatedVectors(const FVector& Forward, const FVector & Axis, float Spread, int32 NumVectors);

实现这里,也没什么好说的,就是将一部分逻辑抽离出来,这两个函数区别就是一个返回的是旋转角度,另一个是返回的朝向向量。

TArray<FRotator> URPGAbilitySystemLibrary::EvenlySpacedRotators(const FVector& Forward, const FVector& Axis, float Spread, int32 NumRotators)
{
	TArray<FRotator> Rotators;
	
	const FVector LeftOfSpread = Forward.RotateAngleAxis(-Spread / 2.f, Axis); //获取到最左侧的角度
	
	if(NumRotators > 1)
	{
		const float DeltaSpread = Spread / NumRotators; //技能分的段数

		for(int32 i=0; i<NumRotators; i++)
		{
			const FVector Direction = LeftOfSpread.RotateAngleAxis(DeltaSpread * (i + 0.5f), Axis); //获取当前分段的角度
			Rotators.Add(Direction.Rotation());
		}
	}
	else
	{
		//如果只需要一个,则将朝向放入即可
		Rotators.Add(Forward.Rotation());
	}

	return Rotators;
}

TArray<FVector> URPGAbilitySystemLibrary::EvenlyRotatedVectors(const FVector& Forward, const FVector& Axis, float Spread, int32 NumVectors)
{
	TArray<FVector> Vectors;
	
	const FVector LeftOfSpread = Forward.RotateAngleAxis(-Spread / 2.f, Axis); //获取到最左侧的角度
	
	if(NumVectors > 1)
	{
		const float DeltaSpread = Spread / NumVectors; //技能分的段数

		for(int32 i=0; i<NumVectors; i++)
		{
			const FVector Direction = LeftOfSpread.RotateAngleAxis(DeltaSpread * (i + 0.5f), Axis); //获取当前分段的角度
			Vectors.Add(Direction);
		}
	}
	else
	{
		//如果只需要一个,则将朝向放入即可
		Vectors.Add(Forward);
	}

	return Vectors;
}

实现了对应的函数后,我们修改生成多段火球术的代码,将生成内容修改为通过调用函数库的方法获取多段角度,并生成火球。

void URPGFireBolt::SpawnProjectiles(const FVector& ProjectileTargetLocation, const FGameplayTag& SocketTag, const FName SocketName, const bool bOverridePitch, const float PitchOverride, AActor* HomingTarget)
{
	const bool bIsServer = GetAvatarActorFromActorInfo()->HasAuthority(); //判断此函数是否在服务器运行
	if (!bIsServer) return;

	if (GetAvatarActorFromActorInfo()->Implements<UCombatInterface>())
	{
		//限制产生火球的最大数量
		NumProjectiles = FMath::Min(MaxNumProjectiles, GetAbilityLevel());
		
		//获取释放位置
		const FVector SocketLocation = ICombatInterface::Execute_GetCombatSocketLocationByTag(GetAvatarActorFromActorInfo(), SocketTag, SocketName);
		FRotator Rotation = (ProjectileTargetLocation - SocketLocation).Rotation(); //将方向转为旋转
		if(bOverridePitch) Rotation.Pitch = PitchOverride; //覆写发射角度

		const FVector Forward = Rotation.Vector(); //获取朝向向量
		//根据函数获取到所有生成的转向
		TArray<FRotator> Rotations = URPGAbilitySystemLibrary::EvenlySpacedRotators(Forward, FVector::UpVector, ProjectileSpread, NumProjectiles);

		//遍历所有朝向,并生成火球术
		for(FRotator& Rot : Rotations)
		{
			FTransform SpawnTransform;
			SpawnTransform.SetLocation(SocketLocation);
			SpawnTransform.SetRotation(Rot.Quaternion());
				
			//SpawnActorDeferred将异步创建实例,在实例创建完成时,相应的数据已经应用到了实例身上
			AProjectile* Projectile = GetWorld()->SpawnActorDeferred<AProjectile>(
				ProjectileClass,
				SpawnTransform,
				GetOwningActorFromActorInfo(),
				Cast<APawn>(GetAvatarActorFromActorInfo()),
				ESpawnActorCollisionHandlingMethod::AlwaysSpawn);

			Projectile->DamageEffectParams = MakeDamageEffectParamsFromClassDefaults();

			//确保变换设置被正确应用
			Projectile->FinishSpawning(SpawnTransform);

			//Debug
			//UKismetSystemLibrary::DrawDebugArrow(GetAvatarActorFromActorInfo(), SocketLocation, SocketLocation + Rot.Vector() * 100.f, 5, FLinearColor::Green, 120, 5);
		}
		
	}
}

实现飞弹跟随目标

在上面,我们实现了释放技能可以一次性生成多个火球去攻击敌人,但是现在有一个问题,就是生成的火球术是一种扩散的方式向外射出,无法准确的攻击到敌人,所以,我们需要实现给生成的飞弹设置攻击目标,并且飞弹可以朝向目标飞行。
实现这个效果,我们需要使用到ProjectileMovement->HomingTargetComponent组件,可以给飞弹的发射组件设置攻击目标。接下来,我们将实现这个功能。
首先,我们在火球术技能类里增加三个参数,用于设置朝向目标移动时的最大速度和最小速度,火球术将在最大值和最小值中随机一个值来设置,并添加一个技能是否需要朝向目标移动的布尔,这些值都可以在技能蓝图中配置

	UPROPERTY(EditDefaultsOnly, Category="FireBolt")
	float HomingAccelerationMin = 1600.f; //移动朝向目标的最小加速度

	UPROPERTY(EditDefaultsOnly, Category="FireBolt")
	float HomingAccelerationMax = 3200.f; //移动朝向目标的最大加速度

	UPROPERTY(EditDefaultsOnly, Category="FireBolt")
	bool bLaunchHomingProjectiles = true; //设置生成的飞弹是否需要朝向目标飞行

接下来,我们在飞弹类里增加一个场景组件,这个组件在无法找到攻击目标时,我们也能够实现它能够朝向目标位置飞行,并且这个值在飞弹被销毁时,也能够被正确的垃圾回收(ProjectileMovement->HomingTargetComponent是弱引用,ProjectileMovement销毁时,不会去销毁HomingTargetComponent)。

	UPROPERTY() //一个场景组件,用于确定当前投掷物的攻击目标(在没有默认目标时,有默认目标直接设置目标的根组件)
	TObjectPtr<USceneComponent> HomingTargetSceneComponent;

接下来,我们在生成多重飞弹的函数里,增加对攻击目标的设置,如果目标继承战斗接口,我们直接获取它的根组件设置给HomingTargetComponent ,如果没有,我们就创建一个,并将目标位置应用。
然后设置朝向目标的加速度,和开启朝向目标移动变量。

//根据目标类型设置HomingTargetComponent,此内容必须在飞弹被生成后设置
if(HomingTarget && HomingTarget->Implements<UCombatInterface>())
{
	//设置攻击的位置为攻击对象的根位置
	Projectile->ProjectileMovement->HomingTargetComponent = HomingTarget->GetRootComponent();
}
else
{
	//如果没有获取到攻击目标,则创建一个可销毁的并应用
	Projectile->HomingTargetSceneComponent = NewObject<USceneComponent>(USceneComponent::StaticClass());
	Projectile->HomingTargetSceneComponent->SetWorldLocation(ProjectileTargetLocation); //设置组件位置
	Projectile->ProjectileMovement->HomingTargetComponent = Projectile->HomingTargetSceneComponent;
}
//设置飞弹朝向目标时的加速度
Projectile->ProjectileMovement->HomingAccelerationMagnitude = FMath::FRandRange(HomingAccelerationMin, HomingAccelerationMax);
Projectile->ProjectileMovement->bIsHomingProjectile = bLaunchHomingProjectiles; //设置为true,飞弹将加速飞向攻击目标

完整代码如下

void URPGFireBolt::SpawnProjectiles(const FVector& ProjectileTargetLocation, const FGameplayTag& SocketTag, const FName SocketName, const bool bOverridePitch, const float PitchOverride, AActor* HomingTarget)
{
	const bool bIsServer = GetAvatarActorFromActorInfo()->HasAuthority(); //判断此函数是否在服务器运行
	if (!bIsServer) return;

	if (GetAvatarActorFromActorInfo()->Implements<UCombatInterface>())
	{
		//限制产生火球的最大数量
		NumProjectiles = FMath::Min(MaxNumProjectiles, GetAbilityLevel());
		
		//获取释放位置
		const FVector SocketLocation = ICombatInterface::Execute_GetCombatSocketLocationByTag(GetAvatarActorFromActorInfo(), SocketTag, SocketName);
		FRotator Rotation = (ProjectileTargetLocation - SocketLocation).Rotation(); //将方向转为旋转
		if(bOverridePitch) Rotation.Pitch = PitchOverride; //覆写发射角度

		const FVector Forward = Rotation.Vector(); //获取朝向向量
		//根据函数获取到所有生成的转向
		TArray<FRotator> Rotations = URPGAbilitySystemLibrary::EvenlySpacedRotators(Forward, FVector::UpVector, ProjectileSpread, NumProjectiles);

		//遍历所有朝向,并生成火球术
		for(FRotator& Rot : Rotations)
		{
			FTransform SpawnTransform;
			SpawnTransform.SetLocation(SocketLocation);
			SpawnTransform.SetRotation(Rot.Quaternion());
				
			//SpawnActorDeferred将异步创建实例,在实例创建完成时,相应的数据已经应用到了实例身上
			AProjectile* Projectile = GetWorld()->SpawnActorDeferred<AProjectile>(
				ProjectileClass,
				SpawnTransform,
				GetOwningActorFromActorInfo(),
				Cast<APawn>(GetAvatarActorFromActorInfo()),
				ESpawnActorCollisionHandlingMethod::AlwaysSpawn);

			Projectile->DamageEffectParams = MakeDamageEffectParamsFromClassDefaults();

			//根据目标类型设置HomingTargetComponent,此内容必须在飞弹被生成后设置
			if(HomingTarget && HomingTarget->Implements<UCombatInterface>())
			{
				//设置攻击的位置为攻击对象的根位置
				Projectile->ProjectileMovement->HomingTargetComponent = HomingTarget->GetRootComponent();
			}
			else
			{
				//如果没有获取到攻击目标,则创建一个可销毁的并应用
				Projectile->HomingTargetSceneComponent = NewObject<USceneComponent>(USceneComponent::StaticClass());
				Projectile->HomingTargetSceneComponent->SetWorldLocation(ProjectileTargetLocation); //设置组件位置
				Projectile->ProjectileMovement->HomingTargetComponent = Projectile->HomingTargetSceneComponent;
			}
			//设置飞弹朝向目标时的加速度
			Projectile->ProjectileMovement->HomingAccelerationMagnitude = FMath::FRandRange(HomingAccelerationMin, HomingAccelerationMax);
			Projectile->ProjectileMovement->bIsHomingProjectile = bLaunchHomingProjectiles; //设置为true,飞弹将加速飞向攻击目标

			//确保变换设置被正确应用
			Projectile->FinishSpawning(SpawnTransform);

			//Debug
			//UKismetSystemLibrary::DrawDebugArrow(GetAvatarActorFromActorInfo(), SocketLocation, SocketLocation + Rot.Vector() * 100.f, 5, FLinearColor::Green, 120, 5);
		}
		
	}
}

完成以后,我们还需要去修改技能蓝图的逻辑,设置飞弹移动的目标。
我们修改蓝图,将鼠标拾取到的目标Actor保存为变量,在生成飞弹时,将变量传入。
在这里插入图片描述
我们现在可以覆写发射垂直角度,让飞弹先朝某个角度飞行,然后再朝向目标飞行
在这里插入图片描述
在飞弹蓝图里,我们可以修改它的初始速度和最大速度,是否受重力影响来实现不同的效果。
在这里插入图片描述
如果你想让飞弹能够准确的朝向目标飞行,那么将发射物的重力范围设置为0,它将不受重力影响,并且准确向目标攻击。
在这里插入图片描述


http://www.kler.cn/news/311121.html

相关文章:

  • C语言——自定义类型
  • Nginx 实现七层的负载均衡
  • 4位整数的数位和
  • OJ在线评测系统 前端开发设计优化通用菜单组件二 调试用户自动登录
  • 面试官:什么是CAS?存在什么问题?
  • 探索RESTful风格的网络请求:构建高效、可维护的API接口【后端 20】
  • AI换脸等违法行为的最关键原因是个人隐私信息的泄露,避免在网络上发布包含个人敏感信息的照片。
  • 图书管理系统(面向对象的编程练习)
  • 高级c语言(一)
  • Mybatis续
  • 36.贪心算法3
  • Android 内置应用裁剪
  • Java集合面试(上)
  • Kafka+PostgreSql,构建一个总线服务
  • k8s 微服务 ingress-nginx 金丝雀发布
  • ESRGAN——老旧照片、视频帧的修复和增强,提高图像的分辨率
  • ozon买家网址是什么,跨境电商ozon买家网址
  • YOLOv8的GPU环境搭建方法
  • 一个基于 laravel 和 amis 开发的后台框架, 友好的组件使用体验,可轻松实现复杂页面(附源码)
  • 一对一,表的设计
  • Nginx中return和rewrite的区别
  • python 实现entropy熵算法
  • c++ static(详解)
  • Snowflake怎么用?
  • 系统架构设计师 云原生架构篇
  • 字符设备驱动 — 4 异常与中断
  • 【Elasticsearch系列七】索引 crud
  • 【Java】网络编程-地址管理-IP协议后序-NAT机制-以太网MAC机制
  • 爬虫逆向学习(六):补环境过某数四代
  • C++初阶学习第六弹------标准库中的string类