Using ElysMusicEngine from C++
While the plugin is designed primarily for Blueprint usage, all functionality is fully accessible from C++. This guide shows how to integrate the music system into your C++ code.
Project Setup
1. Add Module Dependency
Edit your project's [YourProject].Build.cs:
PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine",
"ElysMusicEngine" // Add this
});
2. Include Headers
#include "ERP_MusicSubsystem.h"
#include "ERP_MusicTypes.h"
#include "ElysMusicEngineBPLibrary.h" // Optional: For static helper functions
Accessing the Music Subsystem
The music system is a UGameInstanceSubsystem. Get it from any world context:
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
// Use the subsystem
}
Or use the helper function:
UERP_MusicSubsystem* MusicSubsystem = UElysMusicEngineBPLibrary::GetMusicSubsystem(GetWorld());
Basic Layer Control
Push a Music Layer
void AMyGameMode::StartCombatMusic()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
USoundBase* CombatMusic = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Music/Combat_Theme"));
FERP_MusicLayer Layer;
Layer.LayerName = "Combat";
Layer.Sound = CombatMusic;
Layer.Priority = 50;
Layer.LayerMode = EERP_MusicLayerMode::Replace;
Layer.VolumeMultiplier = 1.0f;
Layer.FadeInTime = 2.0f;
Layer.FadeOutTime = 1.0f;
Layer.bLooping = true;
Layer.bPersistAcrossLevels = false;
MusicSubsystem->PushMusicLayer(Layer);
}
}
Pop a Music Layer
void AMyGameMode::StopCombatMusic()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
MusicSubsystem->PopMusicLayer(FName("Combat"));
}
}
Pop All Layers
MusicSubsystem->PopAllMusicLayers();
Music Presets
Push a Preset
void AMyLevel::StartAmbientMusic()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
FGameplayTag PresetTag = FGameplayTag::RequestGameplayTag(FName("Music.Preset.Forest"));
MusicSubsystem->PushMusicPreset(PresetTag);
}
}
Pop Preset
FGameplayTag PresetTag = FGameplayTag::RequestGameplayTag(FName("Music.Preset.Forest"));
MusicSubsystem->PopMusicPreset(PresetTag);
GameplayTag-Based Control
Push Layer by Tag
void StartBossMusic()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
FGameplayTag BossTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Boss"));
MusicSubsystem->PushMusicLayerByTag(BossTag);
}
}
Pop Layer by Tag
FGameplayTag BossTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Boss"));
MusicSubsystem->PopMusicLayerByTag(BossTag);
Check if Layer is Active
bool bIsBossMusicPlaying = MusicSubsystem->IsLayerActiveByTag(BossTag);
if (bIsBossMusicPlaying)
{
UE_LOG(LogTemp, Warning, TEXT("Boss music is currently playing"));
}
Stingers (One-Shot Audio)
Play a Stinger (Immediate)
void PlayDeathStinger()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
USoundBase* DeathSound = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/Death_Sting"));
MusicSubsystem->PlayStinger(
DeathSound,
true, // bDuckMusic
0.3f, // DuckVolume
0.2f, // DuckFadeTime
0.5f, // RestoreFadeTime
ERP_EQuantization::None // Play immediately
);
}
}
Play Quantized Stinger
Play a stinger synchronized to the current music beat:
void PlayQuantizedStinger()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
USoundBase* StingerSound = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/PowerUp"));
MusicSubsystem->PlayStinger(
StingerSound,
true, // bDuckMusic
0.3f, // DuckVolume
0.2f, // DuckFadeTime
0.5f, // RestoreFadeTime
ERP_EQuantization::Beat // Wait for next beat
);
}
}
Play Stinger with Full Parameters
Pass all parameters directly for full control:
void PlayCustomStinger()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
USoundBase* VictoryStinger = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/Victory"));
MusicSubsystem->PlayStinger(
VictoryStinger,
true, // bDuckMusic
0.2f, // DuckVolume
0.3f, // DuckFadeTime
1.0f, // RestoreFadeTime
ERP_EQuantization::Bar
);
}
}
Play Stinger at Location (3D Spatial Audio)
Play a stinger at a specific world location with spatial attenuation:
void PlayExplosionStinger(FVector ExplosionLocation)
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
USoundBase* ExplosionStinger = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/Explosion_Musical"));
MusicSubsystem->PlayStingerAtLocation(
ExplosionStinger,
ExplosionLocation,
1.0f, // VolumeMultiplier
1.0f, // PitchMultiplier
500.0f, // AttenuationStartDistance (units)
true, // bDuckMusic
0.3f, // DuckVolume
0.2f, // DuckFadeTime
0.5f, // RestoreFadeTime
ERP_EQuantization::Beat // Quantize to next beat
);
}
}
Use Cases:
- Combat impact sounds with musical stingers
- Environmental events tied to 3D space
- Explosions or magical effects that affect music
- Localized audio accents in open worlds
Audio Ducking
Temporarily lower music volume for important audio (dialogue, SFX, narration):
void PlayDialogue(UAudioComponent* DialogueAudioComponent)
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
MusicSubsystem->EnableAudioDucking(
DialogueAudioComponent,
0.3f, // Duck to 30% volume
2.0f, // Fade down over 2 seconds
1.5f // Fade back up over 1.5 seconds
);
}
}
Quartz Clock Synchronization
Set BPM
void SetMusicTempo(float NewBPM)
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
MusicSubsystem->SetBPM(NewBPM);
}
}
Trigger Event on Beat
void SetupBeatListener()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
// Subscribe to beat events
FOnQuartzMetronomeEventBP BeatDelegate;
BeatDelegate.BindUFunction(this, FName("OnMusicBeat"));
MusicSubsystem->SubscribeToBeatEvent(
EQuartzCommandQuantization::Beat,
BeatDelegate,
1.0f // Every beat
);
}
}
UFUNCTION()
void OnMusicBeat(FName ClockName, EQuartzCommandQuantization QuantizationType, int32 NumBars, int32 Beat, float BeatFraction)
{
// React to music beat
UE_LOG(LogTemp, Log, TEXT("Music beat: %d"), Beat);
}
Creating Custom Music State Interface
If you want an actor to communicate its music state to MusicVolumes:
1. Implement the Interface
// MyCharacter.h
#include "ERP_MusicStateInterface.h"
UCLASS()
class AMyCharacter : public ACharacter, public IERP_MusicStateInterface
{
GENERATED_BODY()
public:
// IERP_MusicStateInterface implementation
virtual FGameplayTag GetMusicStateTag_Implementation() const override;
private:
UPROPERTY(EditAnywhere, Category = "Music")
FGameplayTag CurrentMusicState;
};
2. Implement the Function
// MyCharacter.cpp
FGameplayTag AMyCharacter::GetMusicStateTag_Implementation() const
{
return CurrentMusicState;
}
3. Update State Dynamically
void AMyCharacter::EnterCombat()
{
CurrentMusicState = FGameplayTag::RequestGameplayTag(FName("Music.Combat"));
}
void AMyCharacter::ExitCombat()
{
CurrentMusicState = FGameplayTag::RequestGameplayTag(FName("Music.Exploration"));
}
Now MusicVolumes will automatically read this actor's state when it overlaps.
Querying System State
Get Active Layers
TArray<FName> ActiveLayers = MusicSubsystem->GetActiveMusicLayers();
for (const FName& LayerName : ActiveLayers)
{
UE_LOG(LogTemp, Log, TEXT("Active layer: %s"), *LayerName.ToString());
}
Get Active Layer Count
int32 LayerCount = MusicSubsystem->GetActiveMusicLayerCount();
UE_LOG(LogTemp, Log, TEXT("Number of active layers: %d"), LayerCount);
Check if Specific Layer is Active
bool bIsCombatActive = MusicSubsystem->IsLayerActive(FName("Combat"));
Volume Control
Set Master Volume
MusicSubsystem->SetMasterVolume(0.7f); // 70% volume
Get Master Volume
float CurrentVolume = MusicSubsystem->GetMasterVolume();
Example: Complete Combat System Integration
// CombatManager.h
UCLASS()
class ACombatManager : public AActor
{
GENERATED_BODY()
public:
UFUNCTION()
void OnCombatStart();
UFUNCTION()
void OnBossAppears();
UFUNCTION()
void OnCombatEnd();
private:
UPROPERTY()
UERP_MusicSubsystem* MusicSubsystem;
};
// CombatManager.cpp
void ACombatManager::OnCombatStart()
{
MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
// Start combat music
FGameplayTag CombatTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Normal"));
MusicSubsystem->PushMusicLayerByTag(CombatTag);
// Set BPM for combat rhythm
MusicSubsystem->SetBPM(130.0f);
}
}
void ACombatManager::OnBossAppears()
{
if (MusicSubsystem)
{
// Play dramatic stinger
USoundBase* BossStinger = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/Boss_Intro"));
MusicSubsystem->PlayStinger(BossStinger, 1.2f, 1.0f);
// Transition to boss music on next bar
FGameplayTag BossTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Boss"));
MusicSubsystem->PushMusicLayerByTag(BossTag);
// Increase tempo
MusicSubsystem->SetBPM(150.0f);
}
}
void ACombatManager::OnCombatEnd()
{
if (MusicSubsystem)
{
// Pop all combat layers
FGameplayTag CombatTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Normal"));
FGameplayTag BossTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Boss"));
MusicSubsystem->PopMusicLayerByTag(BossTag);
MusicSubsystem->PopMusicLayerByTag(CombatTag);
// Victory stinger
USoundBase* VictoryStinger = LoadObject<USoundBase>(nullptr, TEXT("/Game/Audio/Stingers/Victory"));
MusicSubsystem->PlayStinger(VictoryStinger, 1.0f, 1.0f);
}
}
Best Practices
- Cache Subsystem Reference: Get it once in
BeginPlay()if using frequently - Always Check Validity:
if (MusicSubsystem)before every call - Use GameplayTags for Scalability: Easier to manage than hardcoded layer names
- Leverage Presets: Define complex music states in Data Assets, apply them from code
- Use Quartz for Rhythm Games: Beat synchronization is critical for musical gameplay
- Test in PIE with Multiple Clients: Music subsystem is per-client (no replication)
Multiplayer Considerations
Music playback is client-side only by default. Each client manages its own music independently.
If you need synchronized music events across clients:
// Server RPC to trigger music on all clients
UFUNCTION(Server, Reliable)
void Server_TriggerBossMusic();
void AMyGameMode::Server_TriggerBossMusic_Implementation()
{
Multicast_PlayBossMusic();
}
UFUNCTION(NetMulticast, Reliable)
void Multicast_PlayBossMusic();
void AMyGameMode::Multicast_PlayBossMusic_Implementation()
{
UERP_MusicSubsystem* MusicSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UERP_MusicSubsystem>();
if (MusicSubsystem)
{
FGameplayTag BossTag = FGameplayTag::RequestGameplayTag(FName("Music.Combat.Boss"));
MusicSubsystem->PushMusicLayerByTag(BossTag);
}
}
See Multiplayer Guide for detailed networking patterns.
Further Reading
- API Reference - Complete function list with Blueprint examples
- User Guide - Core concepts and workflows
- Advanced Features - MetaSound, Quartz, GameplayTags
- Multiplayer - Networking considerations