サブシステムを実装する - ピアツーピアでのクイックマッチ - (Unreal Engine モジュール)
注釈:本資料はAI技術を用いて翻訳されています。
ピアツーピアマッチメイキングフロー
以下の図を見て、ピアツーピア(P2P)を使用したマッチメイキングの仕組みを理解してください。
ゲーム設定
ピアツーピア(P2P)を有効にするために、AccelByte Network Utilitiesプラグインを使用しています。P2Pが意図したとおりに動作するように設定する必要があるいくつかの設定があります。これらは /Config/DefaultEngine.ini にある設定です。
[AccelByteNetworkUtilities]
UseTurnManager=true
bNonSeamlessTravelUseNewConnection=true
HostCheckTimeout=5
[/Script/AccelByteNetworkUtilities.IpNetDriverAccelByte]
NetConnectionClassName=AccelByteNetworkUtilities.IpConnectionAccelByte
AllowDownloads=false
チュートリアルに従っている場合、これについて何も変更する必要はありません。これは、独自のゲームに統合する場合の情報セクションです。
ゲームクライアントのオンラインセッションを設定する
マッチメイキングは、ゲームセッションのマッチを見つけるプロセスです。ゲームセッションが見つかると、プレイヤーはゲームセッションに参加し、P2Pサーバー(ホストまたはリッスンサーバーとも呼ばれます)に移動します。ゲームセッションの仕組みの基本については、セッション入門モジュールを参照してください。
このチュートリアルでは、MatchmakingP2POnlineSession_Starter という名前のオンラインセッションクラスを使用します。このオンラインセッションは、ゲームクライアントがマッチメイキングを実行するために使用されます。このクラスはリソースセクションで利用可能で、以下のファイルで構成されています。
- ヘッダーファイル:
/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2POnlineSession_Starter.h - CPPファイル:
/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2POnlineSession_Starter.cpp
このクラスには、チュートリアルに従うために使用できる事前定義された属性と関数がいくつかあります。提供されているものを見てみましょう。
-
セッション入門と同様に、このクラスはセッションインターフェースを使用して、マッチメイキングを含むセッション関連の関数を実行します。
FOnlineSessionV2AccelBytePtr UAccelByteWarsOnlineSessionBase::GetABSessionInt()
{
return StaticCastSharedPtr<FOnlineSessionV2AccelByte>(GetSessionInt());
} -
ヘッダーファイルには、以前に作成したマッチプールIDを定義する以下のマップがあります。ご覧のとおり、IDは管理ポータルで設定したものとまったく同じ名前です。これらは、正しいマッチプールにマッチメイキングをリクエストするために必要です。
private:
// ...
const TMap<TPair<EGameModeType, EGameStyle>, FString> MatchPoolIds = {
{{EGameModeType::FFA, EGameStyle::Zen}, "unreal-elimination-p2p"},
{{EGameModeType::TDM, EGameStyle::Zen}, "unreal-teamdeathmatch-p2p"},
{{EGameModeType::FFA, EGameStyle::Frenzy}, "unreal-frenzy-elimination-p2p"},
{{EGameModeType::TDM, EGameStyle::Frenzy}, "unreal-frenzy-teamdeathmatch-p2p"}
}; -
ヘッダーファイルには、マッチプールIDをゲームがサポートするゲームモードに変換する以下のマップもあります。ゲームサーバーは、このマップを使用して、ゲームモードに基づいてゲームプレイを設定します。
public:
// ...
const TMap<FString, FString> TargetGameModeMap = {
{"unreal-elimination-p2p", "ELIMINATION-P2P"},
{"unreal-teamdeathmatch-p2p", "TEAMDEATHMATCH-P2P"},
{"unreal-frenzy-elimination-p2p", "FRENZY-ELIMINATION-P2P"},
{"unreal-frenzy-teamdeathmatch-p2p", "FRENZY-TEAMDEATHMATCH-P2P"}
};
マッチメイキングを開始する
このセクションでは、マッチメイキングを開始する関数を実装します。
-
MatchmakingP2POnlineSession_Starterヘッダーファイルを開き、マッチメイキングを開始する関数を宣言します。public:
// ...
virtual void StartMatchmaking(
const APlayerController* PC,
const FName& SessionName,
const EGameModeNetworkType NetworkType,
const EGameModeType GameModeType, const EGameStyle GameStyle) override; -
同じファイルで、マッチメイキング開始プロセスが完了したときに呼び出されるコールバック関数を宣言します。
protected:
// ...
virtual void OnStartMatchmakingComplete(
FName SessionName,
const FOnlineError& ErrorDetails,
const FSessionMatchmakingResults& Results) override; -
マッチメイキング開始プロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートを使用して、後でいくつかのイベントをバインドできます。特に、ユーザーインターフェース(UI)をマッチメイキング実装に接続するときに使用します。
public:
// ...
virtual FOnMatchmakingResponse* GetOnStartMatchmakingCompleteDelegates() override
{
return &OnStartMatchmakingCompleteDelegates;
}private:
// ...
FOnMatchmakingResponse OnStartMatchmakingCompleteDelegates; -
マッチメイキングを開始するには、残っているゲームセッションから退出する必要があります。それを処理するために、以下の関数を宣言します。
private:
// ...
void OnLeaveSessionForReMatchmakingComplete(
FName SessionName,
bool bSucceeded,
const int32 LocalUserNum,
const EGameModeType GameModeType, const EGameStyle GameStyle);
FDelegateHandle OnLeaveSessionForReMatchmakingCompleteDelegateHandle; -
上記で宣言した関数を定義します。
MatchmakingP2POnlineSession_StarterCPPファイルを開き、StartMatchmaking()関数から始めます。この関数では、必要なマッチメイキングの種類を定義するためにマッチメイキングハンドルを構築します。マッチプールをマッチメイキングハンドルに設定して、管理ポータルで設定されたマッチプールに基づいてマッチメイキングを開始します。また、マッチメイキングを開始する前に、プレイヤーがすでにゲームセッションにいるかどうかを確認します。いる場合は、最初にゲームセッションから退出してから、マッチメイキングを再試行します。これはOnLeaveSessionForReMatchmakingComplete()関数で処理されます。マッチメイキングが正常に開始されると、OnStartMatchmakingComplete()関数を呼び出します。void UMatchmakingP2POnlineSession_Starter::StartMatchmaking(
const APlayerController* PC,
const FName& SessionName,
const EGameModeNetworkType NetworkType,
const EGameModeType GameModeType, const EGameStyle GameStyle)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
// Abort if the session interface is invalid.
if (!ensure(GetSessionInt()))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Session Interface is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
}));
return;
}
// If the player is already in a session, then leave session first.
if (GetSession(SessionName))
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("Already in session. Leaving session first."))
if (OnLeaveSessionForReMatchmakingCompleteDelegateHandle.IsValid())
{
GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForReMatchmakingCompleteDelegateHandle);
OnLeaveSessionForReMatchmakingCompleteDelegateHandle.Reset();
}
OnLeaveSessionForReMatchmakingCompleteDelegateHandle = GetOnLeaveSessionCompleteDelegates()->AddUObject(
this,
&ThisClass::OnLeaveSessionForReMatchmakingComplete,
GetLocalUserNumFromPlayerController(PC),
GameModeType, GameStyle);
LeaveSession(SessionName);
return;
}
const FUniqueNetIdPtr PlayerNetId = GetLocalPlayerUniqueNetId(PC);
if (!ensure(PlayerNetId.IsValid()))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Player UniqueNetId is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
}));
return;
}
// Get match pool ID based on game mode type
FString MatchPoolId = MatchPoolIds[{GameModeType, GameStyle}];
const FString GameModeCode = TargetGameModeMap[MatchPoolId];
// Override match pool ID if applicable.
if (!UTutorialModuleOnlineUtility::GetMatchPoolP2POverride().IsEmpty())
{
MatchPoolId = UTutorialModuleOnlineUtility::GetMatchPoolP2POverride();
}
// Set up matchmaking search handle, it will be used to store session search results.
TSharedRef<FOnlineSessionSearch> MatchmakingSearchHandle = MakeShared<FOnlineSessionSearch>();
MatchmakingSearchHandle->QuerySettings.Set(SETTING_SESSION_MATCHPOOL, MatchPoolId, EOnlineComparisonOp::Equals);
MatchmakingSearchHandle->QuerySettings.Set(GAMESETUP_GameModeCode, GameModeCode, EOnlineComparisonOp::Equals);
if (!GetSessionInt()->StartMatchmaking(
USER_ID_TO_MATCHMAKING_USER_ARRAY(PlayerNetId.ToSharedRef()),
SessionName,
FOnlineSessionSettings(),
MatchmakingSearchHandle,
FOnStartMatchmakingComplete::CreateUObject(this, &ThisClass::OnStartMatchmakingComplete)))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Failed executing"))
// Failed to start matchmaking.
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
}));
}
} -
OnStartMatchmakingComplete()関数を定義します。この関数は、作成したデリゲートをトリガーして、開始されたマッチメイキングが成功したかどうかを通知します。void UMatchmakingP2POnlineSession_Starter::OnStartMatchmakingComplete(
FName SessionName,
const FOnlineError& ErrorDetails,
const FSessionMatchmakingResults& Results)
{
UE_LOG_MATCHMAKINGP2P(
Log,
TEXT("succeeded: %s | error: (%s) %s"),
*FString(ErrorDetails.bSucceeded ? "TRUE": "FALSE"),
*ErrorDetails.ErrorCode, *ErrorDetails.ErrorMessage.ToString())
OnStartMatchmakingCompleteDelegates.Broadcast(SessionName, ErrorDetails.bSucceeded);
} -
OnLeaveSessionForReMatchmakingComplete()関数を定義します。この関数は、ゲームセッションから退出した後に呼び出されます。その後、マッチメイキングの開始を再試行することが有効かどうかを確認します。void UMatchmakingP2POnlineSession_Starter::OnLeaveSessionForReMatchmakingComplete(
FName SessionName,
bool bSucceeded,
const int32 LocalUserNum,
const EGameModeType GameModeType, const EGameStyle GameStyle)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForReMatchmakingCompleteDelegateHandle);
if (bSucceeded)
{
// Retry matchmaking.
const APlayerController* PC = GetPlayerControllerByLocalUserNum(LocalUserNum);
if (!ensure(PC))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("PlayerController is null."));
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
return;
}
StartMatchmaking(PC, SessionName, EGameModeNetworkType::P2P, GameModeType, GameStyle);
}
else
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Is not a game session."));
OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
}
} -
マッチメイキングが完了したときの処理が必要です。
MatchmakingP2POnlineSession_Starterヘッダーファイルを開き、以下の関数を宣言します。protected:
// ...
virtual void OnMatchmakingComplete(FName SessionName, bool bSucceeded) override; -
マッチメイキングが完了したときに呼び出されるデリゲートを宣言します。このデリゲートを使用して、後でいくつかのイベントをバインドできます。特に、UIをマッチメイキング実装に接続するときに使用します。
public:
// ...
virtual FOnMatchmakingResponse* GetOnMatchmakingCompleteDelegates() override
{
return &OnMatchmakingCompleteDelegates;
}private:
// ...
FOnMatchmakingResponse OnMatchmakingCompleteDelegates; -
MatchmakingP2POnlineSession_StarterCPPファイルを開き、OnMatchmakingComplete()関数を定義します。この関数は、マッチメイキングが完了した後にゲームセッションが見つかったかどうかを確認します。見つかった場合は、ゲームセッションに参加します。また、以前に作成したデリゲートを呼び出して、マッチメイキングプロセスが完了したことをゲームに通知します。void UMatchmakingP2POnlineSession_Starter::OnMatchmakingComplete(FName SessionName, bool bSucceeded)
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("succeeded: %s"), *FString(bSucceeded ? "TRUE": "FALSE"))
const TSharedPtr<FOnlineSessionSearchAccelByte> CurrentMatchmakingSearchHandle = GetABSessionInt()->GetCurrentMatchmakingSearchHandle();
if (!bSucceeded ||
!ensure(CurrentMatchmakingSearchHandle.IsValid()) /*This might happen when matchmaking finishes right as it’s about to be canceled.*/ ||
!ensure(CurrentMatchmakingSearchHandle->SearchResults.IsValidIndex(0)) ||
!ensure(CurrentMatchmakingSearchHandle->GetSearchingPlayerId().IsValid()))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("There is no match result returned."));
OnMatchmakingCompleteDelegates.Broadcast(SessionName, false);
return;
}
OnMatchmakingCompleteDelegates.Broadcast(SessionName, bSucceeded);
} -
上記の関数をバインドして、マッチメイキングプロセスが完了したときに呼び出されるようにします。これは、オンラインセッションが初期化されるときに最初に呼び出される関数である
RegisterOnlineDelegates()関数に以下のコードを追加することで実行できます。void UMatchmakingP2POnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
GetSessionInt()->OnMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnMatchmakingComplete);
// ...
} -
オンラインセッションが非初期化されるときは、イベントのリスニングを停止するためにバインドを解除する必要があります。これは、オンラインセッションが非初期化されるときに最初に呼び出される関数である、事前定義された
ClearOnlineDelegates()関数に以下のコードを追加することで実行できます。void UMatchmakingP2POnlineSession_Starter::ClearOnlineDelegates()
{
// ...
GetSessionInt()->OnMatchmakingCompleteDelegates.RemoveAll(this);
// ...
}
マッチメイキングをキャンセルする
このセクションでは、マッチメイキングをキャンセルする関数を実装します。
-
MatchmakingP2POnlineSession_Starterヘッダーファイルを開き、マッチメイキングをキャンセルする関数を宣言します。public:
// ...
virtual void CancelMatchmaking(APlayerController* PC, const FName& SessionName) override; -
同じファイルで、マッチメイキングキャンセルプロセスが完了したときに呼び出されるコールバック関数を宣言します。
protected:
// ...
virtual void OnCancelMatchmakingComplete(FName SessionName, bool bSucceeded) override; -
マッチメイキングキャンセルプロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートを使用して、後でいくつかのイベントをバインドできます。特に、UIをマッチメイキング実装に接続するときに使用します。
public:
// ...
virtual FOnMatchmakingResponse* GetOnCancelMatchmakingCompleteDelegates() override
{
return &OnCancelMatchmakingCompleteDelegates;
}private:
// ...
FOnMatchmakingResponse OnCancelMatchmakingCompleteDelegates; -
MatchmakingP2POnlineSession_StarterCPPファイルを開き、最初にCancelMatchmaking()関数を定義します。この関数は、アクションを実行する前に、マッチメイキングをキャンセルすることが有効かどうかを確認します。プロセスが完了すると、OnCancelMatchmakingComplete()関数を呼び出します。void UMatchmakingP2POnlineSession_Starter::CancelMatchmaking(APlayerController* PC, const FName& SessionName)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
// Abort if the session interface is invalid.
if (!ensure(GetABSessionInt()))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Session Interface is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnCancelMatchmakingComplete(SessionName, false);
}));
return;
}
if (!(GetABSessionInt()->GetCurrentMatchmakingSearchHandle().IsValid() &&
GetABSessionInt()->GetCurrentMatchmakingSearchHandle()->GetSearchingPlayerId().IsValid()))
{
// This can happen if the cancel was called just as match was found.
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Searching player ID is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnCancelMatchmakingComplete(SessionName, false);
}));
return;
}
if (!GetSessionInt()->CancelMatchmaking(
*GetABSessionInt()->GetCurrentMatchmakingSearchHandle()->GetSearchingPlayerId(),
GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Failed executing"))
// Failed to start matchmaking.
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnCancelMatchmakingComplete(SessionName, false);
}));
}
} -
OnCancelMatchmakingComplete()関数を定義します。この関数は、作成したデリゲートをトリガーして、キャンセルされたマッチメイキングが成功したかどうかを通知します。void UMatchmakingP2POnlineSession_Starter::OnCancelMatchmakingComplete(FName SessionName, bool bSucceeded)
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("succeeded: %s"), *FString(bSucceeded ? "TRUE": "FALSE"))
OnCancelMatchmakingCompleteDelegates.Broadcast(SessionName, bSucceeded);
} -
上記の関数をバインドして、マッチメイキングキャンセルプロセスが完了したときに呼び出されるようにします。これは、オンラインセッションが初期化されるときに最初に呼び出される関数である
RegisterOnlineDelegates()関数に以下のコードを追加することで実行できます。void UMatchmakingP2POnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
GetSessionInt()->OnCancelMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnCancelMatchmakingComplete);
// ...
} -
オンラインセッションが非初期化されるときは、イベントのリスニングを停止するためにバインドを解除する必要があります。これは、オンラインセッションが非初期化されるときに最初に呼び出される関数である、事前定義された
ClearOnlineDelegates()関数に以下のコードを追加することで実行できます。void UMatchmakingP2POnlineSession_Starter::ClearOnlineDelegates()
{
// ...
GetSessionInt()->OnCancelMatchmakingCompleteDelegates.RemoveAll(this);
// ...
}
P2Pホストを受信したときの処理
マッチメイキングが終了し、ゲームクライアントがゲームセッションに参加すると、ゲームクライアントは移動するP2Pホストアドレスを受信するのを待ちます。このセクションでは、これが発生したときの処理方法を学習します。
-
ゲームクライアントをP2Pホストに移動させる関数を作成します。
MatchmakingP2POnlineSession_Starterヘッダーファイルを開き、以下の関数を宣言します。public:
// ...
virtual bool TravelToSession(const FName SessionName) override; -
MatchmakingP2POnlineSession_StarterCPPファイルを開き、上記の関数を定義します。この関数は、ゲームクライアントをP2Pホストに移動する前にいくつかの検証を実行します。有効な場合は、そのアドレスを使用してP2Pホストに移動します。bool UMatchmakingP2POnlineSession_Starter::TravelToSession(const FName SessionName)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
if (GetSessionType(SessionName) != EAccelByteV2SessionType::GameSession)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Not a game session"));
return false;
}
// Get session info
const FNamedOnlineSession* Session = GetSession(SessionName);
if (!Session)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("The session is invalid"));
return false;
}
const TSharedPtr<FOnlineSessionInfo> SessionInfo = Session->SessionInfo;
if (!SessionInfo.IsValid())
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("The session info is invalid"));
return false;
}
const TSharedPtr<FOnlineSessionInfoAccelByteV2> AbSessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(SessionInfo);
if (!AbSessionInfo.IsValid())
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("The session info is not FOnlineSessionInfoAccelByteV2"));
return false;
}
// get player controller of the local owner of the user
APlayerController* PlayerController = GetPlayerControllerByUniqueNetId(Session->LocalOwnerId);
// if nullptr, treat as failed
if (!PlayerController)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Can't find player controller with the session's local owner's unique ID"));
return false;
}
AAccelByteWarsPlayerController* AbPlayerController = Cast<AAccelByteWarsPlayerController>(PlayerController);
if (!AbPlayerController)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Player controller is not (derived from) AAccelByteWarsPlayerController"));
return false;
}
FString ServerAddress = "";
// If local user is not the P2P host -> connect to host
if (!GetABSessionInt()->IsPlayerP2PHost(GetLocalPlayerUniqueNetId(PlayerController).ToSharedRef().Get(), SessionName))
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("Host is not a P2P host, traveling to host"));
GetABSessionInt()->GetResolvedConnectString(SessionName, ServerAddress);
if (ServerAddress.IsEmpty())
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Can't find session's server address"));
return false;
}
}
else
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("Host is a P2P host, traveling as listen server"));
ServerAddress = "MainMenu?listen";
}
if (!bIsInSessionServer)
{
AbPlayerController->DelayedClientTravel(ServerAddress, TRAVEL_Absolute);
bIsInSessionServer = true;
}
else
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Already in session's server"));
}
return true;
} -
次に、
MatchmakingP2POnlineSession_Starterクラスのヘッダーファイルに、マッチメイキング完了後にプレイヤーがゲームセッションに参加したときのハンドラーとして、以下の新しい関数を宣言します。protected:
// ...
virtual void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result) override; -
次に、
MatchmakingP2POnlineSession_StarterクラスのCPPファイルで上記の関数を定義します。この関数では、プレイヤーがゲームセッションに正常に参加すると、プレイヤーはP2Pホストに移動します。void UMatchmakingP2POnlineSession_Starter::OnJoinSessionComplete(
FName SessionName,
EOnJoinSessionCompleteResult::Type Result)
{
Super::OnJoinSessionComplete(SessionName, Result);
TravelToSession(SessionName);
} -
ゲームクライアントがサーバー更新を受信したときにリスニングする関数を作成します。サーバー更新は、ゲームクライアントがバックエンドから移動先のゲームサーバー(この場合はP2Pホスト)を受信したときのイベントです。
MatchmakingP2POnlineSession_Starterヘッダーファイルを開き、以下の関数を宣言します。protected:
// ...
virtual void OnSessionServerUpdateReceived(FName SessionName) override; -
サーバー更新が受信されたときに呼び出されるデリゲートを宣言します。このデリゲートを使用して、後でいくつかのイベントをバインドできます。特に、UIをマッチメイキング実装に接続するときに使用します。
public:
// ...
virtual FOnServerSessionUpdateReceived* GetOnSessionServerUpdateReceivedDelegates() override
{
return &OnSessionServerUpdateReceivedDelegates;
}private:
// ...
FOnServerSessionUpdateReceived OnSessionServerUpdateReceivedDelegates; -
MatchmakingP2POnlineSession_StarterCPPファイルを開き、OnSessionServerUpdateReceived()関数を定義します。この関数は、P2Pホストに移動することが有効かどうかを確認します。また、以前に作成したデリゲートを呼び出して、サーバー更新が受信されたことを通知します。void UMatchmakingP2POnlineSession_Starter::OnSessionServerUpdateReceived(FName SessionName)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
if (bLeavingSession)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("called but leave session is currently running. Canceling attempt to travel to server"))
OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, FOnlineError(true), false);
return;
}
const bool bHasClientTravelTriggered = TravelToSession(SessionName);
OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, FOnlineError(true), bHasClientTravelTriggered);
} -
場合によっては、バックエンドがゲームクライアントのゲームサーバーを見つけられないことがあります。これが発生したときに処理する関数を作成します。
MatchmakingP2POnlineSession_Starterヘッダーファイルを開き、以下の関数を宣言します。protected:
// ...
virtual void OnSessionServerErrorReceived(FName SessionName, const FString& Message) override; -
MatchmakingP2POnlineSession_StarterCPPファイルを開き、上記の関数を定義します。この関数は、以前に作成したセッションサーバー更新デリゲートを呼び出しますが、失敗としてマークします。void UMatchmakingP2POnlineSession_Starter::OnSessionServerErrorReceived(FName SessionName, const FString& Message)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
FOnlineError Error;
Error.bSucceeded = false;
Error.ErrorMessage = FText::FromString(Message);
OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, Error, false);
} -
次に、
MatchmakingP2POnlineSession_Starterクラスのヘッダーファイルに、プレイヤーがゲームセッションから退出したときのハンドラーとして、以下の新しい関数を宣言します。protected:
// ...
virtual void OnLeaveSessionComplete(FName SessionName, bool bSucceeded) override; -
次に、
MatchmakingP2POnlineSession_StarterクラスのCPPファイルで上記の関数を定義します。この関数では、プレイヤーがゲームセッションから退出すると、セッション内ヘルパーステータスをfalseに切り替えます。void UMatchmakingP2POnlineSession_Starter::OnLeaveSessionComplete(FName SessionName, bool bSucceeded)
{
Super::OnLeaveSessionComplete(SessionName, bSucceeded);
if (bSucceeded)
{
bIsInSessionServer = false;
}
} -
上記の関数をバインドして、サーバー更新が受信されたときに呼び出されるようにします。これは、オンラインセッションが初期化されるときに最初に呼び出される関数である
RegisterOnlineDelegates()関数に以下のコードを追加することで実行できます。void UMatchmakingP2POnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
GetABSessionInt()->OnSessionServerUpdateDelegates.AddUObject(this, &ThisClass::OnSessionServerUpdateReceived);
GetABSessionInt()->OnSessionServerErrorDelegates.AddUObject(this, &ThisClass::OnSessionServerErrorReceived);
// ...
} -
オンラインセッションが非初期化されるときは、イベントのリスニングを停止するためにバインドを解除する必要があります。これは、オンラインセッションが非初期化されるときに最初に呼び出される関数である、事前定義された
ClearOnlineDelegates()関数に以下のコードを追加することで実行できます。void UMatchmakingP2POnlineSession_Starter::ClearOnlineDelegates()
{
// ...
GetABSessionInt()->OnSessionServerUpdateDelegates.RemoveAll(this);
GetABSessionInt()->OnSessionServerErrorDelegates.RemoveAll(this);
// ...
}
バックフィルを処理する
バックフィルは、プレイヤーがまだ満員でないゲームサーバーに参加できるようにするフローです。このセクションでは、バックフィルを処理する関数を実装します。
このチュートリアルでは、バックフィル提案が受信されると、常に提案を受け入れます。提案を拒否するには、GetABSessionInt()->RejectBackfillProposal() 関数を使用できます。提案を受け入れるフローと拒否するフローは似ています。
-
MatchmakingP2POnlineSession_Starterヘッダーファイルを開き、以下の関数を宣言します。protected:
// ...
virtual void OnBackfillProposalReceived(FAccelByteModelsV2MatchmakingBackfillProposalNotif Proposal) override; -
バックフィル受け入れプロセスが完了したときに呼び出されるデリゲートを宣言します。
public:
// ...
virtual FOnMatchmakingAcceptBackfillProposalComplete* GetOnAcceptBackfillProposalCompleteDelegates() override
{
return &OnAcceptBackfillProposalCompleteDelegates;
}private:
// ...
FOnMatchmakingAcceptBackfillProposalComplete OnAcceptBackfillProposalCompleteDelegates; -
MatchmakingP2POnlineSession_StarterCPPファイルを開き、OnBackfillProposalReceived()関数を定義します。この関数は、受信したバックフィルを受け入れて、プレイヤーがまだ満員でないゲームサーバーに参加できるようにします。また、以前に作成したデリゲートを呼び出して、バックフィル受け入れプロセスが完了したことをゲームに通知します。void UMatchmakingP2POnlineSession_Starter::OnBackfillProposalReceived(
FAccelByteModelsV2MatchmakingBackfillProposalNotif Proposal)
{
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
// Abort if the session interface is invalid.
if (!ensure(GetABSessionInt().IsValid()))
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Session Interface is not valid."));
return;
}
// Accept backfill proposal.
GetABSessionInt()->AcceptBackfillProposal(
GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession),
Proposal,
false,
FOnAcceptBackfillProposalComplete::CreateWeakLambda(this, [this](bool bSucceeded)
{
UE_LOG_MATCHMAKINGP2P(Log, TEXT("succeeded: %s To accept backfill."), *FString(bSucceeded ? "TRUE": "FALSE"));
OnAcceptBackfillProposalCompleteDelegates.Broadcast(bSucceeded);
}));
} -
上記の関数をバインドして、バックフィル提案が受信されたときに呼び出されるようにします。これは、オンラインセッションが初期化されるときに最初に呼び出される関数である
RegisterOnlineDelegates()関数に以下のコードを追加することで実行できます。void UMatchmakingP2POnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
GetABSessionInt()->OnBackfillProposalReceivedDelegates.AddUObject(this, &ThisClass::OnBackfillProposalReceived);
// ...
} -
オンラインセッションが非初期化されるときは、イベントのリスニングを停止するためにバインドを解除する必要があります。これは、オンラインセッションが非初期化されるときに最初に呼び出される関数である、事前定義された
ClearOnlineDelegates()関数に以下のコードを追加することで実行できます。void UMatchmakingP2POnlineSession_Starter::ClearOnlineDelegates()
{
// ...
GetABSessionInt()->OnBackfillProposalReceivedDelegates.RemoveAll(this);
// ...
}
ピアツーピアホストのオンラインサブシステムを設定する
このチュートリアルでは、MatchmakingP2PServerSubsystem_Starter という名前のゲームインスタンスサブシステムを使用します。このクラスは、P2Pサーバーがマッチメイキングによって生成されたゲームセッションを処理するために使用されます。このクラスはリソースセクションで利用可能で、以下のファイルで構成されています。
- ヘッダーファイル:
/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2PServerSubsystem_Starter.h - CPPファイル:
/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2PServerSubsystem_Starter.cpp
P2Pホストがセッションを受信したときの処理
マッチメイキングが終了すると、バックエンドは接続されたゲームクライアントの1つをP2Pホストとして選択し、ゲームセッションを割り当てます。このセクションでは、P2Pホストが受信したセッションを処理できるように関数を実装します。
-
MatchmakingP2PServerSubsystem_Starterヘッダーファイルを開き、以下の関数を作成します。protected:
virtual void OnServerSessionReceived(FName SessionName) override; -
MatchmakingP2PServerSubsystem_StarterCPPファイルを開き、上記の関数を定義します。P2Pホストがバックエンドからゲームセッションを受信すると、この関数はゲームセッションによって提供される情報に基づいて正しいゲームモードを割り当てます。これにより、P2Pホストは接続されたプレイヤーに正しいゲームプレイを提供できます。void UMatchmakingP2PServerSubsystem_Starter::OnServerSessionReceived(FName SessionName)
{
Super::OnServerSessionReceived(SessionName);
UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))
#pragma region "Assign game mode based on SessionTemplateName from backend"
// Get GameMode
const UWorld* World = GetWorld();
if (!World)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("World is invalid"));
return;
}
AGameStateBase* GameState = World->GetGameState();
if (!GameState)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Game State is invalid"));
return;
}
AAccelByteWarsGameState* AbGameState = Cast<AAccelByteWarsGameState>(GameState);
if (!AbGameState)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Game State is not derived from AAccelByteWarsGameState"));
return;
}
// Get game session
if (MatchmakingOnlineSession->GetSessionType(SessionName) != EAccelByteV2SessionType::GameSession)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Is not a game session"));
return;
}
const FNamedOnlineSession* Session = MatchmakingOnlineSession->GetSession(SessionName);
if (!Session)
{
UE_LOG_MATCHMAKINGP2P(Warning, TEXT("The session is invalid"));
return;
}
FString RequestedGameModeCode = TEXT(""), SessionTemplateName = TEXT("");
Session->SessionSettings.Get(GAMESETUP_GameModeCode, RequestedGameModeCode);
Session->SessionSettings.Get(SETTING_SESSION_MATCHPOOL, SessionTemplateName);
if (!RequestedGameModeCode.IsEmpty())
{
AbGameState->AssignGameMode(RequestedGameModeCode);
}
else if (!SessionTemplateName.IsEmpty())
{
AbGameState->AssignGameMode(MatchmakingOnlineSession->TargetGameModeMap[SessionTemplateName]);
}
#pragma endregion
// Query all currently registered users' info
AuthenticatePlayer_OnRefreshSessionComplete(true);
} -
上記の関数をバインドして、P2Pホストがバックエンドからゲームセッションを受信したときに呼び出されるようにします。これは、サブシステムが初期化されるときに最初に呼び出される関数である
Initialize()関数に以下のコードを追加することで実行できます。void UMatchmakingP2PServerSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UOnlineSession* BaseOnlineSession = GetWorld()->GetGameInstance()->GetOnlineSession();
if (!ensure(BaseOnlineSession))
{
return;
}
MatchmakingOnlineSession = Cast<UMatchmakingP2POnlineSession_Starter>(BaseOnlineSession);
GetABSessionInt()->OnServerReceivedSessionDelegates.AddUObject(this, &ThisClass::OnServerSessionReceived);
} -
サブシステムが非初期化されるときに関数のバインドを解除します。これは、
Deinitialize()関数に以下のコードを追加することで実行できます。void UMatchmakingP2PServerSubsystem_Starter::Deinitialize()
{
Super::Deinitialize();
GetABSessionInt()->OnServerReceivedSessionDelegates.RemoveAll(this);
}
サブシステム内の追加機能
前のセクションでは、サーバーが FNamedOnlineSession* Session = MatchmakingOnlineSession->GetSession(SessionName) を介してセッション情報を取得する方法を学習しました。このセクションでは、それを拡張し、セッションに関する有用な追加情報を提供します。
チーム割り当て
AGSマッチメイキングは、指定されたマッチルールセットに基づいてプレイヤーをチームに分類することもできます。このため、サーバーはチーム情報を取得する方法が必要です。これを行うコードは次のとおりです。
// ...
const FNamedOnlineSession* NamedOnlineSession = GameSessionOnlineSession->GetSession(
GameSessionOnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession));
if (!NamedOnlineSession)
{
return;
}
const TSharedPtr<FOnlineSessionInfo> SessionInfo = NamedOnlineSession->SessionInfo;
if (!SessionInfo.IsValid())
{
return;
}
const TSharedPtr<FOnlineSessionInfoAccelByteV2> AbSessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(SessionInfo);
if (!AbSessionInfo.IsValid())
{
return;
}
TArray<FAccelByteModelsV2GameSessionTeam> Teams = AbSessionInfo->GetTeamAssignments();
さらに、FAccelByteModelsV2GameSessionTeam::UserIDs を使用して、ユーザーがセッションの一部であるかどうかを確認できます。ユーザーIDは文字列として保存されていることに注意してください。FUniqueNetIdReplからABユーザーIDを取得するには、次のコードを使用できます。
#include "OnlineSubsystemAccelByteTypes.h"
const FUniqueNetIdAccelByteUserPtr AbUniqueNetId = FUniqueNetIdAccelByteUser::TryCast(*UniqueNetIdRepl);
const FString AbUserId = AbUniqueNetId->GetAccelByteId();
リソース
-
このチュートリアルセクションで使用されるファイルは、Unreal Byte Wars GitHubリポジトリで利用できます。
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2POnlineSession_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2POnlineSession_Starter.cpp
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2PServerSubsystem_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2PServerSubsystem_Starter.cpp