メインコンテンツまでスキップ

Unreal Engine Peer-to-Peer (P2P)

Last updated on June 12, 2024

概要

AccelByte Gaming Services (AGS) Starter の Unreal 用ピアツーピア (P2P) を使用すると、プレイヤーは AccelByte TURN サーバーを介して非公開ネットワーク全体で P2P 接続を確立できます。AccelByteNetworkUtilities を使用して、 インタラクティブなネットワーク接続を確立します。P2P は、AccelByte サービスを使用して実行するために、 AccelByte オンラインサブシステム (OSS) を必要とします。

STARTER TIER

Starter ティアでは、Unity の設定ファイルをダウンロードして、このインストール用のファイルを保存できます。

クイックリファレンス

リファレンス
#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "OnlineSubsystemAccelByteDefines.h"
セッションインターフェイスの取得
const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();
セッションの作成
FOnlineSessionSettings SessionSettings;

SessionInterface->OnCreateSessionCompleteDelegates.AddUObject(this, &UAccelByteCustomGames::CreateSessionComplete);

SessionInterface->CreateSession(0, NAME_GameSession, SessionSettings);
セッションの検索
TSharedPtr<class FOnlineSessionSearch> SessionSearch = MakeShareable(new FOnlineSessionSearch());

SessionSearch->QuerySettings.Set(SETTING_SEARCH_TYPE, FString(SETTING_SEARCH_TYPE_PEER_TO_PEER_RELAY), EOnlineComparisonOp::Equals);

SessionInterface->OnFindSessionsCompleteDelegates.AddUObject(this, &UAccelByteCustomGames::FindSessionComplete);

SessionInterface->FindSessions(0, SessionSearch.ToSharedRef());
セッションへの参加
SessionInterface->OnJoinSessionCompleteDelegates.AddUObject(this, &UAccelByteServerListEntry::JoinSessionComplete);

SessionInterface->JoinSession(0, NAME_GameSession, SessionData);
セッションの破棄
SessionInterface->OnDestroySessionCompleteDelegates.AddUObject(this, &UAccelByteServerMenu::DestroyCustomGamesSessionComplete);

SessionInterface->DestroySession(NAME_GameSession);
セッションの開始
SessionInterface->OnStartSessionCompleteDelegates.AddUObject(this, &AOssTutGameModeServerMenu::StartCustomGamesSessionInfoComplete);

SessionInterface->StartSession(NAME_GameSession);
セッションの終了
SessionInterface->OnEndSessionCompleteDelegates.AddUObject(this, &UAccelByteInGameMenu::EndSessionComplete);

SessionInterface->EndSession(NAME_GameSession);

クイックスタートガイド

このチュートリアルでは、TURN サーバーの設定方法と P2P サービスの実装方法を説明します。 このガイドでは、既に AccelByte オンラインサブシステムをインストールしていることを前提としています。

TURN サーバーの設定

Config > DefaultEngine.ini ファイルに移動し、次のコードを追加します。

[AccelByteNetworkUtilities]
UseTurnManager=true

P2P サービスの実装

P2P サービスの実装方法はゲームごとに異なる可能性があるため、 AccelByte OSS プラグイン source フォルダの OnlineSessionInterfaceAccelByte.h の 変数、構造体、関数を使用して最初にテストします。

NOTE

ここでの説明では、AccelByte の例を示します。必要に応じて、 OnlineSessionInterfaceAccelByte.h を使用して実装と機能をテストできます。 結果に問題がなければ、続いて結果とデリゲートをカスタマイズできます。

  1. 次のウィジェット C++ クラスを作成して、セッション機能同士のやり取りを指定します。

    a. AccelByteCustomGames:利用可能なすべてのセッションを作成して検索します。

    b. AccelByteServerListEntry:利用可能なセッションを見つけて参加した後に 1 つのセッションを表します。

    c. AccelByteServerMenu:ゲーム内ロビーとして機能し、セッションを破棄するか、ゲームプレイを開始します。

    d. AccelByteInGameMenu:ゲームプレイが終了したら、ポーズメニューを使用してセッションを終了します。

  2. クラスの上部に OSS ヘッダーを追加して、すべてのセッション関連機能が意図したとおりに機能するようにします。

    #include "OnlineSubsystem.h"
    #include "OnlineSubsystemUtils.h"
    #include "OnlineSubsystemAccelByteDefines.h"
    NOTE

    次の各機能には、デリゲートが伴います。 これは、ゲームがリクエストを送信した後に応答を受信するために使用されます。 この応答は、作成するゲームフローに応じて変更できます。

  3. AccelByteCustomGames で、デリゲートを持つ次の機能を実装します。

    a. セッションの作成

    セッションを作成することで、プレイヤーは他のプレイヤーから邪魔されることなく、フレンドやパーティとゲームをプレイできます。

        void UAccelByteCustomGames::CreateCustomGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    FOnlineSessionSettings SessionSettings;

    SessionInterface->OnCreateSessionCompleteDelegates.AddUObject(this, &UAccelByteCustomGames::CreateSessionComplete);

    SessionInterface->CreateSession(0, NAME_GameSession, SessionSettings);
    }

    . . .

    void UAccelByteCustomGames::CreateSessionComplete(FName SessionName, bool bIsSuccess)
    {
    if (bIsSuccess)
    {
    // Call success function to specify the success result
    }
    else
    {
    // Call success function to specify the fail result
    }
    SessionInterface->ClearOnCreateSessionCompleteDelegates(this);
    }

    b. セッションの検索

    利用可能なセッションを検索することで、プレイヤーは最も快適なセッションを見つけることができます。

        void UAccelByteCustomGames::FindCustomGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    TSharedPtr<class FOnlineSessionSearch> SessionSearch = MakeShareable(new FOnlineSessionSearch());

    SessionInterface->OnFindSessionsCompleteDelegates.AddUObject(this, &UAccelByteCustomGames::FindSessionComplete);

    SessionInterface->FindSessions(0, SessionSearch.ToSharedRef());
    }

    . . .

    void UAccelByteCustomGames::FindSessionComplete(bool bIsSuccess)
    {
    if (bIsSuccess)
    {
    // Call success function to specify the success result
    }
    else
    {
    // Call success function to specify the fail result
    }
    SessionInterface->ClearOnFindSessionsCompleteDelegates(this);
    }

  4. AccelByteServerListEntry で、デリゲートを持つ 1 つの機能を実装します。

    a. セッションへの参加

    セッションが見つかったら、プレイヤーはセッションの検索の結果によって生成された SessionData を使用してセッションに参加できます。

    void UAccelByteServerListEntry::JoinCustomGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    SessionInterface->OnJoinSessionCompleteDelegates.AddUObject(this, &UAccelByteServerListEntry::JoinSessionComplete);

    SessionInterface->JoinSession(0, NAME_GameSession, SessionData);
    }

    . . .

    void UAccelByteServerPassword::JoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
    {
    switch (Result)
    {
    case EOnJoinSessionCompleteResult::Success:
    // Call success function to specify the success result
    break;
    default:
    // Call success function to specify the fail result
    break;
    }
    SessionInterface->ClearOnJoinSessionCompleteDelegates(this);
    }

  5. AccelByteServerMenu では、デリゲートを持つ次の機能を実装します。

    a. セッションの破棄

    セッションが使用されなくなった場合、またはプレイヤーがセッションを離脱する必要がある場合は、セッションの破棄を呼び出せます。

        void UAccelByteCustomGames::DestroyCustomGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    SessionInterface->OnDestroySessionCompleteDelegates.AddUObject(this, &UAccelByteServerMenu::DestroyCustomGamesSessionComplete);

    SessionInterface->DestroySession(NAME_GameSession);
    }

    . . .

    void UAccelByteServerMenu::DestroyCustomGamesSessionComplete(FName SessionName, bool bIsSuccess)
    {
    if (bIsSuccess)
    {
    // Call success function to specify the success result
    }
    else
    {
    // Call success function to specify the fail result
    }
    SessionInterface->ClearOnDestroySessionCompleteDelegates(this);
    }

    b. セッションの開始

    プレイヤーがプレイする準備ができたら、この機能を使用してゲームを開始できます。

        void UAccelByteCustomGames::StartCustomGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    SessionInterface->OnStartSessionCompleteDelegates.AddUObject(this, &UAccelByteServerMenu::StartCustomGamesSessionInfoComplete);

    SessionInterface->StartSession(NAME_GameSession);
    }

    . . .

    void UAccelByteServerMenu::StartCustomGamesSessionComplete(FName SessionName, bool bIsSuccess)
    {
    if (bIsSuccess)
    {
    // Call success function to specify the success result
    }
    else
    {
    // Call success function to specify the fail result
    }

    SessionInterface->ClearOnStartSessionCompleteDelegates(this);
    }

  6. UAccelByteInGameMenu で、デリゲートを持つ 1 つの機能を実装します。

    a. セッションの終了

    ゲームが終了し、プレイヤーが UI メニューに戻る必要がある場合は、セッションの終了を呼び出せます。

    void UAccelByteInGameMenu::EndGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    SessionInterface->OnEndSessionCompleteDelegates.AddUObject(this, &UAccelByteInGameMenu::EndSessionComplete);

    SessionInterface->EndSession(NAME_GameSession);
    }

    . . .

    void UAccelByteInGameMenu::EndSessionComplete(FName SessionName, bool bIsSuccess)
    {
    if (bIsSuccess)
    {
    // Call success function to specify the success result
    }
    else
    {
    // Call success function to specify the fail result
    }
    SessionInterface->ClearOnEndSessionCompleteDelegates(this);
    }

お疲れさまでした。P2P サービスの実装方法を理解しました。 ご自身のゲームで試すか、UI とコード実装の手順例に進むことができます。

ステップバイステップガイド

UI の実装

実装の一環として、多数のウィジェットブループリントクラスを作成する必要があります。 このセクションでは、必要なウィジェットについて説明します。

  1. WB_CustomGamesMenu。このウィジェットは、利用可能なすべてのセッションを一覧表示し、セッションを作成します。次のものが含まれます。

    • スクロールボックス:利用可能なセッションすべてを一覧表示する。

    • テキストボックス:名前とセッションパスワードを入力する。

    • 次のボタン

      • Create Server (サーバーを作成)]:新しいセッションを作成する。
      • Refresh List (リストを更新)]:利用可能なセッションを更新する。
      • Log Out (ログアウト)]:ゲームからログアウトする。
![Image shows the Custom Games Menu](../../../../../../apps/main-app/static/images/moved-ags-ja/getting-started/ue-p2p/1.png)
  1. WB_ServerListEntry。このウィジェットは利用可能なセッションを表します。次のテキストボックスが含まれていることを確認してください。

    • サーバー名
    • 現在のセッション容量
    • 利用可能なゲームモード

    Image shows the WB Server List Entry

  2. WB_ServerMenu。このウィジェットは、プレイヤーがゲームプレイを開始する前にロビーとして機能します。次の項目が含まれていることを確認してください。

    • スクロールボックス:現在のセッションに参加しているすべてのプレイヤーを一覧表示する。
    • ボタン
      • セッション開始 (準備完了) 用
      • セッション離脱 (終了) 用

    Image shows the WB Server Menu

  3. WB_ServerPlayerEntry。このウィジェットは、現在のセッションに参加しているプレイヤーを表します。プレイヤーの名前と準備完了ステータスを表示するテキストボックスがあることを確認してください。

    Image shows the WB Server Menu

  4. WB_Pause。このウィジェットは、ゲームプレイが開始された後、ポーズ画面として機能します。セッションを終了する、またはポーズメニューを閉じるボタンがあることを確認してください。

    Image shows the WB Pause window

コードの実装

セッションの作成

  1. AccelByteCustomGames という名前の UUserWidget から継承する新しい C++ クラスを作成し、新しいクラスで WB_CustomGamesMenu を再親化します。

  2. C++ クラスの上部に、OSS ヘッダーファイルを含めます。

    #include "OnlineSubsystem.h"
    #include "OnlineSubsystemUtils.h"
    #include "OnlineSubsystemAccelByteDefines.h"
  3. AccelByteCustomGames.h クラスでサーバーの作成をトリガーするために使用するボタンを作成します。

    private:
    /**
    * @brief Button for create session.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_CreateServer;
  4. セッションの作成機能をトリガーする CreateCustomGamesSession() という関数を作成しましょう。SessionSettings は必要に応じて変更できます。この SessionSettings にはルール、ターゲットマップなどが含まれます。P2P が作成後に機能するようにするには、セッション作成時に SETTING_ACCELBYTE_ICE_ENABLED の値を true に設定します。これは、NAT リレーをオンにするために使用されます。

    void AccelByteCustomGames::CreateCustomGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    //Initiate Session Settings
    FOnlineSessionSettings SessionSettings;
    SessionSettings.bIsLANMatch = false;
    SessionSettings.NumPublicConnections = 2;
    SessionSettings.bShouldAdvertise = true;
    SessionSettings.bUsesPresence = false;

    SessionSettings.Set(SETTING_GAMEMODE, FString(TEXT("1v1")), EOnlineDataAdvertisementType::ViaOnlineService);
    SessionSettings.Set(SETTING_MAPNAME, FString(TEXT("Game")), EOnlineDataAdvertisementType::ViaOnlineService);

    SessionSettings.Set(SETTING_ACCELBYTE_ICE_ENABLED, true);

    SessionSettings.Set(FName("SESSION_NAME"), Etb_ServerName->GetText().ToString(), EOnlineDataAdvertisementType::ViaOnlineService);
    SessionSettings.Set(FName("SESSION_PASSWORD"), Etb_ServerPassword->GetText().ToString(), EOnlineDataAdvertisementType::DontAdvertise);

    SessionInterface->OnCreateSessionCompleteDelegates.AddUObject(this, &UAccelByteCustomGames::CreateSessionComplete);

    SessionInterface->CreateSession(0, NAME_GameSession, SessionSettings);
    }
  5. 次に、NativeConstruct() に初期化を追加して、[Create Server (サーバーを作成)]ボタンでセッションの作成をトリガーします。

    void AccelByteCustomGames::NativeConstruct()
    {
    . . .
    Btn_CreateServer->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::CreateCustomGamesSession);
    . . .
    }
  6. デリゲートとして機能するよう CreateSessionComplete() という別の関数を作成します。これは、新しいセッションの作成リクエストが完了するとトリガーされます。

  7. 次に、セッション名bIsSuccess 応答を指定します。この応答は、新しいセッションの作成リクエスト完了したときの Unreal Engine OSS からのデフォルト応答です。

    void AccelByteCustomGames::CreateSessionComplete(FName SessionName, bool bIsSuccess)
    {
    if (bIsSuccess)
    {
    // log Creating Session Success, Session Name: [SessionName]

    UWorld* World = GetWorld();
    World->ServerTravel("/Game/Maps/ServerMenu?listen");
    }
    else
    {
    // log Creating Session Failed
    }
    SessionInterface->ClearOnCreateSessionCompleteDelegates(this);
    }

セッションの検索

  1. AccelByteCustomGames で、セッションの検索機能をトリガーするために使用されるボタンを作成します。このボタンは、[Refresh List (リストを更新)]ボタンとも呼ばれます。

    private:
    /**
    * @brief Button for find session.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_RefreshList;
  2. セッションを検索する FindCustomGamesSession() という関数を作成します。

  3. 次に、その関数内に SessionSearch と呼ばれるポインタを作成します。このポインタはフィルターとして機能し、セッションの検索結果を含みます。

  4. 今度は、必要なものに応じてポインタを変更します。

    void AccelByteCustomGames::FindCustomGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    TSharedPtr<class FOnlineSessionSearch> SessionSearch = MakeShareable(new FOnlineSessionSearch());

    SessionSearch->bIsLanQuery = false;
    SessionSearch->MaxSearchResults = 100;
    SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);
    SessionSearch->QuerySettings.Set(SETTING_SEARCH_TYPE, FString(SETTING_SEARCH_TYPE_PEER_TO_PEER_RELAY), EOnlineComparisonOp::Equals);

    SessionInterface->OnFindSessionsCompleteDelegates.AddUObject(this, &UAccelByteCustomGames::FindSessionComplete);

    SessionInterface->FindSessions(0, SessionSearch.ToSharedRef());
    }
  5. 次に、NativeConstruct() に初期化を追加して、[Refresh List (リストを更新)]ボタンでセッションの検索をトリガーします。

    void AccelByteCustomGames::NativeConstruct()
    {
    . . .
    Btn_RefreshList->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::FindCustomGamesSession);
    . . .
    }
IMPORTANT

P2P コード実装の次の段階に進む前に、特にスタンドアロンモードを使用して、2 つ以上の異なるデバイスで実装をテストすることをお勧めします。

テスト後に作成した利用可能なセッションを他のプレイヤーが見つけることができた場合、それは準備ができているということです。

セッションへの参加

  1. AccelByteServerListEntry という名前の UUserWidget から継承する新しい C++ クラスを作成し、新しいクラスで WB_ServerListEntry を再親化します。

  2. C++ クラスの上部に、OSS ヘッダーファイルを含めます。

     #include "OnlineSubsystem.h"
    #include "OnlineSubsystemUtils.h"
    #include "OnlineSubsystemAccelByteDefines.h"
  3. 今度は、セッションへの参加機能をトリガーするために使用されるボタンを作成します。このボタンは、[Join Session (セッションに参加)]ボタンとも呼ばれます。

    private:
    /**
    * @brief Button for create session.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_JoinServer;
  4. セッションに参加するための JoinCustomGamesSession() という関数を作成します。

    void AccelByteServerListEntry::JoinCustomGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    SessionInterface->OnJoinSessionCompleteDelegates.AddUObject(this, &UAccelByteServerListEntry::JoinSessionComplete);

    SessionInterface->JoinSession(0, NAME_GameSession, SessionData);
    }
  5. 次に、NativeConstruct() に初期化を追加して、[Join Session (セッションに参加)]ボタンでセッションへの参加をトリガーします。

    void AccelByteServerListEntry::NativeConstruct()
    {
    . . .
    Btn_JoinServer->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::JoinCustomGamesSession);
    . . .
    }
  6. デリゲートとして機能する JoinSessionComplete() という別の関数を作成します。これは、セッションへの参加リクエストが完了するとトリガーされます。

  7. 以下のようにセッション名EOnJoinSessionCompleteResult 応答を指定します。この応答は、Unreal Engine OSS からのデフォルト応答です。

void AccelByteServerListEntry::JoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
switch (Result)
{
case EOnJoinSessionCompleteResult::Success:
if (!SessionInterface->GetResolvedConnectString(SessionName, ServerAddress))
{
// log Joining session failed; Couldn't get connect string
return;
}

APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
PlayerController->ClientTravel(ServerAddress, ETravelType::TRAVEL_Absolute);
break;
default:

// log Joining session failed

break;
}
SessionInterface->ClearOnJoinSessionCompleteDelegates(this);
}

セッションの破棄

  1. AccelByteServerMenu という名前の UUserWidget から継承する新しい C++ クラスを作成し、新しいクラスで WB_ServerMenu を再親化します。

  2. C++ クラスの上部に、オンラインサブシステム (OSS) ヘッダーファイルを含めます。

    #include "OnlineSubsystem.h"
    #include "OnlineSubsystemUtils.h"
    #include "OnlineSubsystemAccelByteDefines.h"
  3. セッションの破棄機能をトリガーするために使用される[Exit Session (セッションを終了)]ボタンを作成します。

    private:
    /**
    * @brief Button for Exit the Server Menu.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_Exit;
  4. セッションの破棄機能をトリガーする DestroyCustomGamesSession という関数を作成しましょう。

    void UAccelByteServerMenu::DestroyCustomGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    SessionInterface->OnDestroySessionCompleteDelegates.AddUObject(this, &UAccelByteServerMenu::DestroyCustomGamesSessionComplete);

    SessionInterface->DestroySession(NAME_GameSession);
    }
  5. 次に、NativeConstruct() に初期化を追加して、[Exit Session (セッションを終了)]ボタンでセッションの破棄をトリガーします。

    void UAccelByteServerMenu::NativeConstruct()
    {
    . . .
    Btn_Exit->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::DestroyCustomGamesSession);
    . . .
    }
  6. デリゲートとして機能するよう DestroyCustomGamesSessionComplete() という別の関数を作成します。これは、セッションの破棄リクエストが完了するとトリガーされます。

  7. 以下のようにセッション名bIsSuccess 応答を指定します。この応答は、Unreal Engine OSS からのデフォルト応答です。

void UAccelByteServerMenu::DestroyCustomGamesSessionComplete(FName SessionName, bool bIsSuccess)
{
if (bIsSuccess)
{
// log Destroy Session success

APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
PlayerController->ClientTravel("/Game/Maps/MainMenu", ETravelType::TRAVEL_Absolute);
}
else
{
// log Destroy Session failed
}
SessionInterface->ClearOnDestroySessionCompleteDelegates(this);
}
IMPORTANT

P2P コード実装の次の部分に進む前に、特にスタンドアロンモードを使用して、2 つ以上の異なるデバイスで実装を再度テストすることをお勧めします。別のプレイヤーがセッションに参加するかセッションから離脱し、ホストがセッションを離脱できれば、それは準備ができているということです。

セッションの開始

  1. セッションの開始機能をトリガーするために使用される[Start Session (セッションを開始)]ボタンを作成します。

    private:
    /**
    * @brief Button for Start to Play.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_Ready;
  2. セッションを開始するために使用される StartCustomGameSession() という関数を作成します。

    void UAccelByteServerMenu::StartCustomGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    SessionInterface->OnStartSessionCompleteDelegates.AddUObject(this, &UAccelByteServerMenu::StartCustomGamesSessionInfoComplete);

    SessionInterface->StartSession(NAME_GameSession);
    }
  3. 次に、NativeConstruct() に初期化を追加して、[Start Session (セッションを開始)]ボタンでセッションの開始をトリガーします。

    void UAccelByteServerMenu::NativeConstruct()
    {
    . . .
    Btn_Ready->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::StartCustomGamesSession);
    . . .
    }
  4. デリゲートとして機能するよう StartCustomGamesSessionComplete() という別の関数を作成します。これは、セッションの開始リクエストが完了するとトリガーされます。

  5. 以下のようにセッション名bIsSuccess 応答を指定します。この応答は、Unreal Engine OSS からのデフォルト応答です。

void UAccelByteServerMenu::StartCustomGamesSessionComplete(FName SessionName, bool bIsSuccess)
{
if (bIsSuccess)
{
// log Start Session success

UWorld* World = GetWorld();
World->ServerTravel("/Game/Maps/Game?listen");
}
else
{
// log Start Session failed
}
}

セッションの終了

  1. UAccelByteInGameMenu という名前の UUserWidget から継承する新しい C++ クラスを作成し、新しいクラスで WB_Pause を再親化します。

  2. C++ クラスの上部に、OSS ヘッダーファイルを含めます。

    #include "OnlineSubsystem.h"
    #include "OnlineSubsystemUtils.h"
    #include "OnlineSubsystemAccelByteDefines.h"
  3. セッションの終了機能をトリガーするために使用される[End Session (セッションを終了)]ボタンを作成します。

    private:
    /**
    * @brief Button for going back to Lobby Menu.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_EndGame;
  4. セッションを終了するために使用される EndGamesSession() という関数を作成します。

    void UAccelByteInGameMenu::EndGamesSession()
    {
    const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

    const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();

    SessionInterface->OnEndSessionCompleteDelegates.AddUObject(this, &UAccelByteInGameMenu::EndSessionComplete);

    SessionInterface->EndSession(NAME_GameSession);
    }
  5. 次に、NativeConstruct() に初期化を追加して、[End Session (セッションを終了)]ボタンでセッションの終了をトリガーします。

    void UAccelByteInGameMenu::NativeConstruct()
    {
    . . .
    Btn_EndGame->OnClicked.AddUniqueDynamic(this, &UAccelByteInGameMenu::EndGamesSession);
    . . .
    }
  6. デリゲートとして機能するよう EndSessionComplete() という別の関数を作成します。これは、セッションの終了リクエストが完了するとトリガーされます。

  7. 以下のようにセッション名bIsSuccess 応答を指定します。この応答は、Unreal Engine OSS からのデフォルト応答です。

void UAccelByteInGameMenu::EndSessionComplete(FName SessionName, bool bIsSuccess)
{
if (bIsSuccess)
{
// log End Session success

APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
PlayerController->ClientTravel("/Game/Maps/MainMenu", ETravelType::TRAVEL_Absolute);
}
else
{
// log End Session failed
}
}

完全なコード

AccelByteCustomGames.h
// Copyright (c) 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.
#pragma once

#include "CoreMinimal.h"

#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "OnlineSubsystemSessionSettings.h"

#include "Blueprint/UserWidget.h"
#include "AccelByteCustomGames.generated.h"

class UButton;
class UScrollBox;
class UEditableTextBox;
class UAccelByteServerListEntry;
class AOssTutorialMenuHUD;

/**
* Custom Games Setup (P2P)
* This code covers AccelByte sevices including:
*
* - Create custom games session
* - Find custom games session
* - Log Out
*/
UCLASS()
class OSSTUTORIALPROJECT_API UAccelByteCustomGames : public UUserWidget
{
GENERATED_BODY()

protected:

virtual void NativeConstruct() override;

/**
* @brief Scroll Box for Custom Games List Widget.
*/
UPROPERTY(meta = (BindWidget))
UScrollBox* Sb_ServerList;

/**
* @brief Editable Text Box for Server Name inside CustomGames Widget.
*/
UPROPERTY(meta = (BindWidget))
UEditableTextBox* Etb_ServerName;

/**
* @brief Editable Text Box for Server Password inside CustomGames Widget.
*/
UPROPERTY(meta = (BindWidget))
UEditableTextBox* Etb_ServerPassword;

/**
* @brief Button for Create Server Session.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_CreateServer;

/**
* @brief Button for Refresh Available Server Session.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_RefreshList;

/**
* @brief Button for going back to Lobby Menu (Log out for now).
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_BackToLobby;

/**
* @brief Instantiate all casting to the tutorial menu HUD.
*/
UPROPERTY()
AOssTutorialMenuHUD* TutorialMenuHUD;

/**
* @brief Create custom games session.
*/
void CreateCustomGamesSession();

/**
* @brief Find custom games session.
*/
void FindCustomGamesSession();

private:
/**
* @brief Create a New Session.
*/
UFUNCTION()
void OnClickCreateSession();

/**
* @brief Refresh The Session List
*/
UFUNCTION()
void RefreshSessionList();

/**
* @brief Closing the custom games menu widget.
*/
UFUNCTION()
void CloseCustomGamesMenu();

/**
* @brief Called after create session process is complete.
*/
void CreateSessionComplete(FName SessionName, bool bIsSuccess);

/**
* @brief Called after create session process is success.
*/
void CreateSessionSuccess(FName SessionName);

/**
* @brief Called after create session process is fail.
*/
void CreateSessionFailed();

/**
* @brief Called after find session process is complete.
*/
void FindSessionComplete(bool bIsSuccess);

/**
* @brief Called after find session process is success.
*/
void FindSessionSuccess();

/**
* @brief Called after find session process is fail.
*/
void FindSessionFailed();

/**
* @brief Varibale Pointer for Online Session.
*/
IOnlineSessionPtr SessionInterface;

/**
* @brief Variable Pointer for Search Parameters and Results.
*/
TSharedPtr<class FOnlineSessionSearch> SessionSearch;

/**
* @brief Reference to Party Player Entry Class.
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteServerListEntry> CustomSessionEntryClass;

/*
* @brief Refresh Time Latency
*/
const float RefreshTime = 120.0f;

/*
* @brief Break To Refresh Latencies
*/
const bool bNeedRefresh = true;

/*
* @brief Timer Handle Delegate
*/
FTimerHandle MemberTimerHandle;
};
AccelByteCustomGames.cpp
// Copyright (c) 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelByteCustomGames.h"
#include "AccelByteServerListEntry.h"
#include "../Authentication/AccelByteAuth.h"

#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "OnlineSubsystemSessionSettings.h"
#include "OnlineSubsystemAccelByteDefines.h"
#include "OnlineSubsystemSessionSettings.h"
#include "OnlineSubsystemTypes.h"

#include "Components/Button.h"
#include "Components/ScrollBox.h"
#include "Components/EditableTextBox.h"
#include "OssTutorialProject/HUD/OssTutorialMenuHUD.h"
#include "OssTutorialProject/OssTutorialProjectGameInstance.h"

void UAccelByteCustomGames::NativeConstruct()
{
Super::NativeConstruct();

TutorialMenuHUD = Cast<AOssTutorialMenuHUD>(GetWorld()->GetFirstPlayerController()->GetHUD());

Btn_CreateServer->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::OnClickCreateSession);
Btn_RefreshList->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::RefreshSessionList);
Btn_BackToLobby->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::CloseCustomGamesMenu);

GetWorld()->GetTimerManager().SetTimer(MemberTimerHandle, this, &UAccelByteCustomGames::RefreshSessionList, RefreshTime, bNeedRefresh);

RefreshSessionList();
}

void UAccelByteCustomGames::CreateCustomGamesSession()
{
const IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);

if (OnlineSub)
{
SessionInterface = OnlineSub->GetSessionInterface();

if (SessionInterface.IsValid())
{
FOnlineSessionSettings SessionSettings;

SessionSettings.NumPublicConnections = 2;
SessionSettings.bShouldAdvertise = true;
SessionSettings.bUsesPresence = false;
SessionSettings.bAllowJoinInProgress = false;

SessionSettings.Set(SETTING_GAMEMODE, FString(TEXT("1v1")), EOnlineDataAdvertisementType::ViaOnlineService);
SessionSettings.Set(SETTING_MAPNAME, FString(TEXT("ServerMenu")), EOnlineDataAdvertisementType::ViaOnlineService);

SessionSettings.Set(SETTING_ACCELBYTE_ICE_ENABLED, true);

SessionSettings.Set(FName("SESSION_NAME"), Etb_ServerName->GetText().ToString(), EOnlineDataAdvertisementType::ViaOnlineService);
SessionSettings.Set(FName("SESSION_PASSWORD"), Etb_ServerPassword->GetText().ToString(), EOnlineDataAdvertisementType::DontAdvertise);

SessionInterface->OnCreateSessionCompleteDelegates.AddUObject(this, &UAccelByteCustomGames::CreateSessionComplete);

SessionInterface->CreateSession(0, NAME_GameSession, SessionSettings);
/*Note: Uncomment this if you need to change the session name variable*/
//SessionInterface->CreateSession(0, FName(Etb_ServerName->GetText().ToString()), SessionSettings);
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Create Session Failed; Session Interface is invalid"));
}
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Create Session Failed; Subsystem Invalid"));
}
}

void UAccelByteCustomGames::FindCustomGamesSession()
{
const IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);

if (OnlineSub)
{
SessionInterface = OnlineSub->GetSessionInterface();

if (SessionInterface.IsValid())
{
SessionSearch = MakeShareable(new FOnlineSessionSearch());

if (SessionSearch.IsValid())
{
SessionSearch->MaxSearchResults = 100;
SessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);
SessionSearch->QuerySettings.Set(SETTING_SEARCH_TYPE, FString(SETTING_SEARCH_TYPE_PEER_TO_PEER_RELAY), EOnlineComparisonOp::Equals);

SessionInterface->OnFindSessionsCompleteDelegates.AddUObject(this, &UAccelByteCustomGames::FindSessionComplete);

SessionInterface->FindSessions(0, SessionSearch.ToSharedRef());
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Looking for Session Failed; FOnlineSessionSearch is invalid"));
}
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Looking for Session Failed; Session Interface is invalid"));
}
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Looking for Session Failed; Subsystem Invalid"));
}
}

void UAccelByteCustomGames::OnClickCreateSession()
{
CreateCustomGamesSession();
}

void UAccelByteCustomGames::RefreshSessionList()
{
Sb_ServerList->ClearChildren();

FindCustomGamesSession();
}

void UAccelByteCustomGames::CloseCustomGamesMenu()
{
TutorialMenuHUD->GetLoginMenu()->OnClickLogoutButton();
}

void UAccelByteCustomGames::CreateSessionComplete(FName SessionName, bool bIsSuccess)
{
if (bIsSuccess)
{
CreateSessionSuccess(SessionName);
}
else
{
CreateSessionFailed();
}
SessionInterface->ClearOnCreateSessionCompleteDelegates(this);
}

void UAccelByteCustomGames::CreateSessionSuccess(FName SessionName)
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, FString::Printf(TEXT("Creating Session Success, Session Name: %s"), *SessionName.ToString()));

auto GameInstance = Cast<UOssTutorialProjectGameInstance>(GetGameInstance());
GameInstance->InitSessionName(SessionName);
/*Note: Uncomment this if you need to change the session name variable*/
//GameInstance->InitSessionName(FName(Etb_ServerName->GetText().ToString()));

APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
PlayerController->ClientTravel("/Game/Maps/ServerMenu?listen", ETravelType::TRAVEL_Absolute);
}

void UAccelByteCustomGames::CreateSessionFailed()
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Creating Session Failed"));
}

void UAccelByteCustomGames::FindSessionComplete(bool bIsSuccess)
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, TEXT("Finish Looking for sessions"));

if (bIsSuccess)
{
FindSessionSuccess();
}
else
{
FindSessionFailed();
}
SessionInterface->ClearOnFindSessionsCompleteDelegates(this);
}

void UAccelByteCustomGames::FindSessionSuccess()
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, TEXT("Successfully found game sessions"));

if (SessionSearch.IsValid())
{
for (const FOnlineSessionSearchResult& SearchResult : SessionSearch->SearchResults)
{
const TWeakObjectPtr<UAccelByteServerListEntry> CustomSessionEntry = MakeWeakObjectPtr<UAccelByteServerListEntry>(CreateWidget<UAccelByteServerListEntry>(this, CustomSessionEntryClass.Get()));

CustomSessionEntry->InitData(SearchResult);

Sb_ServerList->AddChild(CustomSessionEntry.Get());
}
}
}

void UAccelByteCustomGames::FindSessionFailed()
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Failed to find game sessions"));
}
AccelByteServerListEntry.h
// Copyright (c) 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "OnlineSubsystemUtils.h"
#include "AccelByteServerListEntry.generated.h"

class UTextBlock;
class UButton;
class AOssTutorialGameHUD;

/**
*
*/
UCLASS()
class OSSTUTORIALPROJECT_API UAccelByteServerListEntry : public UUserWidget
{
GENERATED_BODY()

protected:
virtual void NativeConstruct() override;

/**
* @brief Text for the name of custom games.
*/
UPROPERTY(meta = (BindWidget))
UTextBlock* Tb_ServerName;

/**
* @brief Text for the game mode of custom games.
*/
UPROPERTY(meta = (BindWidget))
UTextBlock* Tb_ServerGameMode;

/**
* @brief Text for the capacity of custom games.
*/
UPROPERTY(meta = (BindWidget))
UTextBlock* Tb_ServerCapacity;

/**
* @brief Button for join custom games session.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_JoinServer;

/**
* @brief Instantiate all casting to the tutorial menu HUD.
*/
UPROPERTY()
AOssTutorialGameHUD* TutorialGameHUD;

/**
* @brief Join custom games session.
*/
void JoinCustomGamesSession();

public:
/**
* @brief Init Data Game Session.
*/
void InitData(const FOnlineSessionSearchResult& SearchResult);

private:
/**
* @brief Join a Session.
*/
UFUNCTION()
void OnClickJoinButton();

/**
* @brief Called after find session process is complete.
*/
void JoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);

/**
* @brief Called after find session process is success.
*/
void JoinSessionSuccess(FName SessionName);

/**
* @brief Called after find session process is fail.
*/
void JoinSessionFailed();

/**
* @brief Varibale Pointer for Online Session.
*/
IOnlineSessionPtr SessionInterface;

/**
* @brief Variable to Save Search Result Data.
*/
FOnlineSessionSearchResult SessionData;

/**
* @brief Variable to Save The Session Name .
*/
FName CustomGameSessionName;

/**
* @brief Variable to Save The Server Address.
*/
FString ServerAddress;
};
AccelByteServerListEntry.cpp
// Copyright (c) 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelByteServerListEntry.h"
#include "AccelByteCustomGames.h"
#include "../ServerMenu/AccelByteServerMenu.h"

#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "OnlineSubsystemSessionSettings.h"
#include "OnlineSubsystemAccelByteDefines.h"
#include "OnlineSubsystemSessionSettings.h"
#include "OnlineSubsystemTypes.h"

#include "Components/Button.h"
#include "Components/TextBlock.h"
#include "OssTutorialProject/OssTutorialGameHUD.h"
#include "OssTutorialProject/OssTutorialProjectGameInstance.h"

void UAccelByteServerListEntry::NativeConstruct()
{
Super::NativeConstruct();

TutorialGameHUD = Cast<AOssTutorialGameHUD>(GetWorld()->GetFirstPlayerController()->GetHUD());

Btn_JoinServer->OnClicked.AddUniqueDynamic(this, &UAccelByteServerListEntry::OnClickJoinButton);
}

void UAccelByteServerListEntry::JoinCustomGamesSession()
{
const IOnlineSubsystem* const OnlineSub = Online::GetSubsystem(GetWorld());

SessionInterface = OnlineSub->GetSessionInterface();

GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, TEXT("Joining a Session"));

SessionInterface->OnJoinSessionCompleteDelegates.AddUObject(this, &UAccelByteServerListEntry::JoinSessionComplete);

SessionInterface->JoinSession(0, NAME_GameSession, SessionData);
}

void UAccelByteServerListEntry::InitData(const FOnlineSessionSearchResult& SearchResult)
{
CustomGameSessionName = FName(SearchResult.Session.SessionSettings.Settings.FindRef("SESSION_NAME").Data.ToString());

Tb_ServerName->SetText(FText::FromName(CustomGameSessionName));

SessionData = SearchResult;
}

void UAccelByteServerListEntry::OnClickJoinButton()
{
JoinCustomGamesSession();
}

void UAccelByteServerListEntry::JoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
switch (Result)
{
case EOnJoinSessionCompleteResult::Success:
if (!SessionInterface->GetResolvedConnectString(SessionName, ServerAddress))
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Joining session failed; Couldn't get connect string"));
return;
}

APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
PlayerController->ClientTravel(ServerAddress, ETravelType::TRAVEL_Absolute);
break;
default:

GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Joining session failed"));

break;
}
SessionInterface->ClearOnJoinSessionCompleteDelegates(this);
}

void UAccelByteServerListEntry::JoinSessionSuccess(FName SessionName)
{
if (!SessionInterface->GetResolvedConnectString(SessionName, ServerAddress))
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Joining session failed; Couldn't get connect string"));
return;
}

GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, FString::Printf(TEXT("Joining session success, session name %s"), *SessionName.ToString()));


auto GameInstance = Cast<UOssTutorialProjectGameInstance>(GetGameInstance());
GameInstance->InitData(SessionName);

APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
PlayerController->ClientTravel(ServerAddress, ETravelType::TRAVEL_Absolute);
}

void UAccelByteServerListEntry::JoinSessionFailed()
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Joining session failed"));
}
AccelByteServerMenu.h
// Copyright (c) 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "OnlineSubsystemSessionSettings.h"
#include "Blueprint/UserWidget.h"
#include "AccelByteServerMenu.generated.h"

class UTextBlock;
class UScrollBox;
class UButton;
class UAccelByteServerMenuPlayerEntry;
class AOssTutGameModeServerMenu;
class UOssTutorialProjectGameInstance;

/**
* Custom Games Menu (P2P)
* This code covers AccelByte sevices including:
*
* - Destroy custom games session
* - Start custom games session
*/
UCLASS()
class OSSTUTORIALPROJECT_API UAccelByteServerMenu : public UUserWidget
{
GENERATED_BODY()

public:
/**
* @brief Start the game session.
*/
UFUNCTION()
void OnClickStartServerMenuButton();

/**
* @brief Exit or Destroy the Server Menu Widget.
*/
UFUNCTION()
void OnClickExitServerMenuButton();

protected:

virtual void NativeConstruct() override;

/**
* @brief Text Box for Server Name.
*/
UPROPERTY(meta = (BindWidget))
UTextBlock* Tb_ServerName;

/**
* @brief Scroll Box for Players in Team A.
*/
UPROPERTY(meta = (BindWidget))
UScrollBox* Sb_TeamA;

/**
* @brief Scroll Box for Players in Team B.
*/
UPROPERTY(meta = (BindWidget))
UScrollBox* Sb_TeamB;

/**
* @brief Button for Ready to Play.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Ready;

/**
* @brief Button for Exit the Server Menu.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Exit;

/**
* @brief Instantiate Game Mode.
*/
AOssTutGameModeServerMenu* ServerMenuGameMode;

/**
* @brief Exit or Destroy the Server Menu Widget.
*/
void DestroyCustomGamesSession();

/**
* @brief Start the custom game session.
*/
void StartCustomGamesSession();

private:
/**
* @brief Refresh Player Data.
*/
void RefreshPlayerList(FName SessionName, const TArray<FUniqueNetIdRef>& PlayerIds, bool bIsSuccess);

/**
* @brief Manager to spawn player entry
*/
void PlayerEntryManager(const TArray< FUniqueNetIdRef >& PlayerIds);

/**
* @brief Called after Start session process is complete.
*/
void StartCustomGamesSessionComplete(FName SessionName, bool bIsSuccess);

/**
* @brief Called after Start session process is success.
*/
void StartCustomGamesSessionSuccess(FName SessionName);

/**
* @brief Called after Start session process is fail.
*/
void StartCustomGamesSessionFailed();

/**
* @brief Called after Destroy session process is complete.
*/
void DestroyCustomGamesSessionComplete(FName SessionName, bool bIsSuccess);

/**
* @brief Called after Destroy session process is success.
*/
void DestroyCustomGamesSuccess(FName SessionName);

/**
* @brief Called after Destroy session process is fail.
*/
void DestroyCustomGamesFailed();

/**
* @brief Exit or Destroy the Server Menu Widget.
*/
IOnlineSessionPtr SessionInterface;

/**
* @brief Variable to Save The Session Name.
*/
FName CustomGameSessionName;

/**
* @brief Reference to Player Entry Class.
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteServerMenuPlayerEntry> PlayerEntryClass;

/**
* @brief Array containing list of player ids in current session.
*/
TArray<FUniqueNetIdRef> CurrentPlayerIds;
};
AccelByteServerMenu.cpp
// Copyright (c) 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelByteServerMenu.h"

#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "OnlineSubsystemSessionSettings.h"
#include "OnlineSubsystemAccelByteDefines.h"
#include "OnlineSubsystemSessionSettings.h"
#include "OnlineSubsystemTypes.h"

#include "Components/Button.h"
#include "Components/TextBlock.h"
#include "Components/ScrollBox.h"
#include "../ServerMenu/AccelByteServerMenuPlayerEntry.h"
#include "OssTutorialProject/OssTutorialProjectGameInstance.h"
#include "OssTutorialProject/GameMode/OssTutGameModeServerMenu.h"

void UAccelByteServerMenu::OnClickExitServerMenuButton()
{
DestroyCustomGamesSession();
}

void UAccelByteServerMenu::OnClickStartServerMenuButton()
{
if (CurrentPlayerIds.Num() >= 2)
{
StartCustomGamesSession();
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Can't start session when alone"));
}
}

void UAccelByteServerMenu::NativeConstruct()
{
Super::NativeConstruct();

const IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);
SessionInterface = OnlineSub->GetSessionInterface();

SessionInterface->OnRegisterPlayersCompleteDelegates.AddUObject(this, &UAccelByteServerMenu::RefreshPlayerList);
SessionInterface->OnUnregisterPlayersCompleteDelegates.AddUObject(this, &UAccelByteServerMenu::RefreshPlayerList);

if (GetOwningPlayer()->HasAuthority())
{
Btn_Ready->OnClicked.AddUniqueDynamic(this, &UAccelByteServerMenu::OnClickStartServerMenuButton);
}
else
{
Btn_Ready->SetVisibility(ESlateVisibility::Collapsed);
}

Btn_Exit->OnClicked.AddUniqueDynamic(this, &UAccelByteServerMenu::OnClickExitServerMenuButton);

auto GameInstance = Cast<UOssTutorialProjectGameInstance>(GetGameInstance());
CustomGameSessionName = GameInstance->CustomSessionName;

Tb_ServerName->SetText(FText::FromName(CustomGameSessionName));
}

void UAccelByteServerMenu::DestroyCustomGamesSession()
{
const IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);

auto GameInstance = Cast<UOssTutorialProjectGameInstance>(GetGameInstance());
ULocalPlayer* const Player = GameInstance->GetFirstGamePlayer();

if (OnlineSub)
{
SessionInterface = OnlineSub->GetSessionInterface();

if (SessionInterface.IsValid())
{
SessionInterface->OnDestroySessionCompleteDelegates.AddUObject(this, &UAccelByteServerMenu::DestroyCustomGamesSessionComplete);

SessionInterface->UnregisterPlayer(NAME_GameSession, *Player->GetPreferredUniqueNetId().GetUniqueNetId());
SessionInterface->DestroySession(NAME_GameSession);
/*Note: Uncomment this if you need to change the session name variable*/
//SessionInterface->UnregisterPlayer(CustomGameSessionName, *Player->GetPreferredUniqueNetId().GetUniqueNetId());
//SessionInterface->DestroySession(CustomGameSessionName);
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Destroy Session Failed; Session Interface is invalid"));
}
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Destroy Session Failed; Subsystem Invalid"));
}
}

void UAccelByteServerMenu::StartCustomGamesSession()
{
const IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);

if (OnlineSub)
{
SessionInterface = OnlineSub->GetSessionInterface();

if (SessionInterface.IsValid())
{
SessionInterface->OnStartSessionCompleteDelegates.AddUObject(this, &UAccelByteServerMenu::StartCustomGamesSessionComplete);

SessionInterface->StartSession(NAME_GameSession);
/*Note: Uncomment this if you need to change the session name variable*/
//SessionInterface->StartSession(CustomGameSessionName);
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Destroy Session Failed; Session Interface is invalid"));
}
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Destroy Session Failed; Subsystem Invalid"));
}
}

void UAccelByteServerMenu::RefreshPlayerList(FName SessionName, const TArray<FUniqueNetIdRef>& PlayerIds, bool bIsSuccess)
{
const IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);
SessionInterface = OnlineSub->GetSessionInterface();

FNamedOnlineSession* ThisSessionInfo = SessionInterface->GetNamedSession(SessionName);

CurrentPlayerIds = ThisSessionInfo->RegisteredPlayers;

PlayerEntryManager(CurrentPlayerIds);
}

void UAccelByteServerMenu::PlayerEntryManager(const TArray<FUniqueNetIdRef>& PlayerIds)
{
Sb_TeamA->ClearChildren();
Sb_TeamB->ClearChildren();

for (int i = 0; i < PlayerIds.Num(); i++)
{
const TWeakObjectPtr<UAccelByteServerMenuPlayerEntry> CustomSessionEntry = MakeWeakObjectPtr<UAccelByteServerMenuPlayerEntry>(CreateWidget<UAccelByteServerMenuPlayerEntry>(this, PlayerEntryClass.Get()));

if (i % 2 == 0)
{
CustomSessionEntry->InitPlayerData(PlayerIds[i].Get());

Sb_TeamA->AddChild(CustomSessionEntry.Get());
}
else
{
CustomSessionEntry->InitPlayerData(PlayerIds[i].Get());

Sb_TeamB->AddChild(CustomSessionEntry.Get());
}
}
}

void UAccelByteServerMenu::StartCustomGamesSessionComplete(FName SessionName, bool bIsSuccess)
{
if (bIsSuccess)
{
StartCustomGamesSessionSuccess(SessionName);
}
else
{
StartCustomGamesSessionFailed();
}
}

void UAccelByteServerMenu::StartCustomGamesSessionSuccess(FName SessionName)
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, TEXT("Start Session success"));

//travel to the game mape using server menu game mode
UWorld* world = GetWorld();
AOssTutGameModeServerMenu* GameModeServerMenu = Cast<AOssTutGameModeServerMenu>(world->GetAuthGameMode());

GameModeServerMenu->TravelToGame();
}

void UAccelByteServerMenu::StartCustomGamesSessionFailed()
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Start Session failed"));
}

void UAccelByteServerMenu::DestroyCustomGamesSessionComplete(FName SessionName, bool bIsSuccess)
{
if (bIsSuccess)
{
DestroyCustomGamesSuccess(SessionName);
}
else
{
DestroyCustomGamesFailed();
}
SessionInterface->ClearOnDestroySessionCompleteDelegates(this);
}

void UAccelByteServerMenu::DestroyCustomGamesSuccess(FName SessionName)
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, TEXT("Destroy Session success"));

APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
PlayerController->ClientTravel("/Game/Maps/MainMenu", ETravelType::TRAVEL_Absolute);
}

void UAccelByteServerMenu::DestroyCustomGamesFailed()
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Destroy Session failed"));
}
AccelByteInGameMenu.h
// Copyright (c) 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "AccelByteInGameMenu.generated.h"

class UButton;

/**
* In Games Menu (P2P)
* This code covers AccelByte sevices including:
*
* - Destroy custom games session
* - End custom games session
*/
UCLASS()
class OSSTUTORIALPROJECT_API UAccelByteInGameMenu : public UUserWidget
{
GENERATED_BODY()

protected:

virtual void NativeConstruct() override;

/**
* @brief Scroll Box for Custom Games List Widget.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_EndGame;

/**
* @brief Editable Text Box for Server Name inside CustomGames Widget.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Cancel;

/**
* @brief Varibale Pointer for Online Session.
*/
IOnlineSessionPtr SessionInterface;

/**
* @brief Create custom games session.
*/
void EndGamesSession();

/**
* @brief Exit or Destroy the Server Menu Widget.
*/
void DestroyCustomGamesSession();

private:
/**
* @brief End current running session.
*/
UFUNCTION()
void ClickEndSession();

/**
* @brief Close current in game menu pop up.
*/
UFUNCTION()
void ClickCancelInGameMenu();

/**
* @brief Called after create session process is complete.
*/
void EndSessionComplete(FName SessionName, bool bIsSuccess);

/**
* @brief Called after create session process is success.
*/
void EndSessionSuccess(FName SessionName);

/**
* @brief Called after create session process is fail.
*/
void EndSessionFailed();

/**
* @brief Called after Destroy session process is complete.
*/
void DestroyCustomGamesSessionComplete(FName SessionName, bool bIsSuccess);

/**
* @brief Called after Destroy session process is success.
*/
void DestroyCustomGamesSuccess(FName SessionName);

/**
* @brief Called after Destroy session process is fail.
*/
void DestroyCustomGamesFailed();

/**
* @brief Variable to save the current session name.
*/
FName CurrentGameSessionName;
};
AccelByteInGameMenu.cpp
// Copyright (c) 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelByteInGameMenu.h"

#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "OnlineSubsystemSessionSettings.h"
#include "OnlineSubsystemAccelByteDefines.h"
#include "OnlineSubsystemSessionSettings.h"
#include "OnlineSubsystemTypes.h"

#include "Components/Button.h"
#include "OssTutorialProject/OssTutorialProjectGameInstance.h"

void UAccelByteInGameMenu::NativeConstruct()
{
Super::NativeConstruct();

Btn_EndGame->OnClicked.AddUniqueDynamic(this, &UAccelByteInGameMenu::ClickEndSession);
Btn_Cancel->OnClicked.AddUniqueDynamic(this, &UAccelByteInGameMenu::ClickCancelInGameMenu);

APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
PlayerController->bShowMouseCursor = true;

auto GameInstance = Cast<UOssTutorialProjectGameInstance>(GetGameInstance());
CurrentGameSessionName = GameInstance->CustomSessionName;
}

void UAccelByteInGameMenu::EndGamesSession()
{
const IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);

if (OnlineSub)
{
SessionInterface = OnlineSub->GetSessionInterface();

if (SessionInterface.IsValid())
{
SessionInterface->OnEndSessionCompleteDelegates.AddUObject(this, &UAccelByteInGameMenu::EndSessionComplete);

SessionInterface->EndSession(NAME_GameSession);
/*Todo: change this if you need it*/
//SessionInterface->EndSession(CurrentGameSessionName);
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Destroy Session Failed; Session Interface is invalid"));
}
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Destroy Session Failed; Subsystem Invalid"));
}
}

void UAccelByteInGameMenu::DestroyCustomGamesSession()
{
const IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);

auto GameInstance = Cast<UOssTutorialProjectGameInstance>(GetGameInstance());
ULocalPlayer* const Player = GameInstance->GetFirstGamePlayer();

if (OnlineSub)
{
SessionInterface = OnlineSub->GetSessionInterface();

if (SessionInterface.IsValid())
{
SessionInterface->OnDestroySessionCompleteDelegates.AddUObject(this, &UAccelByteInGameMenu::DestroyCustomGamesSessionComplete);

SessionInterface->UnregisterPlayer(NAME_GameSession, *Player->GetPreferredUniqueNetId().GetUniqueNetId());
SessionInterface->DestroySession(NAME_GameSession);
/*Note: Uncomment this if you need to change the session name variable*/
//SessionInterface->UnregisterPlayer(CurrentGameSessionName, *Player->GetPreferredUniqueNetId().GetUniqueNetId());
//SessionInterface->DestroySession(CurrentGameSessionName);
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Destroy Session Failed; Session Interface is invalid"));
}
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Destroy Session Failed; Subsystem Invalid"));
}
}

void UAccelByteInGameMenu::ClickEndSession()
{
const IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);
SessionInterface = OnlineSub->GetSessionInterface();

if (SessionInterface->GetNamedSession(NAME_GameSession)->bHosting)
{
EndGamesSession();
}
else
{
DestroyCustomGamesSession();
}
}

void UAccelByteInGameMenu::ClickCancelInGameMenu()
{
this->RemoveFromViewport();

APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
PlayerController->bShowMouseCursor = false;
}

void UAccelByteInGameMenu::EndSessionComplete(FName SessionName, bool bIsSuccess)
{
if (bIsSuccess)
{
EndSessionSuccess(SessionName);
}
else
{
EndSessionFailed();
}
}

void UAccelByteInGameMenu::EndSessionSuccess(FName SessionName)
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, TEXT("End Session success"));

DestroyCustomGamesSession();
}

void UAccelByteInGameMenu::EndSessionFailed()
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("End Session failed"));
}

void UAccelByteInGameMenu::DestroyCustomGamesSessionComplete(FName SessionName, bool bIsSuccess)
{
if (bIsSuccess)
{
DestroyCustomGamesSuccess(SessionName);
}
else
{
DestroyCustomGamesFailed();
}
SessionInterface->ClearOnDestroySessionCompleteDelegates(this);
}

void UAccelByteInGameMenu::DestroyCustomGamesSuccess(FName SessionName)
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, TEXT("Destroy Session success"));

APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
PlayerController->ClientTravel("/Game/Maps/MainMenu", ETravelType::TRAVEL_Absolute);
}

void UAccelByteInGameMenu::DestroyCustomGamesFailed()
{
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Red, TEXT("Destroy Session failed"));
}

Frequently Asked Questions

Q: I will implement a multiplayer game with P2P, but I don't want to use Unreal Engine replication. Does AGS OSS support that?

A: No, AGS OSS only supports Unreal Engine replication for online games.

Q: I don't use OSS for our Unreal Engine game. How can I implement P2P?

A: Implementing P2P into your game requires the use of AccelByte OSS, which includes built-in P2P functionality.