Integrate P2P with session browser (Deprecated)
The session browser is deprecated and replaced by game sessions. For more information, see Peer-To-Peer (P2P) using game session.
Overview
This article covers 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-to-peer (P2P) sessions without having knowledge of the host player.
Prerequisites
To complete the steps in this guide, you need:
- Knowledge of Unreal Engine, including use of the OSS.
- The AGS Game SDK, NetworkUtilities, and OnlineSubsystem plugins installed to your project.
- Access to the AGS Admin Portal and a namespace for your game.
- A session template created with the type set to P2P and the joinability set to OPEN.
Configure the plugins
Follow these steps to prepare your plugins for P2P session browser integration.
Enable V2 sessions in your
DefaultEngine.ini
file:[OnlineSubsystemAccelByte]
bEnableV2Sessions=trueSet up the turn server and net driver:
[AccelByteNetworkUtilities]
UseTurnManager=true
TurnServerSecret=<your-turn-secret>
[/Script/AccelByteNetworkUtilities.IpNetDriverAccelByte]
NetConnectionClassName=AccelByteNetworkUtilities.IpConnectionAccelByteConfigure 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 a session template. First, you'll need to do some setup, then you'll call CreateSession
on the OSS session interface.
Get the AGS Session interface:
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
if (!ensure(Subsystem != nullptr))
{
return;
}
FOnlineSessionV2AccelBytePtr SessionInterface;
if (!FOnlineSessionV2AccelByte::GetFromSubsystem(Subsystem, SessionInterface))
{
return;
}Configure the Session settings:
FOnlineSessionSettings NewSessionSettings;
// This would be the name of the session template created in the Admin Portal
NewSessionSettings.Set(SETTING_SESSION_TEMPLATE_NAME, TEXT("P2PSession"));
// You 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);
// You 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, you
// can add a map name that you'll later use when you're hosting the P2P session
NewSessionSettings.Set(SETTING_MAPNAME, TEXT("MapName"));Make the call to actually create the session:
// You create a delegate which will be triggered when the session creation is
// complete, inside of which you'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.
Inside the handler that was added in step 3, do the following:
Check that this is the correct session using the
SessionName
delegate parameter:if(SessionName != NAME_GameSession)
{
return;
}tipMake sure you check the above for any of the delegate handlers that receive a session name as a parameter.
Get the session interface again. After that, you can grab the actual session instance and mark the session as started:
FNamedOnlineSession* Session = SessionInterface->GetNamedSession(SessionName);
if (!ensure(Session != nullptr))
{
return;
}
SessionInterface->StartSession(SessionName);Construct a travel URL and perform the travel:
// You'll use the 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 you 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);tipGenerally, 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, you're going to use the Session interface's FindSessions
method with the IS_P2P_SESSION
setting from the previous step. To begin, you'll need to again grab the AGS Session interface. Then, you'll set up the attributes you want to query with:
TSharedPtr<FOnlineSessionSearch> QuerySessionsHandle =
MakeShared<FOnlineSessionSearch>();
// You'll set the maximum search results to some arbitrary value
QuerySessionsHandle->MaxSearchResults = 100;
// Search for sessions with the P2P setting you used earlier
QuerySessionsHandle->QuerySettings.Set(
FName(TEXT("IS_P2P_SESSION")), TEXT("true"));
// You 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, you'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 the handler for the OnFindSessionsComplete
delegate, you'll 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 would often 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, you'd grab the Session interface again, and then you'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, you'd want to call DestroySession
, and then join the session once the delegate for that method is triggered.
Inside the JoinSessionComplete
delegate handler, you'd want to then perform a travel. Again, you'd start by grabbing the Session interface, then you'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 this 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.