Implementing the subsystem - Game server integration - (Unreal Engine module)
注釈:本資料はAI技術を用いて翻訳されています。
AMS を使用したゲームサーバーのフロー
始める前に、AccelByte Multiplayer Servers (AMS) によってゲームサーバーがどのように管理されるかを理解しましょう。以下の状態図は、サーバーが経過する状態と、それぞれの状態間の遷移方法を示しています。
ゲームサーバーが起動すると、WebSocket 経由で AMS に接続します。接続が確立された後、ゲームサーバーは準備完了を示すメッセージを送信します。ゲームサーバーの準備が整うと、バックエンドサービスがそれを要求して、ゲームクライアントのゲームセッションを提供できるようになります。ゲームセッションが終了した場合、ゲームサーバーは AMS WebSocket から切断し、自身をシャットダウンする必要があります。
AMS は、未使用のサーバーを削除するために、ゲームサーバーにドレインイベントを送信することもできます。このイベントが発生した場合、ゲームサーバーがゲームセッションを提供していない場合(例: サーバーにプレイヤーがいない、またはゲームが終了している)、不要なコストを避けるためにゲームサーバーをシャットダウンする必要があります。
サブシステムの詳細
このチュートリアルに従うために、AMSModuleSubsystem_Starter という Game Instance Subsystem が用意されています。このクラスはリソースセクションで入手でき、以下のファイルで構成されています。
- ヘッダーファイル:
/Source/AccelByteWars/TutorialModules/Play/AMSModule/AMSModuleSubsystem_Starter.cpp - CPP ファイル:
/Source/AccelByteWars/TutorialModules/Play/AMSModule/AMSModuleSubsystem_Starter.h
AMSModuleSubsystem_Starter クラスには、いくつかの機能が提供されています。
- Byte Wars コアゲームのデリゲートにいくつかの関数をバインドおよびアンバインドする関数。
public:
// ...
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
void UAMSModuleSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
AAccelByteWarsGameSession::OnRegisterServerDelegates.AddUObject(this, &ThisClass::RegisterServer);
AAccelByteWarsGameSession::OnUnregisterServerDelegates.AddUObject(this, &ThisClass::UnregisterServer);
}
void UAMSModuleSubsystem_Starter::Deinitialize()
{
// ...
AAccelByteWarsGameSession::OnRegisterServerDelegates.RemoveAll(this);
AAccelByteWarsGameSession::OnUnregisterServerDelegates.RemoveAll(this);
}
- 他のオブジェクトがこのクラスと対話するために使用する関数。これらの関数の定義は現在空です。後でこれらの関数を定義します。
public:
// ...
void RegisterServer(const FName SessionName);
void UnregisterServer(const FName SessionNam);
- デリゲートをクリアする関数。これにより、バインドされた可能性のある他のオブジェクトから自身のデリゲートをアンバインドします。
private:
// ...
void UnbindConnectDelegates();
void UnbindAllDelegates();
void UAMSModuleSubsystem_Starter::UnbindConnectDelegates()
{
bIsRegistering = false;
OnConnectSuccessDelegate.Unbind();
OnConnectErrorDelegate.Unbind();
OnConnectClosedDelegate.Unbind();
}
void UAMSModuleSubsystem_Starter::UnbindAllDelegates()
{
UnbindConnectDelegates();
OnDrainReceivedDelegate.Unbind();
}
- 接続タイムアウトを処理し、複数の登録が同時に実行されるのを防ぎ、クラウドセッションではなくゲーム内セッションに使用されるセッション名を保持する変数。
private:
// ...
FName CurrentSessionName;
bool bIsRegistering = false;
const int32 ConnectionTimeOut = 30;
const int32 DisconnectionTimeOut = 30;
const float TimerRate = 1.0f;
int32 ConnectionTimeOutTimer = 0;
int32 DisconnectionTimeOutTimer = 0;
int32 ConnectionRetry = 3;
- タイマーハンドル。これは後で接続タイムアウトを処理するために使用します。
private:
// ...
FTimerHandle ConnectionTimerHandle;
FTimerHandle DisconnectionTimerHandle;
- ゲームサーバー用にコンパイルする場合にのみコードがコンパイルされるようにするマクロ。
#if UE_EDITOR || UE_SERVER
//...
#endif
AMS をゲームサーバーに統合する
-
AMSModuleSubsystem_Starterヘッダーファイルを開き、以下の関数宣言を追加します。これらは、サーバーが AMS にメッセージを送信するために能動的にトリガーする必要がある関数です。private:
void Connect();
void Disconnect();
void SendServerReady(); -
AMSModuleSubsystem_StarterCPP ファイルを開き、Connect関数を定義します。この関数は、WebSocket 接続の実行以外にもいくつかのことを行います。- すべての接続デリゲートをバインドします。これにより、接続関数が失敗、成功、またはクローズされた場合を処理します。
- ドレインシグナルのハンドラーをバインドします。
- タイムアウトタイマーをトリガーします。これは、接続を呼び出した後、一定時間内に何も起こらない場合を処理します。
- 接続関数を呼び出します。
void UAMSModuleSubsystem_Starter::Connect()
{
if (AccelByte::FRegistry::ServerAMS.IsConnected())
{
UE_LOG_AMS_MODULE(Log, TEXT("Already connected to AMS websocket."));
OnConnectSuccess();
return;
}
OnConnectSuccessDelegate = AccelByte::GameServerApi::ServerAMS::FConnectSuccess::CreateUObject(this, &ThisClass::OnConnectSuccess);
OnConnectErrorDelegate = AccelByte::GameServerApi::ServerAMS::FConnectError::CreateUObject(this, &ThisClass::OnConnectError);
OnConnectClosedDelegate = AccelByte::GameServerApi::ServerAMS::FConnectionClosed::CreateUObject(this, &ThisClass::OnConnectClosed);
AccelByte::FRegistry::ServerAMS.SetOnConnectSuccess(OnConnectSuccessDelegate);
AccelByte::FRegistry::ServerAMS.SetOnConnectError(OnConnectErrorDelegate);
AccelByte::FRegistry::ServerAMS.SetOnConnectionClosed(OnConnectClosedDelegate);
OnDrainReceivedDelegate = AccelByte::GameServerApi::ServerAMS::FOnAMSDrainReceived::CreateUObject(this, &ThisClass::OnDrainReceived);
AccelByte::FRegistry::ServerAMS.SetOnAMSDrainReceivedDelegate(OnDrainReceivedDelegate);
// If bServerUseAMS is true, the connection to AMS websocket is already started by SDK automatically. Else, try to connect manually.
GetWorld()->GetTimerManager().SetTimer(ConnectionTimerHandle, this, &ThisClass::CheckConnection, TimerRate, true);
if (!AccelByte::FRegistry::ServerSettings.bServerUseAMS)
{
UE_LOG_AMS_MODULE(Log, TEXT("Connecting to AMS websocket."));
AccelByte::FRegistry::ServerAMS.Connect();
}
} -
Disconnect関数を定義します。これもいくつかのことを行います。- ドレインおよびすべての接続デリゲートをアンバインドします。
- タイムアウトタイマーを開始します。これは、切断を呼び出した後、一定時間内に何も起こらない場合を処理します。
- 切断関数を呼び出します。
void UAMSModuleSubsystem_Starter::Disconnect()
{
if (!AccelByte::FRegistry::ServerAMS.IsConnected())
{
UE_LOG_AMS_MODULE(Warning, TEXT("Cannot disconnect AMS websocket. The AMS websocket connection is not established."));
return;
}
UnbindAllDelegates();
UE_LOG_AMS_MODULE(Log, TEXT("Disconnecting from AMS websocket."));
GetWorld()->GetTimerManager().SetTimer(DisconnectionTimerHandle, this, &ThisClass::CheckDisconnection, TimerRate, true);
AccelByte::FRegistry::ServerAMS.Disconnect();
} -
SendServerReady関数を定義します。これにより、サーバーがプレイヤーを受け入れる準備ができていることを AMS に通知します。void UAMSModuleSubsystem_Starter::SendServerReady()
{
if (!AccelByte::FRegistry::ServerAMS.IsConnected())
{
UE_LOG_AMS_MODULE(Warning, TEXT("Cannot set server ready. The AMS websocket connection is not established."));
return;
}
UE_LOG_AMS_MODULE(Log, TEXT("Send server ready message to AMS."));
AccelByte::FRegistry::ServerAMS.SendReadyMessage();
} -
AMSModuleSubsystem_Starterヘッダーファイルを開き、以下の関数宣言を追加します。これらの関数は、ゲームが AMS への接続を試みた際のレスポンスを処理します。private:
// ...
void OnConnectSuccess();
void OnConnectError(const FString& ErrorMessage);
void OnConnectClosed(int32 StatusCode, FString const& Reason, bool bWasClean); -
AMSModuleSubsystem_StarterCPP ファイルを開き、OnConnectSuccess関数を定義します。これにより、接続デリゲートをアンバインドし、すぐにサーバー準備完了メッセージを送信します。このステップでゲームにサーバー準備完了メッセージをすぐに送信する必要はありません。ゲームが何かをセットアップする時間が必要な場合は、ここでセットアップを呼び出し、その後にサーバー準備完了メッセージを送信する必要があります。void UAMSModuleSubsystem_Starter::OnConnectSuccess()
{
UE_LOG_AMS_MODULE(Log, TEXT("Success to connect to AMS websocket."));
UnbindConnectDelegates();
/* It is not required to set the server as ready immediately after the AMS websocket connection is established.
* If the server needs to perform setup tasks before welcoming the player, the server ready message should be sent afterward.
* Since Byte Wars does not require such setup, the server ready message is sent immediately here. */
SendServerReady();
} -
OnConnectError関数を定義します。これは、サーバーが失敗した場合の処理を行います。単純に接続デリゲートをアンバインドします。ただし、次に何が起こるかを処理する別のハンドラーがあり、これは後で実装します。void UAMSModuleSubsystem_Starter::OnConnectError(const FString& ErrorMessage)
{
UE_LOG_AMS_MODULE(Warning, TEXT("Failed to connect to AMS websocket. Error: %s"), *ErrorMessage);
CurrentSessionName = FName(FString());
UnbindConnectDelegates();
} -
OnConnectClosed関数を定義します。これは、接続が閉じられたときの処理を行います。前の関数と同様に、単純に接続デリゲートをアンバインドします。別のハンドラーが次に何が起こるかを引き継ぎます。void UAMSModuleSubsystem_Starter::OnConnectClosed(int32 StatusCode, FString const& Reason, bool bWasClean)
{
UE_LOG_AMS_MODULE(Warning, TEXT("Failed to connect to AMS websocket. Connection is closed. Reason: %s"), *Reason);
CurrentSessionName = FName(FString());
UnbindConnectDelegates();
} -
AMSModuleSubsystem_Starterヘッダーファイルに戻り、以下の関数宣言を追加します。private:
// ...
void OnDisconnected(bool bIsSucceeded, const FString& ErrorMessage); -
AMSModuleSubsystem_StarterCPP ファイルを開き、関数を定義します。これによりサーバーがシャットダウンされます。void UAMSModuleSubsystem_Starter::OnDisconnected(bool bIsSucceeded, const FString& ErrorMessage)
{
UE_LOG_AMS_MODULE(Log, TEXT("Disconnected from AMS websocket. Success: %s. Error: %s"), bIsSucceeded ? TEXT("TRUE") : TEXT("FALSE"), *ErrorMessage);
FPlatformMisc::RequestExit(false);
} -
AMSModuleSubsystem_Starterヘッダーファイルを開き、以下の関数宣言を追加します。名前が示すように、これはゲームがドレインシグナルを受信したときのハンドラーで、前のステップでバインドしたものです。private:
// ...
void OnDrainReceived(); -
AMSModuleSubsystem_StarterCPP ファイルを開き、関数を定義します。これを処理する理想的な方法は、サーバーが現在プレイヤーにサービスを提供している場合はドレインシグナルを無視し、そうでない場合はすぐにサーバーをシャットダウンすることです。この関数はまさにそれを行います。void UAMSModuleSubsystem_Starter::OnDrainReceived()
{
UE_LOG_AMS_MODULE(Log, TEXT("Drain event received."));
/**
* When a drain event occurs, the server may perform some clean-up tasks.
* Drain behavior on Byte Wars:
* When the server is currently waiting for players, unregisters and shuts down.
* Otherwise, keep it running as normal.
*/
if (const AAccelByteWarsMainMenuGameState* ABMainMenuGameState = Cast<AAccelByteWarsMainMenuGameState>(GetWorld()->GetGameState()))
{
if (ABMainMenuGameState->LobbyStatus == ELobbyStatus::IDLE)
{
UE_LOG_AMS_MODULE(Log, TEXT("Game is currently waiting for players, shutting down."));
UnregisterServer(CurrentSessionName);
return;
}
}
UE_LOG_AMS_MODULE(Log, TEXT("Game is currently in progress, drain event ignored."));
} -
AMSModuleSubsystem_Starterヘッダーファイルを開き、以下の関数宣言を追加します。前のステップでタイムアウトについて言及されていることに注意してください。これらは、それらのタイムアウトを処理する関数です。private:
// ...
void CheckConnection();
void CheckDisconnection(); -
AMSModuleSubsystem_StarterCPP ファイルを開き、CheckConnection関数を定義します。この関数は、ゲームサーバーが AMS に接続されているかどうかを定期的にチェックします。事前定義された時間が経過してもゲームサーバーがまだ接続されていない場合、再接続を試みます。このプロセスは、事前定義された回数繰り返されます。それでも接続が失敗する場合、関数はそれを失敗として扱い、OnConnectError関数を呼び出します。void UAMSModuleSubsystem_Starter::CheckConnection()
{
if (AccelByte::FRegistry::ServerAMS.IsConnected())
{
ConnectionTimeOutTimer = ConnectionTimeOut;
GetWorld()->GetTimerManager().ClearTimer(ConnectionTimerHandle);
return;
}
// On time-out, retry to connect.
if (ConnectionTimeOutTimer <= 0)
{
ConnectionTimeOutTimer = ConnectionTimeOut;
GetWorld()->GetTimerManager().ClearTimer(ConnectionTimerHandle);
UnbindConnectDelegates();
if (ConnectionRetry > 0)
{
ConnectionRetry--;
Connect();
}
else
{
OnConnectError(TEXT("Failed to connect to AMS websocket. Connection time out."));
}
return;
}
ConnectionTimeOutTimer--;
} -
CheckDisconnection関数を定義します。この関数はCheckConnectionと同様の目的を果たしますが、接続をチェックする代わりに切断をチェックします。この関数は、失敗した場合に再度切断を呼び出すことはしません。最初のタイムアウト後すぐにOnDisconnectedを呼び出し、サーバーをシャットダウンします。void UAMSModuleSubsystem_Starter::CheckDisconnection()
{
if (!AccelByte::FRegistry::ServerAMS.IsConnected())
{
DisconnectionTimeOutTimer = DisconnectionTimeOut;
GetWorld()->GetTimerManager().ClearTimer(DisconnectionTimerHandle);
OnDisconnected(true, TEXT(""));
return;
}
// On time-out, force to disconnect abruptly.
if (DisconnectionTimeOutTimer <= 0)
{
DisconnectionTimeOutTimer = DisconnectionTimeOut;
GetWorld()->GetTimerManager().ClearTimer(DisconnectionTimerHandle);
OnDisconnected(false, TEXT("Failed to disconnect from AMS websocket. Connection time out."));
return;
}
DisconnectionTimeOutTimer--;
} -
CPP ファイル内で、
RegisterServer関数に移動し、定義を以下のコードに置き換えます。この関数はいくつかのことを行います。- すでに接続されている場合、または現在接続を試みている場合に AMS への接続を試みることを防ぎます。
- タイムアウトタイマーをリセットします。
- AMS への接続関数を実行します。
void UAMSModuleSubsystem_Starter::RegisterServer(const FName SessionName)
{
if (bIsRegistering)
{
UE_LOG_AMS_MODULE(Warning, TEXT("Cannot register server. Register server is already in progress."));
return;
}
if (AccelByte::FRegistry::ServerAMS.IsConnected())
{
UE_LOG_AMS_MODULE(Warning, TEXT("Server is already registered and connected to AMS websocket."));
return;
}
UE_LOG_AMS_MODULE(Log, TEXT("Register server to AMS."));
bIsRegistering = true;
CurrentSessionName = SessionName;
ConnectionTimeOutTimer = ConnectionTimeOut;
DisconnectionTimeOutTimer = DisconnectionTimeOut;
Connect();
} -
UnregisterServer関数に移動し、定義を以下のコードに置き換えます。切断関数は、ゲームが現在 AMS に接続されていない場合の処理をすでに処理しているため、この関数は単にDisconnectを呼び出し、保存されているゲームセッション名をリセットします。void UAMSModuleSubsystem_Starter::UnregisterServer(const FName SessionNam)
{
UE_LOG_AMS_MODULE(Log, TEXT("Unregister server from AMS."));
CurrentSessionName = FName(FString());
Disconnect();
} -
プロジェクトをビルドし、Unreal Engine エディターで開きます。エディターで、
/Content/TutorialModules/Play/AMSModule/に移動します。DA_AMSModuleというデータアセットが見つかります。それを開き、Is Starter Mode Activeを有効にします。次に、データアセットを保存します。これにより、ウィジェットがアクティブになり、ゲームをプレイするときにそれらをナビゲートできるようになります。
リソース
- このチュートリアルセクションで使用されるファイルは、Byte Wars Unreal GitHub リポジトリで入手できます。