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

P2P をセッションブラウザ (非推奨) に統合する

Last updated on October 23, 2024
備考

Session browser is deprecated and replaced by game session. For more information, see Peer-To-Peer (P2P) using game session.

Overview

This guide will walk you through the basics of hosting, joining, and browsing peer-to-peer sessions using AccelByte Gaming Services (AGS) Online Subsystem (OSS) for Unreal Engine. This allows game clients to connect and play together without the need for a dedicated server. Additionally, a session browser implementation allows players to find and join existing open Peer-2-Peer (P2P) sessions without having knowledge of the host player.

Goals

  • Configure AccelByte Unreal Engine plugins
  • Host a peer-to-peer session
  • Browse and join P2P sessions

Prerequisites

Before you begin this guide, you should have the following:

  • Knowledge of Unreal Engine, including use of the OSS.
  • The AccelByte SDK, NetworkUtilities, and OnlineSubsystem plugins.
  • Access to the AccelByte Admin Portal and the Namespace for your game.
  • A session template with the type set to P2P and the joinability setting set to OPEN.

Configure the Plugins

  1. Enable V2 sessions in your DefaultEngine.ini file:

    [OnlineSubsystemAccelByte]
    bEnableV2Sessions=true
  2. Set up the turn server and net driver:

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

    [/Script/AccelByteNetworkUtilities.IpNetDriverAccelByte]
    NetConnectionClassName=AccelByteNetworkUtilities.IpConnectionAccelByte
  3. Configure the net driver definitions per platform. For instance, with Windows, add the following to your WindowsEngine.ini:

    [/Script/Engine.GameEngine]
    !NetDriverDefinitions=ClearArray
    +NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="AccelByteNetworkUtilities.IpNetDriverAccelByte",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
    +NetDriverDefinitions=(DefName="DemoNetDriver",DriverClassName="/Script/Engine.DemoNetDriver",DriverClassNameFallback="/Script/Engine.DemoNetDriver")

Host a P2P Session

Here is an example of creating a session using our new session template from the previous step. First, you'll need to do some setup, then you'll call CreateSession on the OSS session interface.

  1. Get the AccelByte session interface:

    const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
    if (!ensure(Subsystem != nullptr))
    {
    return;
    }

    FOnlineSessionV2AccelBytePtr SessionInterface;
    if (!FOnlineSessionV2AccelByte::GetFromSubsystem(Subsystem, SessionInterface))
    {
    return;
    }

2. Configure our session settings:

```cpp
FOnlineSessionSettings NewSessionSettings;

// This would be the name of the session template created in the Admin Portal
NewSessionSettings.Set(SETTING_SESSION_TEMPLATE_NAME, TEXT("P2PSession"));

// We want the new session to be a game session, as opposed to a party session
NewSessionSettings.Set(SETTING_SESSION_TYPE, SETTING_SESSION_TYPE_GAME_SESSION);

// We need some kind of parameter that the session browser can later use
// to query for sessions
NewSessionSettings.Set(FName(TEXT("IS_P2P_SESSION")), TEXT("true"));

// At this point, any other custom settings can be applied. For example, we
// can add a map name that we'll later use when we're hosting the P2P session
NewSessionSettings.Set(SETTING_MAPNAME, TEXT("MapName"));
```

3. Make the call to actually create the session:

```cpp
// We create a delegate which will be triggered when session creation is
// complete, inside of which we'll call StartSession and perform a travel
const FOnCreateSessionCompleteDelegate OnCreateSessionCompleteDelegate =
FOnCreateSessionCompleteDelegate::CreateUObject(this,
&MyClass::OnCreateSessionComplete);
FDelegateHandle CreateSessionDelegateHandle =
SessionInterface->AddOnCreateSessionCompleteDelegate_Handle(
OnCreateSessionCompleteDelegate);
// PlayerId is an FUniqueNetIdPtr for the player
SessionInterface->CreateSession(
PlayerId.ToSharedRef().Get(), NAME_GameSession, NewSessionSettings);
```

In the above code, a handler for the session creation complete delegate is added.

4. Inside the handler that was added in step 3, do the following:

1. Check that this is the correct session using the SessionName delegate parameter:

```cpp
if(SessionName != NAME_GameSession)
{
return;
}
```

:::tip
Do the above sanity check in any of the delegate handlers that receive a session name as a parameter.
:::

2. Get the session interface again. After that, we can grab the actual session instance and mark the session as started:

```cpp
FNamedOnlineSession* Session = SessionInterface->GetNamedSession(SessionName);
if (!ensure(Session != nullptr))
{
return;
}

SessionInterface->StartSession(SessionName);
```

3. Construct a travel URL and perform the travel:

```cpp
// We'll use our previously set map name for traveling
FString MapName;
Session->SessionSettings.Get(SETTING_MAPNAME, MapName);

// Constructing the travel URL with "?listen" appended to the map name so that we host a listen server
const FString TravelUrl = FString::Printf(TEXT("%s?listen"), *MapName);

// PlayerController is a pointer to an APlayerController for the local player
Controller->ClientTravel(TravelUrl, TRAVEL_Absolute);
```

:::tip
Generally, it can be helpful to add in a session setting indicating whether the session is ready for other players to join, which would be set to some indicative value after the map loads.
:::

## Browse and Join P2P Sessions

In order to browse for P2P sessions, we're going to use the session interface's `FindSessions` method with the `IS_P2P_SESSION` setting from the previous step. To begin with, we'll need to again grab the AccelByte session interface. Then, we'll set up the attributes we want to query with:

```cpp
TSharedPtr<FOnlineSessionSearch> QuerySessionsHandle =
MakeShared<FOnlineSessionSearch>();

// We'll set the maximum search results to some arbitrary value
QuerySessionsHandle->MaxSearchResults = 100;

// Search for sessions with the P2P setting we used earlier
QuerySessionsHandle->QuerySettings.Set(
FName(TEXT("IS_P2P_SESSION")), TEXT("true"));

// We can also query for other session settings, such as MAPNAME
QuerySessionsHandle->QuerySettings.Set(SETTING_MAPNAME, TEXT("MapName"));
注記

You'll want to keep the FOnlineSessionSearch handle around for use in the FindSessions completion delegate, as it will contain the search results.

Next, we'll make the call to FindSessions:

const FOnFindSessionsCompleteDelegate OnFindSessionsCompleteDelegate = 
FOnFindSessionsCompleteDelegate::CreateUObject(
this, &MyClass::OnFindSessionsComplete);
FDelegateHandle FindSessionsCompleteDelegateHandle =
SessionInterface->AddOnFindSessionsCompleteDelegate_Handle(
OnFindSessionsCompleteDelegate);
// PlayerId is an FUniqueNetIdPtr for the player
SessionInterface->FindSessions(
PlayerId.ToSharedRef().Get(), QuerySessionsHandle.ToSharedRef());

Inside our handler for the OnFindSessionsComplete delegate, we'll then have access to an array of FOnlineSessionSearchResult, which could be used to display a list of sessions in a session browser UI. For example, the handler often would pass this array to another delegate and then reset the search handle:

SomeSessionBrowserListingDelegate.Broadcast(QuerySessionsHandle->SearchResults);
QuerySessionsHandle.Reset();

In order to join one of these sessions, the client simply invokes the JoinSession method, passing one of the session search results from the array mentioned above. First, we'd grab the session interface again, and then we'd perform the join:

const FOnJoinSessionCompleteDelegate OnJoinSessionCompleteDelegate = 
FOnJoinSessionCompleteDelegate::CreateUObject(
this, &MyClass::OnJoinSessionComplete);
FDelegateHandle JoinSessionDelegateHandle =
SessionInterface->AddOnJoinSessionCompleteDelegate_Handle(
OnJoinSessionCompleteDelegate);

// PlayerId is an FUniqueNetIdPtr for the player, and Session is simply an instance of FOnlineSessionSearchResult
return SessionInterface->JoinSession(
PlayerId.ToSharedRef().Get(), NAME_GameSession, Session);
注記

It is generally good practice, before a join, to check if the player is already in a session using GetNamedSession. If so, we'd want to call DestroySession, and then join the session once the delegate for that method is triggered.

Inside the JoinSessionComplete delegate handler, we'd want to then perform a travel. Again, we'd start by grabbing the session interface, then we'd grab the travel URL and perform the client travel:

FString TravelUrl{};
// SessionName is a parameter from the join delegate
if (SessionInterface->GetResolvedConnectString(SessionName, TravelUrl,
NAME_GamePort) && !TravelUrl.IsEmpty())
{
// PlayerController is a pointer to an APlayerController for the local player
PlayerController->ClientTravel(TravelUrl, TRAVEL_Absolute);
}
注記

For P2P sessions, the travel URL will be of the format accelbyte.<host_user_id>:<port>. In this case, the call to fetch the resolved connect string is simply generating the travel URL from the local session information.

Troubleshooting

In this section, you can find common errors and issues that may occur when using the service, along with recommendations on how to resolve them.

Players joining too early

It's possible to encounter an issue where a joining player (non-host) tries to travel to the server before the map has loaded.

Suggestions

A solution for this is to add a session setting (e.g., SETTING_JOIN_READY) which would be set to something like “true” inside a delegate added to FCoreUObjectDelegates::PostLoadMapWithWorld:

FOnlineSessionSettings* SessionSettings = 
SessionInterface->GetSessionSettings(NAME_GameSession);
SessionSettings->Set(FName(TEXT("JOIN_READY")), TEXT("true"));

SessionInterface->UpdateSession(NAME_GameSession, *SessionSettings);

Then, on the joiner side, you'd want to listen to the UpdateReceived delegate on the session interface and look for that session setting before attempting the join. It could also be helpful to add this parameter to the query settings in your session browser's use of FindSessions so that the browser only displays sessions that are ready to be joined.