【UE4】OnlineSubsystemEOSの使い方まとめ

今回は、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を使ったゲーム制作の実装

 

基本的な導入は公式ドキュメントを参考にしてください。

プラグイン

f:id:shiratori00:20211221223756p:plain

プロジェクト設定

f:id:shiratori00:20211221223214p:plain

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)
            {
                //コントローラIDの取得
                int ControllerId = LPC->GetControllerId();
                if (Identity->GetLoginStatus(ControllerId) != ELoginStatus::LoggedIn)
                {
                    UE_LOG_ONLINE(Warning, TEXT("ComandLine: %s"), FCommandLine::Get());

                    //OnlineSubsystemのLoginCompleteデリゲートを登録
                    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();
            //現在のユニークIDの取得
            FUniqueNetIdRepl uniqueId = LPC->PlayerState->GetUniqueId();
            //通信用UniqueNetIDを設定
            uniqueId.SetUniqueNetId(FUniqueNetIdWrapper(UserId).GetUniqueNetId());
            //UniqueIDを更新
            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;
            //Session開始後でも参加を許可する
            SessionSettings->bAllowJoinInProgress = true;
            //招待を許可する
            SessionSettings->bAllowInvites = true;
            //プレゼンスにロビー情報をのせる
            SessionSettings->bUsesPresence = true;
            //SessionIDを知っているユーザーであれば参加可能にする
            SessionSettings->bAllowJoinViaPresence = true;
            //EOSロビーサービスを利用するように設定
            SessionSettings->bUseLobbiesIfAvailable = true;
            //ロビーによる音声通話の設定
            SessionSettings->bUseLobbiesVoiceChatIfAvailable = false;

            //検索ワードとしてSearchKeywordを設定
            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);
            //const TCHAR* SessionId = *SearchSettings->SearchResults[0].GetSessionIdStr();

            //JoinSessionEOS(SearchSettings->SearchResults[0]);
        }
    }
}

セッション検索結果を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();
                    //Sessions->ClearOnJoinSessionCompleteDelegate_Handle(&AMyPlayerController::OnJoinSessionCompleteDelegate);
                    UE_LOG_ONLINE_SESSION(Log, TEXT("JoinSessionComplete : Join Session: traveling to %s"), *ConnectString);
                    LPC->ClientTravel(ConnectString, TRAVEL_Absolute);
                }
            }
        }
    }
}

以上の用意した関数をBPから呼びます。

 

f:id:shiratori00:20211221223008p:plain

f:id:shiratori00:20211221223024p:plain

こちらの画像ではデリゲートを用意し、セッションを検索して出てきた結果をUIに表示しています。

 

実行手順としては

  1. ログインを行う
  2. ホスト側がセッションを作成
  3. クライアント側がセッションを検索→参加

で動作します。

 

 

 

NAT超えて動作することは確認済みです。みんなも気軽にオンラインゲーム作ろう!