Hey, I’m trying to play a random song in my game’s soundtrack by using a random node inside a sound cue, but in a packaged game, it starts off with the same song every time.
bump
I’m not sure what the “correct” way to do this is (it might just be a checkbox somewhere), but you could try using the switch node in the sound cue and setting it randomly in blueprint.
This node:
Scroll down this page to learn how to use.
I ended up creating a cpp actor to acheive a random music playlist:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MusicPlaylist.generated.h"
UCLASS()
class STARSURFER_API AMusicPlaylist : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMusicPlaylist();
void ResetQueue();
void PlayNextSong();
void ShuffleArray(TArray<class USoundCue*>& myArray);
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Music")
TArray<USoundCue*> Playlist;
TQueue<USoundCue*> PlaylistQueue;
float SongTimer = 0;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
#include "MusicPlaylist.h"
#include "Sound/SoundCue.h"
#include "Kismet/GameplayStatics.h"
// Sets default values
AMusicPlaylist::AMusicPlaylist()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bTickEvenWhenPaused = true;
}
void AMusicPlaylist::ResetQueue()
{
PlaylistQueue.Empty();
ShuffleArray(Playlist);
for (USoundCue* Cue : Playlist)
{
PlaylistQueue.Enqueue(Cue);
}
}
void AMusicPlaylist::PlayNextSong()
{
USoundCue* Sound = *PlaylistQueue.Peek();
SongTimer = Sound->Duration;
UGameplayStatics::PlaySound2D(GWorld, Sound);
PlaylistQueue.Pop();
}
// Called when the game starts or when spawned
void AMusicPlaylist::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AMusicPlaylist::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
SongTimer-=DeltaTime;
if (SongTimer < 0.f)
{
if (PlaylistQueue.IsEmpty())
{
ResetQueue();
}
PlayNextSong();
}
}
void AMusicPlaylist::ShuffleArray(TArray<USoundCue*>& myArray)
{
if (myArray.Num() > 0)
{
int32 LastIndex = myArray.Num() - 1;
for (int32 i = 0; i <= LastIndex; ++i)
{
int32 Index = FMath::RandRange(i, LastIndex);
if (i != Index)
{
myArray.Swap(i, Index);
}
}
}
}
For anyone else that reaches this page wondering about the random node not working. As Detach789 mentioned you can achieve the same results with the switch node. It just requires a little more setup. It works in both blueprint and C++.
First create your sound cue with the switch.
Then in either blueprint or c++ execute some logic on the audio player such as an AudioComponent.
Blueprint:
C++
Include module “AudioExtensions” in your project.build.cs file inside the PublicDependencyModuleNames.AddRange sub section.
Include this header in your .cpp file:
#include "AudioParameterControllerInterface.h"
Then in your code get a reference to the interface from the audio component to reference SetIntParameter().
It will look like this:
if (!MenuMusic)
{
return;
}
AudioComponent->SetSound(MenuMusic);
if (IAudioParameterControllerInterface* Interface = Cast<IAudioParameterControllerInterface>(AudioComponent))
{
Interface->SetIntParameter("MenuMusic", FMath::RandRange(0, 18));
}
AudioComponent->Play(0.0f);
Just be very careful with the RandRange function. Be sure not to put values that are outside the range of the switch in your cue.
** Note **
After testing this, I found that if you are using a looper node in the sound cue it will simply loop the same index in the switch over and over. So, in this case you want to remove the looper and bind an event from the audio component called OnAudioFinished()
This can be done in both blueprint and c++. The blueprint way is the normal way you bind regular delegates.
In c++ it looks like this:
In your class header file, create a UFUNCTION() function that will bind to the audio component delegate:
.h
UFUNCTION()
void OnAudioFinished()
In your cpp file add the below logic to begin play and the logic for changing the switch parameter and playing the audio component again inside your new bound function:
.cpp
void AYourActor::BeginPlay()
{
AudioComponent->OnAudioFinished.AddDynamic(this, &AYourActor::OnAudioFinished();
}
void AYourActor::OnAudioFinished()
{
if (IAudioParameterControllerInterface* Interface = Cast<IAudioParameterControllerInterface>(AudioComponent))
{
if (bIsMainMenuActive)
{
Interface->SetIntParameter("MenuMusic", FMath::RandRange(MenuAudioIndexMin, MenuAudioIndexMax));
AudioComponent->Play(0.0f);
}
else
{
Interface->SetIntParameter("GameplayMusic", FMath::RandRange(GameplayAudioIndexMin, GameplayAudioIndexMax));
AudioComponent->Play(0.0f);
}
}
}
In my code I even made a few UPROPERTY variables in my player controller that I use to define the min and max values of sound cue waves.