#はじめに
今回の記事では、「同一ワールドに合流せずにホストとクライアントで通信するための仕組み」であるOnlineBeaconについて解説します。
OnlineBeacon@UnrealEngine公式ドキュメント
元々はGearsOfWarのマッチングをスムーズに行うために、素のTCP通信で席の予約やロビーのようなシステムを行うための仕組みだったものをUE4のRPC(遠隔関数呼び出し)で実装できるようにしたものだそうです。
通常のJoinSessionでの接続とは違いレベルの移動などの動作を含まずに通信できるため、高速にセッションのホストと通信して確実な状態になってから改めてJoinSessionしたりすることができます。
通信を行う主体はActorなのでもちろんブループリントを用いて実装を行うことも可能です!
同じレベルで通信するからわかりにくい事に気付いたので別のレベルから通信してみたデモ pic.twitter.com/za17VoIXvx
— Takashi.Suzuki (@wankotank) March 9, 2021
#◆ 関連クラス説明
公式ドキュメントと被る部分も多々ありますが、まずクラスの構成を解説します。
関連クラスはOnlineSubsystemUtilsモジュールに配置されています。
いくつかのクラスは基底クラスだけが実装されていてタイトルのニーズに合わせて追加の実装が必要な部分があります。
ブループリントを用いて実装したい場合は、主要な関数はブループリントに露出していないのでc++で基底となるクラスを実装する必要があります。
##▼ AOnlineBeacon
オンラインビーコンの基底クラス。BeaconNetDriverを介して通信を行う機能を持ちます。
##▼ AOnlineBeaconHost
ホスト側のプロセス内に作成されて、登録されたOnlineBeaconHostObjectを保持します。
クライアントから送られてきた通信を適切にBeaconHostObjectに振り分けます。
##▼ AOnlineBeaconHostObject
ビーコン名とどのBeaconClientを使うかを紐づけるための情報を持ちOnlineBeaconClientと対になるようにタイトルに合わせた実装を行うことになります。注意点としてこれがBeaconClientと直接通信を行うわけではないことを覚えておいてください。
通常のUnrealEngineのワールドの仕組みの中で言うとゲームモードに近い立ち位置になります。
一番重要な仕事はクライアントからの要求に応じて対応するBeaconClientを作成することです。
ホスト側はセッションを作成した後(直接IPでやりとり場合はSessionは不要)、このアクターとOnlineBeaconHostの両方をスポーンし、
自作のBeaconHostObjectをOnlineBeaconHostに登録します。
//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有効化方法
前準備としていくつか設定が必要な部分があります。
##DefaultEngine.iniファイル
DefaultEngine.iniファイル中のEngine.GameEngineセクションにBeaconNetDriverの定義が必要です。
[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName="BeaconNetDriver",DriverClassName="OnlineSubsystemUtils.IpNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
上記定義が無い場合、
LogNet: CreateNamedNetDriver failed to create driver from definition BeaconNetDriver
というエラーが出力されOnlineBeaconは通信をはじめることが出来なくなります。
Beacon自体は例えばIPアドレスの直接指定でも動作可能ですが、通常はセッションなどと組み合わせて使うので、
DefaultEngine.iniにて適切なオンラインサブシステムを有効化しておく必要があります。以下の例ではOnlineSubsystemNullを有効化しています。
[OnlineSubsystem]
DefaultPlatformService=Null
#◆ 処理の流れ
##▼ 1. 通信前の準備
###ホスト側
ホスト側で自作したAMyOnlineBeaconHost(AOnlineBeaconHostObjectを継承)とAOnlineBeaconHostを作成し、前者を後者に登録します。
成功するとBeaconNetDriverが作成され動作を始めます。
またセッション検索用にCreateSessionを行っておきます。
###クライアント側
AMyBeaconClient(AOnlineBeaconClientを継承)を通常のアクターと同じようにスポーンさせておきます。
クライアント側
FindSessionを実行し、ホストのセッションを見つけます。この後JoinSessionせずに SessionInterface::GetResolvedConnectString() を用いて接続先アドレスを取得し、それを引数にAOnlineBeaconClient::InitClient(FURL)を呼び出します。
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
以下サンプル中のブループリントのスクリーンショット
OnlineBeaconによる接続を試みているところ
#◆ Advanced
レプリケーションについて
OnlineBeaconはClient同士の通信の他HostからClientへのアクターのレプリケーションにも対応しています。
詳しくはALobbyBeaconHost::SetupLobbyState()、LobbyBeaconState.hにあるALobbyBeaconStateを参照してください。
基本的には通常のレプリケートに対応したアクターを作成したあと、SetNetDriverNameを呼ぶだけです。
クライアントからの接続に成功するとサーバー側で指定されているアクターがクライアントにレプリケートされます。