40. UE5 RPG给火球术增加特效和音效

前面,我们将火球的转向和人物的转向问题解决了,火球术可以按照我们的想法朝向目标发射。现在,我们解决接下来的问题,在角色释放火球术时,会产生释放音效,火球也会产生对应的音效,在火球击中目标时,我们需要一个爆炸特效以及爆炸音效。

接下来,我们将一步步实现它们。

添加C++依赖

要在代码里面能够使用特效,我们需要在Build.cs里面增加它的依赖,在UE5里面,我们已经修改为使用Niagara粒子系统了。

cpp 复制代码
PrivateDependencyModuleNames.AddRange(new string[] { "GameplayTags", "GameplayTasks", "NavigationSystem", "Niagara" });

添加击中特效和音效

我们增加两个可以设置的属性,分别用于设置击中特效和击中音效

cpp 复制代码
UPROPERTY(EditAnywhere)
TObjectPtr<UNiagaraSystem> ImpactEffect;

UPROPERTY(EditAnywhere)
TObjectPtr<USoundBase> ImpactSound;

增加一个私有函数,用于播放相关特效和音效

cpp 复制代码
	void PlayImpact() const;

在函数实现这里,调用对应函数去播放

cpp 复制代码
void AProjectile::PlayImpact() const
{
	//播放音效
	UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, GetActorLocation(), FRotator::ZeroRotator);
	//播放粒子特效
	UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, ImpactEffect, GetActorLocation());
}

接下来增加一个变量,用于判断当前火球是否和其它物体产生了碰撞, 这个变量设置为私有即可

cpp 复制代码
bool bHit;

接下里就是设置重叠回调,在重叠回调里面,我们要调用播放函数,然后判断对当前对象是否拥有权威性,如果拥有权威性,在触发重叠后,销毁它,如果没有,将bHit设置为true,代表它已经触发了重叠事件,并且已经播放了几种特效,但是没有对火球的控制权,无法自身直接销毁。

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

	//在重叠后,销毁自身
	if(HasAuthority())
	{
		Destroy();
	}
	else
	{
		//如果对actor没有权威性,将bHit设置为true,证明当前已经播放了击中特效
		bHit = true;
	}
}

接着我们要重写销毁事件

cpp 复制代码
virtual void Destroyed() override;

在销毁事件的实现这里,如果没有对火球的权威性控制权,并且现在还没有触发碰撞体的重叠事件,那么我们在销毁时播放击中特效。

cpp 复制代码
void AProjectile::Destroyed()
{
	//如果没有权威性,并且bHit没有修改为true,证明当前没有触发Overlap事件,在销毁前播放击中特效
	if(!bHit && !HasAuthority())
	{
		PlayImpact();
	}
	Super::Destroyed();
}

编译代码,打开UE进行测试,首先将配置项设置上对应的Niagara粒子特效和音效

为了防止技能太快在没有创建火球之前被销毁,我们延迟0.5秒结束技能

运行,选择人物释放技能击中

添加技能释放音效

火球的击中特效我们制作出来了 ,接下来,我们实现一下技能释放时的音效,这个音效需要在蒙太奇里面添加,我们在角色释放技能的那一帧,给一个播放音效的通知。

打开释放技能的蒙太奇,在标准位置添加一个播放音效的通知

选中添加的通知,可以在右侧调整相关参数,我这里就添加一个声音

增加火球移动音效

现在释放火球有了音效,并且击中时有击中音效和击中特效,接下来我们添加一下火球在移动中,发出的音效,它是一个循环音效。

首先打开代码编辑器,添加一个用于添加音效文件的变量,然后添加一个存储生产的循环音效的变量,由于它是一直循环播放的,所以我们需要在Actor被销毁时,一同销毁掉这个音效组件。

cpp 复制代码
//移动循环音效
UPROPERTY(EditAnywhere)
TObjectPtr<USoundBase> LoopingSound;

//储存循环音效的变量,后续用于删除
UPROPERTY()
TObjectPtr<UAudioComponent> LoopingSoundComponent;

在Actor的事件开始回调时,添加一个附加在根组件的音效,这样可以让音效跟随组件移动。

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

	//添加一个音效,并附加到根组件上面,在技能移动时,声音也会跟随移动
	LoopingSoundComponent = UGameplayStatics::SpawnSoundAttached(LoopingSound, GetRootComponent());
}

如果我们添加的是循环播放的音效,它无法被停止,所以,我们将在火球术结束时,将音效组件暂停。创建的附加音效默认会在音效播放完成或者暂停后自动销毁。

cpp 复制代码
void AProjectile::PlayImpact() const
{
	//播放声效
	UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, GetActorLocation(), FRotator::ZeroRotator);
	//播放粒子特效
	UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, ImpactEffect, GetActorLocation());
	//将音乐停止后会自动销毁
	LoopingSoundComponent->Stop();
}

接着编译打开UE,我们找到对应的音频文件,将其设置为默认循环,这样在停止之前,它会一直播放

然后将音效文件设置上去

设置火球的存在时间

如果火球没有击中任何内容,它将会一直存在,所以,我们需要实现设置它的存在时间,如果它在一定时间内没有击中任何物体,那它将会自行销毁。

首先创建一个私有属性,可以在面板修改它的存在时间,单位秒。

cpp 复制代码
private:

	//此物体的存在时间
	UPROPERTY(EditDefaultsOnly)
	float LifeSpan = 15.f;

然后在事件开始时设置它的生存时间,如果值设置为0,它的生存时间为无限

cpp 复制代码
void AProjectile::BeginPlay()
{
	Super::BeginPlay();

	//设置此物体的存在时间
	SetLifeSpan(LifeSpan);
	
	...
}

完整代码

Projectile.h

cpp 复制代码
// 版权归暮志未晚所有。

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Projectile.generated.h"

class UNiagaraSystem;
class UProjectileMovementComponent;
class USphereComponent;

UCLASS()
class AURA_API AProjectile : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AProjectile();

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UProjectileMovementComponent> ProjectileMovement;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	virtual void Destroyed() override;

	UFUNCTION()
	void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
private:

	//此物体的存在时间
	UPROPERTY(EditDefaultsOnly)
	float LifeSpan = 15.f;

	void PlayImpact() const;

	bool bHit;

	//碰撞球
	UPROPERTY(VisibleAnywhere)
	TObjectPtr<USphereComponent> Sphere;

	//击中粒子特效
	UPROPERTY(EditAnywhere)
	TObjectPtr<UNiagaraSystem> ImpactEffect;

	//击中音效
	UPROPERTY(EditAnywhere)
	TObjectPtr<USoundBase> ImpactSound;

	//移动循环音效
	UPROPERTY(EditAnywhere)
	TObjectPtr<USoundBase> LoopingSound;

	//储存循环音效的变量,后续用于删除
	UPROPERTY()
	TObjectPtr<UAudioComponent> LoopingSoundComponent;
};

Projectile.cpp

cpp 复制代码
// 版权归暮志未晚所有。


#include "Actor/Projectile.h"

#include "NiagaraFunctionLibrary.h"
#include "Components/AudioComponent.h"
#include "Components/SphereComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Kismet/GameplayStatics.h"

// Sets default values
AProjectile::AProjectile()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;
	bReplicates = true; //服务器负责计算并更新Actor的状态,然后通过网络将这些更新复制到所有连接的客户端上。

	//初始化碰撞体
	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类型物体产生重叠事件

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

// Called when the game starts or when spawned
void AProjectile::BeginPlay()
{
	Super::BeginPlay();

	//设置此物体的存在时间
	SetLifeSpan(LifeSpan);
	
	Sphere->OnComponentBeginOverlap.AddDynamic(this, &AProjectile::OnSphereOverlap);

	//添加一个音效,并附加到根组件上面,在技能移动时,声音也会跟随移动
	LoopingSoundComponent = UGameplayStatics::SpawnSoundAttached(LoopingSound, GetRootComponent());
}

void AProjectile::Destroyed()
{
	//如果没有权威性,并且bHit没有修改为true,证明当前没有触发Overlap事件,在销毁前播放击中特效
	if(!bHit && !HasAuthority())
	{
		PlayImpact();
	}
	Super::Destroyed();
}

void AProjectile::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
                                  UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	PlayImpact();

	//在重叠后,销毁自身
	if(HasAuthority())
	{
		Destroy();
	}
	else
	{
		//如果对actor没有权威性,将bHit设置为true,证明当前已经播放了击中特效
		bHit = true;
	}
}

void AProjectile::PlayImpact() const
{
	//播放声效
	UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, GetActorLocation(), FRotator::ZeroRotator);
	//播放粒子特效
	UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, ImpactEffect, GetActorLocation());
	//将音乐停止后会自动销毁
	if(LoopingSoundComponent) LoopingSoundComponent->Stop();
}
相关推荐
UTwelve7 小时前
【UE5】使用基元数据对材质传参,从而避免新建材质实例
ue5·材质
UTwelve7 小时前
【UE5】在材质中计算模型在屏幕上的比例
ue5·材质
心怀梦想的咸鱼1 天前
UE5 第一人称射击项目学习(二)
学习·ue5
暮志未晚Webgl1 天前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
心怀梦想的咸鱼1 天前
UE5 第一人称射击项目学习(完结)
学习·ue5
暮志未晚Webgl2 天前
110. UE5 GAS RPG 实现玩家角色数据存档
java·前端·ue5
Zhichao_973 天前
【UE5】Slider控件样式
ue5
流行易逝3 天前
23.UE5删除存档
ue5
心怀梦想的咸鱼3 天前
UE5 第一人称射击项目学习(三)
学习·ue5
流行易逝3 天前
22.UE5控件切换器,存档列表,
ue5