Building the Party Screen in Unreal Engine #

In this section, we’ll implement the party screen. As we complete each section, our game client will be able to create, join, and leave parties, as well as make character selections. Additionally, we’ll be able to see the character selection and ready state of the other player in the party.

Start a local instance of Pragma Engine #

We’ll need a locally running Pragma Engine in order to test our work as we build each piece of the Party screen.

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.

Create the party #

Let’s create the party. In your header, add the following:

protected:
    UFUNCTION(BlueprintCallable, Category="Pragma")
    void CreateParty();

Define the CreateParty() function:

void APC_TutorialPlayerController::CreateParty()
{
    UPragmaGameLoopApi::FOnCompleteDelegate OnPartyCreatedDelegate;
    OnPartyCreatedDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
    {
        if (Result.IsSuccessful())
        {
            UE_LOG(LogTemp, Display, TEXT("Pragma party created."));
        }
        else
        {
            UE_LOG(LogTemp, Warning,
            TEXT("Pragma unable to create party: %s"), *Result.Error().ToString());
        }
    });

    Player->GameLoopApi().CreateParty(
        FPragma_Party_ExtCreateRequest{},
        FPragma_Party_ExtPlayerJoinRequest{},
        TArray<FString>(),
        TMap<FString, int32>(),
        OnPartyCreatedDelegate
        );
}

Remember that this function is a BlueprintCallable. Do the necessary corresponding work in your Blueprint to create a corresponding button and have it invoke this function when clicked. You should then be able to check your work by clicking the play button in Unreal Editor. Once you’ve logged in, clicking the Create Party button should successfully create a party with you as the party leader. You can confirm this by checking your Unreal log, where you should see the following output:

Output log: Party creation
LogPragma: Verbose: RESPONSE +0 PartyRpc.CreateV1Response (seq: 2): Ok, message body: {
    "sequenceNumber": 2,
    "response": {
        "requestId": 60,
        "type": "PartyRpc.CreateV1Response",
        "payload": {
            "party": {
                "extPartySelections": {},
                "extPrivatePlayerSelections": {},
                "partyId": "032aaa83-03f9-48ec-b546-fe72da6367da",
                "partyMembers": [
                    {
                        "ext": {
                            "visibleCharacter": "UNSPECIFIED","teamNumber":"0"},
                            "playerId": "ff358871-e301-40ec-95ad-7c97ad2312ff",
                            "displayName": {
                                "displayName": "test01",
                                "discriminator": "4746"
                            },
                        "isReady": false,
                        "isLeader": true
                    }
                ],
                "inviteCode": "RTHNRP",
                "preferredGameServerZones": []
            }
        }
    }
}.

Register the event listener for party changes #

Any given player in a party can cause the party’s state to change, so it’s not sufficient to only update the party UI when the local player makes changes. The Pragma SDK provides an event that fires every time the party’s state changes.

For now, let’s create a function that logs to the console whenever the party state changes. Add the following under private to your header:

void HandlePartyUpdate(const UPragmaParty* Party);

And add the following to the source file:

void APC_TutorialPlayerController::HandlePartyUpdate(const UPragmaParty* Party)
{
	UE_LOG(LogTemp, Display, TEXT("Party state updated."));
    // We will handle updating our party UI here in a later step.
}

Then register an event handler for OnPartyChanged that will call HandlePartyUpdate() by adding the following line to the end of your Init() function:

Player->GameLoopApi().OnPartyChanged.AddUObject(
	this, &APC_TutorialPlayerController::HandlePartyUpdate);

Whenever the party’s state changes, you’ll see “Party state updated.” printed to your output log. We’ll revisit this function later to update the UI.

Join the party #

We’ve implemented creating and leaving parties so far, and now we’ll make it possible for other players to join our party.

In your UI, make the following elements: a place to show the lobby invite code, a text box for players to input an invite code, and a “join party” button.

Upon party creation, parties are automatically assigned a unique invite code. To actually get the invite code, use the following function.

In your header under protected:

    UFUNCTION(BlueprintCallable, Category="Pragma")
    FString GetInviteCode();

In your source file:

FString APC_TutorialPlayerController::GetInviteCode()
{
	if (!Player->GameLoopApi().GetParty())
	{
		return FString{};
	}
	return Player->GameLoopApi().GetParty()->GetInviteCode();
}

This function returns the invite code, but we need to do additional work to actually display the invite code in the UI.

We need to create a BlueprintImplementableEvent that gets called by HandlePartyUpdate(). Add the following to the public section of your header file:

UFUNCTION(BlueprintImplementableEvent, Category = "Widget")
void UpdatePartyUI();

Then have HandlePartyUpdate() call UpdatePartyUI():

void APC_TutorialPlayerController::HandlePartyUpdate(const UPragmaParty* Party)
{
	UE_LOG(LogTemp, Display, TEXT("Party state updated."));
    UpdatePartyUI();
}

Switch over to Unreal Editor and open your PC_TutorialPlayerController Blueprint. You now have an UpdatePartyUI event that you can use to call all your BlueprintCallable C++ functions that actually update your party UI, ensuring that your UI always shows the latest party state. Up to this point, we haven’t created those UI updating functions yet. To get started, you can have the UpdatePartyUI event call GetInviteCode() in your player controller Blueprint, which returns a string that represents the party’s invite code (or an empty string if you’re not in a party). You will then need to create a Blueprint function that takes the output of GetInviteCode() and places it into the appropriate UI element.

Next, create the JoinByInviteCode() function.

In your header under protected:

    UFUNCTION(BlueprintCallable, Category="Pragma")
    void JoinByInviteCode(const FString& InviteCode);

In your source file:

void APC_TutorialPlayerController::JoinByInviteCode(const FString& InviteCode)
{
	UPragmaGameLoopApi::FOnCompleteDelegate JoinWithInviteCodeDelegate;
	JoinWithInviteCodeDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Joined party using invite code %s"), *InviteCode);
		}
		else
		{
			UE_LOG(LogTemp, Warning,
				TEXT("Unable to join party: %s"), *Result.Error().ToString());
		}
	});

	Player->GameLoopApi().JoinWithInviteCode(
		FPragma_Party_ExtPlayerJoinRequest{},
		InviteCode,
		JoinWithInviteCodeDelegate
		);
}

Hook this up to the UI such that it gets its argument from the invite code text box and is called by the join button. Once you’ve done so, you should be able to verify that you can have another player join your party.

You can test this by building a standalone client. Log in on two clients (you can use the accounts test01 and test02). Have one client create a party, then join the party using the invite code on the second client.

You should a message similar to the following in your output log upon the second client successfully joining the party:

Output log: Joining a party
LogPragma: Verbose: RESPONSE 290 PartyRpc.JoinWithInviteCodeV1Response (seq: 2): Ok, message body: {
    "sequenceNumber": 2,
    "response": {
        "requestId": 290,
        "type": "PartyRpc.JoinWithInviteCodeV1Response",
        "payload": {
            "party": {
                "extPartySelections": {},
                "extPrivatePlayerSelections": {},
                "partyId": "8ce582dd-77df-4ffb-8a07-01b02bc4cf23",
                "partyMembers": [
                    {
                        "ext": {
                            "visibleCharacter": "UNSPECIFIED",
                            "teamNumber": "0"
                        },
                        "playerId": "f38a875f-ff69-4202-b40c-6128fd1f16e0",
                        "displayName": {
                            "displayName": "test02",
                            "discriminator": "1434"
                        },
                        "isReady": false,
                        "isLeader": true
                    },
                    {
                        "ext": {
                            "visibleCharacter": "UNSPECIFIED",
                            "teamNumber": "0"
                        },
                        "playerId": "ff358871-e301-40ec-95ad-7c97ad2312ff",
                        "displayName": {
                            "displayName": "test01",
                            "discriminator": "4746"
                        },
                        "isReady": false,
                        "isLeader": false
                    }
                ],
                "inviteCode": "3DQN7Z",
                "preferredGameServerZones": []
            }
        }
    }
}.

Make character selections #

In an actual game, there are many selections that players can make prior to getting in game. To keep our example simple, we’ll limit what the player can do to making a character selection.

Create a dropdown menu so that players can select their desired character.

Make the following declaration under protected:

    UFUNCTION(BlueprintCallable, Category="Pragma")
    void SendCharacterSelection(const FString& CharacterSelection);

Then define the following function:

void APC_TutorialPlayerController::SendCharacterSelection(const FString& CharacterSelection)
{
	const EPragma_Party_Character PartyCharacter = static_cast<EPragma_Party_Character>(
		StaticEnum<EPragma_Party_Character>()->GetValueByNameString(
			CharacterSelection, EGetByNameFlags::None)
		);
	UE_LOG(LogTemp, Display, TEXT("character: %s."), *CharacterSelection);

	UPragmaGameLoopApi::FOnCompleteDelegate UpdatePlayerSelectionsDelegate;
	UpdatePlayerSelectionsDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display,
				TEXT("Changed character selection to %s"), *CharacterSelection);
		}
		else
		{
			UE_LOG(LogTemp, Warning,
				TEXT("Unable to change character: %s"), *Result.Error().ToString());
		}
	});

	FPragma_Party_ExtUpdatePlayerSelectionsRequest Request;
	Request.Update.SetNewCharacter(PartyCharacter);
	
	Player->GameLoopApi().UpdatePlayerSelections(Request, UpdatePlayerSelectionsDelegate);
}

In Unreal Editor, right click an empty area of your lobby’s Blueprint’s graph and add a Pre Construct event. Iterate over the EPragma_Party_Character enum and populate the character selection dropdown with each enum. Next, make it so that the SendCharacterSelection() function is called by the character selection dropdown’s On Selection Changed event.

If done successfully, when in a party, you should see that the character selection dropdown is populated by four values:

  • UNSPECIFIED
  • KNIGHT
  • MAGE
  • ROGUE

Additionally, we should see something similar to the following in our output log whenever we make a character selection.

Output log: making a character selection

The following output log shows the party leader test01 changing their character selection to KNIGHT. There are no other players in the party.

LogPragma: Verbose: RESPONSE 301 PartyRpc.UpdatePlayerSelectionsV1Response (seq: 3): Ok, message body: {
    "sequenceNumber": 3,
    "response": {
        "requestId": 301,
        "type": "PartyRpc.UpdatePlayerSelectionsV1Response",
        "payload": {
            "party": {
                "extPartySelections": {},
                "extPrivatePlayerSelections": {},
                "partyId": "bc6b0e5f-1968-4029-a931-b96e66488136",
                "partyMembers": [
                    {
                        "ext": {
                            "visibleCharacter": "KNIGHT",
                            "teamNumber": "0"
                        },
                        "playerId": "ff358871-e301-40ec-95ad-7c97ad2312ff",
                        "displayName": {
                            "displayName": "test01",
                            "discriminator": "4746"
                        },
                        "isReady": true,
                        "isLeader": true
                    }
                ],
                "inviteCode": "DH2TPL",
                "preferredGameServerZones": []
            }
        }
    }
}.
LogTemp: Display: Party state updated.
LogTemp: Display: Changed character selection to KNIGHT

Make the party state visible to players #

At this point, we’ve built out all the necessary party functionality but players can’t view the state of the party. Let’s build the UI so that the state of the party is visible.

In your party UI Blueprint, add a text box. We’ll use this to show the players in the party along with their respective roles and leader status.

Make the following protected declarations:

    UFUNCTION(BlueprintCallable, Category="Pragma")
    FString GetDisplayName();

    UFUNCTION(BlueprintCallable, Category="Pragma")
    TArray<FString> GetPartyPlayerDisplayNames();

Add the following function to retrieve the player’s display name:

FString APC_TutorialPlayerController::GetDisplayName()
{
	return Player->DisplayName();
}

Then add the following function to populate the text box we just created:

TArray<FString> APC_TutorialPlayerController::GetPartyPlayerDisplayNames()
{
	if (!Player->GameLoopApi().GetParty())
	{
		return TArray<FString>{};
	}

	const TArray<UPragmaPartyPlayer*> PartyPlayers =
		Player->GameLoopApi().GetParty()->GetPlayers();
	
    TArray<FString> DisplayNames;

	const FString& YourPlayerId = Player->Id();

	for (const UPragmaPartyPlayer* PartyPlayer : PartyPlayers)
	{
		FString DisplayName = PartyPlayer->GetDisplayName().DisplayName;
		FString PlayerId = PartyPlayer->GetPlayerId();

		if (PlayerId == YourPlayerId)
		{
			DisplayName += " (You)";
		}

		if (PartyPlayer->IsLeader())
		{
			DisplayName += " (Leader)";
		}

		if (PartyPlayer->IsReady())
		{
			DisplayName += " (Ready)";
		}

		DisplayNames.Add(DisplayName);
	}

	return DisplayNames;
}

GetPartyPlayerDisplayNames() should be invoked by the UpdatePartyUI event (via the Blueprint editor) and its output inserted into the text box. While we’re merely concatenating strings here, this example shows you how to get the data you need to build a full UI.

Leave the party #

Next, let’s make it so that we can leave the party by creating the LeaveParty() function. In your header under protected, add:

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

Then in your source file, add:

void APC_TutorialPlayerController::LeaveParty()
{
	UPragmaGameLoopApi::FOnCompleteDelegate LeavePartyDelegate;
	LeavePartyDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Successfully left party."));
		}
		else
		{
			UE_LOG(LogTemp, Warning,
				TEXT("Unable to leave party: %s"), *Result.Error().ToString());
		}
	});
	Player->GameLoopApi().LeaveParty(LeavePartyDelegate);
}

Create and hook up a leave party button in your UI Blueprint. When you successfully leave a party, you should see the following in your output log:

LogPragma: Verbose: RESPONSE 338 PartyRpc.LeaveV1Response (seq: 23): Ok, message body: {
    "sequenceNumber": 23,
    "response": {
        "requestId": 338,
        "type": "PartyRpc.LeaveV1Response",
        "payload": {}
    }
}.
LogTemp: Display: Successfully left party.

Pragma Engine is now set up with parties, along with a corresponding Unreal game client that can log into the engine with full party functionality. Players can create and join parties, make character selections, and leave parties.