Skip to main content

Add quick play menu - Quick match with peer-to-peer - (Unreal Engine module)

Last updated on October 24, 2024

About the Quick Play UI

In Byte Wars, Quick Play is where you can access the matchmaking feature, where a player chooses a game mode and server type to matchmake to. In the Byte Wars project, Quick Play is a widget to play the game by using matchmaking. In the widget, there are two types of game modes to select: Elimination and Team Deathmatch. Elimination is a four-player free-for-all (FFA), while Team Deathmatch consists of two competing teams of two players.

The Quick Play widget is available in the Resources section and consists of the following files:

  • Header file: /Source/AccelByteWars/TutorialModules/Play/MatchmakingEssentials/UI/QuickPlayWidget.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Play/MatchmakingEssentials/UI/QuickPlayWidget.cpp
  • Blueprint widget: /Content/TutorialModules/Play/MatchmakingEssentials/UI/W_QuickPlay.uasset

This widget has different states to show the relevant user interface (UI) based on the matchmaking state. The states are:

  • Select Game Mode: showing buttons to select a game mode.
  • Select Server Type: showing buttons to select what type of server to use for matchmaking. This is where the matchmaking peer-to-peer (P2P) button will be spawned.

The important thing to note from this widget is the way to access the selected game mode.

EGameModeType UQuickPlayWidget::GetSelectedGameModeType() const
{
return SelectedGameModeType;
}

About the Matchmaking P2P UI

The Matchmaking P2P UI menu is where the actual matchmaking flow will be shown. The related files are available in the Resources section and consists of the following files:

  • Header file: /Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/UI/MatchmakingP2PWidget_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/UI/MatchmakingP2PWidget_Starter.cpp
  • Blueprint widget: /Content/TutorialModules/Play/MatchmakingP2P/UI/W_MatchmakingP2P.uasset

This widget already has some functions provided, so you can focus on the integration.

  • OnlineSession variable, which is your gateway to the session functions.
private:
UPROPERTY()
UAccelByteWarsOnlineSessionBase* OnlineSession;
void UMatchmakingP2PWidget_Starter::NativeOnActivated()
{
// ...
UOnlineSession* BaseOnlineSession = GetWorld()->GetGameInstance()->GetOnlineSession();
if (!ensure(BaseOnlineSession))
{
return;
}

OnlineSession = Cast<UAccelByteWarsOnlineSessionBase>(BaseOnlineSession);
ensure(OnlineSession);
// ...
}
  • SessionInvite variable to store the current session invite of the match.
private:
// ...
TSharedPtr<FOnlineSessionInviteAccelByte> SessionInvite;
  • SelectedGameModeType variable to store the selected game mode type. Retrieved from the Quick Play UI.
private:
// ...
EGameModeType SelectedGameModeType;
void UMatchmakingP2PWidget_Starter::NativeOnActivated()
{
// ...
UAccelByteWarsBaseUI* BaseUIWidget = Cast<UAccelByteWarsGameInstance>(GetGameInstance())->GetBaseUIWidget();
for (const UCommonActivatableWidget* Widget : BaseUIWidget->Stacks[EBaseUIStackType::Menu]->GetWidgetList())
{
if (const UQuickPlayWidget* QuickPlayWidget = Cast<UQuickPlayWidget>(Widget))
{
SelectedGameModeType = QuickPlayWidget->GetSelectedGameModeType();
}
}
// ...
}
  • High-level states to represent each matchmaking state:

    • Request Sent: Loading screen indicating that a request has just been sent and is currently waiting for a response.
    • Finding Match: Loading screen with a cancel button indicating that its currently waiting to find a match.
    • Match Found: Loading screen indicating that the match has just been found and its about to move to the next step.
    • Canceling Match: Loading screen indicating that the cancel match request has just been sent and is currently waiting for a response.
    • Waiting for Player: A screen where the player can Join or Reject the found match.
    • Rejecting Match: Loading screen indicating that the reject match request has just been sent and is currently waiting for a response.
    • Joining Match: Loading screen indicating that the join session request has just been sent and is currently waiting for a response.
    • Session Joined: Loading screen indicating that the join session success response have just been received and its about to move to the next step.
    • Requesting Server: Loading screen indicating that the server have been requested and is currently waiting for the server to be ready.
    • Error: General error screen with a retry button.
  • A function to switch between those high-level states. The Matchmaking P2P UI has the following function:

// ...
protected:
// ...
void ChangeWidgetState(const EWidgetState State);
  • Low-level states which are the actual distinct widget components to represent the high-level states:

    • Loading: A screen indicating a task is in progress with text, subtext, and a cancel button.

    • Error: A screen indicating a task has failed with error text and a retry button.

    • Waiting for Player: A screen with countdown text, a join button, and a reject button.

      The state changes are possible using the Unreal Motion Graphics's Widget Switcher with three widget components. Here are the declarations of those components:

// ...
private:
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UWidgetSwitcher* Ws_Root;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UWidget* W_Loading;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UWidget* W_Error;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UWidget* W_WaitingForPlayer;

Loading state

Here, the player will see text, subtext (depending on the high-level state), and a cancel button that might be disabled depending on the high-level state.

Preview of the Loading state

Those components are declared in the Header file:

// ...
private:
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase* Btn_Cancel;
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UTextBlock* Tb_LoadingText;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UTextBlock* Tb_LoadingSubText;

Error state

In this state, the player will see error text and a retry button.

Preview of the Error state

Those components are declared in the Header file:

// ...
private:
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase* Btn_Retry;
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UTextBlock* Tb_ErrorText;

Waiting for Player state

This state is where the player can choose whether to join a match or reject it. It consist of a countdown, a join button, and a reject button.

Preview of the Waiting for Player state

// ...
private:
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase* Btn_Reject;

UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase* Btn_Join;
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UTextBlock* Tb_WaitingForPlayersCountdown;

The countdown will be used as an auto join function. If the player has not clicked anything when the countdown hits zero, it will activate an action that you will define later. Here are the countdown variables declaration in the Header file and its implementation in the CPP file:

// ...
private:
// ...
const float AutoJoinDelay = 10;
// ...
float AutoJoinCurrentCountdown = 0;

Ready the UI

In this section, you are going to prepare the MatchmakingP2PWidget_Starter widget class so it is displayed in the Quick Play widget.

  1. Declare some functions to be called to trigger matchmaking events. Open the MatchmakingP2PWidget_Starter Header file and add the following code:

    protected:
    UFUNCTION()
    void StartMatchmaking();

    UFUNCTION()
    void JoinSession();

    UFUNCTION()
    void CancelMatchmaking();

    UFUNCTION()
    void RejectSessionInvite();
  2. Open the MatchmakingP2PWidget_Starter CPP file and define the StartMatchmaking() function. Later, this function will trigger matchmaking, but for now, add the dummy function below. The W_Parent variable is a reference to the parent widget, which is the Quick Play widget. This way, you can switch the Quick Play's state.

    void UMatchmakingP2PWidget_Starter::StartMatchmaking()
    {
    if (OnlineSession->ValidateToStartMatchmaking.IsBound() &&
    !OnlineSession->ValidateToStartMatchmaking.Execute(SelectedGameModeType))
    {
    return;
    }

    // Reset stored invite
    SessionInvite = nullptr;

    // Reset auto join session countdown.
    AutoJoinCurrentCountdown = AutoJoinDelay;
    MatchFoundCurrentCountdown = MatchFoundDelay;
    SessionJoinedCurrentCountdown = SessionJoinedDelay;
    Tb_WaitingForPlayersCountdown->SetText(FText::FromString(FString::FromInt(AutoJoinCurrentCountdown)));

    ChangeWidgetState(EWidgetState::REQUEST_SENT);
    // ...
    }
  3. Define the CancelMatchmaking() function. Later, this function will trigger to cancel the matchmaking, but for now, add the following dummy function:

    void UMatchmakingP2PWidget_Starter::CancelMatchmaking()
    {
    ChangeWidgetState(EWidgetState::CANCELING_MATCH);
    // ...
    }
  4. Back to the MatchmakingP2PWidget_Starter Header file. Declare the functions below that will handle the callback when the matchmaking events complete.

    protected:
    // ...
    void OnStartMatchmakingComplete(FName SessionName, bool bSucceeded);
    void OnMatchmakingComplete(FName SessionName, bool bSucceeded);
    void OnSessionInviteReceived(
    const FUniqueNetId& UserId,
    const FUniqueNetId& FromId,
    const FOnlineSessionInviteAccelByte& Invite);
    void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);
    void OnSessionServerUpdateReceived(
    const FName SessionName,
    const FOnlineError& Error,
    const bool bHasClientTravelTriggered);

    void OnCancelMatchmakingComplete(FName SessionName, bool bSucceeded);
    void OnRejectSessionInviteComplete(bool bSucceeded);
  5. Open the MatchmakingP2PWidget_Starter CPP file and define the OnStartMatchmakingComplete() function. This function handles the callback when the start matchmaking process completes to switch the Quick Play widget to a relevant state. The OnlineSession variable is a reference to the current active online session. More about online sessions will be covered later.

    void UMatchmakingP2PWidget_Starter::OnStartMatchmakingComplete(FName SessionName, bool bSucceeded)
    {
    // Abort if not a game session.
    if (!SessionName.IsEqual(OnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
    {
    return;
    }

    if (bSucceeded)
    {
    ChangeWidgetState(EWidgetState::FINDING_MATCH);
    }
    else
    {
    Tb_ErrorText->SetText(TEXT_FAILED_FIND_MATCH);
    ChangeWidgetState(EWidgetState::ERROR);
    }
    }
  6. Define the OnMatchmakingComplete() function. This function handles the callback when the matchmaking process completes to switch the Quick Play widget to a relevant state.

    void UMatchmakingP2PWidget_Starter::OnMatchmakingComplete(FName SessionName, bool bSucceeded)
    {
    // Abort if not a game session.
    if (!SessionName.IsEqual(OnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
    {
    return;
    }

    if (bSucceeded)
    {
    ChangeWidgetState(EWidgetState::MATCH_FOUND);
    }
    else
    {
    Tb_ErrorText->SetText(TEXT_FAILED_FIND_MATCH);
    ChangeWidgetState(EWidgetState::ERROR);
    }
    }
  7. Define the OnCancelMatchmakingComplete() function. This function handles the callback when the cancel matchmaking process completes to switch the Quick Play widget to a relevant state.

    void UMatchmakingP2PWidget_Starter::OnCancelMatchmakingComplete(FName SessionName, bool bSucceeded)
    {
    // Abort if not a game session.
    if (!SessionName.IsEqual(OnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
    {
    return;
    }

    if (bSucceeded)
    {
    DeactivateWidget();
    }
    else
    {
    Tb_ErrorText->SetText(TEXT_FAILED_CANCEL_MATCH);
    ChangeWidgetState(EWidgetState::ERROR);
    }
    }
  8. Define the OnJoinSessionComplete() function. This function handles the callback when the join game session process completes to switch the Quick Play widget to a relevant state.

    void UMatchmakingP2PWidget_Starter::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
    {
    // Abort if not a game session.
    if (!SessionName.IsEqual(OnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
    {
    return;
    }

    if(Result == EOnJoinSessionCompleteResult::Success)
    {
    ChangeWidgetState(EWidgetState::SESSION_JOINED);
    }
    else
    {
    Tb_ErrorText->SetText(TEXT_FAILED_JOIN_MATCH);
    ChangeWidgetState(EWidgetState::ERROR);
    }
    }
  9. Define the OnCancelJoinSessionComplete() function. This function handles the callback when the join game session process is canceled and switches the Quick Play widget to a relevant state.

    void UMatchmakingP2PWidget_Starter::OnCancelJoinSessionComplete(FName SessionName, bool bSucceeded) const
    {
    // Abort if not a game session.
    if (!SessionName.IsEqual(OnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
    {
    return;
    }

    if (bSucceeded)
    {
    W_Parent->SwitchContent(UQuickPlayWidget::EContentType::SELECTGAMEMODE);
    }
    else
    {
    W_Parent->SetErrorMessage(TEXT_FAILED_CANCEL_MATCH, false);
    W_Parent->SwitchContent(UQuickPlayWidget::EContentType::ERROR);
    }
    }
  10. Bind the buttons to start matchmaking. In the NativeOnActivated() function, add the code below.

    void UMatchmakingP2PWidget_Starter::NativeOnActivated()
    {
    // ...
    Btn_Join->OnClicked().AddUObject(this, &ThisClass::JoinSession);
    Btn_Cancel->OnClicked().AddUObject(this, &ThisClass::CancelMatchmaking);
    Btn_Reject->OnClicked().AddUObject(this, &ThisClass::RejectSessionInvite);
    Btn_Retry->OnClicked().AddUObject(this, &ThisClass::StartMatchmaking);
    // ...
    }
  11. Unbind those buttons when the widget is not active. In the NativeOnDeactivated(), add the following code:

    void UMatchmakingP2PWidget_Starter::NativeOnDeactivated()
    {
    Btn_Join->OnClicked().RemoveAll(this);
    Btn_Cancel->OnClicked().RemoveAll(this);
    Btn_Reject->OnClicked().RemoveAll(this);
    Btn_Retry->OnClicked().RemoveAll(this);
    // ...
    Super::NativeOnDeactivated();}
  12. Build your project and open it in the Unreal Engine Editor. In the Editor, go to /Content/TutorialModules/Play/MatchmakingP2P/. There, you will find a data asset called DA_MatchmakingP2PEssentials. Open it and enable the Is Starter Mode Active. Then, save the data asset. This will activate the widgets so you can navigate through them when you play the game.

    Activate tutorial module data asset starter mode Unreal Byte Wars quick match peer-to-peer

  13. Play the game in the Editor. From the Main Menu, navigate to Play Online > Quick Play > Elimination > P2P. You will see the loading state and can cancel it to go back to the server selection state if the implementation was successful.

Resources