UE4のOnlineSubsystemをさわってみた

UE4でネットワークとボイスチャットを利用したくてOnlineSubsystemを触ってみてたので内容をメモしておく。
UE4初心者なのとざっくり触ってみたうえで書いた内容なので過不足ありそう。
Windowsデスクトップ環境とOculus Goの環境で1:1通信で試した。
UE4.22.1かUE4.22.2の環境で確認。

# ボイスチャット

基本的には[[UE4] UE 4.19 でアップデートされたボイスチャット機能について｜株式会社ヒストリア](http://historia.co.jp/archives/8827/)を参照した試した。

## 試す際にはまったところ

* CreateSessionのPublicConnectionsやFindSessionsのMaxResultsを0より大きくする必要あり
* OpenLevelのOptionsでListenを指定する必要あり
* リンク先にもちゃんと書いてあるけどコンソールコマンド open を使ってサーバー IP アドレスを直接指定して接続する方法では利用不可（実際にできなかった）
* Oculus Go(Android)でボイスチャットを試す場合、設定でExtra Permissionsにandroid.permission.RECORD_AUDIO設定が必要
 * 詳細は[[UE4] AndroidManifest.xmlをカスタマイズする｜株式会社ヒストリア](http://historia.co.jp/archives/5892/)参照

## 音量の設定

[2020/06/23追記、UE4.25.1で確認]

SoundClassを作成してVolumeを調整し
[Edit] > [Project Settings] > [Audio] > [VOIP Sound Class]に設定する。
ボイスチャットの音量が小さい場合があったので上記の方法で調整した。

## 音声がちゃんと聞こえない時は

[2020/06/23追記、UE4.25.1で確認]

接続の帯域幅設定がデフォルトで低く音声パケットが破棄されるらしいので以下のような感じでDefaulEngine.iniに設定が必要（値は要調整）

```ini
 [/Script/Engine.Player]
  ConfiguredInternetSpeed=800000
  ConfiguredLanSpeed=800000

  [/Script/OnlineSubsystemUtils.IpNetDriver]
  MaxClientRate=800000
  MaxInternetClientRate=800000
```

[2021/10/28追記、UE4.25.4で確認]

単語間で音声が停止したりところどころ聞こえないような場合は以下を設定

```ini
[SystemSettings]
voice.SilenceDetectionThreshold=0.01
voice.MicNoiseGateThreshold=0.01
```

## 自分の音声のミュート、アンミュート

[2020/07/16追記、UE4.25.1で確認]

以下のようにしてできる。
[Voice Chat (VoIP) using Platform Solutions](https://developer.oculus.com/documentation/unreal/ps-voip/)参照

```c++
Online::GetVoiceInterface()->StopNetworkedVoice(0); // ミュート
Online::GetVoiceInterface()->StartNetworkedVoice(0); // アンミュート
```

# OnlineSubsystemNull

## 接続範囲

* FindSessionsで検索できる範囲はbroadcastが届くサブネットの範囲
 * 単一サブネットの家庭内LANなどでは可、複数のサブネットに分割された企業LANなどでは不可 
* コンソールでopen IPアドレスでサブネット範囲外に接続できるがボイスチャットは使えない
 * [Find Sessions / Join Sessions by IP address - Unreal Engine Forums](https://forums.unrealengine.com/development-discussion/blueprint-visual-scripting/27417-find-sessions-join-sessions-by-ip-address)参照

## WindowsのFindSessionsでセッションが見つからない
* VirtualBoxやDockerを入れている場合ネットワーク（Dockerの場合vEthernet(DockerNAT)）を無効にする必要がある。
 * [How do I search for sessions in a LAN? - UE4 AnswerHub](https://answers.unrealengine.com/questions/185604/how-do-i-search-for-sessions-in-a-lan.html)参照

# OnlineSubsystemSteam

## 接続手順

基本的には公式サイトのドキュメントを参照すればいいが[Setup Unreal Engine for Steam Multiplayer Game – takoji3 – Medium](https://medium.com/@takoji3/setup-unreal-engine-for-steam-multiplayer-game-e73cc5d4f99f)が参考になる。

* PCにSteamアプリが入っていてログインしておく必要がある。
* うまく接続できると右下にsteamのアイコンがポップアップしPlayerStateのPlayerNameがsteamアカウントのものになる。
* 基本FindSessionsなどでLanモードをオフにする方が良さそう(オンだと接続できなかったりした)

## 接続範囲

* モバイル端末はサポートしていなさそう。
 * [How can I package to use OnlineSubsystemSteam - UE4 AnswerHub](https://answers.unrealengine.com/questions/263622/how-can-i-package-to-use-onlinesubsystemsteam.html?sort=oldest)参照

## shippingビルド時の接続

shippingビルド版をローカルで動かす場合はビルドした結果のWindowsNoEditr/Binaries/Win64/steam_appid.txtを追加してアプリIDの数字を入れておく。
Steamにリリースする場合は不要そう。
[Steam shipping build only works when adding steam_appid.txt - UE4 AnswerHub](https://answers.unrealengine.com/questions/365346/view.html)参照

# OnlineSubsystemOculus

## 接続手順

基本的には[公式サイトの手順](https://developer.oculus.com/documentation/platform/latest/concepts/pgsg-unreal-gsg/)参照

### アプリの作成と設定

* RiftかGoにあわせてアプリを作成する。
* RiftとGo間で通信する場合はそれぞれのアプリを作成してグループ化しておく必要がある。
* テストに使うOculusアカウントを登録しておく。
* アプリの[プラットフォームサービス]>[ルームとマッチメイク]の設定でプールを作成しておく。
 * プールキーはメモしておく。
 * Should Pool Manage Rooms?ははいにしておく。

![oculuspoolsetting.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16768/8d781d76-67c1-fde4-d641-09200e6b4322.png)

### コンフィグの設定

公式サイトのDefaultEngine.iniの設定に追加して以下のサイトの設定を追加する。
[Oculus Platform Features and Online Subsystems](https://developer.oculus.com/documentation/unreal/1.11/concepts/unreal-platform/)

* VoIP

```ini:DefaultEngine.ini
[OnlineSubsystem]
DefaultPlatformService=Oculus
bHasVoiceEnabled=true
   
[Voice]
bEnabled=true
```

* p2p

```ini:DefaultEngine.ini
[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemOculus.OculusNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
      
[/Script/OnlineSubsystemOculus.OculusNetDriver]
NetConnectionClassName="OnlineSubsystemOculus.OculusNetConnection"
```

### プラグインの設定

基本的にはOnlineSubsystemOculusをオンにすればよい。
Oculus Audioプラグインは一応オンにしてるけど必要性は未検証。

#### RiftとGo用にAppIDを使い分ける

DefaultEngine.iniのOnlineSubsystemOculusセクションにRiftAppIdにRift用アプリのIDを設定、GearVRAppIdにGo用アプリのIDを設定する。

OnlineSubsystemOculus.cppを見ると以下のようになっている

```cpp:[EditorDIR]/Engine/Plugins/Online/OnlineSubsystemOculus/Source/Private/OnlineSubsystemOculus.cpp

FString FOnlineSubsystemOculus::GetAppId() const
{
	// Try to get the platform specific field before the generic one
#if PLATFORM_WINDOWS
	auto AppId = GConfig->GetStr(TEXT("OnlineSubsystemOculus"), TEXT("RiftAppId"), GEngineIni);
#elif PLATFORM_ANDROID
	auto AppId = GConfig->GetStr(TEXT("OnlineSubsystemOculus"), TEXT("GearVRAppId"), GEngineIni);
#endif
	if (!AppId.IsEmpty()) {
		return AppId;
	}
#if PLATFORM_WINDOWS
	UE_LOG_ONLINE(Warning, TEXT("Could not find 'RiftAppId' key in engine config.  Trying 'OculusAppId'.  Move your oculus app id to 'RiftAppId' to use in your rift app and make this warning go away."));
#elif PLATFORM_ANDROID
	UE_LOG_ONLINE(Warning, TEXT("Could not find 'GearVRAppId' key in engine config.  Trying 'OculusAppId'.  Move your oculus app id to 'GearVRAppId' to use in your gearvr app make this warning go away."));
#endif
	return GConfig->GetStr(TEXT("OnlineSubsystemOculus"), TEXT("OculusAppId"), GEngineIni);
}
```

### セッションの接続

ブループリントを利用した接続ではデフォルトのFindSessions、CreateSessionの代わりにOculusのFind Matchmaking Sessions、Create Sessionを使ってそれぞのOculus Matchmakeing Poolに作成したプールのキーを指定する。

[UE4 Blueprints - Oculus Find Sessions Matchmaking (0 results found) — Oculus](https://forums.oculusvr.com/developer/discussion/62117/ue4-blueprints-oculus-find-sessions-matchmaking-0-results-found)参照

うまくいくとCreateSessionでPlayerStateのPlayerNameがOculusアカウントのものになる。

#### Oculus GoでFindSessionやCreateSessionで失敗する

以下のサイトにあるようにUE4 Editorから起動ではできない。
パッケージ化してGoにインストールして起動する必要がある(リリースチャンネルを使うと楽、[アルファテストが格段に捗るリリースチャンネルの使い方【OculusGo】 - Qiita](https://qiita.com/tanacooo/items/00c5d94f35fa4f13cf78)参照)。
[The user isn't signed in — Oculus](https://forums.oculusvr.com/developer/discussion/69947/the-user-isnt-signed-in)参照

### ボイスチャット

以下のサイトにあるようにブループリントからは不可でC++から実施する必要がある。
[Cannot join an Oculus session: "Cannot map local player to unique net ID" — Oculus](https://forums.oculusvr.com/developer/discussion/52541/cannot-join-an-oculus-session-cannot-map-local-player-to-unique-net-id)
android.permission.RECORD_AUDIOの権限追加が必要か未検証。

ひとまず以下のようなC++とブループリントで接続できた。

```cpp:[プロジェクト名].Build.cs
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class VoiceChatOculusCpp : ModuleRules
{
	public VoiceChatOculusCpp(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(new string[] 
		{
			"Core", 
			"CoreUObject", 
			"Engine", 
			"InputCore", 
			"HeadMountedDisplay",
			"LibOVRPlatform",
			"OnlineSubsystem",
		});

		PrivateDependencyModuleNames.AddRange(new string[]
		{
			"OnlineSubsystemOculus",
		});
	}
}
```


```cpp:サードパーソンのCharactor.h
...

class AVoiceChatOculusCppCharacter : public ACharacter
{
	...

public:
	...
	UFUNCTION(BlueprintCallable)
	bool RegisterRemoteTalker(FString PlayerId);

	UFUNCTION(BlueprintCallable)
	FString GetNetId();
};
```

```cpp:サードパーソンのCharactor.cpp
...
#include "Online.h"
#include "OVR_Platform.h"

...

bool AVoiceChatOculusCppCharacter::RegisterRemoteTalker(FString PlayerId)
{
    auto uniqueNetId = Online::GetIdentityInterface()->CreateUniquePlayerId(PlayerId);
    UE_LOG(LogTemp, Log, TEXT("uniqueNetId=%s"), *(uniqueNetId->ToString()));
    return Online::GetVoiceInterface()->RegisterRemoteTalker(*uniqueNetId);
}

FString AVoiceChatOculusCppCharacter::GetNetId()
{
    auto uniqueNetId = Online::GetIdentityInterface()->GetUniquePlayerId(0);
    if (!uniqueNetId.IsValid()) {
        return FString(TEXT(""));
    }

    uint64* RawUniqueId = (uint64*)uniqueNetId->GetBytes();
    ovrID OculusId(*RawUniqueId);
    char Idstr[21];
    ovrID_ToString(Idstr, sizeof(Idstr), OculusId);
    return FString(ANSI_TO_TCHAR(Idstr));
}
```

![OnlineSubsystemOculusVoiceChatBP.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16768/3f4a74c0-e179-a1bb-2b85-71fec3f54885.png)


TestEventはサーバーで実行に設定し、クライアント側で自分のOculusユーザのID取得してサーバー側からそのIDに接続している。

## 接続範囲

[OculusPlatformについて - Qiita](https://qiita.com/yuma1217/items/5dca28e0b99f59d9edc5)にもあるように基本Oculusデバイスでの利用になる。

# その他のOnlineSubsystemXXX

UE4エディタのサポートしている範囲だと[EditorDIR]/Engine/Plugins/Online/OnlineSubsystemXXX/Source/Private/OnlineSubsystemXXX.cppかPublic/OnlineSubsystemXXX.hあたりを見たがセッションやボイスチャットはサポートしてなさそうだった。