Setting Up Matchmaking #

The next part in building our game flow is enabling players to enter matchmaking.

Pragma Engine uses a Matchmaking Plugin to provide matchmaking functionality. You can build your own matchmaking plugins to customize matchmaking behavior to suit the unique needs of your game. As this is a simplified tutorial, we’ll be using a Pragma-provided matchmaking plugin that performs warm body matchmaking. This means the matchmaking plugin we’re using simply matches the first available players together until a complete match is formed.

For the front end, we’ll be making the necessary modifications by adding a start matchmaking button and outputting status information so that the party leader can start matchmaking and see when they are in a match.

Enable the Matchmaking Plugin #

For this example, we’ll be using the WarmBodyMatchmakingPlugin. This plugin is a configurable implementation of the Matchmaking Plugin, meaning we can pass specific parameters in our configuration YAML to change its behavior. Enable the plugin by editing 5-ext/config/dev.yml to add the following config:

game:
  pluginConfigs:
    MatchmakingService.matchmakingPlugin:
      class: "pragma.matchmaking.WarmBodyMatchmakingPlugin"
      config:
        numberOfTeams: 1
        playersPerTeam: 2

This configuration enables the WarmBodyMatchmakingPlugin and tells it to create a two-player match where both players are on the same team.

Next, we need to implement two matchmaking-related functions in GameFlowTutorialPartyPlugin:

override suspend fun buildMatchmakingKey(party: Party): ExtMatchmakingKey {
    return ExtMatchmakingKey.getDefaultInstance()
}

override suspend fun buildExtMatchmakingParty(party: Party): ExtMatchmakingParty {
    return ExtMatchmakingParty.getDefaultInstance()
}
Method overview

These two methods must be implemented so that parties can enter matchmaking. Note that we don’t actually need to perform any logic here in our simplified example:

  • Matchmaking Keys are used to separate distinct queues. As our simplified example only consists of one queue, we don’t need to implement actual logic in buildMatchmakingKey().
  • Similarly, because we’re using warm body matchmaking with no custom behavior, we have not defined any custom data in the ExtEnterMatchmakingV2Request proto, so we have nothing to handle in buildExtEnterMatchmakingV2Request().

Since we’ve implemented new functionality in our Party Plugin, we’ll need to rebuild 5-ext. Run the following command in a terminal from the platform directory:

make ext pragma-sdks

Run Pragma Engine via one of the following methods.

Running via Make
Run make run to start the platform. Run this in a terminal with platform as the working directory.
Running in IntelliJ

From the IntelliJ toolbar in the upper right, ensure MainKt - LocalConfigured is selected, then click the play button.

If MainKt - LocalConfigured isn’t available, you will need to configure it. In the IntelliJ toolbar, click the dropdown next to the run button, then click Edit Configurations…. In the Run/Debug Configurations window that appears, expand Kotlin in the left hand side, then select MainKt - LocalConfigured. Click OK. Click the play button in the IntelliJ toolbar to start Pragma Engine.

Once the engine has started successfully, it prints the message [main] INFO main - Pragma server startup complete.

Implement the enter matchmaking service call #

We’ve previously built the party flow up until the point players enter matchmaking: players can create a new party, invite other players, select their characters, and ready up. We’ll add a button to enter matchmaking, then have that button issue the service call to begin matchmaking.

Add a button to your Party screen to enter matchmaking, then add the following code:

Add the following to your header file under protected:

UFUNCTION(BlueprintCallable, Category="Pragma")
void EnterMatchmaking();

Then add the corresponding function definition to your source file:

void APC_TutorialPlayerController::EnterMatchmaking()
{
  Player->GameLoopApi().EnterMatchmaking();
}

Then hook up the EnterMatchmaking() method to the button in Unreal Editor. While we won’t be implementing any client-side checks in this tutorial, the platform does perform validation and won’t enter parties into matchmaking unless they meet all criteria, such as all players being ready.

At this point, you can launch two clients and have them enter matchmaking. They can either be in the same party or they can enter matchmaking as two separate parties of one player. You’ll know matchmaking succeeded when you see the following line in the Unreal log (formatted for readability; the log isn’t pretty printed in Unreal Editor):

LogPragma: Verbose: RESPONSE 355 PartyRpc.EnterMatchmakingV1Response (seq: 35): Ok, message body: {
    "sequenceNumber": 35,
    "response": {
        "requestId": 355,
        "type": "PartyRpc.EnterMatchmakingV1Response",
        "payload": {
            "party": {
                "extPartySelections": {},
                "extPrivatePlayerSelections": {},
                "partyId": "b11f0901-e794-4f04-8fb1-6be09414d3c7",
                "partyMembers": [
                    {
                        "ext": {
                            "visibleCharacter": "KNIGHT",
                            "teamNumber": "0"
                        },
                        "playerId": "ff358871-e301-40ec-95ad-7c97ad2312ff",
                        "displayName": {
                            "displayName": "test01",
                            "discriminator": "4746"
                        },
                        "isReady": true,
                        "isLeader": true
                    }
                ],
                "inviteCode": "B33RJ7",
                "preferredGameServerZones": []
            }
        }
    }
}.

However, this isn’t the most intuitive way to see that matchmaking has succeeded, so let’s add status messages to the UI and register the appropriate event handlers.

Add matchmaking event handlers to the client #

In production scenarios, it’s necessary to show players when they are in matchmaking and when a match has been found. In our simplified tutorial, we’ll be writing text to a multiline text box. In either case, the same event handlers apply.

First, let’s register the event handlers by adding the following lines to the header file. Under private, add:

void HandleEnteredMatchmaking();
void HandleGameInstanceIdChanged(const FString GameInstanceId);

Then in your source file, add the following function definitions:

void APC_TutorialPlayerController::HandleEnteredMatchmaking()
{
    AddStatusMessage("You are in matchmaking!");
    UpdatePartyUI();
}

void ATutorialPlayerController::HandleGameInstanceIdChanged(const FString GameInstanceId)
{
	UE_LOG(LogTemp, Display, TEXT("Match ID: %p"), *FString(GameInstanceId) );

	FString Status;
	(GameInstanceId.IsEmpty()) ? Status = "GameInstanceId is empty." : Status = "You are in a game! GameInstanceId: " + GameInstanceId;
	AddStatusMessage(Status);
	UpdatePartyUI();
}

These methods use a class variable PartyStatus and a helper method AddStatusMessage(), so let’s create those.

In your header file, add the following under private:

UPROPERTY(BlueprintReadWrite, meta = (AllowPrivateAccess))
TArray<FString> PartyStatus;

Then add the function declaration for AddStatusMessage() which also goes under private:

void AddStatusMessage(FString StatusMessage);

Then in your source file, add the function definition:

void APC_TutorialPlayerController::AddStatusMessage(FString StatusMessage)
{
  if (!StatusMessage.IsEmpty())
    {
      PartyStatus.Add(StatusMessage);
    }
}

Lastly, we need to actually register these functions with the appropriate event handlers. In the source file under the Init() function, add the following lines:

    Player->GameLoopApi().OnEnteredMatchmaking.AddUObject(
      this, &APC_TutorialPlayerController::HandleEnteredMatchmaking);

    Player->GameLoopApi().OnMatchIdChanged.AddUObject(
      this, &APC_TutorialPlayerController::HandleGameInstanceIdChanged);
Method overview

We’ve written two functions and registered each of them with event handlers.

  • HandleEnteredMatchmaking() is called when the party enters matchmaking.
  • Similarly, HandleGameInstanceIdChanged() is called when the match ID changes. This happens when matchmaking successfully creates a match by grouping together the appropriate number of players to play a game.

Add a multiline text box to your UI, then create a ShowPartyStatus() Blueprint Function that takes in the PartyStatus string array and sets the multiline text box text with the contents of PartyStatus. In your controller, make sure that your UI update function UpdatePartyUI() calls this new ShowPartyStatus() function.

You can now enter matchmaking after creating a party and selecting a character. When you have two players enter matchmaking, the matchmaker pairs them together. You should also see status messages upon entering matchmaking and when the matchmaker successfully finds a match.