今回は、4.27から導入されたOnlineSubsystemEOSを使ってP2PNATで動かせる環境を作るまでの情報をまとめた記事になります。
以下参考にさせていただいた情報
OnlineSubsystemEOSの導入について(エディタでローカルで試す方法)
qiita.com
公式のドキュメント
docs.unrealengine.com
公式のOnlineSubsystemEOSのドキュメント
docs.unrealengine.com
RPCに関して説明は下記の記事を参考にしてください
マルチプレイヤーゲーム実装について説明してくれているUE4アドカレ2021の神記事
qiita.com
項目 |
内容 |
エンジンバージョン |
4.27.2 |
テンプレート |
ThirdPersonCPP |
また以下の内容は今回の記事では扱いません。
- ほかのプラットフォームと併用(EOSPlus)の設定
- ボイスチャット
- DedicatedServerでの利用
- RPCを使ったゲーム制作の実装
基本的な導入は公式ドキュメントを参考にしてください。
プロジェクト設定
ArtifactsにはEOSのDeveloperPortalで製品登録をして各情報を作成して、設定をしてください。
DefaultEngine.ini(追加部分)
[OnlineSubsystemEOS]
bEnabled=true
[OnlineSubsystem]
DefaultPlatformService=EOS
[/Script/Engine.GameEngine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemEOS.NetDriverEOS",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
[/Script/OnlineSubsystemEOS.NetDriverEOS]
bIsUsingP2PSockets=true
ここからはC++での実装になります。基本的には冒頭で紹介したOnlineSubsystemEOSの使い方を説明してくださっている記事のものと同じです。
クラスはGameInstanceで行っていますが、PlayerControllerでも構いません。
その場合、PlayerControllerを取得してくる処理が必要なくなるので適宜読み替えをお願いします。
ログイン処理
void UMyGameInstance::LoginEOS()
{
IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{
IOnlineIdentityPtr Identity = OnlineSub->GetIdentityInterface();
if (Identity.IsValid())
{
ULocalPlayer* LPC = GetWorld()->GetFirstPlayerController()->GetLocalPlayer();
if (LPC != NULL)
{
int ControllerId = LPC->GetControllerId();
if (Identity->GetLoginStatus(ControllerId) != ELoginStatus::LoggedIn)
{
UE_LOG_ONLINE(Warning, TEXT("ComandLine: %s"), FCommandLine::Get());
Identity->AddOnLoginCompleteDelegate_Handle(ControllerId, FOnLoginCompleteDelegate::CreateUObject(this, &UMyGameInstance::OnLoginCompleteDelegate));
FOnlineAccountCredentials acountCredentials;
acountCredentials.Type = "accountportal";
Identity->Login(ControllerId, acountCredentials);
}
ELoginStatus::Type status = Identity->GetLoginStatus(ControllerId);
}
}
}
}
EOSでログインする場合FOnlineAccountCredentialsに設定する項目はログイン方法の種類によって変わります。
設定は
FOnlineAccountCredentials.Type
で行います。
exchangecode
EpicGamesLauncherを利用してログインする場合にこれを使います。
追加で
FOnlineAccountCredentials.Token
を設定する必要があります。
調査不足でここのトークンの取得方法はわかってません。
developer
EOSの開発ツールを使ってログインする方法になります。冒頭で紹介したOnlineSubsystemEOSの使用方法を説明してくださってる記事の中に詳細があるので割愛します。
基本的には起動時引数を設定しておいて自動でログインする方法になります。
accountportal
Webブラウザを介して自動で認証を通してくれるやり方で、特に情報を設定せずに使えるので今回はこれで行います。
ログイン成功時デリゲート
void UMyGameInstance::OnLoginCompleteDelegate(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error)
{
IOnlineIdentityPtr Identity = Online::GetIdentityInterface();
if (Identity.IsValid())
{
APlayerController* LPC = GetWorld()->GetFirstPlayerController();
ULocalPlayer* LP = Cast<ULocalPlayer>(LPC->GetLocalPlayer());
if (LP != NULL)
{
int ControllerId = LP->GetControllerId();
FUniqueNetIdRepl uniqueId = LPC->PlayerState->GetUniqueId();
uniqueId.SetUniqueNetId(FUniqueNetIdWrapper(UserId).GetUniqueNetId());
LPC->PlayerState->SetUniqueId(uniqueId);
}
}
}
セッションの作成
bool UMyGameInstance::HostSession()
{
IOnlineSubsystem* const OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
APlayerController* LPC = GetWorld()->GetFirstPlayerController();
TSharedPtr<class FOnlineSessionSettings> SessionSettings = MakeShareable(new FOnlineSessionSettings());
SessionSettings->NumPublicConnections = 4;
SessionSettings->NumPrivateConnections = 0;
SessionSettings->bShouldAdvertise = true;
SessionSettings->bAllowJoinInProgress = true;
SessionSettings->bAllowInvites = true;
SessionSettings->bUsesPresence = true;
SessionSettings->bAllowJoinViaPresence = true;
SessionSettings->bUseLobbiesIfAvailable = true;
SessionSettings->bUseLobbiesVoiceChatIfAvailable = false;
SessionSettings->Set(SEARCH_KEYWORDS, SearchKeyword, EOnlineDataAdvertisementType::ViaOnlineService);
Sessions->AddOnCreateSessionCompleteDelegate_Handle(FOnCreateSessionCompleteDelegate::CreateUObject(this, &UMyGameInstance::OnCreateSessionCompleteDelegate));
TSharedPtr<const FUniqueNetId> UniqueNetIdptr = LPC->GetLocalPlayer()->GetPreferredUniqueNetId().GetUniqueNetId();
bool bResult = Sessions->CreateSession(*UniqueNetIdptr, HostSessionName, *SessionSettings);
if (bResult) {
return true;
}
}
}
return false;
}
セッション検索用のSearchKeywordにはCustomという文字列を設定してます。
セッション作成成功時デリゲート
void UMyGameInstance::OnCreateSessionCompleteDelegate(FName InSessionName, bool bWasSuccessful)
{
if (bWasSuccessful)
{
FString path = MapRootPath + TargetMapName;
UGameplayStatics::OpenLevel(this, FName(path), true, "listen");
}
}
セッション作成が成功したらゲーム用レベルに移動します。
今回はThirdPersonテンプレートなので
MapRootPath : /Game/ThirdPersonBP/Maps/
TargetMapName : ThirdPersonExampleMap
を設定しています。
セッション検索
void UMyGameInstance::FindSession()
{
IOnlineSubsystem* const OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
APlayerController* LP = GetWorld()->GetFirstPlayerController();
FOnlineSessionSearch* search = new FOnlineSessionSearch();
search->MaxSearchResults = 10;
search->PingBucketSize = 50;
search->bIsLanQuery = false;
SearchSettings = MakeShareable(search);
SearchSettings->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);
SearchSettings->QuerySettings.Set(SEARCH_LOBBIES, true, EOnlineComparisonOp::Equals);
SearchSettings->QuerySettings.Set(SEARCH_KEYWORDS, SearchKeyword, EOnlineComparisonOp::Equals);
Sessions->AddOnFindSessionsCompleteDelegate_Handle(FOnFindSessionsCompleteDelegate::CreateUObject(this, &UMyGameInstance::OnFindSessionsCompleteDelegate));
TSharedRef<FOnlineSessionSearch> SearchSettingsRef = SearchSettings.ToSharedRef();
TSharedPtr<const FUniqueNetId> UniqueNetIdptr = LP->GetLocalPlayer()->GetPreferredUniqueNetId().GetUniqueNetId();
if (UniqueNetIdptr != NULL)
{
bool bIsSuccess = Sessions->FindSessions(*UniqueNetIdptr, SearchSettingsRef);
}
}
}
}
セッション検索成功時デリゲート
void UMyGameInstance::OnFindSessionsCompleteDelegate(bool bWasSuccessful)
{
if (bWasSuccessful)
{
if (SearchSettings->SearchResults.Num() == 0)
{
}
else
{
TArray<FBlueprintSessionResult> resultList;
for (auto result : SearchSettings->SearchResults)
{
FBlueprintSessionResult val;
val.OnlineResult = result;
resultList.Add(val);
}
FindSessionsCompleteDelegate.Broadcast(resultList);
}
}
}
セッション検索結果をUIに投げるためにデリゲートを用意しBP側で受け取れるようにしています。
軽く動かすだけならコメントアウトしている部分のようにリザルトの0番目で後述するセッション参加関数を呼べば動きます。
セッション参加
void UMyGameInstance::JoinSessionEOS(FOnlineSessionSearchResult SearchResult)
{
IOnlineSubsystem* const OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
if (SearchResult.IsValid())
{
APlayerController* LPC = GetWorld()->GetFirstPlayerController();
Sessions->AddOnJoinSessionCompleteDelegate_Handle(FOnJoinSessionCompleteDelegate::CreateUObject(this, &UMyGameInstance::OnJoinSessionCompleteDelegate));
TSharedPtr<const FUniqueNetId> UniqueNetIdptr = LPC->GetLocalPlayer()->GetPreferredUniqueNetId().GetUniqueNetId();
Sessions->JoinSession(*UniqueNetIdptr, CurrentSessionName, SearchResult);
}
}
}
}
セッション参加成功時デリゲート
void UMyGameInstance::OnJoinSessionCompleteDelegate(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
IOnlineSubsystem* const OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{
IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
if (Sessions.IsValid())
{
if (Result == EOnJoinSessionCompleteResult::Success)
{
FString ConnectString;
if (Sessions->GetResolvedConnectString(CurrentSessionName, ConnectString))
{
APlayerController* LPC = GetWorld()->GetFirstPlayerController();
UE_LOG_ONLINE_SESSION(Log, TEXT("JoinSessionComplete : Join Session: traveling to %s"), *ConnectString);
LPC->ClientTravel(ConnectString, TRAVEL_Absolute);
}
}
}
}
}
以上の用意した関数をBPから呼びます。
こちらの画像ではデリゲートを用意し、セッションを検索して出てきた結果をUIに表示しています。
実行手順としては
- ログインを行う
- ホスト側がセッションを作成
- クライアント側がセッションを検索→参加
で動作します。
NAT超えて動作することは確認済みです。みんなも気軽にオンラインゲーム作ろう!