Implement subsystem - Quick match with peer-to-peer - (Unreal Engine module)
Peer-to-peer matchmaking flow
Take some time to understand how matchmaking using peer-to-peer (P2P) works by looking at the following diagram:
Set up game client online session
Matchmaking is the process of finding a match for a game session. Once the game session is found, the player will join the game session and travel to the P2P server (also known as a host or listen server). For a refresher of the basics on how game sessions work, refer back to the Introduction to Session module.
In this tutorial, you will be working with an online session class named MatchmakingP2POnlineSession_Starter
. This online session is used by the game client to perform matchmaking. This class is available in the Resources section and consists of the following files:
- Header file:
/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2POnlineSession_Starter.h
- CPP file:
/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2POnlineSession_Starter.cpp
This class has several predefined attributes and functions you can use to follow the tutorial. Take a look at what has been provided.
- Similar to the Introduction to Session, this class uses session interfaces to perform session-related functions, including matchmaking.
FOnlineSessionV2AccelBytePtr UAccelByteWarsOnlineSessionBase::GetABSessionInt()
{
return StaticCastSharedPtr<FOnlineSessionV2AccelByte>(GetSessionInt());
}
- The Header file has the following map that defines the Match Pool IDs you created previously. As you can see, the IDs are the same exact names as the ones you set up in Admin Portal. These are required so you can request matchmaking to the correct Match Pool.
private:
// ...
const TMap<EGameModeType, FString> MatchPoolIds = {
{EGameModeType::FFA, "unreal-elimination-p2p"},
{EGameModeType::TDM, "unreal-teamdeathmatch-p2p"}
};
- The Header file also has the following map that translates the Match Pool IDs to game modes supported by the game. The game server uses this map to configure the gameplay based on the game mode.
public:
// ...
const TMap<FString, FString> TargetGameModeMap = {
{"unreal-elimination-p2p", "ELIMINATION-P2P"},
{"unreal-teamdeathmatch-p2p", "TEAMDEATHMATCH-P2P"}
};
Start matchmaking
In this section, you will implement functions to start matchmaking.
Open the
MatchmakingP2POnlineSession_Starter
Header file and then declare a function to start matchmaking.public:
// ...
virtual void StartMatchmaking(
const APlayerController* PC,
const FName& SessionName,
const EGameModeNetworkType NetworkType,
const EGameModeType GameModeType) override;Still in the same file, declare a callback function that will be called when the start matchmaking process completes.
protected:
// ...
virtual void OnStartMatchmakingComplete(
FName SessionName,
const FOnlineError& ErrorDetails,
const FSessionMatchmakingResults& Results) override;Declare a delegate that will be called when the start matchmaking process completes. You can use this delegate to bind some events later, especially when connecting the user interface (UI) with the matchmaking implementation.
public:
// ...
virtual FOnMatchmakingResponse* GetOnStartMatchmakingCompleteDelegates() override
{
return &OnStartMatchmakingCompleteDelegates;
}private:
// ...
FOnMatchmakingResponse OnStartMatchmakingCompleteDelegates;To start matchmaking, you will need to leave any leftover game sessions. Declare the following function to handle that:
private:
// ...
void OnLeaveSessionForReMatchmakingComplete(
FName SessionName,
bool bSucceeded,
const int32 LocalUserNum,
const EGameModeType GameModeType);
FDelegateHandle OnLeaveSessionForReMatchmakingCompleteDelegateHandle;Define the functions you just declared above. Open the
MatchmakingP2POnlineSession_Starter
CPP file and begin with theStartMatchmaking()
function. In this function, you construct the matchmaking handle to define what kind of matchmaking you want. We set the Match Pool to the matchmaking handle so it will start the matchmaking based on the Match Pool configured in the Admin Portal. Also, before it begins matchmaking, it first checks whether the player is already in the game session or not. If yes, then it leaves the game session first and then retries matchmaking, which will be handled by theOnLeaveSessionForReMatchmakingComplete()
function. When the matchmaking successfully starts, it then calls theOnStartMatchmakingComplete()
function.void UMatchmakingP2POnlineSession_Starter::StartMatchmaking(
const APlayerController* PC,
const FName& SessionName,
const EGameModeNetworkType NetworkType,
const EGameModeType GameModeType)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
// Abort if the session interface is invalid.
if (!ensure(GetSessionInt()))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Session Interface is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
}));
return;
}
// If the player is already in a session, then leave session first.
if (GetSession(SessionName))
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("Already in session. Leaving session first."))
if (OnLeaveSessionForReMatchmakingCompleteDelegateHandle.IsValid())
{
GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForReMatchmakingCompleteDelegateHandle);
OnLeaveSessionForReMatchmakingCompleteDelegateHandle.Reset();
}
OnLeaveSessionForReMatchmakingCompleteDelegateHandle = GetOnLeaveSessionCompleteDelegates()->AddUObject(
this,
&ThisClass::OnLeaveSessionForReMatchmakingComplete,
GetLocalUserNumFromPlayerController(PC),
GameModeType);
LeaveSession(SessionName);
return;
}
const FUniqueNetIdPtr PlayerNetId = GetLocalPlayerUniqueNetId(PC);
if (!ensure(PlayerNetId.IsValid()))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Player UniqueNetId is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
}));
return;
}
// Get match pool ID based on game mode type
FString MatchPoolId = MatchPoolIds[GameModeType];
const FString GameModeCode = TargetGameModeMap[MatchPoolId];
// Override match pool ID if applicable.
if (!UTutorialModuleOnlineUtility::GetMatchPoolP2POverride().IsEmpty())
{
MatchPoolId = UTutorialModuleOnlineUtility::GetMatchPoolP2POverride();
}
// Set up matchmaking search handle, it will be used to store session search results.
TSharedRef<FOnlineSessionSearch> MatchmakingSearchHandle = MakeShared<FOnlineSessionSearch>();
MatchmakingSearchHandle->QuerySettings.Set(SETTING_SESSION_MATCHPOOL, MatchPoolId, EOnlineComparisonOp::Equals);
MatchmakingSearchHandle->QuerySettings.Set(GAMESETUP_GameModeCode, GameModeCode, EOnlineComparisonOp::Equals);
if (!GetSessionInt()->StartMatchmaking(
USER_ID_TO_MATCHMAKING_USER_ARRAY(PlayerNetId.ToSharedRef()),
SessionName,
FOnlineSessionSettings(),
MatchmakingSearchHandle,
FOnStartMatchmakingComplete::CreateUObject(this, &ThisClass::OnStartMatchmakingComplete)))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Failed executing"))
// Failed to start matchmaking.
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
}));
}
}Define the
OnStartMatchmakingComplete()
function. This function triggers the delegate you created to inform whether the matchmaking that has started is successful or not.void UMatchmakingP2POnlineSession_Starter::OnStartMatchmakingComplete(
FName SessionName,
const FOnlineError& ErrorDetails,
const FSessionMatchmakingResults& Results)
{
UE_LOG_MATCHMAKINGP2P(
Log,
TEXT("succeeded: %s | error: (%s) %s"),
*FString(ErrorDetails.bSucceeded ? "TRUE": "FALSE"),
*ErrorDetails.ErrorCode, *ErrorDetails.ErrorMessage.ToString())
OnStartMatchmakingCompleteDelegates.Broadcast(SessionName, ErrorDetails.bSucceeded);
}Define the
OnLeaveSessionForReMatchmakingComplete()
function. This function will be called after leaving the game session. It then checks whether if it is valid to retry to start matchmaking or not.void UMatchmakingP2POnlineSession_Starter::OnLeaveSessionForReMatchmakingComplete(
FName SessionName,
bool bSucceeded,
const int32 LocalUserNum,
const EGameModeType GameModeType)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForReMatchmakingCompleteDelegateHandle);
if (bSucceeded)
{
// Retry matchmaking.
const APlayerController* PC = GetPlayerControllerByLocalUserNum(LocalUserNum);
if (!ensure(PC))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("PlayerController is null."));
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
return;
}
StartMatchmaking(PC, SessionName, EGameModeNetworkType::P2P, GameModeType);
}
else
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Is not a game session."));
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
}
}You need to handle when the matchmaking completes. Open the
MatchmakingP2POnlineSession_Starter
Header file and then declare the following function:protected:
// ...
virtual void OnMatchmakingComplete(FName SessionName, bool bSucceeded) override;Declare a delegate that will be called when the matchmaking completes. You can use this delegate to bind some events later, especially when connecting the UI with the matchmaking implementation.
public:
// ...
virtual FOnMatchmakingResponse* GetOnMatchmakingCompleteDelegates() override
{
return &OnMatchmakingCompleteDelegates;
}private:
// ...
FOnMatchmakingResponse OnMatchmakingCompleteDelegates;Open the
MatchmakingP2POnlineSession_Starter
CPP file and then define theOnMatchmakingComplete()
function. This function will check whether a game session was found after the matchmaking completes. If found, it then joins the game session. It also invokes the delegate you created earlier to inform the game that the matchmaking process has completed.void UMatchmakingP2POnlineSession_Starter::OnMatchmakingComplete(FName SessionName, bool bSucceeded)
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("succeeded: %s"), *FString(bSucceeded ? "TRUE": "FALSE"))
const TSharedPtr<FOnlineSessionSearchAccelByte> CurrentMatchmakingSearchHandle = GetABSessionInt()->GetCurrentMatchmakingSearchHandle();
if (!bSucceeded ||
!ensure(CurrentMatchmakingSearchHandle.IsValid()) /*This might happen when matchmaking finishes right as it’s about to be canceled.*/ ||
!ensure(CurrentMatchmakingSearchHandle->SearchResults.IsValidIndex(0)) ||
!ensure(CurrentMatchmakingSearchHandle->GetSearchingPlayerId().IsValid()))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("There is no match result returned."));
OnMatchmakingCompleteDelegates.Broadcast(SessionName, false);
return;
}
OnMatchmakingCompleteDelegates.Broadcast(SessionName, bSucceeded);
}Bind the function above so it will be called when the matchmaking process completes. You can do this by adding the code below to the
RegisterOnlineDelegates()
function, which is the first function to be called when the online session initializes.void UMatchmakingP2POnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
GetSessionInt()->OnMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnMatchmakingComplete);
// ...
}When the online session is deinitialized, you need to unbind to stop listening to the event. You can do this by adding the code below in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session deinitializes.void UMatchmakingP2POnlineSession_Starter::ClearOnlineDelegates()
{
// ...
GetSessionInt()->OnMatchmakingCompleteDelegates.RemoveAll(this);
// ...
}
Cancel matchmaking
In this section, you will implement functions to cancel matchmaking.
Open the
MatchmakingP2POnlineSession_Starter
Header file and declare a function to cancel matchmaking.public:
// ...
virtual void CancelMatchmaking(APlayerController* PC, const FName& SessionName) override;Still in the same file, declare a callback function that will be called when the cancel matchmaking process completes.
protected:
// ...
virtual void OnCancelMatchmakingComplete(FName SessionName, bool bSucceeded) override;Declare a delegate that will be called when the cancel matchmaking process completes. You can use this delegate to bind some events later, especially when connecting the UI with the matchmaking implementation.
public:
// ...
virtual FOnMatchmakingResponse* GetOnCancelMatchmakingCompleteDelegates() override
{
return &OnCancelMatchmakingCompleteDelegates;
}private:
// ...
FOnMatchmakingResponse OnCancelMatchmakingCompleteDelegates;Open the
MatchmakingP2POnlineSession_Starter
CPP file and define theCancelMatchmaking()
function first. This function checks whether it is valid to cancel matchmaking or not before executing the action. When the process completes, it calls theOnCancelMatchmakingComplete()
function.void UMatchmakingP2POnlineSession_Starter::CancelMatchmaking(APlayerController* PC, const FName& SessionName)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
// Abort if the session interface is invalid.
if (!ensure(GetSessionInt()))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Session Interface is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnCancelMatchmakingComplete(SessionName, false);
}));
return;
}
if (!ensure(GetABSessionInt()->GetCurrentMatchmakingSearchHandle().IsValid() &&
GetABSessionInt()->GetCurrentMatchmakingSearchHandle()->GetSearchingPlayerId().IsValid()))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Searching player ID is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnCancelMatchmakingComplete(SessionName, false);
}));
return;
}
if (!GetSessionInt()->CancelMatchmaking(
*GetABSessionInt()->GetCurrentMatchmakingSearchHandle()->GetSearchingPlayerId(),
GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Failed executing"))
// Failed to start matchmaking.
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnCancelMatchmakingComplete(SessionName, false);
}));
}
}Define the
OnCancelMatchmakingComplete()
function. This function triggers the delegate you created to inform whether the matchmaking that has been canceled was successful or not.void UMatchmakingP2POnlineSession_Starter::OnCancelMatchmakingComplete(FName SessionName, bool bSucceeded)
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("succeeded: %s"), *FString(bSucceeded ? "TRUE": "FALSE"))
OnCancelMatchmakingCompleteDelegates.Broadcast(SessionName, bSucceeded);
}Bind the function above so it will be called when the cancel matchmaking process completes. You can do this by adding the code below to the
RegisterOnlineDelegates()
function, which is the first function to be called when the online session initializes.void UMatchmakingP2POnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
GetSessionInt()->OnCancelMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnCancelMatchmakingComplete);
// ...
}When the online session is deinitialized, you need to unbind to stop listening to the event. You can do this by adding the code below in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session deinitializes.void UMatchmakingP2POnlineSession_Starter::ClearOnlineDelegates()
{
// ...
GetSessionInt()->OnCancelMatchmakingCompleteDelegates.RemoveAll(this);
// ...
}
Handle when a P2P host is received
When the matchmaking is finished and the game client joins the game session, the game client then waits to receive the P2P host address to travel. In this section, you will learn how to handle when this happens.
Create a function to travel the game client to the P2P host. Open the
MatchmakingP2POnlineSession_Starter
Header file and declare the following function:public:
// ...
virtual bool TravelToSession(const FName SessionName) override;Open the
MatchmakingP2POnlineSession_Starter
CPP file and define the functions above. This function performs some validation before traveling the game client to the P2P host. If it is valid, then it travels to the P2P host using its address.bool UMatchmakingP2POnlineSession_Starter::TravelToSession(const FName SessionName)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
if (GetSessionType(SessionName) != EAccelByteV2SessionType::GameSession)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Not a game session"));
return false;
}
// Get session info
const FNamedOnlineSession* Session = GetSession(SessionName);
if (!Session)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("The session is invalid"));
return false;
}
const TSharedPtr<FOnlineSessionInfo> SessionInfo = Session->SessionInfo;
if (!SessionInfo.IsValid())
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("The session info is invalid"));
return false;
}
const TSharedPtr<FOnlineSessionInfoAccelByteV2> AbSessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(SessionInfo);
if (!AbSessionInfo.IsValid())
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Session Info is not FOnlineSessionInfoAccelByteV2"));
return false;
}
// get player controller of the local owner of the user
APlayerController* PlayerController = GetPlayerControllerByUniqueNetId(Session->LocalOwnerId);
// if nullptr, treat as failed
if (!PlayerController)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Can't find player controller with the session's local owner's Unique ID"));
return false;
}
AAccelByteWarsPlayerController* AbPlayerController = Cast<AAccelByteWarsPlayerController>(PlayerController);
if (!AbPlayerController)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Player controller is not (derived from) AAccelByteWarsPlayerController"));
return false;
}
FString ServerAddress = "";
// If local user is not the P2P host -> connect to host
if (!GetABSessionInt()->IsPlayerP2PHost(GetLocalPlayerUniqueNetId(PlayerController).ToSharedRef().Get(), SessionName))
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("Host is not a P2P host, traveling to host"));
GetABSessionInt()->GetResolvedConnectString(SessionName, ServerAddress);
if (ServerAddress.IsEmpty())
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Can't find session's server address"));
return false;
}
}
else
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("Host is a P2P host, traveling as listen server"));
ServerAddress = "MainMenu?listen";
}
if (!bIsInSessionServer)
{
AbPlayerController->DelayedClientTravel(ServerAddress, TRAVEL_Absolute);
bIsInSessionServer = true;
}
else
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Already in session's server"));
}
return true;
}Next, declare the new function as shown below in the
MatchmakingP2POnlineSession_Starter
class Header file as a handler for when the player joins the game session after matchmaking completes.protected:
// ...
virtual void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result) override;Then, define the function above in the
MatchmakingP2POnlineSession_Starter
class CPP file. In this function, when the player successfully joins the game session, the player will travel to the P2P host.void UMatchmakingP2POnlineSession_Starter::OnJoinSessionComplete(
FName SessionName,
EOnJoinSessionCompleteResult::Type Result)
{
Super::OnJoinSessionComplete(SessionName, Result);
TravelToSession(SessionName);
}Create a function to listen for when the game client receives a server update. A server update is an event when the game client receives a game server to travel from the backend, in this case the P2P host. Open the
MatchmakingP2POnlineSession_Starter
Header file and declare the following function:protected:
// ...
virtual void OnSessionServerUpdateReceived(FName SessionName) override;Declare a delegate that will be called when the server update is received. You can use this delegate to bind some events later, especially when connecting the UI with the matchmaking implementation.
public:
// ...
virtual FOnServerSessionUpdateReceived* GetOnSessionServerUpdateReceivedDelegates() override
{
return &OnSessionServerUpdateReceivedDelegates;
}private:
// ...
FOnServerSessionUpdateReceived OnSessionServerUpdateReceivedDelegates;Open the
MatchmakingP2POnlineSession_Starter
CPP file and define theOnSessionServerUpdateReceived()
function. This function checks whether it is valid to travel to the P2P host or not. It also invokes the delegate you created earlier to inform that the server update has been received.void UMatchmakingP2POnlineSession_Starter::OnSessionServerUpdateReceived(FName SessionName)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
if (bLeavingSession)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("called but leave session is currently running. Canceling attempt to travel to server"))
OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, FOnlineError(true), false);
return;
}
const bool bHasClientTravelTriggered = TravelToSession(SessionName);
OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, FOnlineError(true), bHasClientTravelTriggered);
}On some occasions, the backend might fail to find a game server for the game client. Create a function to handle when this happens. Open the
MatchmakingP2POnlineSession_Starter
Header file and declare the following function:protected:
// ...
virtual void OnSessionServerErrorReceived(FName SessionName, const FString& Message) override;Open the
MatchmakingP2POnlineSession_Starter
CPP file and define the function above. This function invokes the session server update delegate you created earlier but marks it as failed.void UMatchmakingP2POnlineSession_Starter::OnSessionServerErrorReceived(FName SessionName, const FString& Message)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
FOnlineError Error;
Error.bSucceeded = false;
Error.ErrorMessage = FText::FromString(Message);
OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, Error, false);
}Next, declare a new function below in the
MatchmakingP2POnlineSession_Starter
class Header file as a handler for when the player leaves the game session.protected:
// ...
virtual void OnLeaveSessionComplete(FName SessionName, bool bSucceeded) override;Then, define the function above in the
MatchmakingP2POnlineSession_Starter
class CPP file. In this function, when the player leaves the game session, it toggles the in-session helper status to false.void UMatchmakingP2POnlineSession_Starter::OnLeaveSessionComplete(FName SessionName, bool bSucceeded)
{
Super::OnLeaveSessionComplete(SessionName, bSucceeded);
if (bSucceeded)
{
bIsInSessionServer = false;
}
}Bind the function above so it will be called when the server update is received. You can do this by adding the code below to the
RegisterOnlineDelegates()
function, which is the first function to be called when the online session initializes.void UMatchmakingP2POnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
GetABSessionInt()->OnSessionServerUpdateDelegates.AddUObject(this, &ThisClass::OnSessionServerUpdateReceived);
GetABSessionInt()->OnSessionServerErrorDelegates.AddUObject(this, &ThisClass::OnSessionServerErrorReceived);
// ...
}When the online session is deinitialized, you need to unbind to stop listening to the event. You can do this by adding the code below in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session deinitializes.void UMatchmakingP2POnlineSession_Starter::ClearOnlineDelegates()
{
// ...
GetABSessionInt()->OnSessionServerUpdateDelegates.RemoveAll(this);
GetABSessionInt()->OnSessionServerErrorDelegates.RemoveAll(this);
// ...
}
Handle backfill
Backfill is a flow that allows players to join the game server that is not full yet. In this section, you will implement functions to handle backfill.
In this tutorial, when the backfill proposal is received, it will always accept the proposal. To reject the proposal, you can use the GetABSessionInt()->RejectBackfillProposal()
function. The flow between accepting and rejecting the proposal is similar.
Open the
MatchmakingP2POnlineSession_Starter
Header file and declare the following function:protected:
// ...
virtual void OnBackfillProposalReceived(FAccelByteModelsV2MatchmakingBackfillProposalNotif Proposal) override;Declare a delegate that will be called when the process of accepting backfill completes.
public:
// ...
virtual FOnMatchmakingAcceptBackfillProposalComplete* GetOnAcceptBackfillProposalCompleteDelegates() override
{
return &OnAcceptBackfillProposalCompleteDelegates;
}private:
// ...
FOnMatchmakingAcceptBackfillProposalComplete OnAcceptBackfillProposalCompleteDelegates;Open the
MatchmakingP2POnlineSession_Starter
CPP file and define theOnBackfillProposalReceived()
function. This function accepts the received backfill so the player can join the game server that is not full yet. It also invokes the delegate you created earlier to inform the game that the accepting backfill process has completed.void UMatchmakingP2POnlineSession_Starter::OnBackfillProposalReceived(
FAccelByteModelsV2MatchmakingBackfillProposalNotif Proposal)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
// Abort if the session interface is invalid.
if (!ensure(GetABSessionInt().IsValid()))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Session Interface is not valid."));
return;
}
// Accept backfill proposal.
GetABSessionInt()->AcceptBackfillProposal(
GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession),
Proposal,
false,
FOnAcceptBackfillProposalComplete::CreateWeakLambda(this, [this](bool bSucceeded)
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("succeeded: %s To accept backfill."), *FString(bSucceeded ? "TRUE": "FALSE"));
OnAcceptBackfillProposalCompleteDelegates.Broadcast(bSucceeded);
}));
}Bind the function above so it will be called when the backfill proposal is received. You can do this by adding the code below to the
RegisterOnlineDelegates()
function, which is the first function to be called when the online session initializes.void UMatchmakingP2POnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
GetABSessionInt()->OnBackfillProposalReceivedDelegates.AddUObject(this, &ThisClass::OnBackfillProposalReceived);
// ...
}When the online session is deinitialized, you need to unbind to stop listening to the event. You can do this by adding the code below in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session deinitializes.void UMatchmakingP2POnlineSession_Starter::ClearOnlineDelegates()
{
// ...
GetABSessionInt()->OnBackfillProposalReceivedDelegates.RemoveAll(this);
// ...
}
Set up peer-to-peer host online subsystem
In this tutorial, you will be working with a game instance subsystem named MatchmakingP2PServerSubsystem_Starter
. This class is used by the P2P server to handle the game session produced by matchmaking. This class is available in the Resources section and consists of the following files:
- Header file:
/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2PServerSubsystem_Starter.h
- CPP file:
/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2PServerSubsystem_Starter.cpp
Handle the P2P host receiving a session
When matchmaking is finished, the backend selects one of the connected game clients to be the P2P host and assigns a game session to it. In this section, you will implement a function so the P2P host can handle the received session.
Open the
MatchmakingP2PServerSubsystem_Starter
Header file and create the following function:protected:
virtual void OnServerSessionReceived(FName SessionName) override;Open the
MatchmakingP2PServerSubsystem_Starter
CPP file and define the function above. When the P2P host receives a game session from the backend, this function assigns the correct game mode based on the information provided by the game session. This way, the P2P host can provide the correct gameplay to the connected players.void UMatchmakingP2PServerSubsystem_Starter::OnServerSessionReceived(FName SessionName)
{
Super::OnServerSessionReceived(SessionName);
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
#pragma region "Assign game mode based on SessionTemplateName from backend"
// Get GameMode
const UWorld* World = GetWorld();
if (!World)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("World is invalid"));
return;
}
AGameStateBase* GameState = World->GetGameState();
if (!GameState)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Game State is invalid"));
return;
}
AAccelByteWarsGameState* AbGameState = Cast<AAccelByteWarsGameState>(GameState);
if (!AbGameState)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Game State is not derived from AAccelByteWarsGameState"));
return;
}
// Get Game Session
if (MatchmakingOnlineSession->GetSessionType(SessionName) != EAccelByteV2SessionType::GameSession)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Is not a game session"));
return;
}
const FNamedOnlineSession* Session = MatchmakingOnlineSession->GetSession(SessionName);
if (!Session)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("The session is invalid"));
return;
}
FString RequestedGameModeCode = TEXT(""), SessionTemplateName = TEXT("");
Session->SessionSettings.Get(GAMESETUP_GameModeCode, RequestedGameModeCode);
Session->SessionSettings.Get(SETTING_SESSION_MATCHPOOL, SessionTemplateName);
if (!RequestedGameModeCode.IsEmpty())
{
AbGameState->AssignGameMode(RequestedGameModeCode);
}
else if (!SessionTemplateName.IsEmpty())
{
AbGameState->AssignGameMode(MatchmakingOnlineSession->TargetGameModeMap[SessionTemplateName]);
}
#pragma endregion
// Query all currently registered users' info
AuthenticatePlayer_OnRefreshSessionComplete(true);
}Bind the function above so it will be called when the P2P host receives a game session from the backend. You can do this by adding the code below to the
Initialize()
function, which is the first function to be called when the subsystem initializes.void UMatchmakingP2PServerSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UOnlineSession* BaseOnlineSession = GetWorld()->GetGameInstance()->GetOnlineSession();
if (!ensure(BaseOnlineSession))
{
return;
}
MatchmakingOnlineSession = Cast<UMatchmakingP2POnlineSession_Starter>(BaseOnlineSession);
GetABSessionInt()->OnServerReceivedSessionDelegates.AddUObject(this, &ThisClass::OnServerSessionReceived);
}Unbind the function when the subsystem is deinitialized. You can do this by adding the following code to the
Deinitialize()
function:void UMatchmakingP2PServerSubsystem_Starter::Deinitialize()
{
Super::Deinitialize();
GetABSessionInt()->OnServerReceivedSessionDelegates.RemoveAll(this);
}
Additional features within the subsystem
In the previous section, you learned how the server can retrieve the session info via FNamedOnlineSession* Session = MatchmakingOnlineSession->GetSession(SessionName)
. This section expands on that and provides additional information you might find useful regarding the session.
Team assignment
AGS Matchmaking can also sort players into teams based on the specified match ruleset. For this, the server needs to have a way to retrieve the team info. Here's the code to do that:
// ...
const FNamedOnlineSession* NamedOnlineSession = GameSessionOnlineSession->GetSession(
GameSessionOnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession));
if (!NamedOnlineSession)
{
return;
}
const TSharedPtr<FOnlineSessionInfo> SessionInfo = NamedOnlineSession->SessionInfo;
if (!SessionInfo.IsValid())
{
return;
}
const TSharedPtr<FOnlineSessionInfoAccelByteV2> AbSessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(SessionInfo);
if (!AbSessionInfo.IsValid())
{
return;
}
TArray<FAccelByteModelsV2GameSessionTeam> Teams = AbSessionInfo->GetTeamAssignments();
Additionally, you can use the FAccelByteModelsV2GameSessionTeam::UserIDs
to check if a user is a part of the session or not. Notice that the User ID is stored a string. You can use this code to retrieve the AB User ID from FUniqueNetIdRepl:
#include "OnlineSubsystemAccelByteTypes.h"
const FUniqueNetIdAccelByteUserPtr AbUniqueNetId = FUniqueNetIdAccelByteUser::TryCast(*UniqueNetIdRepl);
const FString AbUserId = AbUniqueNetId->GetAccelByteId();
Resources
The files used in this tutorial section are available in the Unreal Byte Wars GitHub repository.
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2POnlineSession_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2POnlineSession_Starter.cpp
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2PServerSubsystem_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2PServerSubsystem_Starter.cpp