LoginSignup
3
3

More than 3 years have passed since last update.

UE4のOnlineSubsystemOculusで空間音声のボイスチャットをしてみる

Posted at

OnlineSubsystemOculusが空間音声に対応していないようだったので無理やり対応してみた。
空間音声になると声で方向が分かっていい。

環境

UE4.22.3

やったこと

ボイスチャットは接続している前提(UE4のOnlineSubsystemをさわってみた - Qiita参照)。

OnlineSubsystemOculusをプロジェクトのプラグインにコピー

UE4 Built-In Pluginの改造方法 [紫苑ノ遊戯三昧]を参照して以下のようにコピー

コピー元:[Engineのパス]/Engine/Plugins/Online/OnlineSubsystemOculus
コピー先:[プロジェクトのパス]/Plugins/Online/OnlineSubsystemOculus

OnlineSubsystemOculusの改造

Voice Chat Spatialization - UE4 AnswerHubを参照した。

以下のファイルを変更してOnlineSubsystemOculusでボイスチャットに使っているAudioComponentにアクセスできるようにし、Sound Attenuationも設定できるようにした。
AudioComponentのPlay()実行後にSoundAttenuationを設定しても効かなさそうだった(Stop() => Play()してもダメそうだった)。

  • [プロジェクトのパス]/Plugins/Online/OnlineSubsystemOculus/Source/Private/OnlineVoiceOculus.h
class FOnlineVoiceOculus : public IOnlineVoice
{

public:
    ...
    UAudioComponent* FindOrCreateAudioComponent(const FUniqueNetId& UniqueId, bool& isCreate); // === 追加 === 

    ...
    static USoundAttenuation* SoundAttenuation; // === 追加 === 
  • [プロジェクトのパス]/Plugins/Online/OnlineSubsystemOculus/Source/Private/OnlineVoiceOculus.cpp
USoundAttenuation* FOnlineVoiceOculus::SoundAttenuation = nullptr; // === 追加 === 

// === 以下のメソッド追加 === 
UAudioComponent*  FOnlineVoiceOculus::FindOrCreateAudioComponent(const FUniqueNetId& UniqueId, bool& isCreate)
{
    auto OculusId = static_cast<const FUniqueNetIdOculus&>(UniqueId);
    FRemoteTalkerDataOculus& QueuedData = RemoteTalkerBuffers.FindOrAdd(OculusId);
    isCreate = false;

    if (QueuedData.AudioComponent == nullptr || QueuedData.AudioComponent->IsPendingKill())
    {
        QueuedData.AudioComponent = CreateVoiceAudioComponent(OCULUS_VOICE_SAMPLE_RATE, OCULUS_NUM_VOICE_CHANNELS);
        if (QueuedData.AudioComponent)
        {
            QueuedData.AudioComponent->AddToRoot(); // make sure this doesn't get deleted by the GC
            QueuedData.AudioComponent->OnAudioFinishedNative.AddRaw(this, &FOnlineVoiceOculus::OnAudioFinished);
            QueuedData.AudioComponent->bAllowSpatialization = true;
            if (SoundAttenuation)
            {
                UE_LOG_ONLINE_VOICE(Warning, TEXT("FindOrCreate SetSoundAttenuationSettings"));
                QueuedData.AudioComponent->AttenuationSettings = SoundAttenuation;
            }
        }

        UE_LOG_ONLINE_VOICE(Log, TEXT("Create VoiceAudioComponent %s"), *UniqueId.ToString());
        isCreate = true;
    }

    return QueuedData.AudioComponent;
}

...

void FOnlineVoiceOculus::ProcessRemoteVoicePackets()
{
    ...
        if (ElementsWritten > 0)
        {
            // Mark this remote talker as talking
            if (!RemoteTalker.bIsTalking)
            {
                RemoteTalker.bIsTalking = true;
                TriggerOnPlayerTalkingStateChangedDelegates(RemoteTalker.TalkerId.ToSharedRef(), true);
            }

            FRemoteTalkerDataOculus& QueuedData = RemoteTalkerBuffers.FindOrAdd(RemoteTalkerId);

            QueuedData.LastSeen = CurrentTime;

            if (QueuedData.AudioComponent == nullptr || QueuedData.AudioComponent->IsPendingKill())
            {
                QueuedData.AudioComponent = CreateVoiceAudioComponent(OCULUS_VOICE_SAMPLE_RATE, OCULUS_NUM_VOICE_CHANNELS);
                if (QueuedData.AudioComponent)
                {
                    QueuedData.AudioComponent->AddToRoot(); // make sure this doesn't get deleted by the GC
                    QueuedData.AudioComponent->OnAudioFinishedNative.AddRaw(this, &FOnlineVoiceOculus::OnAudioFinished);
                    //  === 以下の行を追加 === 
                    QueuedData.AudioComponent->bAllowSpatialization = true;
                    if (SoundAttenuation)
                    {
                        UE_LOG_ONLINE_VOICE(Warning, TEXT("SetSoundAttenuationSettings"));
                        QueuedData.AudioComponent->AttenuationSettings = SoundAttenuation;
                    }
                    //  === 追加ここまで ===
                }
            }
   ...
}

以下のクラスを追加してブループリントから使うメソッドを実装した。

  • [プロジェクトのパス]/Plugins/Online/OnlineSubsystemOculus/Source/Classes/OculusMyVoice.h
#pragma once

#include "UObject/Object.h"
#include "Net/OnlineBlueprintCallProxyBase.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "Components/AudioComponent.h"
#include "Sound/SoundAttenuation.h"
#include "OculusMyVoice.generated.h"

UCLASS()
class UOculusMyVoice : public UBlueprintFunctionLibrary
{
    GENERATED_UCLASS_BODY()

    UFUNCTION(BlueprintCallable, Category = "Oculus|Voice")
    static UAudioComponent* FindOrCreateVoiceAudioComponent(FString OculusID, bool& IsCreate);

    UFUNCTION(BlueprintCallable, Category = "Oculus|Voice")
    static void SetSoundAttenuationForVoiceAudioComponent(USoundAttenuation* Attenuation);

    UFUNCTION(BlueprintCallable, Category = "Oculus|Voice")
    static void UpdateAudioComponentTransform(UAudioComponent* Component);
};
  • [プロジェクトのパス]/Plugins/Online/OnlineSubsystemOculus/Source/Private/OculusMyVoice.cpp
#include "OculusMyVoice.h"
#include "Online.h"
#include "OnlineVoiceOculus.h"

UOculusMyVoice::UOculusMyVoice(class FObjectInitializer const &init) : Super(init)
{
}

UAudioComponent* UOculusMyVoice::FindOrCreateVoiceAudioComponent(FString OculusID, bool& IsCreate)
{
    IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
    if (!OnlineSub)
    {
        return nullptr;
    }

    auto IdentityInterface = OnlineSub->GetIdentityInterface();
    if (!IdentityInterface.IsValid())
    {
        return nullptr;
    }

    auto VoiceInterface = OnlineSub->GetVoiceInterface();
    if (!VoiceInterface.IsValid())
    {
        return nullptr;
    }

    UE_LOG(LogTemp, Warning, TEXT("FindOrCreateVoiceAudioComponent Step1"));

    auto uniqueNetId = IdentityInterface->CreateUniquePlayerId(OculusID);
    if (uniqueNetId.IsValid())
    {
        UE_LOG(LogTemp, Warning, TEXT("FindOrCreateVoiceAudioComponent Step2"));
        auto oculusVoiceInterface = StaticCastSharedPtr<FOnlineVoiceOculus>(VoiceInterface);
        return oculusVoiceInterface->FindOrCreateAudioComponent(*uniqueNetId, IsCreate);
    }
    return nullptr;
}

void UOculusMyVoice::SetSoundAttenuationForVoiceAudioComponent(USoundAttenuation* Attenuation)
{
    UE_LOG(LogTemp, Warning, TEXT("Set FOnlineVoiceOculus::SoundAttenuation"))
    FOnlineVoiceOculus::SoundAttenuation = Attenuation;
}

void UOculusMyVoice::UpdateAudioComponentTransform(UAudioComponent* Component)
{
    Component->OnUpdateTransform(EUpdateTransformFlags::PropagateFromParent);
}

SoundAttenuation作成

  • Editor上でAdd New>Sounds>Sound Attenuationで作成
  • 作成したSound AttenuationのAttenuation FunctionをCustomにしてCustom Attenuation CurveでTime:0.0、Valuie:1.0とTime:1.0、Value:0.01にキーを打つ。
    • 遠く離れて声が聞こえなくなってから再び声が聞こえるようになると聞こえていなかった分の声が聞こえて音声が遅延する。このためとりあえず遠く離れてもボリュームが0にならないようにする。

SoundAttenuationCap.PNG

Blueprintの設定

  • GameInstanceクラスのEvent Initで作成したSound AttenuationをOnlineSubsystemOculusに設定する。
    • とりあえずゲーム開始時の早いうち(AudioComponentが作成されない間)に設定するのがいいのかなということでGameInstanceで設定してみた。

GameInstanceCap.png

  • 空間音声をアタッチしたいActorのEvent BeginPlayでAudioComponentを取得してアタッチ
    • SetupVoiceAudioComponentはBeginPlayから呼び出し
    • OculusIDは適当に取得しておいたものを利用
    • CameraコンポーネントはAudioComponentをアタッチしたいコンポーネント

PawnBeginPlayCap.PNG

  • ActorのEvent Tickで作成したSound AttenuationのTransformを更新する

TickCap.PNG

3
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3