I'm powering my computer with a bicycle..., just show me the code!: (copyable code-blocks below)
The final result of our pitch modulated Sound Cue
Sound cue refresher (skip me if you're familiar with a sound cue)
A sound cue represents an encapsulated group of logic around a sound or sounds. A sound cue can switch between playing multiple sounds, apply effects to the sound based on input, etc. To the engine, a sound cue is a singular object/sound, but may contain quite a lot of complexity, and mix together many different sounds. Sound cue's contain parameters just like materials contain parameters, and these parameters can be set during runtime to manipulate how the sound-cue behaves. In our case we are going to modify a modulation parameter to control the pitch of a looping sound cue.
Setup our sound cue in the editor (skip me if you have a sound cue w/parameters)
Step 1.) Create a new sound cue (content browser - Add New - sounds - sound cue)
Step 3.) Add a Wave Player to the Sound Cue.
Step 4.) Add a Continuous Modulator to the Sound Cue
Step 6.) Click on the Wave Player to configure it, and set what sound it plays (.wav file)
In our case, I want the sound to loop continiously, so I check 'Looping'.
Step 7.) Configure the modulator
It's important the parameter value is set, in my case I only care about manipulating pitch at runtime, and not volume, however you can set parameters for both if you need to manipulate both during runtime. We will use this parameter name 'pitch' later in our code.
Step 8.) My real configuration for my propeller
Step 9.) Save the cue and close it...
Code Configuration
First, I'm going to place all my logic in a Pawn class that inherits from AActor... you can theoretically place your sound logic anywhere, but this example will include a pawn.
Step 1.) We need to setup some fields in our header to hold references to our objects. Ensure you use the UPROPERTY() macros, as this prevents unreal from garbage collecting the object before it should.
Create a pawn class and in your header file ensure these properties and methods are defined.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
UCLASS() | |
class FLIGHTMODEL_API AAircraftPawn : public APawn | |
{ | |
GENERATED_BODY() | |
public: | |
// Sets default values for this pawn's properties | |
AAircraftPawn(); | |
// Called when the game starts or when spawned | |
virtual void BeginPlay() override; | |
virtual void PostInitializeComponents() override; | |
// Called every frame | |
virtual void Tick(float DeltaSeconds) override; | |
private: | |
UPROPERTY(BlueprintReadOnly, Category = "Audio") | |
USoundCue* propellerAudioCue; | |
UPROPERTY(BlueprintReadOnly, Category = "Audio") | |
USoundCue* propellerStartupCue; | |
UPROPERTY(BlueprintReadOnly, Category = "Audio") | |
UAudioComponent* propellerAudioComponent; | |
}; |
Step 2.) We need to configure our constructor to get references to and configure our sound objects.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Sets default values | |
AAircraftPawn::AAircraftPawn() | |
{ | |
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it. | |
PrimaryActorTick.bCanEverTick = true; | |
// Load our Sound Cue for the propeller sound we created in the editor... note your path may be different depending | |
// on where you store the asset on disk. | |
static ConstructorHelpers::FObjectFinder<USoundCue> propellerCue(TEXT("'/Game/airplane-engine.airplane-engine'")); | |
// Store a reference to the Cue asset - we'll need it later. | |
propellerAudioCue = propellerCue.Object; | |
// Create an audio component, the audio component wraps the Cue, and allows us to ineract with | |
// it, and its parameters from code. | |
propellerAudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("PropellerAudioComp")); | |
// I don't want the sound playing the moment it's created. | |
propellerAudioComponent->bAutoActivate = false; // don't play the sound immediately. | |
// I want the sound to follow the pawn around, so I attach it to the Pawns root. | |
propellerAudioComponent->AttachParent = RootComponent; | |
// I want the sound to come from slighty in front of the pawn. | |
propellerAudioComponent->SetRelativeLocation(FVector(100.0f, 0.0f, 0.0f)); | |
} |
Step 3.) We need to add our Sound Cue to our Audio Component, which you should avoid doing in the constructor, so we'll perform the same action in PostInitializeComponents()
Step 4.) When our pawn is spawned (-1 for rhyming) we need to have it start playing the Cue audio. You may want to do this based on some other event, but for my needs I want the sound to start playing on BeginPlay
Step 5.) Each frame we need to update the sound parameter pitch based on our engines speed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void AAircraftPawn::PostInitializeComponents() | |
{ | |
Super::PostInitializeComponents(); | |
if (propellerAudioCue->IsValidLowLevelFast()) { | |
propellerAudioComponent->SetSound(propellerAudioCue); | |
} | |
} |
Step 4.) When our pawn is spawned (-1 for rhyming) we need to have it start playing the Cue audio. You may want to do this based on some other event, but for my needs I want the sound to start playing on BeginPlay
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Called when the game starts or when spawned | |
void AAircraftPawn::BeginPlay() | |
{ | |
Super::BeginPlay(); | |
// Note because the Cue Asset is set to loop the sound, | |
// once we start playing the sound, it will play | |
// continiously... | |
// You can fade the sound in... | |
float startTime = 9.f; | |
float volume = 1.0f; | |
float fadeTime = 1.0f; | |
propellerAudioComponent->FadeIn(fadeTime, volume, startTime); | |
// Or you can start playing the sound immediately. | |
propellerAudioComponent->Play(); | |
} |
Step 5.) Each frame we need to update the sound parameter pitch based on our engines speed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Called every frame | |
void AAircraftPawn::Tick(float DeltaTime) | |
{ | |
// Modulate the pitch of the sound cue by passing in the RPM of the aircraft propeller | |
// Notice we reference the parameter we set in the Cue Object here: 'pitch'. | |
float propRpm = propeller->lastEngineRpm * propeller->reductionRatio; | |
propellerAudioComponent->SetFloatParameter(FName("pitch"), propRpm); | |
} |
Hi,
ReplyDeleteThanks for the detailed review. Clearly understandable and well explained. Helped me a lot.
Thanks.
Hi,
ReplyDeleteOnce i get to line 17 "propellerAudioComponent->bAutoActivate = false;" i get an error "Pointer to incomplete class type is not allowed"
Do you know how to solve this issue
Thanks.
Unknown: You need to include the header file so it knows what an audiocomponent class is:
ReplyDelete#include "Components/AudioComponent.h"
Also, AttachParent is now private, use this instead:
->SetupAttachment(GetRootComponent());
Ben: Great tutorial, very useful.
SetupAttachment(GetRootComponent()); is not working.
Deleteshould look like this
DeletepropellerAudioComponent->SetupAttachment(RootComponent);
This isn't working at my project :(
ReplyDeleteThe line: "propellerAudioComponent = CreateDefaultSubobject(TEXT("PropellerAudioComp"));"
generates this error: No object initializer found during construction. But I don't know why. Can you help me?
This comment has been removed by the author.
DeleteThis comment has been removed by the author.
DeleteThank you so much for this step-by-step explanation. Really helped me out!
ReplyDeleteBest UE4 tutorial I've ever seen -- solved my audio looping problem. Thanks so much!
ReplyDeleteRespect and that i have a dandy offer you: What Home Renovation Shows Are On Netflix house and renovation show
ReplyDelete