Unreal Engine Module - Create a Joinable Session Using a Dedicated Server - add Browse Match menu
This tutorial isn't applicable to the AccelByte Gaming Service (AGS) Starter tier. It requires the AccelByte Multiplayer Server (AMS) or Armada, which isn't currently supported on AGS Starter.
Just like the Create Match Session UI, to simultaneously support both server modes, Peer to Peer (P2P) and Dedicated Server (DS), you must separate the menu widget and the widget that actually connects to the browse session functionality, which will be shown inside the menu widget. Those widgets, in order, are the Browse Match widget and Browse Match DS widget.
The widget that you're going to prepare is the Browse Match DS.
About the Browse Match menu
Browse Match menu is a widget in Byte Wars used to display all the sessions that are created from the Browse Match menu and a way to join them.
The Browse Match menu consists of two parts:
- The C++ class
BrowseMatchWidget
, where most of our implementation will be in.- Header file:
\Source\AccelByteWars\TutorialModules\MatchSessionEssentials\UI\BrowseMatchWidget.h
- CPP file:
\Source\AccelByteWars\TutorialModules\MatchSessionEssentials\UI\BrowseMatchWidget.cpp
- Header file:
- The widget blueprint class
W_BrowseMatch
that was created and designed using Unreal Motion Graphics (UMG).- Widget Blueprint file:
\Content\TutorialModules\MatchSessionEssentials\UI\W_BrowseMatch.uasset
- Widget Blueprint file:
Browse Match menu has six states:
- Browse Loading : showing a loading status for the browse session.
- Browse Empty : showing a text stating that there's currently no session available.
- Browse Not Empty : showing a list of all the available sessions and a join button for each of them.
- Browse Error : showing an error text for when the browse session returns an error.
- Join Loading : showing the current joining status when the player click any join button.
- Join Error : showing an error text for when the join session returns an error.
The state changes are possible using a combination of Unreal Motion Graphic's Widget Switcher and our AccelByteWars Widget Switcher. The AccelByteWars Widget Switcher is a custom Widget Switcher with predefined states: empty, loading, error, and success. Here are the declarations of the mentioned components in the header file.
private:
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UWidgetSwitcher- Ws_Root;
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UAccelByteWarsWidgetSwitcher- Ws_Browse_Content;
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UAccelByteWarsWidgetSwitcher- Ws_Joining;
To switch between states, the Browse Match widget has the following function.
void UBrowseMatchWidget::SwitchContent(const EContentType Type)
{
bool bBrowseMenu = false;
EAccelByteWarsWidgetSwitcherState State = EAccelByteWarsWidgetSwitcherState::Loading;
bool bJoin_ShowBackButton = false;
UWidget- FocusTarget = Btn_Back;
switch (Type)
{
case EContentType::BROWSE_LOADING:
bBrowseMenu = true;
State = EAccelByteWarsWidgetSwitcherState::Loading;
CameraTargetY = 600.0f;
FocusTarget = Btn_Back;
break;
case EContentType::BROWSE_EMPTY:
bBrowseMenu = true;
State = EAccelByteWarsWidgetSwitcherState::Empty;
CameraTargetY = 600.0f;
FocusTarget = Btn_Back;
break;
case EContentType::BROWSE_NOT_EMPTY:
bBrowseMenu = true;
State = EAccelByteWarsWidgetSwitcherState::Not_Empty;
CameraTargetY = 600.0f;
FocusTarget = Lv_Sessions;
break;
case EContentType::BROWSE_ERROR:
bBrowseMenu = true;
State = EAccelByteWarsWidgetSwitcherState::Error;
CameraTargetY = 600.0f;
FocusTarget = W_ActionButtonsOuter->HasAnyChildren() ? W_ActionButtonsOuter->GetChildAt(0) : Btn_Back;
break;
case EContentType::JOIN_LOADING:
bBrowseMenu = false;
State = EAccelByteWarsWidgetSwitcherState::Loading;
bJoin_ShowBackButton = false;
CameraTargetY = 750.0f;
FocusTarget = Ws_Joining;
break;
case EContentType::JOIN_ERROR:
bBrowseMenu = false;
State = EAccelByteWarsWidgetSwitcherState::Error;
bJoin_ShowBackButton = true;
CameraTargetY = 750.0f;
FocusTarget = Btn_Joining_Back;
break;
}
FocusTarget->SetUserFocus(GetOwningPlayer());
Ws_Root->SetActiveWidget(bBrowseMenu ? W_Browse_Outer : W_Joining_Outer);
if (bBrowseMenu)
{
Ws_Browse_Content->SetWidgetState(State);
}
else
{
Btn_Joining_Back->SetVisibility(bJoin_ShowBackButton ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
Ws_Joining->SetWidgetState(State);
}
// Set FTUE
if (Type == EContentType::BROWSE_EMPTY || Type == EContentType::BROWSE_NOT_EMPTY)
{
InitializeFTEUDialogues(true);
}
else
{
DeinitializeFTUEDialogues();
}
}
Take a look at the BrowseMatchWidget
header file. You will see a variable called W_ActionButtonsOuter
. This is a Panel Widget that will house the Browse Match DS widget that you will prepare later in this submodule.
private:
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UPanelWidget- W_ActionButtonsOuter;
Browse Loading state
This state consists of a text that can be set to any message. To do so, call SetLoadingMessage
with the const bool bBrowse
parameter as true
just before calling the SwitchContent(EContentType::BROWSE_LOADING)
.
void UBrowseMatchWidget::SetLoadingMessage(const FText& Text, const bool bBrowse, const bool bEnableCancelButton) const
{
if (bBrowse)
{
Ws_Browse_Content->LoadingMessage = Text;
}
else
{
Ws_Joining->LoadingMessage = Text;
Ws_Joining->bEnableCancelButton = bEnableCancelButton;
}
}
Browse Empty state
This state of a static text stating that there's no session currently available.
Browse Not Empty state
Here, the player will see a list of all available sessions and can join one of them. This consists solely of a List View, the pointers of which can be retrieved using GetListViewWidgetComponent
. The UObject
called UMatchSessionData
can be found in the AccelByteWarsOnlineSessionModels.h
to pass data to the entry widget of the List View.
public:
UListView- GetListViewWidgetComponent() const { return Lv_Sessions; }
private:
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UListView- Lv_Sessions;
Browse Error state
This sate is comprised of an error message which can be set to any message. To do so, call SetErrorMessage
with the const bool bBrowse
parameter as true
just before calling SwitchContent(EContentType::BROWSE_ERROR)
.
void UBrowseMatchWidget::SetErrorMessage(const FText& Text, const bool bBrowse) const
{
if (bBrowse)
{
Ws_Browse_Content->ErrorMessage = Text;
}
else
{
Ws_Joining->ErrorMessage = Text;
}
}
Join Loading state
This state consists of a cancel button and a text that can be set to any message. To do so, call SetLoadingMessage
, the same way as the Browse Loading state, except set the const bool bBrowse
parameter as false
just before calling the SwitchContent(EContentType::BROWSE_LOADING)
.
Join Error state
Comprised of an error message which can be set to any message. To do so, call SetErrorMessage
with the const bool bBrowse
parameter as false
just before calling SwitchContent(EContentType::JOIN_ERROR)
.
About the Browse Match DS
The Browse Match DS widget consists of only one button that will be spawned inside the Browse Match menu. This widget will be the one responsible to control the Browse Match menu widget.
The Browse Match DS widget consists of two parts:
- The C++ class
BrowseMatchDSWidget_Starter
where most of our implementation will be in.- Header file:
\Source\AccelByteWars\TutorialModules\Play\MatchSessionDS\UI\BrowseMatchDSWidget_Starter.h
- CPP file:
\Source\AccelByteWars\TutorialModules\MatchSessionDS\UI\BrowseMatchDSWidget_Starter.cpp
- Header file:
- The widget blueprint class
W_BrowseMatchDS_Starter
that was created and designed using Unreal Motion Graphics (UMG).- Widget Blueprint file:
\Content\TutorialModules\MatchSessionDS\UI\W_BrowseMatchDS_Starter.uasset
- Widget Blueprint file:
In the header file, you will see OnlineSession
which is your gateway to the session functionalities, the button itself, and a pointer to the parent widget.
private:
UPROPERTY()
UAccelByteWarsOnlineSessionBase- OnlineSession;
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase- Btn_Refresh;
UPROPERTY()
UBrowseMatchWidget- W_Parent;
The codes that assigned the Online Session class and to the parent widget, is seen on the NativeOnActivated
function.
void UBrowseMatchDSWidget_Starter::NativeOnActivated()
{
Super::NativeOnActivated();
UOnlineSession- BaseOnlineSession = GetWorld()->GetGameInstance()->GetOnlineSession();
if (!ensure(BaseOnlineSession))
{
return;
}
OnlineSession = Cast<UAccelByteWarsOnlineSessionBase>(BaseOnlineSession);
ensure(OnlineSession);
W_Parent = GetFirstOccurenceOuter<UBrowseMatchWidget>();
if (!ensure(W_Parent))
{
return;
}
...
}
Here is a preview of the Browse Match DS widget.
Ready the Browse Match DS widget
The functionalities needed for the Browse Match DS widget are browse session, cancel joining session, and join session. You are going to prepare the widget for those functionalities.
Open the
BrowseMatchDSWidget_Starter
header file and add the following function declarations.protected:
void FindSessions(const bool bForce) const;
void OnFindSessionComplete(const TArray<FMatchSessionEssentialInfo> SessionEssentialsInfo, bool bSucceeded);Open the
BrowseMatchDSWidget_Starter
CPP file and add these implementations. ForFindSessions
, change the Browse Match menu widget state to its Browse Loading state. If theOnCreateSessionComplete
succeeds and theSessionEssentialsInfo
array is not empty, transition the menu widget state to Browse Not Empty and populate the parent widget List View. If it succeeds but theSessionEssentialsInfo
is empty, transition to Browse Empty state. Otherwise, transition to Browse Error state.void UBrowseMatchDSWidget_Starter::FindSessions(const bool bForce) const
{
W_Parent->SetLoadingMessage(TEXT_LOADING_DATA, true, false);
W_Parent->SwitchContent(UBrowseMatchWidget::EContentType::BROWSE_LOADING);
Btn_Refresh->SetIsEnabled(false);
// TODO: Call the find session through Online Session
}
void UBrowseMatchDSWidget_Starter::OnFindSessionComplete(const TArray<FMatchSessionEssentialInfo> SessionEssentialsInfo, bool bSucceeded)
{
Btn_Refresh->SetIsEnabled(true);
if (bSucceeded)
{
if (SessionEssentialsInfo.IsEmpty())
{
W_Parent->SwitchContent(UBrowseMatchWidget::EContentType::BROWSE_EMPTY);
}
else
{
TArray<UMatchSessionData*> MatchSessionDatas;
for (const FMatchSessionEssentialInfo& SessionEssentialInfo : SessionEssentialsInfo)
{
UMatchSessionData- MatchSessionData = NewObject<UMatchSessionData>();
MatchSessionData->SessionEssentialInfo = SessionEssentialInfo;
MatchSessionData->OnJoinButtonClicked.BindUObject(this, &ThisClass::JoinSession);
MatchSessionDatas.Add(MatchSessionData);
}
W_Parent->GetListViewWidgetComponent()->ClearListItems();
W_Parent->GetListViewWidgetComponent()->SetListItems<UMatchSessionData*>(MatchSessionDatas);
W_Parent->SwitchContent(UBrowseMatchWidget::EContentType::BROWSE_NOT_EMPTY);
}
}
else
{
W_Parent->SetErrorMessage(TEXT_FAILED_TO_RETRIEVE_DATA, true);
W_Parent->SwitchContent(UBrowseMatchWidget::EContentType::BROWSE_ERROR);
}
}For the cancel joining functionality, go back to
BrowseMatchDSWidget_Starter
header file and add the following function declarations.protected:
void CancelJoining() const;
void OnCancelJoiningComplete(FName SessionName, bool bSucceeded) const;For the implementation, open the
BrowseMatchDSWidget_Starter
CPP file and add these function implementations. For theCancelJoiningSession
, change the menu state to "Join Loading" with the cancel button disabled. If theOnCancelJoiningSessionComplete
succeeds, transition back to the "Browse Not Empty" state. Otherwise, transition to "Join Error" state.void UBrowseMatchDSWidget_Starter::CancelJoining() const
{
W_Parent->SetLoadingMessage(TEXT_LEAVING_SESSION, false, false);
W_Parent->SwitchContent(UBrowseMatchWidget::EContentType::JOIN_LOADING);
// TODO: Call the cancel joining through Online Session
}
void UBrowseMatchDSWidget_Starter::OnCancelJoiningComplete(FName SessionName, bool bSucceeded) const
{
// Abort if not a game session.
if (!SessionName.IsEqual(OnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
{
return;
}
if (bSucceeded)
{
W_Parent->SwitchContent(UBrowseMatchWidget::EContentType::BROWSE_NOT_EMPTY);
}
else
{
W_Parent->SetErrorMessage(TEXT_FAILED_TO_LEAVE_SESSION, false);
W_Parent->SwitchContent(UBrowseMatchWidget::EContentType::JOIN_ERROR);
}
}Now, implement the UI logic for join session. Go back to
BrowseMatchDSWidget_Starter
header file and add the following function declarations.protected:
void JoinSession(const FOnlineSessionSearchResult&SessionSearchResult) const;
void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type CompletionType) const;Open
BrowseMatchDSWidget_Starter
CPP file and add the following implementations.JoinSession
will transition the menu widget to its Join Loading state.OnJoinSessionComplete
, if succeeded, will basically do nothing. Otherwise, transition to Join Error and displays the error type in the error message.void UBrowseMatchDSWidget_Starter::JoinSession(const FOnlineSessionSearchResult& SessionSearchResult) const
{
if (OnlineSession->ValidateToJoinSession.IsBound() &&
!OnlineSession->ValidateToJoinSession.Execute(SessionSearchResult))
{
return;
}
W_Parent->SetLoadingMessage(TEXT_JOINING_SESSION, false, false);
W_Parent->SwitchContent(UBrowseMatchWidget::EContentType::JOIN_LOADING);
// TODO: Call the join session through Online Session
}
void UBrowseMatchDSWidget_Starter::OnJoinSessionComplete(
FName SessionName,
EOnJoinSessionCompleteResult::Type CompletionType) const
{
// Abort if not a game session.
if (!SessionName.IsEqual(OnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
{
return;
}
bool bSucceeded;
FText ErrorMessage;
switch (CompletionType)
{
case EOnJoinSessionCompleteResult::Success:
bSucceeded = true;
ErrorMessage = FText();
break;
case EOnJoinSessionCompleteResult::SessionIsFull:
bSucceeded = false;
ErrorMessage = TEXT_FAILED_SESSION_FULL;
break;
case EOnJoinSessionCompleteResult::SessionDoesNotExist:
bSucceeded = false;
ErrorMessage = TEXT_FAILED_SESSION_NULL;
break;
case EOnJoinSessionCompleteResult::CouldNotRetrieveAddress:
bSucceeded = false;
ErrorMessage = TEXT_FAILED_TO_JOIN_SESSION;
break;
case EOnJoinSessionCompleteResult::AlreadyInSession:
bSucceeded = false;
ErrorMessage = TEXT_FAILED_ALREADY_IN_SESSION;
break;
case EOnJoinSessionCompleteResult::UnknownError:
bSucceeded = false;
ErrorMessage = TEXT_FAILED_TO_JOIN_SESSION;
break;
default:
bSucceeded = true;
ErrorMessage = FText();
}
W_Parent->SetErrorMessage(ErrorMessage, false);
W_Parent->SetLoadingMessage(TEXT_JOINING_SESSION, false, true);
W_Parent->SwitchContent(bSucceeded ?
UBrowseMatchWidget::EContentType::JOIN_LOADING :
UBrowseMatchWidget::EContentType::JOIN_ERROR);
}You can also let the players know when the DS is ready or when there's an error. Go back to the
BrowseMatchDSWidget_Starter
header file and add this function declaration.protected:
void OnSessionServerUpdateReceived(
const FName SessionName,
const FOnlineError& Error,
const bool bHasClientTravelTriggered) const;Open the
BrowseMatchDSWidget_Starter
CPP file and add this function implementation. If this function succeeds, change the loading message. Otherwise, show the error.void UBrowseMatchDSWidget_Starter::OnSessionServerUpdateReceived(
const FName SessionName,
const FOnlineError& Error,
const bool bHasClientTravelTriggered) const
{
// Abort if not a game session.
if (!SessionName.IsEqual(OnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
{
return;
}
if (Error.bSucceeded && !bHasClientTravelTriggered)
{
// waiting for further update
W_Parent->SetLoadingMessage(TEXT_JOINING_SESSION, false, false);
W_Parent->SwitchContent(UBrowseMatchWidget::EContentType::JOIN_LOADING);
}
else if (!bHasClientTravelTriggered && !Error.bSucceeded)
{
W_Parent->SetErrorMessage(TEXT_FAILED_TO_JOIN_SESSION, false);
W_Parent->SwitchContent(UBrowseMatchWidget::EContentType::JOIN_ERROR);
}
}With all the functions declared and implemented, bind the widget components to those functions. In the
BrowseMatchDSWidget_Starter
CPP file, navigate to theNativeOnActivated
function and add the highlighted lines in the following codes. Notice that theFindSessions
is called in this function. This will trigger that function every time the player open the menu widget.void UBrowseMatchDSWidget_Starter::NativeOnActivated()
{
...
// TODO: Add your Online Session delegates setup here
Btn_Refresh->OnClicked().AddUObject(this, &ThisClass::FindSessions, true);
W_Parent->GetJoiningWidgetComponent()->OnCancelClicked.AddUObject(this, &ThisClass::CancelJoining);
FindSessions(false);
}With the binding done, time to implement codes to unbind it when the widget is no longer in use. Still in the CPP file, navigate to
NativeOnDeactivated
and add the highlighted lines in the following codes.void UBrowseMatchDSWidget_Starter::NativeOnDeactivated()
{
...
// TODO: Add your Online Session delegates cleanup here
Btn_Refresh->OnClicked().RemoveAll(this);
W_Parent->GetJoiningWidgetComponent()->OnCancelClicked.RemoveAll(this);
}Now, build the
AccelByteWars
project and open it with Unreal Editor once it's done building.In the Unreal Editor, from the Content Browser, navigate to
Content\TutorialModules\Play\MatchSessionDS\UI\
and openW_BrowseMatchDS_Starter
. Make sure that all widgets are bound properly in the Bind Widgets tab, stacked just beside the Hierarchy tab by default, and the Parent class is set toBrowseMatchDSWidget_Starter
.To test out the Browse Match DS widget, open
Content\TutorialModules\Play\MatchSessionDS\DA_MatchSessionDSEssentials.uasset
and enable theIs Starter Mode Active
. Save the Data Asset.Click Play in the editor, navigate to Online Play > Play Online > Browse Match. The starter UI appears.
Congratulations! You have finished setting up the Browse Match DS widget.
Resources
- The files used in this tutorial section are available in the Byte Wars GitHub repository.
- AccelByteWars/Content/TutorialModules/Play/MatchSessionDS/UI/W_BrowseMatchDS_Starter.uasset
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchSessionDS/UI/BrowseMatchDSWidget_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchSessionDS/UI/BrowseMatchDSWidget_Starter.cpp
- AccelByteWars/Content/TutorialModules/Play/MatchSessionDS/UI/DA_MatchSessionDSEssentials.uasset