icon안동민 개발노트

오디오 컴포넌트 프로그래밍


 언리얼 엔진의 오디오 컴포넌트는 게임 내 사운드를 세밀하게 제어할 수 있는 강력한 도구입니다.

 이 절에서는 C++를 사용하여 오디오 컴포넌트를 프로그래밍하고 제어하는 방법을 살펴보겠습니다.

UAudioComponent의 주요 메서드

 UAudioComponent 클래스는 다양한 메서드와 속성을 제공합니다.

UCLASS()
class MYGAME_API AAudioManager : public AActor
{
    GENERATED_BODY()
 
public:
    AAudioManager();
 
    UPROPERTY(VisibleAnywhere, Category = "Audio")
    UAudioComponent* AudioComponent;
 
    UFUNCTION(BlueprintCallable, Category = "Audio")
    void PlaySound();
 
    UFUNCTION(BlueprintCallable, Category = "Audio")
    void StopSound();
 
    UFUNCTION(BlueprintCallable, Category = "Audio")
    void SetVolume(float NewVolume);
};
 
AAudioManager::AAudioManager()
{
    AudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("AudioComponent"));
    RootComponent = AudioComponent;
}
 
void AAudioManager::PlaySound()
{
    if (AudioComponent)
    {
        AudioComponent->Play();
    }
}
 
void AAudioManager::StopSound()
{
    if (AudioComponent)
    {
        AudioComponent->Stop();
    }
}
 
void AAudioManager::SetVolume(float NewVolume)
{
    if (AudioComponent)
    {
        AudioComponent->SetVolumeMultiplier(NewVolume);
    }
}

오디오 컴포넌트의 동적 생성 및 첨부

 런타임에 오디오 컴포넌트를 동적으로 생성하고 액터에 첨부할 수 있습니다.

void AMyActor::CreateDynamicAudioComponent()
{
    UAudioComponent* NewAudioComponent = NewObject<UAudioComponent>(this, TEXT("DynamicAudioComponent"));
    if (NewAudioComponent)
    {
        NewAudioComponent->SetupAttachment(RootComponent);
        NewAudioComponent->RegisterComponent();
        
        // 사운드 설정
        USoundBase* Sound = LoadObject<USoundBase>(nullptr, TEXT("/Game/Sounds/MySoundCue"));
        if (Sound)
        {
            NewAudioComponent->SetSound(Sound);
        }
    }
}

런타임에 오디오 속성 조절

 볼륨, 피치, 필터 등의 오디오 속성을 실시간으로 조절할 수 있습니다.

void AMyActor::UpdateAudioProperties(float DeltaTime)
{
    if (AudioComponent)
    {
        // 볼륨 조절
        float NewVolume = FMath::Sin(GetWorld()->GetTimeSeconds()) * 0.5f + 0.5f;
        AudioComponent->SetVolumeMultiplier(NewVolume);
 
        // 피치 조절
        float NewPitch = FMath::Clamp(AudioComponent->PitchMultiplier + DeltaTime * 0.1f, 0.5f, 2.0f);
        AudioComponent->SetPitchMultiplier(NewPitch);
 
        // 로우패스 필터 조절
        float NewLPF = FMath::Clamp(AudioComponent->LowPassFilterFrequency + DeltaTime * 100.0f, 0.0f, 20000.0f);
        AudioComponent->SetLowPassFilterFrequency(NewLPF);
    }
}

오디오 컴포넌트 이벤트 처리

 오디오 컴포넌트의 다양한 이벤트를 처리할 수 있습니다.

void AMyActor::SetupAudioEvents()
{
    if (AudioComponent)
    {
        AudioComponent->OnAudioPlaybackPercent.AddDynamic(this, &AMyActor::OnAudioPlaybackProgress);
        AudioComponent->OnAudioFinished.AddDynamic(this, &AMyActor::OnAudioFinished);
    }
}
 
void AMyActor::OnAudioPlaybackProgress(const UAudioComponent* AudioComponent, const USoundWave* PlayingSoundWave, float PlaybackPercent)
{
    UE_LOG(LogAudio, Log, TEXT("Audio Playback Progress: %f"), PlaybackPercent);
}
 
void AMyActor::OnAudioFinished()
{
    UE_LOG(LogAudio, Log, TEXT("Audio Playback Finished"));
    // 다음 트랙 재생 또는 다른 로직 실행
}

멀티트랙 오디오 시스템 구현

 여러 오디오 트랙을 관리하고 전환하는 시스템을 구현할 수 있습니다.

UCLASS()
class MYGAME_API UMultiTrackAudioManager : public UObject
{
    GENERATED_BODY()
 
public:
    void Initialize(AActor* Owner);
    void SwitchToTrack(int32 TrackIndex);
 
private:
    UPROPERTY()
    TArray<UAudioComponent*> AudioTracks;
 
    int32 CurrentTrackIndex;
};
 
void UMultiTrackAudioManager::Initialize(AActor* Owner)
{
    // 여러 오디오 트랙 초기화
    for (int32 i = 0; i < 4; ++i)
    {
        UAudioComponent* NewTrack = NewObject<UAudioComponent>(Owner);
        NewTrack->SetupAttachment(Owner->GetRootComponent());
        NewTrack->RegisterComponent();
        AudioTracks.Add(NewTrack);
    }
}
 
void UMultiTrackAudioManager::SwitchToTrack(int32 TrackIndex)
{
    if (AudioTracks.IsValidIndex(TrackIndex) && TrackIndex != CurrentTrackIndex)
    {
        if (AudioTracks[CurrentTrackIndex])
        {
            AudioTracks[CurrentTrackIndex]->FadeOut(0.5f, 0.0f);
        }
        
        AudioTracks[TrackIndex]->FadeIn(0.5f);
        CurrentTrackIndex = TrackIndex;
    }
}

인터랙티브 음악 시스템 구축

 게임 상황에 따라 동적으로 변화하는 인터랙티브 음악 시스템을 구현할 수 있습니다.

UCLASS()
class MYGAME_API UInteractiveMusicSystem : public UObject
{
    GENERATED_BODY()
 
public:
    void Initialize(AActor* Owner);
    void UpdateMusicState(EGameState NewState);
 
private:
    UPROPERTY()
    TMap<EGameState, UAudioComponent*> MusicLayers;
 
    void CrossfadeLayers(UAudioComponent* FadeOutLayer, UAudioComponent* FadeInLayer);
};
 
void UInteractiveMusicSystem::Initialize(AActor* Owner)
{
    // 각 게임 상태에 대한 음악 레이어 초기화
    for (EGameState State : TEnumRange<EGameState>())
    {
        UAudioComponent* NewLayer = NewObject<UAudioComponent>(Owner);
        NewLayer->SetupAttachment(Owner->GetRootComponent());
        NewLayer->RegisterComponent();
        MusicLayers.Add(State, NewLayer);
    }
}
 
void UInteractiveMusicSystem::UpdateMusicState(EGameState NewState)
{
    UAudioComponent* NewLayer = MusicLayers[NewState];
    UAudioComponent* OldLayer = MusicLayers[CurrentState];
    
    if (NewLayer != OldLayer)
    {
        CrossfadeLayers(OldLayer, NewLayer);
    }
}
 
void UInteractiveMusicSystem::CrossfadeLayers(UAudioComponent* FadeOutLayer, UAudioComponent* FadeInLayer)
{
    if (FadeOutLayer && FadeInLayer)
    {
        FadeOutLayer->FadeOut(1.0f, 0.0f);
        FadeInLayer->FadeIn(1.0f);
    }
}

오디오 컴포넌트의 성능 최적화 기법

 오디오 컴포넌트의 성능을 최적화하기 위한 전략

  1. 오디오 풀링 시스템 구현
  2. 거리 기반 컬링
  3. 오디오 스트리밍 활용
UCLASS()
class MYGAME_API UAudioOptimizer : public UObject
{
    GENERATED_BODY()
 
public:
    UAudioComponent* GetPooledAudioComponent(AActor* Owner);
    void ReturnToPool(UAudioComponent* AudioComp);
    void UpdateDistanceBasedCulling(const FVector& ListenerLocation);
 
private:
    UPROPERTY()
    TArray<UAudioComponent*> AudioPool;
 
    UPROPERTY()
    TArray<UAudioComponent*> ActiveAudioComponents;
};
 
UAudioComponent* UAudioOptimizer::GetPooledAudioComponent(AActor* Owner)
{
    if (AudioPool.Num() > 0)
    {
        UAudioComponent* AudioComp = AudioPool.Pop();
        ActiveAudioComponents.Add(AudioComp);
        return AudioComp;
    }
    else
    {
        UAudioComponent* NewAudioComp = NewObject<UAudioComponent>(Owner);
        NewAudioComp->RegisterComponent();
        ActiveAudioComponents.Add(NewAudioComp);
        return NewAudioComp;
    }
}
 
void UAudioOptimizer::UpdateDistanceBasedCulling(const FVector& ListenerLocation)
{
    const float CullDistance = 1000.0f;
    for (UAudioComponent* AudioComp : ActiveAudioComponents)
    {
        float Distance = FVector::Dist(AudioComp->GetComponentLocation(), ListenerLocation);
        AudioComp->SetComponentTickEnabled(Distance <= CullDistance);
    }
}

멀티플레이어 게임에서의 오디오 동기화

 네트워크 게임에서 오디오를 동기화하는 방법

UCLASS()
class MYGAME_API ANetworkedAudioManager : public AActor
{
    GENERATED_BODY()
 
public:
    UFUNCTION(NetMulticast, Reliable)
    void MulticastPlaySound(USoundBase* Sound, FVector Location);
 
    UFUNCTION(Server, Reliable, WithValidation)
    void ServerRequestPlaySound(USoundBase* Sound, FVector Location);
};
 
void ANetworkedAudioManager::MulticastPlaySound_Implementation(USoundBase* Sound, FVector Location)
{
    UGameplayStatics::PlaySoundAtLocation(this, Sound, Location);
}
 
void ANetworkedAudioManager::ServerRequestPlaySound_Implementation(USoundBase* Sound, FVector Location)
{
    MulticastPlaySound(Sound, Location);
}
 
bool ANetworkedAudioManager::ServerRequestPlaySound_Validate(USoundBase* Sound, FVector Location)
{
    return true; // 필요에 따라 유효성 검사 로직 구현
}

게임 상태에 따른 동적 오디오 믹싱

 게임 상태에 따라 동적으로 오디오 믹싱을 조절할 수 있습니다.

UCLASS()
class MYGAME_API UDynamicAudioMixer : public UObject
{
    GENERATED_BODY()
 
public:
    void UpdateAudioMix(EGameState NewState);
 
private:
    UPROPERTY()
    USoundMix* CombatMix;
 
    UPROPERTY()
    USoundMix* ExplorationMix;
};
 
void UDynamicAudioMixer::UpdateAudioMix(EGameState NewState)
{
    switch (NewState)
    {
        case EGameState::Combat:
            UGameplayStatics::PushSoundMixModifier(GetWorld(), CombatMix);
            break;
        case EGameState::Exploration:
            UGameplayStatics::PushSoundMixModifier(GetWorld(), ExplorationMix);
            break;
        // 기타 상태 처리
    }
}

협업을 위한 오디오 시스템 설계

 사운드 디자이너와 효과적으로 협업하기 위해서는 유연하고 확장 가능한 오디오 시스템을 설계해야 합니다.

 다음은 이를 위한 주요 전략과 구현 방법입니다.

  1. 데이터 주도 접근 방식 사용
  2. 유연한 파라미터 시스템 구현
  3. 디버깅 및 시각화 도구 제공
  4. 모듈화된 오디오 이벤트 시스템 구축
  5. 실시간 오디오 파라미터 조정 인터페이스 제공

 데이터 주도 접근 방식

 사운드 설정을 데이터 에셋으로 관리하여 사운드 디자이너가 프로그래머의 도움 없이도 쉽게 수정할 수 있도록 합니다.

UCLASS(BlueprintType)
class MYGAME_API USoundSettings : public UDataAsset
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Sound Settings")
    TMap<FName, float> DefaultParameters;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Sound Settings")
    TMap<FName, USoundCue*> SoundEvents;
};
 
UCLASS()
class MYGAME_API UAudioManager : public UObject
{
    GENERATED_BODY()
 
public:
    void Initialize(USoundSettings* Settings);
    void PlaySoundEvent(FName EventName);
 
private:
    UPROPERTY()
    USoundSettings* CurrentSettings;
};
 
void UAudioManager::Initialize(USoundSettings* Settings)
{
    CurrentSettings = Settings;
    // 기본 파라미터 설정 등의 초기화 작업
}
 
void UAudioManager::PlaySoundEvent(FName EventName)
{
    if (CurrentSettings && CurrentSettings->SoundEvents.Contains(EventName))
    {
        USoundCue* SoundCue = CurrentSettings->SoundEvents[EventName];
        UGameplayStatics::PlaySound2D(this, SoundCue);
    }
}

 유연한 파라미터 시스템

 동적으로 오디오 파라미터를 관리하고 업데이트할 수 있는 시스템을 구현합니다.

UCLASS()
class MYGAME_API UAudioParameterManager : public UObject
{
    GENERATED_BODY()
 
public:
    void SetParameter(FName ParameterName, float Value);
    float GetParameter(FName ParameterName) const;
    void UpdateAllAudioComponents();
 
    UFUNCTION(BlueprintCallable, Category = "Audio")
    void ExposedSetParameter(FName ParameterName, float Value);
 
private:
    UPROPERTY()
    TMap<FName, float> AudioParameters;
 
    UPROPERTY()
    TArray<UAudioComponent*> ManagedAudioComponents;
};
 
void UAudioParameterManager::SetParameter(FName ParameterName, float Value)
{
    AudioParameters.Add(ParameterName, Value);
}
 
float UAudioParameterManager::GetParameter(FName ParameterName) const
{
    return AudioParameters.FindRef(ParameterName);
}
 
void UAudioParameterManager::UpdateAllAudioComponents()
{
    for (UAudioComponent* AudioComp : ManagedAudioComponents)
    {
        for (const auto& Param : AudioParameters)
        {
            AudioComp->SetFloatParameter(Param.Key, Param.Value);
        }
    }
}
 
void UAudioParameterManager::ExposedSetParameter(FName ParameterName, float Value)
{
    SetParameter(ParameterName, Value);
    UpdateAllAudioComponents();
}

 디버깅 및 시각화 도구

 사운드 디자이너가 오디오 시스템의 상태를 쉽게 확인하고 디버그할 수 있는 도구를 제공합니다.

UCLASS()
class MYGAME_API UAudioDebugger : public UObject
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, Category = "Audio Debug")
    void LogAllActiveAudioComponents();
 
    UFUNCTION(BlueprintCallable, Category = "Audio Debug")
    void VisualizeAudioSources();
 
private:
    void DrawDebugSphere(const FVector& Center, float Radius, const FColor& Color);
};
 
void UAudioDebugger::LogAllActiveAudioComponents()
{
    TArray<UAudioComponent*> AllAudioComponents;
    for (TObjectIterator<UAudioComponent> Itr; Itr; ++Itr)
    {
        if (Itr->IsActive())
        {
            UE_LOG(LogAudio, Log, TEXT("Active Audio Component: %s"), *Itr->GetName());
        }
    }
}
 
void UAudioDebugger::VisualizeAudioSources()
{
    for (TObjectIterator<UAudioComponent> Itr; Itr; ++Itr)
    {
        if (Itr->IsActive())
        {
            FVector Location = Itr->GetComponentLocation();
            float Radius = Itr->AttenuationSettings.FalloffDistance;
            FColor Color = Itr->IsPlaying() ? FColor::Green : FColor::Red;
            DrawDebugSphere(Location, Radius, Color);
        }
    }
}

 모듈화된 오디오 이벤트 시스템

 사운드 디자이너가 복잡한 오디오 이벤트를 쉽게 구성하고 관리할 수 있는 모듈화된 시스템을 구현합니다.

UCLASS(BlueprintType)
class MYGAME_API UAudioEvent : public UObject
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audio Event")
    USoundBase* Sound;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audio Event")
    TArray<FName> Tags;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Audio Event")
    TMap<FName, float> Parameters;
 
    virtual void Play(UAudioComponent* AudioComponent);
};
 
UCLASS()
class MYGAME_API UAudioEventManager : public UObject
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, Category = "Audio")
    void PlayAudioEvent(FName EventName);
 
private:
    UPROPERTY()
    TMap<FName, UAudioEvent*> AudioEvents;
};
 
void UAudioEventManager::PlayAudioEvent(FName EventName)
{
    if (UAudioEvent* Event = AudioEvents.FindRef(EventName))
    {
        UAudioComponent* AudioComp = UGameplayStatics::CreateSound2D(this, Event->Sound);
        Event->Play(AudioComp);
    }
}

 실시간 오디오 파라미터 조정 인터페이스

 사운드 디자이너가 게임 플레이 중에 실시간으로 오디오 파라미터를 조정할 수 있는 인터페이스를 제공합니다.

UCLASS()
class MYGAME_API UAudioTuningInterface : public UObject
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, Category = "Audio Tuning")
    void AdjustGlobalVolume(float NewVolume);
 
    UFUNCTION(BlueprintCallable, Category = "Audio Tuning")
    void SetReverbEffect(USoundEffectSubmixPreset* ReverbPreset);
 
    UFUNCTION(BlueprintCallable, Category = "Audio Tuning")
    void UpdateSoundCueParameter(USoundCue* SoundCue, FName ParameterName, float Value);
};
 
void UAudioTuningInterface::AdjustGlobalVolume(float NewVolume)
{
    UGameplayStatics::SetGlobalTimeDilation(GetWorld(), NewVolume);
}
 
void UAudioTuningInterface::SetReverbEffect(USoundEffectSubmixPreset* ReverbPreset)
{
    if (UWorld* World = GetWorld())
    {
        USubmixEffectReverbPreset* ReverbEffect = Cast<USubmixEffectReverbPreset>(ReverbPreset);
        if (ReverbEffect)
        {
            World->GetAudioDevice()->SetSubmixEffectChainOverride(0, ReverbEffect, 0.0f);
        }
    }
}
 
void UAudioTuningInterface::UpdateSoundCueParameter(USoundCue* SoundCue, FName ParameterName, float Value)
{
    if (SoundCue)
    {
        SoundCue->SetFloatParameter(ParameterName, Value);
    }
}

 이러한 접근 방식을 통해 프로그래머와 사운드 디자이너 간의 원활한 협업이 가능해집니다.

 사운드 디자이너는 프로그래머의 개입 없이도 많은 부분을 직접 제어하고 조정할 수 있으며 필요한 경우 프로그래머의 지원을 받아 새로운 기능을 추가하거나 시스템을 확장할 수 있습니다.

 또한 이러한 시스템은 반복적인 오디오 튜닝 과정을 더욱 효율적으로 만들어 줍니다. 사운드 디자이너는 실시간으로 변경사항을 적용하고 그 결과를 즉시 확인할 수 있으며 디버깅 도구를 통해 문제를 빠르게 식별하고 해결할 수 있습니다.