在游戏中,许多音效需要在动画恰当的时机出现,例如行走、奔跑,就需要恰好在足部落地瞬间播放。 而AnimNotify就能非常方便地处理此类问题。 AnimNotify,顾名思义就是动画通知,能在特定的动画片段播放到特定进度时“发出消息”。 目前我们的工程有前、后、左、右、左前、右前、左后、右后八向的跑动动画。 先以向前跑为例,用右键添加通知的方式,分别在右脚、左脚落地时添加了lfoot_touchground与rfoot_touchground的两个自定义通知 当然直接添加playsound通知也是可以的,能很方便地直接设置声音,但为了功能的可扩展性我们还是使用自定义通知。 然而我们如何才能获取这些通知呢?
if (mesh) { UAnimInstance* anim=mesh->GetAnimInstance(); if (anim) { TArray<const struct FAnimNotifyEvent*> AnimNotifies=anim->NotifyQueue.AnimNotifies; range(i,0,AnimNotifies.Num()) { FString NotifyName=AnimNotifies[i]->NotifyName.ToString(); /*GEngine->A/ddOnScreenDebugMessage (-1, 5.f, FColor::Red, FString::FromInt(i)+" "+ NotifyName);*/ if (NotifyName=="lfoot_touchground") { audio_lfoot->SetSound(sb_walks[0]); audio_lfoot->Play(); } else if (NotifyName == "rfoot_touchground") { audio_rfoot->SetSound(sb_walks[1]); audio_rfoot->Play(); } else { } } } }
  没错,就是这么简单anim->NotifyQueue即为动画通知队列,AnimNotifies就能获取当前所有通知事件,上述代码去掉注释即可打印当前帧所有动画通知名称。 为了实现播放音效,我们还需要绑定在左右脚的AudioComponent,如果当前通知队列中有对应的通知,就先SetSound设置将要播放的声音资源后再Play。 我把所有需要绑定在Chracter的Mesh上的AudioComponent“打包装入“了一个叫AudioController的类,在Character的构造函数中进行了AudioController的构造,并将AudioController中的各音效组件绑定到对应的socket上。   AudioController.h文件
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "Components/ActorComponent.h" #include "Components/AudioComponent.h" #include "Components/SkeletalMeshComponent.h" #include "AudioController.generated.h" UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class MYSOUNDANDEFFECTS_API UAudioController : public UActorComponent { GENERATED_BODY() public: // Sets default values for this component's properties UAudioController(); //UAudioController(USkeletalMeshComponent* mesh_); protected: // Called when the game starts virtual void BeginPlay() override; public: // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; UPROPERTY(Category = Audio, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) UAudioComponent* audio_lfoot = NULL; UPROPERTY(Category = Audio, EditDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) FString bonename_lfoot = "Bip01-L-Foot"; UPROPERTY(Category = Audio, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) UAudioComponent* audio_rfoot = NULL; UPROPERTY(Category = Audio, EditDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) FString bonename_rfoot = "Bip01-R-Foot"; USkeletalMeshComponent* mesh = NULL; void my_init(USkeletalMeshComponent* mesh_); UPROPERTY(Category = Audio, EditDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) TArray<class USoundBase*> sb_walks; };
AudioController.cpp文件
// Fill out your copyright notice in the Description page of Project Settings. #include "MySoundAndEffects.h" #include "Animation/AnimInstance.h" #include "AudioController.h" // Sets default values for this component's properties UAudioController::UAudioController() { // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features // off to improve performance if you don't need them. PrimaryComponentTick.bCanEverTick = true; // ... audio_lfoot = CreateDefaultSubobject<UAudioComponent>("audio_lfoot"); audio_rfoot = CreateDefaultSubobject<UAudioComponent>("audio_rfoot"); } //UAudioController::UAudioController(USkeletalMeshComponent* mesh_) //{ // // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features // // off to improve performance if you don't need them. // PrimaryComponentTick.bCanEverTick = true; // // // ... // // // //} void UAudioController::my_init(USkeletalMeshComponent* mesh_) { this->mesh = mesh_; //UAudioController(); audio_lfoot->SetupAttachment(mesh_, FName(*bonename_lfoot)); audio_rfoot->SetupAttachment(mesh_, FName(*bonename_rfoot)); } // Called when the game starts void UAudioController::BeginPlay() { Super::BeginPlay(); // ... } // Called every frame void UAudioController::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); // ... if (mesh) { UAnimInstance* anim=mesh->GetAnimInstance(); if (anim) { TArray<const struct FAnimNotifyEvent*> AnimNotifies=anim->NotifyQueue.AnimNotifies; range(i,0,AnimNotifies.Num()) { FString NotifyName=AnimNotifies[i]->NotifyName.ToString(); /*GEngine->A/ddOnScreenDebugMessage (-1, 5.f, FColor::Red, FString::FromInt(i)+" "+ NotifyName);*/ if (NotifyName=="lfoot_touchground") { audio_lfoot->SetSound(sb_walks[0]); audio_lfoot->Play(); } else if (NotifyName == "rfoot_touchground") { audio_rfoot->SetSound(sb_walks[1]); audio_rfoot->Play(); } else { } } } } else { throw std::exception("mesh not exist!!"); } }
  还有character类中audiocontroller的定义和创建:
AAimPraticeHuman::AAimPraticeHuman() { #ifdef AudioController_h audiocontroller = CreateDefaultSubobject<UAudioController>("audiocontroller"); audiocontroller->my_init(GetMesh()); #endif }
 
UPROPERTY(Category = Audio, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
  class UAudioController* audiocontroller = NULL;
  最后就是音效导入,看似简单其实有很多细节。 建议直接拖入wav格式文件,如果导入失败应该是wav文件具体参数的问题。 (我用Audition把一长串的跑步音效分割出了两声脚碰地的声音,直接导入ue4报错,然而先用格式工厂转一下就ok了)   当然现在的音效还不能直接用于游戏脚步,还需要先设置并发和立体效果。 Concurrency项勾选override即可,使用”先远后旧“的并发停止策略问题也不大。 Attenuation项我新建了一个叫SceneSoundAttenuation蓝图,设置如下: 这些选项看字面都比较好理解,3D Stereo Spread的意思其实就是左右耳间距。   最后别忘了设置character蓝图的sb_walks数组的值,以及左右脚的socketname     大功告成啦! 具体效果。。。反正你们也听不到。。。   ------------------------------------------------ 下期预告:美妙的IK(反向动力学)