【UE4】OnlineBeacon使ってみた！(サンプルあり)

#はじめに
今回の記事では、「同一ワールドに合流せずにホストとクライアントで通信するための仕組み」である**OnlineBeacon**について解説します。

[OnlineBeacon@UnrealEngine公式ドキュメント](https://docs.unrealengine.com/ja/InteractiveExperiences/Networking/OnlineBeacons/index.html)

元々はGearsOfWarのマッチングをスムーズに行うために、素のTCP通信で席の予約やロビーのようなシステムを行うための仕組みだったものをUE4のRPC(遠隔関数呼び出し)で実装できるようにしたものだそうです。
通常のJoinSessionでの接続とは違いレベルの移動などの動作を含まずに通信できるため、高速にセッションのホストと通信して確実な状態になってから改めてJoinSessionしたりすることができます。
通信を行う主体はActorなのでもちろんブループリントを用いて実装を行うことも可能です！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">同じレベルで通信するからわかりにくい事に気付いたので別のレベルから通信してみたデモ <a href="https://t.co/za17VoIXvx">pic.twitter.com/za17VoIXvx</a></p>&mdash; Takashi.Suzuki (@wankotank) <a href="https://twitter.com/wankotank/status/1369283236510142464?ref_src=twsrc%5Etfw">March 9, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>




#◆ 関連クラス説明
公式ドキュメントと被る部分も多々ありますが、まずクラスの構成を解説します。
関連クラスはOnlineSubsystemUtilsモジュールに配置されています。
いくつかのクラスは基底クラスだけが実装されていてタイトルのニーズに合わせて追加の実装が必要な部分があります。
ブループリントを用いて実装したい場合は、主要な関数はブループリントに露出していないのでc++で基底となるクラスを実装する必要があります。

![OnlineBeacon_ Lucidchart - Google Chrome 2021-03-0 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/75dc22dc-83a3-3230-900c-5a329f7eca70.png)


##▼ AOnlineBeacon
オンラインビーコンの基底クラス。BeaconNetDriverを介して通信を行う機能を持ちます。

##▼ AOnlineBeaconHost
ホスト側のプロセス内に作成されて、登録されたOnlineBeaconHostObjectを保持します。
クライアントから送られてきた通信を適切にBeaconHostObjectに振り分けます。

##▼ AOnlineBeaconHostObject
ビーコン名とどのBeaconClientを使うかを紐づけるための情報を持ちOnlineBeaconClientと対になるようにタイトルに合わせた実装を行うことになります。注意点として**これがBeaconClientと直接通信を行うわけではない**ことを覚えておいてください。

通常のUnrealEngineのワールドの仕組みの中で言うとゲームモードに近い立ち位置になります。
**一番重要な仕事はクライアントからの要求に応じて対応するBeaconClientを作成することです。**
ホスト側はセッションを作成した後(直接IPでやりとり場合はSessionは不要)、このアクターとOnlineBeaconHostの両方をスポーンし、
自作のBeaconHostObjectをOnlineBeaconHostに登録します。

```c++
	//OnlineBeaconHostを作成する
	AOnlineBeaconHost* BeaconHost = GetWorld()->SpawnActor<AOnlineBeaconHost>(AOnlineBeaconHost::StaticClass()); 
	if (BeaconHost && BeaconHost->InitHost())
	{
		//OnlineBeaconHostObjectを作成する
		AOnlineBeaconHostObject* BeaconHostObject = GetWorld()->SpawnActor<AOnlineBeaconHostObject>(ATestBeaconHost::StaticClass());
		if (BeaconHostObject)
		{
			//BeaconHostObject->Init();
			BeaconHost->RegisterHost(BeaconHostObject); //登録する
			Beacon = BeaconHost;
			BeaconHost->PauseBeaconRequests(false);
		}
	}
```


##▼ AOnlineBeaconClient
**OnlineBeaconの仕組みの中でアプリケーションに必要な通信をUnrealのRPCと同じインターフェイスを用いて直接行うことができるアクターです。**
一つの通信を行うときクライアントに一つ、それに対応する形でサーバー側に一つ生成されます。
サーバー側で複数のクライアントから要求があった場合はサーバー側では複数のクライアントが作成されることになります。

![OnlineBeacon_ Lucidchart - Google Chrome 2021-03-0 (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/e3ab5ab9-0e51-7219-b750-66c42b19bac9.png)

#◆ OnlineBeacon有効化方法
前準備としていくつか設定が必要な部分があります。
##DefaultEngine.iniファイル
DefaultEngine.iniファイル中のEngine.GameEngineセクションにBeaconNetDriverの定義が必要です。

```DefaultEngine.ini
[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="BeaconNetDriver",DriverClassName="OnlineSubsystemUtils.IpNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
```

上記定義が無い場合、

```log
LogNet: CreateNamedNetDriver failed to create driver from definition BeaconNetDriver
```
というエラーが出力されOnlineBeaconは通信をはじめることが出来なくなります。

Beacon自体は例えばIPアドレスの直接指定でも動作可能ですが、通常はセッションなどと組み合わせて使うので、
DefaultEngine.iniにて適切なオンラインサブシステムを有効化しておく必要があります。以下の例ではOnlineSubsystemNullを有効化しています。

```DefaultEngine.ini
[OnlineSubsystem]
DefaultPlatformService=Null
```

#◆ 処理の流れ

##▼ 1. 通信前の準備

![OnlineBeacon_ Lucidchart - Google Chrome 2021-03-0 (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/16adcbed-4cd8-3623-feeb-b5a595f20ebf.png)

###ホスト側
ホスト側で自作したAMyOnlineBeaconHost(AOnlineBeaconHostObjectを継承)とAOnlineBeaconHostを作成し、前者を後者に登録します。
成功するとBeaconNetDriverが作成され動作を始めます。

またセッション検索用にCreateSessionを行っておきます。

###クライアント側
AMyBeaconClient(AOnlineBeaconClientを継承)を通常のアクターと同じようにスポーンさせておきます。

##▼ 2. 接続
![OnlineBeacon_ Lucidchart - Google Chrome 2021-03-0 (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/4b3ecbff-fc24-9d06-558a-8ed6832ebb3c.png)

### クライアント側
FindSessionを実行し、ホストのセッションを見つけます。この後**JoinSessionせず**に SessionInterface::GetResolvedConnectString() を用いて接続先アドレスを取得し、それを引数にAOnlineBeaconClient::InitClient(FURL)を呼び出します。



```c++:MyOnlineBeaconClientBase.cpp
bool AMyOnlineBeaconClientBase::ConnectToBeaconHost(const FBlueprintSessionResult& InSessionSearchResult)
{
	const FOnlineSessionSearchResult& SearchResult = InSessionSearchResult.OnlineResult;
	// Make this stuff common?
	bool bSuccess = false;
	IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
	if (OnlineSub == nullptr) { return bSuccess; }

	IOnlineSessionPtr SessionInt = OnlineSub->GetSessionInterface();
	if (SessionInt.IsValid() == false) { return bSuccess; }

	FString ConnectInfo;
	if (SessionInt->GetResolvedConnectString(SearchResult, NAME_BeaconPort, ConnectInfo))
	{
		FURL ConnectURL(NULL, *ConnectInfo, TRAVEL_Absolute);
		if (this->InitClient(ConnectURL) && SearchResult.Session.SessionInfo.IsValid())
		{
			//			DestSessionId = DesiredHost.Session.SessionInfo->GetSessionId().ToString();
			bSuccess = true;
		}
		else
		{
			UE_LOG(LogBeaconSample, Warning, TEXT("ConnectToBeaconHost: Failure to init client beacon with %s."), *ConnectURL.ToString());
		}
	}
	return bSuccess;
}
```

この関数によってクライアント側にBeaconNetDriverが作成され、ホストへの接続を試みてくれます。

### ホスト側
クライアントからの要求に応じてMyBeaconHost内に登録されているMyBeaconClientがホスト側にも作成されます。
こうして作成されたクライアントとホスト側のMyBeaconClientに実装されたRPCによって通信が可能になります。

##▼ 3. 切断
サーバー側もクライアントも不要になったアクター(MyBeaconHost/MyBeaconClient)は**手動**でDestroyActorする必要があります。

#◆ 実装サンプル
エンジンに含まれるサンプルとしてはATestBeaconHost/Client、ASpectatorBeaconHost/Client、ALobbyBeaconHost/ClientがOnlineSubstemUtilsモジュール内にあります。
実装の前にこれらのコードをじっくり読んでみることをお勧めします。

##ホスト/クライアント/レプリケート用の情報保持アクター(State)をブループリントで実装してみたサンプル
最低限の実装しかありませんが、ブループリントでタイトルにあった実装を加えるサンプルになっています。
接続の削除や複数回接続する際の実装などは考慮されておりません。ご了承ください。
[サンプルプロジェクト@Github](https://github.com/wankotank/BeaconSample)

以下サンプル中のブループリントのスクリーンショット

![BP_BeaconHost 2021-03-09 23.29.54 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/76db7c8a-e70e-9aac-fded-7704010b4b32.png)


![BP_BeaconClient 2021-03-09 23.29.13 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/cea8af5e-09c7-4ea3-5e26-0fb56d9fc42b.png)


![BP_BeaconState 2021-03-09 23.31.09 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/2ff6c94d-5fd8-2b7b-048f-5ea04f7d6c6b.png)

OnlineBeaconによる接続を試みているところ

![BP_JoinBeacon 2021-03-09 23.36.10 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/464248/ba67e322-64b9-8fc8-5b01-a7dd024a3236.png)


#◆ Advanced
## レプリケーションについて
OnlineBeaconはClient同士の通信の他HostからClientへのアクターのレプリケーションにも対応しています。
詳しくはALobbyBeaconHost::SetupLobbyState()、LobbyBeaconState.hにあるALobbyBeaconStateを参照してください。
基本的には通常のレプリケートに対応したアクターを作成したあと、SetNetDriverNameを呼ぶだけです。
クライアントからの接続に成功するとサーバー側で指定されているアクターがクライアントにレプリケートされます。