マッチセッション作成 UI を追加する - ピアツーピアで参加可能なセッション - (Unreal Engine モジュール)
To support both server modes at once (peer-to-peer (P2P) and dedicated server (DS)), separate the menu widget and the widget that connects to the session creation functionality that will be shown inside the menu widget. Those widgets are the Create Match Session widget and Create Match Session P2P widget.
In this tutorial, the widget that you'll prepare is the Create Match Session P2P.
About the Create Match Session menu
The Create Match Session menu is a widget in Byte Wars used to select game mode and server type for the session creation. It is available in the Resources section and consists of two parts:
CreateMatchSessionWidget
: The C++ class where most of our implementation will be.- Header file:
\Source\AccelByteWars\TutorialModules\MatchSessionEssentials\UI\CreateMatchSessionWidget.h
- CPP file:
\Source\AccelByteWars\TutorialModules\MatchSessionEssentials\UI\CreateMatchSessionWidget.cpp
- Header file:
W_CreateMatchSession
: A widget Blueprint class that was created and designed using Unreal Motion Graphics (UMG).- Widget Blueprint file:
\Content\TutorialModules\MatchSessionEssentials\UI\W_CreateMatchSession.uasset
- Widget Blueprint file:
The Create Match Session menu has four states:
- Select Game Mode: showing a selection of game modes, Elimination and Team Deathmatch.
- Select Network Type: showing a selection of network types. In this module, this will display P2P.
- Loading: showing the creation status and a cancel button.
- Error: showing a retry and back button.
The state changes are possible using a combination of Unreal Motion Graphics's Widget Switcher and the 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_ContentOuter;
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UAccelByteWarsWidgetSwitcher* Ws_Processing;
To switch between states, the Create Match Session widget has the following function:
void UCreateMatchSessionWidget::SwitchContent(const EContentType ContentType)
{
UWidget* Target = nullptr;
UWidget* FocusTarget = Btn_GameModeType_BackToCreateSession;
bool bShowBackButton = true;
switch (ContentType)
{
case EContentType::SELECT_GAMEMODE:
Target = W_SelectGameModeType;
CameraTargetY = 600.0f;
FocusTarget = Btn_Elimination;
break;
case EContentType::SELECT_NETWORKTYPE:
Target = W_SelectGameModeNetworkType;
CameraTargetY = 750.0f;
FocusTarget = W_SelectGameModeNetworkTypeButtonOuter->HasAnyChildren() ?
W_SelectGameModeNetworkTypeButtonOuter->GetChildAt(0) :
Btn_ServerType_BackToCreateSession;
break;
case EContentType::LOADING:
Target = W_Processing;
CameraTargetY = 825.0f;
Ws_Processing->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Loading);
FocusTarget = Ws_Processing;
bShowBackButton = false;
break;
case EContentType::ERROR:
Target = W_Processing;
Ws_Processing->SetWidgetState(EAccelByteWarsWidgetSwitcherState::Error);
CameraTargetY = 825.0f;
FocusTarget = Ws_Processing;
bShowBackButton = false;
break;
}
DesiredFocusTargetButton = FocusTarget;
FocusTarget->SetUserFocus(GetOwningPlayer());
Ws_ContentOuter->SetActiveWidget(Target);
Btn_GameModeType_BackToCreateSession->SetVisibility(bShowBackButton ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
RequestRefreshFocus();
}
Select Game Mode state
Here, the player will see two buttons to select one of the offered game modes, Elimination or Team Deathmatch. Upon clicking the button, the widget will store the game mode the player selected.
Those buttons are declared in the Header file.
private:
// ...
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase* Btn_Elimination;
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase* Btn_TeamDeathMatch;
The stored selected game mode can be accessed via this function:
public:
// ...
EGameModeType GetSelectedGameModeType() const { return SelectedGameModeType; }
Select Network Type state
This state is where the player can select which network type the session will use. By default, this state is empty. This is what you will attach your Create Match Session P2P widget to.
Loading state
This state is comprised of text that can be set to any message and a cancel button which can be enabled or disabled. To do so, call SetLoadingMessage
just before calling SwitchContent(EContentType::LOADING)
.
void UCreateMatchSessionWidget::SetLoadingMessage(const FText& Message, const bool bEnableCancelButton) const
{
Ws_Processing->LoadingMessage = Message;
Ws_Processing->bEnableCancelButton = bEnableCancelButton;
}
Error state
This state is comprised of text that can be set to any message and a retry button which can be enabled or disabled. To do so, call SetErrorMessage
just before calling SwitchContent(EContentType::Error)
.
void UCreateMatchSessionWidget::SetErrorMessage(const FText& Message, const bool bShowRetryButton) const
{
Ws_Processing->ErrorMessage = Message;
Ws_Processing->bShowRetryButtonOnError = bShowRetryButton;
}
About the Create Match Session P2P
The Create Match Session P2P widget has a button that will be spawned inside the Create Match Session menu. It is available in the Resources section and consists of two parts:
CreateMatchSessionP2PWidget_Starter
: The C++ class where most of our implementation will be.- Header file:
\Source\AccelByteWars\TutorialModules\Play\MatchSessionP2P\UI\CreateMatchSessionP2PWidget_Starter.h
- CPP file:
\Source\AccelByteWars\TutorialModules\MatchSessionP2P\UI\CreateMatchSessionP2PWidget_Starter.cpp
- Header file:
W_CreateMatchSession
: A widget Blueprint class that was created and designed using Unreal Motion Graphics (UMG).- Widget Blueprint file:
\Content\TutorialModules\MatchSessionP2P\UI\W_CreateMatchSession.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.
protected:
// ...
UPROPERTY()
UAccelByteWarsOnlineSessionBase* OnlineSession;
private:
UPROPERTY(BlueprintReadOnly, meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
UCommonButtonBase* Btn_StartMatchSessionP2P;
UPROPERTY()
UCreateMatchSessionWidget* W_Parent;
The codes that assigns the Online Session class and the parent widget is in the NativeOnActivated
function.
void UCreateMatchSessionP2PWidget_Starter::NativeOnActivated()
{
// ...
// Get online session
UOnlineSession* BaseOnlineSession = GetWorld()->GetGameInstance()->GetOnlineSession();
if (!ensure(BaseOnlineSession))
{
return;
}
OnlineSession = Cast<UAccelByteWarsOnlineSessionBase>(BaseOnlineSession);
ensure(OnlineSession);
// Get parent menu widget
W_Parent = GetFirstOccurenceOuter<UCreateMatchSessionWidget>();
if (!ensure(W_Parent))
{
return;
}
// ...
}
Here is a preview of the Create Match Session P2P widget:
Ready the Create Match Session P2P widget
The functionality that we need for the Create Match Session P2P widget is the session creation and the cancel joining session. You are going to prepare the widget for those functionalities.
Open the
CreateMatchSessionP2PWidget_Starter
Header file and add the following function declarations:protected:
UFUNCTION()
void CreateSession() const;
void OnCreateSessionComplete(FName SessionName, bool bSucceeded) const;Open the
CreateMatchSessionP2PWidget_Starter
CPP file and add the implementations below. In theCreateSession
, you're changing the Create Match Session menu widget state to its Loading state with the cancel button disabled since we can't cancel while the request is being sent. For theOnCreateSessionComplete
, if the request succeeds, we change the menu widget state to Loading with the cancel button enabled. If not, we change the menu widget state to Error.void UCreateMatchSessionP2PWidget_Starter::CreateSession() const
{
if (OnlineSession->ValidateToStartSession.IsBound() &&
!OnlineSession->ValidateToStartSession.Execute())
{
return;
}
// ...
W_Parent->SetLoadingMessage(TEXT_REQUESTING_SESSION_CREATION, false);
W_Parent->SwitchContent(UCreateMatchSessionWidget::EContentType::LOADING);
// ...
}void UCreateMatchSessionP2PWidget_Starter::OnCreateSessionComplete(FName SessionName, bool bSucceeded) const
{
// Abort if not a game session.
if (!SessionName.IsEqual(OnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
{
return;
}
if (bSucceeded)
{
W_Parent->SetLoadingMessage(TEXT_JOINING_SESSION, true);
W_Parent->SwitchContent(UCreateMatchSessionWidget::EContentType::LOADING);
}
else
{
W_Parent->SetErrorMessage(TEXT_FAILED_TO_CREATE_SESSION, true);
W_Parent->SwitchContent(UCreateMatchSessionWidget::EContentType::ERROR);
}
}Add code for the cancel joining functionality. Go back to the
CreateMatchSessionP2PWidget_Starter
Header file and add the following function declarations:protected:
// ...
UFUNCTION()
void CancelJoiningSession() const;
void OnCancelJoiningSessionComplete(FName SessionName, bool bSucceeded) const;Open the
CreateMatchSessionP2PWidget_Starter
CPP file and add the function implementations below. For theCancelJoiningSession
, you're changing the Create Match Session menu widget state to Loading with the cancel button disabled. For theOnCancelJoiningSessionComplete
, if successful, you're transitioning back to the Select Game Mode state. Otherwise, it transitions to the Error state.void UCreateMatchSessionP2PWidget_Starter::CancelJoiningSession() const
{
W_Parent->SetLoadingMessage(TEXT_LEAVING_SESSION, false);
W_Parent->SwitchContent(UCreateMatchSessionWidget::EContentType::LOADING);
// ...
}void UCreateMatchSessionP2PWidget_Starter::OnCancelJoiningSessionComplete(FName SessionName, bool bSucceeded) const
{
// Abort if not a game session.
if (!SessionName.IsEqual(OnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
{
return;
}
if (bSucceeded)
{
W_Parent->SwitchContent(UCreateMatchSessionWidget::EContentType::SELECT_GAMEMODE);
}
else
{
W_Parent->SetErrorMessage(TEXT_FAILED_TO_LEAVE_SESSION, false);
W_Parent->SwitchContent(UCreateMatchSessionWidget::EContentType::ERROR);
}
}Let the player know when the server is ready or when there's an error. In P2P, ideally, clients will be able to join the server directly after
OnJoinComplete
was called, but you are implementing this as a safety net. Go back to theCreateMatchSessionP2PWidget_Starter
Header file and add this function declaration:protected:
// ...
void OnSessionServerUpdateReceived(
const FName SessionName,
const FOnlineError& Error,
const bool bHasClientTravelTriggered) const;Open the
CreateMatchSessionP2PWidget_Starter
CPP file and add the function implementation below. For this function, if successful, you simply change the loading message. Otherwise, show the error.void UCreateMatchSessionP2PWidget_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)
{
// Keep showing the loading state until the client travels to the P2P host.
W_Parent->SetLoadingMessage(TEXT_JOINING_SESSION, true);
W_Parent->SwitchContent(UCreateMatchSessionWidget::EContentType::LOADING);
}
else if (!bHasClientTravelTriggered && !Error.bSucceeded)
{
W_Parent->SetErrorMessage(TEXT_FAILED_TO_JOIN_SESSION, false);
W_Parent->SwitchContent(UCreateMatchSessionWidget::EContentType::ERROR);
}
}Now that you have all the functions declared and implemented, bind the widget components to those functions. In the
CreateMatchSessionP2PWidget_Starter
CPP file, navigate to theNativeOnActivated
function and add the following code:void UCreateMatchSessionP2PWidget_Starter::NativeOnActivated()
{
// ...
Btn_StartMatchSessionP2P->OnClicked().AddUObject(this, &ThisClass::CreateSession);
}With the binding done, implement code to unbind it when the widget is no longer in use. Still in the CPP file, navigate to
NativeOnDeactivated
and add the following code:void UCreateMatchSessionP2PWidget_Starter::NativeOnDeactivated()
{
// ...
Btn_StartMatchSessionP2P->OnClicked().RemoveAll(this);
W_Parent->GetProcessingWidgetComponent()->OnRetryClicked.RemoveAll(this);
W_Parent->GetProcessingWidgetComponent()->OnCancelClicked.RemoveAll(this);
}Build the project and open it in the Unreal Editor.
In the Unreal Editor, from the Content Browser, navigate to
Content\TutorialModules\Play\MatchSessionP2P\UI\
and openW_CreateMatchSessionP2P_Starter
. Make sure that all widgets are bound properly in the Bind Widgets tab and the Parent class is set toCreateMatchSessionP2PWidget_Starter
.Open
Content\TutorialModules\Play\MatchSessionP2P\DA_MatchSessionP2PEssentials.uasset
and enable theIs Starter Mode Active
. Save the Data Asset.Play the game in the editor. Navigate to Online Play > Play Online > Create Match Session. The starter UI will be shown if implemented correctly.
Resources
The files used in this tutorial section are available in the Unreal Byte Wars GitHub repository.
- AccelByteWars/Content/TutorialModules/Play/MatchSessionP2P/UI/W_CreateMatchSessionP2P_Starter.uasset
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchSessionP2P/UI/CreateMatchSessionP2PWidget_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchSessionP2P/UI/CreateMatchSessionP2PWidget_Starter.cpp
- AccelByteWars/Content/TutorialModules/Play/MatchSessionP2P/UI/DA_MatchSessionP2PEssentials.uasset