35. UE5 RPG制作火球术技能

接下来,我们将制作技能了,总算迈进了一大步。首先回顾一下之前是如何实现技能触发的,然后再进入正题。

如果想实现我之前的触发方式的,请看此栏目的31-33篇文章,讲解了实现逻辑,这里总结一下:

  1. 首先创建一个DataAsset用于存储InputAction和GameplayTag对应的数据
  2. 在触发InputAction的时候,将GameplayTag作为参数去调用输入回调
  3. 在技能身上绑定对应的GameplayTag,在回调中遍历角色身上的应用的技能,如果Tag相同,则激活技能。

现在技能可以被激活了,需要我们实现技能内的逻辑,接下来,我们将从简单的开始实现,那就是火球术。

要创建技能的完整内容我们需要:

  • 创建一个Actor,在里面增加一个碰撞体和一个发射器,用于实现子弹移动和碰撞检测。
  • 创建一个基于技能基类的用于发射火球的技能,通过里面逻辑进行动画播放和火球发射。

创建Projectile类

首先创建一个Projectile类,继承Actor,可以放置到场景中。并在内部实现碰撞检测和发射器组件。

打开以后,将里面的Tick函数删除,我们不需要每帧更新

cpp 复制代码
public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

将其每帧更新设置为false

cpp 复制代码
PrimaryActorTick.bCanEverTick = false;

并且,将此类设置为在服务器运行

cpp 复制代码
bReplicates = true; //此类在服务器运行,然后复制到每个客户端

首先,我们添加一个碰撞体,这里添加了一个球型碰撞体

cpp 复制代码
private:
	UPROPERTY(VisibleAnywhere)
	TObjectPtr<USphereComponent> Sphere;

在构造函数中,对碰撞体进行初始化

cpp 复制代码
	//初始化碰撞体
	Sphere = CreateDefaultSubobject<USphereComponent>("Sphere");
	SetRootComponent(Sphere); //设置其为根节点,
	Sphere->SetCollisionEnabled(ECollisionEnabled::QueryOnly); //设置其只用作查询使用
	Sphere->SetCollisionResponseToChannels(ECR_Ignore); //设置其忽略所有碰撞检测
	Sphere->SetCollisionResponseToChannel(ECC_WorldDynamic, ECR_Overlap); //设置其与世界动态物体产生重叠事件
	Sphere->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Overlap); //设置其与世界静态物体产生重叠事件
	Sphere->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); //设置其与Pawn类型物体产生重叠事件

接着增加对应的碰撞检测回调,这个回调函数内部实现我们将在后续实现

cpp 复制代码
	void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

然后再BeginPlay回调用,触发重叠时,绑定此回调

cpp 复制代码
Sphere->OnComponentBeginOverlap.AddDynamic(this, &AProjectile::OnSphereOverlap);

接着,我们创建一个发射组件,发射组件通常用于控制投射物的移动,例如子弹或火箭。这个组件通常负责处理投射物的速度、加速度、路径等。

cpp 复制代码
public:	
	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UProjectileMovementComponent> ProjectileMovement;

在构造函数中对其进行初始化

cpp 复制代码
	//创建发射组件
	ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>("ProjectileMovement");
	ProjectileMovement->InitialSpeed = 550.f; //设置初始速度
	ProjectileMovement->MaxSpeed = 550.f; //设置最大速度
	ProjectileMovement->ProjectileGravityScale = 0.f; //设置重力影响因子,0为不受影响

接下来,编译打开UE,我们创建一个对应的蓝图

打开以后,如果左侧有我们创建的对应的组件

碰撞体的碰撞类型,也是按我们的设置来的

在根节点(碰撞体)下面添加一个Niagara组件,用于播放粒子特效

添加上对应的Nigara粒子特效,可以放置到场景中查看效果

创建ProjectileSpell

ProjectileSpell是基于技能类创建的子类,我们可以查看源码对基类增加更多的了解,这里我对基类的h文件进行的翻译:
UE5 GameplayAbility 源码定义解析

炮弹创建好了,但是它没有发射器,所以我们接下来实现一下火球的发射器,在里面实现角色发射动画,以及可以在内部实现对火球的发射位置和发射朝向的设置。

首先基于之前创建的技能基类创建一个子类,命名为ProjectileSpell,我们将其作为这种炮弹类的技能的特定类型的技能类

在函数内部,我们首先添加了一个保护函数,覆盖父类的ActivateAbility,这是一个回调函数,在技能激活时,会触发此回调

回调中会返回四个参数,技能实例句柄(可以用此获取实例),激活角色的相关信息,技能激活的相关信息(手动激活还是自动激活,按键激活),激活事件以及传递的数据。

cpp 复制代码
protected:

	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;

我们在实现这里先打印一条Log用于测试运行顺序,接着在蓝图中也会打印。

cpp 复制代码
void UProjectileSpell::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
                                       const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
                                       const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

	UKismetSystemLibrary::PrintString(this, FString("在c++中打印数据"), true, true, FLinearColor::Blue, 3);
}

接着编译项目,创建一个基于ProjectileSpell的技能蓝图

在技能蓝图中,设置鼠标左键触发

在触发技能激活回调中,触发打印

需要在角色创建的时候将技能蓝图应用,所以,我们在角色蓝图中属性中设置技能应用。

运行,点击敌人,发现打印,看来在c++中输入汉字不支持,顺序就是先调用了蓝图,然后又调用的c++内的回调。

接下来,我们实现使用技能发射火球,首先在类里增加一个属性来设置火球的类,在技能激活时去实例化

cpp 复制代码
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSubclassOf<AProjectile> ProjectileClass;

我们还需要一个位置去发射火球,这个位置我们选择武器上面的一个骨骼节点作为位置。在之前的战斗接口类里面增加一个获取骨骼插槽位置的函数,这个函数需要在子类去覆盖

cpp 复制代码
	virtual FVector GetCombatSocketLocation();

然后再角色基类中,添加一个设置骨骼节点名称的变量,并覆盖这个函数

cpp 复制代码
	UPROPERTY(EditAnywhere, Category = "Combat")
	FName WeaponTipSocketName;

	virtual FVector GetCombatSocketLocation() override;

函数实现,直接调用获取骨骼接口名称的位置

cpp 复制代码
FVector ACharacterBase::GetCombatSocketLocation()
{
	return Weapon->GetSocketLocation(WeaponTipSocketName);
}

有了火球术的类,有了发射的位置,我们就可以生成火球了,接着回到ProjectileSpell里面,首先将控制的Actor转换为战斗接口

cpp 复制代码
if (ICombatInterface* CombatInterface = Cast<ICombatInterface>(GetAvatarActorFromActorInfo()))

如果转换成功,那么我们就可以通过接口函数去获取位置信息,创建一个变换变量

cpp 复制代码
		FTransform SpawnTransform;
		SpawnTransform.SetLocation(CombatInterface->GetCombatSocketLocation());
		SpawnTransform.SetRotation(GetAvatarActorFromActorInfo()->GetActorQuat());

现在,我们火球类有了,位置变换有了,最后,使用通用方法生成火球,

cpp 复制代码
		//SpawnActorDeferred将异步创建实例,在实例创建完成时,相应的数据已经应用到了实例身上
		GetWorld()->SpawnActorDeferred<AProjectile>(
			ProjectileClass,
			SpawnTransform,
			GetOwningActorFromActorInfo(),
			Cast<APawn>(GetOwningActorFromActorInfo()),
			ESpawnActorCollisionHandlingMethod::AlwaysSpawn);

如果你是异步生成的Actor,还需要调用FinishSpawning函数确保设置正确的应用到actor上面。

cpp 复制代码
//确保变换设置被正确应用
Projectile->FinishSpawning(SpawnTransform);

首先在技能上面设置需要生成的火球术的类

设置完成以后,我们需要设置使用的插槽 ,也可以在武器骨骼上面创建插槽使用。

案例里面武器顶部有个TipSocket插槽,我们将使用此位置发射火球。

记得在角色设置里面设置对应骨骼插槽名称。

到现在,我们实现了一个最基础的火球术,接着运行项目查看效果。

在技能里,我使用了蒙太奇播放角色动画,在蒙太奇播放完成结束当前技能,如果技能结束还可以再次触发。

现在,我们实现了一个最简单的通过火球术技能,效果很差,接下来,我们将接着实现火球术,并让效果看起来更合理,并在后面实现对敌人造成伤害。

相关推荐
A小辣椒20 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux