Use the OSS for server matchmaking - Quick match with dedicated server - (Unreal Engine module)
Matchmaking with dedicated server flow
Take some time to understand how matchmaking with a dedicated server (DS) works using 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 game server. If necessary, remind yourself of the basic knowledge of how game sessions work by referring back to the Introduction to Session module.
In this tutorial, you will be working with an online session class named MatchmakingDSOnlineSession_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/MatchmakingDS/MatchmakingDSOnlineSession_Starter.h
- CPP file:
/Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSOnlineSession_Starter.cpp
This class has several predefined attributes and functions you can use to follow the tutorial. Take a look at what it provides.
- Similar to the Introduction to Session module, this class uses session interfaces to perform session-related functions, including matchmaking.
FOnlineSessionV2AccelBytePtr UAccelByteWarsOnlineSessionBase::GetABSessionInt()
{
return StaticCastSharedPtr<FOnlineSessionV2AccelByte>(GetSessionInt());
}
- The Header file has the map below that defines the Match Pool IDs that you created in the previous tutorial. As you can see, the IDs are the exact same as the ones you set up in the Admin Portal. The name must be the same so you can request matchmaking to the correct Match Pool.
private:
// ...
const TMap<EGameModeType, FString> MatchPoolIds = {
{EGameModeType::FFA, "unreal-elimination-ds-ams"},
{EGameModeType::TDM, "unreal-teamdeathmatch-ds-ams"}
};
- The Header file also has the map below that translates 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-ds-ams", "ELIMINATION-DS"},
{"unreal-teamdeathmatch-ds-ams", "TEAMDEATHMATCH-DS"}
};
- In the CPP file, there are several functions to query player information. You will not use these functions directly in this tutorial. These functions are called on the game server side, particularly in the
UAccelByteWarsServerSubsystemBase::AuthenticatePlayer_OnRefreshSessionComplete()
. The queried information is saved in the game state which is then used by the Match Lobby widget to display the player information.
void UMatchmakingDSOnlineSession_Starter::DSQueryUserInfo(
const TArray<FUniqueNetIdRef>& UserIds,
const FOnDSQueryUsersInfoComplete& OnComplete)
{
UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))
const TArray<const FBaseUserInfo*> UserInfo;
if (DSRetrieveUserInfoCache(UserIds, UserInfo))
{
UE_LOG_MATCHMAKINGDS(Log, TEXT("Cache found"))
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, OnComplete, UserInfo]()
{
OnComplete.ExecuteIfBound(true, UserInfo);
}));
}
else
{
// Gather user IDs.
TArray<FString> AbUserIds;
for (const FUniqueNetIdRef& UserId : UserIds)
{
const FUniqueNetIdAccelByteUserPtr AbUniqueNetId = FUniqueNetIdAccelByteUser::TryCast(*UserId);
const FString AbUserId = AbUniqueNetId->GetAccelByteId();
if (!AbUserId.IsEmpty())
{
AbUserIds.Add(AbUserId);
}
}
AccelByte::FRegistry::User.BulkGetUserInfo(
AbUserIds,
THandler<FListBulkUserInfo>::CreateWeakLambda(this, [OnComplete, this](FListBulkUserInfo UserInfo)
{
OnDSQueryUserInfoComplete(UserInfo, OnComplete);
}),
FErrorHandler::CreateWeakLambda(this, [this, OnComplete](int32, const FString&)
{
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, OnComplete]()
{
OnDSQueryUserInfoComplete(FListBulkUserInfo(), OnComplete);
}));
})
);
}
}
Start matchmaking
In this section, you will implement functions to start matchmaking.
Open the
MatchmakingDSOnlineSession_Starter
Header file and 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 declared above. Open the
MatchmakingDSOnlineSession_Starter
CPP file and begin with theStartMatchmaking()
function. In this function, you construct the matchmaking handle to define what kind of matchmaking you want. It sets the Match Pool to the matchmaking handle so it will start the matchmaking based on the Match Pool you configured in the Admin Portal. Before it begins matchmaking, it first checks whether the player is already in the game session or not. If yes, it leaves the game session first and then retries matchmaking, which will be handled by theOnLeaveSessionForReMatchmakingComplete()
function. When the matchmaking is successfully started, it then calls theOnStartMatchmakingComplete()
function.void UMatchmakingDSOnlineSession_Starter::StartMatchmaking(
const APlayerController* PC,
const FName& SessionName,
const EGameModeNetworkType NetworkType,
const EGameModeType GameModeType)
{
UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))
// Abort if the session interface is invalid.
if (!ensure(GetSessionInt()))
{
UE_LOG_MATCHMAKINGDS(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_MATCHMAKINGDS(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_MATCHMAKINGDS(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];
// AMS is the default multiplayer server on AGS. If the game runs on legacy AGS Armada, remove the -ams suffix.
if(!UTutorialModuleOnlineUtility::GetIsServerUseAMS())
{
MatchPoolId = MatchPoolId.Replace(TEXT("-ams"), TEXT(""));
}
// Override match pool ID if applicable.
if (!UTutorialModuleOnlineUtility::GetMatchPoolDSOverride().IsEmpty())
{
MatchPoolId = UTutorialModuleOnlineUtility::GetMatchPoolDSOverride();
}
// 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);
// Check for DS version override.
const FString OverriddenDSVersion = UTutorialModuleOnlineUtility::GetDedicatedServerVersionOverride();
if (!OverriddenDSVersion.IsEmpty())
{
MatchmakingSearchHandle->QuerySettings.Set(SETTING_GAMESESSION_CLIENTVERSION, OverriddenDSVersion, EOnlineComparisonOp::Equals);
}
// Set local server name for matchmaking request if any.
// This is useful if you want to try matchmaking using local dedicated server.
FString ServerName;
FParse::Value(FCommandLine::Get(), TEXT("-ServerName="), ServerName);
if (!ServerName.IsEmpty())
{
UE_LOG_MATCHMAKINGDS(Log, TEXT("Requesting local server with name: %s"), *ServerName)
MatchmakingSearchHandle->QuerySettings.Set(SETTING_GAMESESSION_SERVERNAME, ServerName, EOnlineComparisonOp::Equals);
}
// Include region preferences into matchmaking setting.
if (const UTutorialModuleDataAsset* ModuleDataAsset = UTutorialModuleUtility::GetTutorialModuleDataAsset(
FPrimaryAssetId{ "TutorialModule:REGIONPREFERENCES" },
this,
true))
{
if (!ModuleDataAsset->IsStarterModeActive())
{
UAccelByteWarsGameInstance* GameInstance = StaticCast<UAccelByteWarsGameInstance*>(GetGameInstance());
ensure(GameInstance);
URegionPreferencesSubsystem* RegionPreferencesSubsystem = GameInstance->GetSubsystem<URegionPreferencesSubsystem>();
if(RegionPreferencesSubsystem != nullptr)
{
TArray<FString> EnabledRegion = RegionPreferencesSubsystem->GetEnabledRegion();
if(!EnabledRegion.IsEmpty())
{
FOnlineSearchSettingsAccelByte::Set(MatchmakingSearchHandle->QuerySettings, SETTING_GAMESESSION_REQUESTEDREGIONS, RegionPreferencesSubsystem->GetEnabledRegion(), EOnlineComparisonOp::In);
}
}
}
}
if (!GetSessionInt()->StartMatchmaking(
USER_ID_TO_MATCHMAKING_USER_ARRAY(PlayerNetId.ToSharedRef()),
SessionName,
FOnlineSessionSettings(),
MatchmakingSearchHandle,
FOnStartMatchmakingComplete::CreateUObject(this, &ThisClass::OnStartMatchmakingComplete)))
{
UE_LOG_MATCHMAKINGDS(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 was started is successful or not.void UMatchmakingDSOnlineSession_Starter::OnStartMatchmakingComplete(
FName SessionName,
const FOnlineError& ErrorDetails,
const FSessionMatchmakingResults& Results)
{
UE_LOG_MATCHMAKINGDS(
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 it is valid to retry matchmaking or not.void UMatchmakingDSOnlineSession_Starter::OnLeaveSessionForReMatchmakingComplete(
FName SessionName,
bool bSucceeded,
const int32 LocalUserNum,
const EGameModeType GameModeType)
{
UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))
GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForReMatchmakingCompleteDelegateHandle);
if (bSucceeded)
{
// Retry matchmaking.
const APlayerController* PC = GetPlayerControllerByLocalUserNum(LocalUserNum);
if (!ensure(PC))
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("PlayerController is null."));
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
return;
}
StartMatchmaking(PC, SessionName, EGameModeNetworkType::DS, GameModeType);
}
else
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("Is not a game session."));
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
}
}You need to handle when the matchmaking completes. Open the
MatchmakingDSOnlineSession_Starter
Header file and declare the following function:protected:
// ...
virtual void OnMatchmakingComplete(FName SessionName, bool bSucceeded) override;Declare a delegate that will be called when the 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* GetOnMatchmakingCompleteDelegates() override
{
return &OnMatchmakingCompleteDelegates;
}private:
// ...
FOnMatchmakingResponse OnMatchmakingCompleteDelegates;Open the
MatchmakingDSOnlineSession_Starter
CPP file and define theOnMatchmakingComplete()
function. This function will check if a game session was found after the matchmaking completes. If found, it joins the game session. It also invokes the delegate you created earlier to inform the game that the matchmaking process has completed.void UMatchmakingDSOnlineSession_Starter::OnMatchmakingComplete(FName SessionName, bool bSucceeded)
{
UE_LOG_MATCHMAKINGDS(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_MATCHMAKINGDS(Warning, TEXT("There is no match result returned."));
OnMatchmakingCompleteDelegates.Broadcast(SessionName, false);
return;
}
OnMatchmakingCompleteDelegates.Broadcast(SessionName, bSucceeded);
}You need to bind the function above so it will be called when the matchmaking process is 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 UMatchmakingDSOnlineSession_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 is deinitialized.void UMatchmakingDSOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
GetSessionInt()->OnMatchmakingCompleteDelegates.RemoveAll(this);
// ...
}
Cancel matchmaking
In this section, you will implement functions to cancel matchmaking.
Open the
MatchmakingDSOnlineSession_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 is 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
MatchmakingDSOnlineSession_Starter
CPP file and define theCancelMatchmaking()
function. This function checks whether it is valid to cancel matchmaking or not before executing the action. When the process is complete, it then calls theOnCancelMatchmakingComplete()
function.void UMatchmakingDSOnlineSession_Starter::CancelMatchmaking(APlayerController* PC, const FName& SessionName)
{
UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))
// Abort if the session interface is invalid.
if (!ensure(GetSessionInt()))
{
UE_LOG_MATCHMAKINGDS(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_MATCHMAKINGDS(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_MATCHMAKINGDS(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 was started is successful or not.void UMatchmakingDSOnlineSession_Starter::OnCancelMatchmakingComplete(FName SessionName, bool bSucceeded)
{
UE_LOG_MATCHMAKINGDS(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 UMatchmakingDSOnlineSession_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 UMatchmakingDSOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
GetSessionInt()->OnCancelMatchmakingCompleteDelegates.RemoveAll(this);
// ...
}
Handle when a dedicated server is received
When the matchmaking is finished and the game client joins the game session, the game client then waits to receive a dedicated server to travel to. In this section, you will learn how to handle when that happens.
Create a function to travel the game client to the dedicated server. Open the
MatchmakingDSOnlineSession_Starter
Header file and declare the following function:public:
// ...
virtual bool TravelToSession(const FName SessionName) override;Open the
MatchmakingDSOnlineSession_Starter
CPP file and define the function above. This function performs some validation before traveling the game client to the dedicated server. If it's valid, it travels to the dedicated server using the server address.bool UMatchmakingDSOnlineSession_Starter::TravelToSession(const FName SessionName)
{
UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))
if (GetSessionType(SessionName) != EAccelByteV2SessionType::GameSession)
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("Not a game session"));
return false;
}
// Get session info
const FNamedOnlineSession* Session = GetSession(SessionName);
if (!Session)
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("The session is invalid"));
return false;
}
const TSharedPtr<FOnlineSessionInfo> SessionInfo = Session->SessionInfo;
if (!SessionInfo.IsValid())
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("The session info is invalid"));
return false;
}
const TSharedPtr<FOnlineSessionInfoAccelByteV2> AbSessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(SessionInfo);
if (!AbSessionInfo.IsValid())
{
UE_LOG_MATCHMAKINGDS(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_MATCHMAKINGDS(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_MATCHMAKINGDS(Warning, TEXT("Player controller is not (derived from) AAccelByteWarsPlayerController"));
return false;
}
// Make sure this is not a P2P session
if (GetABSessionInt()->IsPlayerP2PHost(GetLocalPlayerUniqueNetId(PlayerController).ToSharedRef().Get(), SessionName))
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("The session is a P2P session"));
return false;
}
FString ServerAddress = "";
GetABSessionInt()->GetResolvedConnectString(SessionName, ServerAddress);
if (ServerAddress.IsEmpty())
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("Can't find session's server address"));
return false;
}
if (!bIsInSessionServer)
{
AbPlayerController->DelayedClientTravel(ServerAddress, TRAVEL_Absolute);
bIsInSessionServer = true;
}
else
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("Already in session's server"));
}
return true;
}Create a function to listen when the game client receives a server update. Open the
MatchmakingDSOnlineSession_Starter
Header file and declare the following function:protected:
// ...
virtual void OnSessionServerUpdateReceived(FName SessionName) override;Declare a delegate that will be called when the dedicated server 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
MatchmakingDSOnlineSession_Starter
CPP file and define theOnSessionServerUpdateReceived()
function. This function checks whether it is valid to travel to the dedicated server or not. It also invokes the delegate you created earlier to inform that the server update is received.void UMatchmakingDSOnlineSession_Starter::OnSessionServerUpdateReceived(FName SessionName)
{
UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))
if (bLeavingSession)
{
UE_LOG_MATCHMAKINGDS(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 not be able to provide dedicated server to the game client. Create a function to handle when that happens by opening the
MatchmakingDSOnlineSession_Starter
Header file and declaring the following function:protected:
// ...
virtual void OnSessionServerErrorReceived(FName SessionName, const FString& Message) override;Open the
MatchmakingDSOnlineSession_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 UMatchmakingDSOnlineSession_Starter::OnSessionServerErrorReceived(FName SessionName, const FString& Message)
{
UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))
FOnlineError Error;
Error.bSucceeded = false;
Error.ErrorMessage = FText::FromString(Message);
OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, Error, 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 UMatchmakingDSOnlineSession_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 UMatchmakingDSOnlineSession_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, we 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
MatchmakingDSOnlineSession_Starter
Header file and declare the following function:protected:
// ...
virtual void OnBackfillProposalReceived(FAccelByteModelsV2MatchmakingBackfillProposalNotif Proposal) override;Declare a delegate that will be called when the accepting backfill process completes.
public:
// ...
virtual FOnMatchmakingAcceptBackfillProposalComplete* GetOnAcceptBackfillProposalCompleteDelegates() override
{
return &OnAcceptBackfillProposalCompleteDelegates;
}private:
// ...
FOnMatchmakingAcceptBackfillProposalComplete OnAcceptBackfillProposalCompleteDelegates;Open the
MatchmakingDSOnlineSession_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 UMatchmakingDSOnlineSession_Starter::OnBackfillProposalReceived(
FAccelByteModelsV2MatchmakingBackfillProposalNotif Proposal)
{
UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))
// Abort if the session interface is invalid.
if (!ensure(GetABSessionInt().IsValid()))
{
UE_LOG_MATCHMAKINGDS(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_MATCHMAKINGDS(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 UMatchmakingDSOnlineSession_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 is deinitialized.void UMatchmakingDSOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
GetABSessionInt()->OnBackfillProposalReceivedDelegates.RemoveAll(this);
// ...
}
Set up dedicated server online subsystem
In this tutorial, you will be working with a game instance subsystem class named MatchmakingDSServerSubsystem_Starter
. This class is used by the dedicated 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/MatchmakingDS/MatchmakingDSServerSubsystem_Starter.h
- CPP file:
/Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSServerSubsystem_Starter.cpp
Handle when a dedicated server receives a session
When the matchmaking is finished, the backend creates and claims a dedicated server and assigns a game session to it. In this section, you will implement a function so the server can handle the received session.
Open the
MatchmakingDSServerSubsystem_Starter
Header file and create the following function:protected:
virtual void OnServerSessionReceived(FName SessionName) override;Open the
MatchmakingDSServerSubsystem_Starter
CPP file and define the function above. When the dedicated server 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 server can provide the correct gameplay to the connected players. Be advised that most of this implementation is specific to Byte Wars, but the main point of this function is to get data from the session info on the backend, which is done by the highlighted lines. You can use this as a reference on how to get specific data from the session info.void UMatchmakingDSServerSubsystem_Starter::OnServerSessionReceived(FName SessionName)
{
Super::OnServerSessionReceived(SessionName);
UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))
#pragma region "Assign game mode based on SessionTemplateName from backend"
// Get GameMode
const UWorld* World = GetWorld();
if (!World)
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("World is invalid"));
return;
}
AGameStateBase* GameState = World->GetGameState();
if (!GameState)
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("Game State is invalid"));
return;
}
AAccelByteWarsGameState* AbGameState = Cast<AAccelByteWarsGameState>(GameState);
if (!AbGameState)
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("Game State is not derived from AAccelByteWarsGameState"));
return;
}
// Get Game Session
if (MatchmakingOnlineSession->GetSessionType(SessionName) != EAccelByteV2SessionType::GameSession)
{
UE_LOG_MATCHMAKINGDS(Warning, TEXT("Is not a game session"));
return;
}
const FNamedOnlineSession* Session = MatchmakingOnlineSession->GetSession(SessionName);
if (!Session)
{
UE_LOG_MATCHMAKINGDS(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);
}You need to bind the function above so it will be called when the dedicated server 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 UMatchmakingDSServerSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UOnlineSession* BaseOnlineSession = GetWorld()->GetGameInstance()->GetOnlineSession();
if (!ensure(BaseOnlineSession))
{
return;
}
MatchmakingOnlineSession = Cast<UMatchmakingDSOnlineSession_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 UMatchmakingDSServerSubsystem_Starter::Deinitialize()
{
Super::Deinitialize();
GetABSessionInt()->OnServerReceivedSessionDelegates.RemoveAll(this);
}Build your project again and make sure there are no errors.
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/MatchmakingDS/MatchmakingDSOnlineSession_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSOnlineSession_Starter.cpp
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSServerSubsystem_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSServerSubsystem_Starter.cpp