前面,我们将火球的转向和人物的转向问题解决了,火球术可以按照我们的想法朝向目标发射。现在,我们解决接下来的问题,在角色释放火球术时,会产生释放音效,火球也会产生对应的音效,在火球击中目标时,我们需要一个爆炸特效以及爆炸音效。
接下来,我们将一步步实现它们。
添加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();
}