Last Updated: 11/8/2022, 8:40:17 AM

# AccelByte Cloud P2P for Unreal Engine

# Overview

The AccelByte Cloud Peer-to-Peer (P2P) for Unreal allows players to establish P2P connections across private networks through the AccelByte Turn Server. It uses AccelByteNetworkUtilities to establish interactive network connectivity. P2P needs the AccelByte Online Subsystem (OSS) to allow it to run using the AccelByte Service.

# Quick References

References
#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "OnlineSubsystemAccelByteDefines.h"
Get Session Interface
const IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld(), ACCELBYTE_SUBSYSTEM);

const IOnlineSessionPtr SessionInterface = OnlineSub->GetSessionInterface();
Creating Session
FOnlineSessionSettings SessionSettings;
 
SessionInterface->OnCreateSessionCompleteDelegates.AddUObject(this, &UAccelByteCustomGames::CreateSessionComplete);

SessionInterface->CreateSession(0, NAME_GameSession, SessionSettings);
Find Session
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());
Join Session
SessionInterface->OnJoinSessionCompleteDelegates.AddUObject(this, &UAccelByteServerListEntry::JoinSessionComplete);
 
SessionInterface->JoinSession(0, NAME_GameSession, SessionData);
Destroy Session
SessionInterface->OnDestroySessionCompleteDelegates.AddUObject(this, &UAccelByteServerMenu::DestroyCustomGamesSessionComplete);
 
SessionInterface->DestroySession(NAME_GameSession);
Start Session
SessionInterface->OnStartSessionCompleteDelegates.AddUObject(this, &AOssTutGameModeServerMenu::StartCustomGamesSessionInfoComplete);
 
SessionInterface->StartSession(NAME_GameSession);
End Session
SessionInterface->OnEndSessionCompleteDelegates.AddUObject(this, &UAccelByteInGameMenu::EndSessionComplete);
 
SessionInterface->EndSession(NAME_GameSession);

# Quickstart Guide

In this tutorial, you will learn how to set up the Turn Server and implement the P2P service. This guide assumes that you have already installed the AccelByte OSS (opens new window).

# Set up the Turn Server

To run your game on the P2P service, you need to add Turn Server credentials.

Ask your AccelByte developer partner manager for your TurnServerSecret.

Afterwards, go to the Config > DefaultEngine.ini file and add the following code:

[AccelByteNetworkUtilities]
UseTurnManager=true
TurnServerSecret=<your-turn-secret>

# Implement the P2P Service

Because the way you implement the P2P service may be different for each game, you test it first using the variables, structs and functions in OnlineSessionInterfaceAccelByte.h in the AccelByte OSS plugins source folder.

NOTE

In these explanations we will show you the AccelByte example. If you wish, you can test your implementation and the functionalities using OnlineSessionInterfaceAccelByte.h. When you are comfortable with the result, you can go on to customize the results and delegates.

  1. Create the following widget C++ classes to specify the interactions between session functionalities:

    • AccelByteCustomGames To create and find all available sessions.
    • AccelByteServerListEntry To represent one session after finding it available and joining the session.
    • AccelByteServerMenu To act as an in-game lobby and to destroy a session or start the gameplay.
    • AccelByteInGameMenu To end the session using the pause menu once the gameplay is finished.
  2. Add the OSS header at the top of the classes, to ensure all the session related functionalities work as intended.

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

    NOTE

    Each of the following functionalities comes with a delegate. It is used to receive the response after the game sends a request. This response can be modified depending on the game flow you want to create.

  3. In AccelByteCustomGames, we will implement the following functionalities with their delegates:

    • Create Session

    By creating sessions, players can play a game with their friends, or parties without interruption from another player.

     ```cpp
     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);
     }
     ```
    
    • Find Session

    By finding the available sessions, players can find the session they are most comfortable with.

     ```cpp
     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. In AccelByteServerListEntry, we will implement one functionality with the delegate:

    • Join Session

    When they have found a session, players can join the session using the SessionData, which is generated by the result of Find Session.

     ```cpp
     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. In AccelByteServerMenu, we will implement the following functionalities with their delegates:

    • Destroy Session

    If a session is no longer being used, or the player needs to leave the session, we can call destroy session.

     ```cpp
     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);
     }
     ```
    
    • Start Session

    When the players are ready to play, they can start the game using this function.

     ```cpp
     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. In UAccelByteInGameMenu, we will implement one functionality with the delegate:

    • End Session

    If the game is finished, and the players need to be returned to a UI menu, we can call the end session.

     ```cpp
     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);
     }
     ```
    

Congratulations! You have learned how to implement the P2P service!

You can give it a try in your own game or continue on for a step-by-step example of the UI and code implementation.

# Step-by-Step Guide

# UI Implementation

As part of the implementation you need to create a number of widget blueprint classes. This section explains the widgets you will need:

  1. WB_CustomGamesMenu. This widget will list all of the available sessions and create a session. Include the following:

    • Scroll Box to list all the available sessions.
    • Text Box to enter a name and session password.
    • The following buttons:
      • Create Server to create a new session.
      • Refresh List to refresh the available session.
      • Log Out to log out from the game.

    p2p-ue

  2. WB_ServerListEntry. This widget will represent available sessions. Make sure includes a Text Box for:

    • Server name
    • Current session capacity
    • The available game mode.

    p2p-ue

  3. WB_ServerMenu. This widget acts as a Lobby before the players start their gameplay. Make sure it has the following items:

    • Scroll Box to list all the players who join the current session.
    • Buttons to:
      • Start the session (Ready).
      • Leave the session (Exit).

    p2p-ue

  4. WB_ServerPlayerEntry. This widget will represent the players who join the current session. Make sure it has a Text Box to display the player’s name and their ready status.

    p2p-ue

  5. WB_Pause. This widget acts as a pause screen, after the gameplay is started. Make sure it has a Button to end the session or close the pause menu.

    p2p-ue

# Code Implementation

# Create a Session

  1. Create a new C++ class that inherits from UUserWidget named AccelByteCustomGames, and re-parent WB_CustomGamesMenu with our new class.

  2. At the top of C++ class, include OSS header files.

    #include "OnlineSubsystem.h"
    #include "OnlineSubsystemUtils.h"
    #include "OnlineSubsystemAccelByteDefines.h"
    
  3. Create a button that will be used to trigger create server in the AccelByteCustomGames.h class.

    private:
      /**
      * @brief Button for create session.
      */
      UPROPERTY(meta = (BindWidget))
      UButton* Btn_CreateServer;
    
  4. Let’s create a function called CreateCustomGamesSession() to trigger the create session functionality. You can modify the SessionSettings as you wish. This SessionSettings will contain rules, target map, etc. To make sure the P2P works after its creation, set the value of SETTING_ACCELBYTE_ICE_ENABLED to true when you create a session. This is used to turn on the NAT relay.

    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);
    }
    
    protected:
    
    
      /**
       * @brief Create custom games session.
       */
      UFUNCTION() 
      void CreateCustomGamesSession();
    
  5. Now, add initialization in the NativeConstruct() for the Create Server button to trigger the create session.

    void AccelByteCustomGames::NativeConstruct()
    {
      . . .
      Btn_CreateServer->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::CreateCustomGamesSession);
      . . .
    }
    
    protected:
      virtual void NativeConstruct() override;
    
  6. Create another function called CreateSessionComplete() to act as delegate. This will be triggered after a request to create a new session is complete.

  7. Now, let’s specify the session name and bIsSuccess response, which is a default response from Unreal Engine OSS when the create a new session request is complete.

    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);
    }
    
    private:
      /**
       * @brief Called after create session process is complete.
       */
      void CreateSessionComplete(FName SessionName, bool bIsSuccess);
    

# Find Session

  1. In the AccelByteCustomGames, create a button that will be used to trigger find session functionality. This button also can be called as Refresh List Button.

    private:
      /**
      * @brief Button for find session.
      */
      UPROPERTY(meta = (BindWidget))
      UButton* Btn_RefreshList;
    
  2. Create a function called FindCustomGamesSession() to find sessions.

  3. Then, create a pointer inside that function called SessionSearch which will act as a filter and will contain the session find result.

  4. Now, modify that pointer depending on what you need.

    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());
    }
    
    protected:
    
    
      /**
       * @brief Find custom games session.
       */
      UFUNCTION() 
      void FindCustomGamesSession();
    
  5. Now, add initialization in the NativeConstruct() for the Refresh List button to trigger find session.

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

    IMPORTANT

    Before moving to the next stage of P2P code implementation, we suggest that you test your implementation in two or more different devices, especially using stand alone mode. / The criterium is, if another player is able to find the available session that you have created after the test, it means you are good to go!

# Join Session

  1. Create a new C++ class that inherits from UUserWidget named AccelByteServerListEntry, and re-parent WB_ServerListEntry with our new class.

  2. At the top of C++ class, include OSS header files.

    #include "OnlineSubsystem.h"
    #include "OnlineSubsystemUtils.h"
    #include "OnlineSubsystemAccelByteDefines.h"
    
  3. Now, let’s create a button that will be used to trigger join session functionality. This button also can be called as Join Session Button.

    private:
      /**
      * @brief Button for create session.
      */
      UPROPERTY(meta = (BindWidget))
      UButton* Btn_JoinServer;
    
  4. Create a function called JoinCustomGamesSession() to join a session.

    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);
    }
    
    protected:
    
    
      /**
       * @brief Join custom games session.
       */
      UFUNCTION()
      void JoinCustomGamesSession();
    
  5. Next, add initialization in the NativeConstruct() for the Join Session button to trigger join session.

    void AccelByteServerListEntry::NativeConstruct()
    {
      . . .
      Btn_JoinServer->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::JoinCustomGamesSession);
      . . .
    }
    
    protected:
      virtual void NativeConstruct() override;
    
  6. Create another function called JoinSessionComplete() to act as delegate. This will be triggered after a request to join a session is complete.

  7. Now, let’s specify the session name and EOnJoinSessionCompleteResult response, which is the default response from the Unreal Engine OSS, as shown below:

    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);
    }
    
    private:
      /**
       * @brief Called after create session process is complete.
       */
      void JoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);
    

# Destroy Session

  1. Create a new C++ class that inherits from UUserWidget named AccelByteServerMenu, and re-parent WB_ServerMenu with our new class.

  2. At the top of C++ class, include Online Subsystem (OSS) header files.

    #include "OnlineSubsystem.h"
    #include "OnlineSubsystemUtils.h"
    #include "OnlineSubsystemAccelByteDefines.h"
    
  3. Let’s create a button called Exit Session button that will be used to trigger the destroy session functionality.

    private:
      /**
      * @brief Button for Exit the Server Menu.
      */
      UPROPERTY(meta = (BindWidget))
      UButton* Btn_Exit;
    
  4. Now, create a function called DestroyCustomGamesSession to trigger the destroy session.

    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);
    }
    
    protected:
    
    
      /**
       * @brief Exit or Destroy the Server Menu Widget.
       */
      UFUNCTION() 
      void DestroyCustomGamesSession();
    
  5. Next, add initialization in the NativeConstruct() for the Exit Session button to trigger destroy session.

    void UAccelByteServerMenu::NativeConstruct()
    {
      . . .
      Btn_Exit->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::DestroyCustomGamesSession);
      . . .
    }
    
    protected:
      virtual void NativeConstruct() override;
    
  6. Create another function called DestroyCustomGamesSessionComplete() to act as delegate. This will be triggered after a request to destroy a session is complete.

  7. Now, let’s specify the session name and bIsSuccess response, which is default response from Unreal Engine OSS, as shown below:

    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);
    }
    
    private:
      /**
       * @brief Called after Destroy session process is complete.
       */
      void DestroyCustomGamesSessionComplete(FName SessionName, bool bIsSuccess);
    
    

IMPORTANT

Before you move on to the next part of P2P code implementation, we suggest that you test your implementation on two or more different devices again, especially using stand alone mode.

The criterium is, if another player joins or leaves your session, and you as host can leave the sessions, it means you are good to go!

# Start Session

  1. Create a button called Start Session button that will be used to trigger the start session functionality.

    private:
      /**
      * @brief Button for Start to Play.
      */
      UPROPERTY(meta = (BindWidget))
      UButton* Btn_Ready;
    
  2. Create a function called StartCustomGameSession() that will be used to start the session.

    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);
    }
    
    protected:
    
    
      /**
       * @brief Find custom games session.
       */
      UFUNCTION() 
      void StartCustomGamesSession();
    
  3. Next, add initialization in the NativeConstruct() for the Start Session button to trigger the start session.

    void UAccelByteServerMenu::NativeConstruct()
    {
      . . .
      Btn_Ready->OnClicked.AddUniqueDynamic(this, &UAccelByteCustomGames::StartCustomGamesSession);
      . . .
    }
    
    protected:
      virtual void NativeConstruct() override;
    
  4. Create another function called StartCustomGamesSessionComplete() to act as delegate. This will be triggered after a request to start a session is complete.

  5. Now, let’s specify the session name and bIsSuccess response, which is the default response from Unreal Engine OSS, as shown below:

    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
        }
    }
    
    private:
      /**
       * @brief Called after Start session process is complete.
       */
      void StartCustomGamesSessionComplete(FName SessionName, bool bIsSuccess);
    
    

# End Session

  1. Create a new C++ class that inherits from UUserWidget named UAccelByteInGameMenu, and re-parent WB_Pause with our new class.

  2. At the top of C++ class, include OSS header files.

    #include "OnlineSubsystem.h"
    #include "OnlineSubsystemUtils.h"
    #include "OnlineSubsystemAccelByteDefines.h"
    
  3. Create a button called End Session button that will be used to trigger the end session functionality.

    private:
      /**
      * @brief Button for going back to Lobby Menu.
      */
      UPROPERTY(meta = (BindWidget))
      UButton* Btn_EndGame;
    
  4. Create a function called EndGamesSession() that will be used to end the session.

    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);
    }
    
    protected:
    
    
      /**
       * @brief Create custom games session.
       */
      UFUNCTION() 
      void EndGamesSession();
    
  5. Next, add initialization in the NativeConstruct() for the End Session button to trigger the end session.

    void UAccelByteInGameMenu::NativeConstruct()
    {
      . . .
      Btn_EndGame->OnClicked.AddUniqueDynamic(this, &UAccelByteInGameMenu::EndGamesSession);
      . . .
    }
    
    protected:
      virtual void NativeConstruct() override;
    
  6. Create another function called EndSessionComplete() to act as delegate. This will be triggered after a request to end a session is complete.

  7. Specify the session name and bIsSuccess response which is the default response from the Unreal Engine OSS as follows:

    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
        }
    }
    
    private:
      /**
       * @brief Called after create session process is complete.
       */
      void EndSessionComplete(FName SessionName, bool bIsSuccess);
    

# Full Code

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"));
}