オンラインサブシステムを使用してサーバーを設定する - AccelByte マルチプレイヤーサーバー (AMS) による専用サーバー - (Unreal Engine モジュール)
Understand the dedicated server flow
Begin by taking a look at the flow of how a game server is managed by AccelByte Multiplayer Servers (AMS). Read more about the AMS dedicated server states in the Integrate dedicated servers with AMS using AGS SDK guide.
About the AGS Online Subsystem
You are now ready to use the AccelByte Gaming Services (AGS) Online Subsystem (OSS). In the Byte Wars project, you will see a subsystem called the MultiplayerDSEssentialsSubsystemAMS_Starter
. This class provides necessary declarations and definitions so you can begin using them to implement dedicated server functionalities right away.
The MultiplayerDSEssentialsSubsystemAMS_Starter
class files can be found at the following locations:
- Header file:
/Source/AccelByteWars/TutorialModules/Play/MultiplayerDSEssentials/MultiplayerDSEssentialsSubsystemAMS_Starter.h
- CPP file:
/Source/AccelByteWars/TutorialModules/Play/MultiplayerDSEssentials/MultiplayerDSEssentialsSubsystemAMS_Starter.cpp
Take a look at what is provided in the class.
In the
MultiplayerDSEssentialsSubsystemAMS_Starter
class header file, you will see three variable declarations.private:
// ...
bool bServerAlreadyRegister;
bool bUnregisterServerRunning;
FOnlineSessionV2AccelBytePtr ABSessionInt;bServerAlreadyRegister
: used to indicate whether we have successfully called theRegisterServer
to prevent the server sending an unnecessary HTTP call.bUnregisterServerRunning
: used to prevent the server callingUnregisterServer
when it has been called and is currently waiting for the response.ABSessionInt
: our interface to the OSS itself.
Navigate to and open
MultiplayerDSEssentialsSubsystemAMS_Starter
CPP file, and then toUMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize
inside of it. Notice that there is an implementation to get the interface itself. This allows you to interact with the OSS right away using theABSessionInt
.void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
ABSessionInt = StaticCastSharedPtr<FOnlineSessionV2AccelByte>(Online::GetSessionInterface());
ensure(ABSessionInt);
// ...
}
Server login
Your game server needs to log in to access AMS, which you have set up in the previous tutorial (Set up IAM).
The server login will be performed automatically since the Unreal Engine has a built-in auto-login feature, as long as you set the AccelByte config below in the DefaultEngine.ini
file.
[OnlineSubsystem]
DefaultPlatformService=AccelByte
Register server
After logging in successfully, your game server needs to be registered with AMS. This will allow AMS to recognize and manage your game server properly.
Unreal Engine has a built-in
RegisterServer()
function in itsGameSession
class. In the Byte Wars project, a class has been created inAGameSession
(AccelByteWarsGameSession
), which overrides theRegisterServer()
to call theOnRegisterServerDelegates
delegate. In this implementation, you will bind the register server function to that delegate. For reference, theAccelByteWarsGameSession
CPP file is located in/Source/AccelByteWars/Core/System/AccelByteWarsGameSession.cpp
.Now that you understand the basics, you will implement registering servers using AGS OSS. Open the
MultiplayerDSEssentialsSubsystemAMS_Starter
Header file and declare the following function:private:
void RegisterServer(const FName SessionName);Still in the Header file, add another function declaration that we will use as the callback when
RegisterServer
completes:private:
// ...
void OnRegisterServerComplete(const bool bSucceeded);Create the function definition in the
MultiplayerDSEssentialsSubsystemAMS_Starter
CPP file. Then, add the following code to register your game server with AMS:void UMultiplayerDSEssentialsSubsystemAMS_Starter::RegisterServer(const FName SessionName)
{
UE_LOG_MultiplayerDSEssentials(Verbose, TEXT("called"))
// Abort if the session interface is invalid.
if (!ABSessionInt)
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("Session interface is null"))
OnRegisterServerComplete(false);
return;
}
if (!IsRunningDedicatedServer())
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("The game instance is not a dedicated server"));
OnRegisterServerComplete(false);
return;
}
if (bServerAlreadyRegister)
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("The server is already registered"));
OnRegisterServerComplete(false);
return;
}
ABSessionInt->RegisterServer(SessionName, FOnRegisterServerComplete::CreateUObject(
this, &ThisClass::OnRegisterServerComplete));
}Create a definition for the callback. You will call the log and flag to the dedicated server as already registered to cancel any additional register attempts, saving an HTTP call.
void UMultiplayerDSEssentialsSubsystemAMS_Starter::OnRegisterServerComplete(const bool bSucceeded)
{
UE_LOG_MultiplayerDSEssentials(Log, TEXT("succeeded: %s"), *FString(bSucceeded ? "TRUE": "FALSE"))
if (bSucceeded)
{
bServerAlreadyRegister = true;
}
AAccelByteWarsGameMode::OnRegisterServerCompleteDelegates.Broadcast(bSucceeded);
}Bind the
RegisterServer
to theOnRegisterServerDelegates
delegate we mentioned earlier. Place it in theMultiplayerDSEssentialsSubsystemAMS_Starter
class CPP file in this predefined function:void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
AAccelByteWarsGameSession::OnRegisterServerDelegates.AddUObject(this, &ThisClass::RegisterServer);
// ...
}Unbind the delegate when the
MultiplayerDSEssentialsSubsystemAMS_Starter
is uninitialized. You can do it inside this predefined function:void UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
{
// ...
AAccelByteWarsGameSession::OnRegisterServerDelegates.RemoveAll(this);
// ...
}Compile your project and make sure there are no errors.
Send server ready
The register server implementation you have created before will automatically set your game server as ready to AMS, but only if you set bManualRegisterServer=false
in your SDK config. Marking a server as ready means that your server is ready to accept incoming players.
But for some games, you might want to handle things before the server is ready to handle incoming players, such as loading big assets. Therefore, manually marking your server as ready might be a suitable option. In this section, you will learn how to send the request to mark your server as ready on AMS.
First, make sure you have enabled
bManualRegisterServer=true
in your SDK config.Open the
MultiplayerDSEssentialsSubsystemAMS_Starter
Header file and declare the following function:private:
// ...
void SendServerReady(const FName SessionName);Still in the Header file, add another function declaration that we will use as the callback when
SendServerReady
completes:private:
// ...
void OnSendServerReadyComplete(const bool bSucceeded);Create the function definition in the
MultiplayerDSEssentialsSubsystemAMS_Starter
CPP file. Then, add the following code to set your AMS server in the ready state:void UMultiplayerDSEssentialsSubsystemAMS_Starter::SendServerReady(const FName SessionName)
{
if (!ABSessionInt)
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("Session interface is null"));
OnSendServerReadyComplete(false);
return;
}
if (!IsRunningDedicatedServer())
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("The game instance is not a dedicated server"));
OnSendServerReadyComplete(false);
return;
}
if (bServerAlreadyRegister)
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("The server is already registered and is in the ready state"));
OnSendServerReadyComplete(false);
return;
}
// Registering the server manually by setting it as ready.
ABSessionInt->SendServerReady(SessionName, FOnRegisterServerComplete::CreateUObject(this, &ThisClass::OnSendServerReadyComplete));
}Create a definition for the callback. In this function, we enable a flag to the server as already registered to prevent additional register attempts.
void UMultiplayerDSEssentialsSubsystemAMS_Starter::OnSendServerReadyComplete(const bool bSucceeded)
{
UE_LOG_MultiplayerDSEssentials(Log, TEXT("succeeded: %s"), *FString(bSucceeded ? TEXT("TRUE") : TEXT("FALSE")))
if (bSucceeded)
{
bServerAlreadyRegister = true;
}
}Since Byte Wars is a simple game, the game server doesn't need to handle anything before it is ready to accept incoming players. Thus, we will send the server-ready request once the game server is registered. You might want to handle it differently on your game project, for example, set the server ready when the game assets are fully loaded, when some important async process is completed, etc. Bind the
SendServerReady
to theOnRegisterServerDelegates
delegate we mentioned earlier. Place it in theMultiplayerDSEssentialsSubsystemAMS_Starter
class CPP file in this predefined function:void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
AAccelByteWarsGameSession::OnRegisterServerDelegates.AddUObject(this, &ThisClass::SendServerReady);
// ...
}Unbind the delegate when the
MultiplayerDSEssentialsSubsystemAMS_Starter
is uninitialized. You can do it inside this predefined function:void UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
{
// ...
AAccelByteWarsGameSession::OnRegisterServerDelegates.RemoveAll(this);
// ...
}Compile your project and make sure there are no errors.
Unregister and shut down server
Once the game is over, you should unregister your game server from AMS and shut it down. This prevents your servers from becoming zombie servers (session ended but the server is still active).
Just like the register server functionality, you used
GameSession
to call the unregister server, but the built-inGameSession
(AGameSession
) does not have an unregister server or equivalent function. So, a new function calledUnregisterServer
has been created inAccelByteWarsGameSession
and triggered inAAccelByteWarsInGameGameMode::CloseGame
. To implement the unregister function itself, you will need to bind the implementation to theAccelByteWarsGameSession::OnUnregisterServerDelegates
delegate.Implement the unregister server functionality. Open the
MultiplayerDSEssentialsSubsystemAMS_Starter
Header file and add the following function declaration:private:
// ...
void UnregisterServer(const FName SessionName);Add one more function declaration as the callback for
UnregisterServer
:private:
// ...
void OnUnregisterServerComplete(const bool bSucceeded);Create the definition for the
UnregisterServer
. Open theMultiplayerDSEssentialsSubsystemAMS_Starter
CPP file and add the following code:void UMultiplayerDSEssentialsSubsystemAMS_Starter::UnregisterServer(const FName SessionName)
{
UE_LOG_MultiplayerDSEssentials(Verbose, TEXT("called"))
// Abort if the session interface is invalid.
if (!ABSessionInt)
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("Session interface is null"))
OnUnregisterServerComplete(false);
return;
}
if (!IsRunningDedicatedServer())
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("The game instance is not a dedicated server"));
OnUnregisterServerComplete(false);
return;
}
ABSessionInt->UnregisterServer(SessionName, FOnUnregisterServerComplete::CreateUObject(
this, &ThisClass::OnUnregisterServerComplete));
bUnregisterServerRunning = true;
}Create a definition for the callback function. You will be adding logic to close the dedicated server upon receiving the callback:
void UMultiplayerDSEssentialsSubsystemAMS_Starter::OnUnregisterServerComplete(const bool bSucceeded)
{
UE_LOG_MultiplayerDSEssentials(Log, TEXT("succeeded: %s"), *FString(bSucceeded ? "TRUE": "FALSE"))
bUnregisterServerRunning = false;
FPlatformMisc::RequestExit(false);
}Bind
UnregisterServer
to theOnUnregisterServerDelegate
delegate we mentioned earlier. Place the binding in theMultiplayerDSEssentialsSubsystemAMS_Starter
CPP file in this predefined function:void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
AAccelByteWarsGameSession::OnUnregisterServerDelegates.AddUObject(this, &ThisClass::UnregisterServer);
// ...
}Unbind the delegate. Just like before, you will call the unbind in the
Deinitialize
function:void UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
{
// ...
AAccelByteWarsGameSession::OnUnregisterServerDelegates.RemoveAll(this);
// ...
}Compile your project and make sure there are no errors.
Handle drain state
The dedicated server will be set to the draining state and is subjected to the drain timeout. Upon reaching the drain timeout, the watchdog will automatically terminate the dedicated server. This gives a configurable period of time for your dedicated server to do any last minute actions and then terminate itself.
Open the
MultiplayerDSEssentialsSubsystemAMS_Starter
Header file and add the following function declaration:private:
// ...
void OnAMSDrainReceived();Implement the drain callback. Open the
MultiplayerDSEssentialsSubsystemAMS_Starter
Header file and add the following function declaration:void UMultiplayerDSEssentialsSubsystemAMS_Starter::OnAMSDrainReceived()
{
UE_LOG_MultiplayerDSEssentials(Log, TEXT("Received AMS drain message; Shutting down the server now!"));
OnUnregisterServerComplete(true);
}Add
OnAMSDrainReceived
underUMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize:
.void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
ABSessionInt->OnAMSDrainReceivedDelegates.AddUObject(this, &ThisClass::OnAMSDrainReceived);
// ...
}Remove the delegate under the
UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
function:void UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
{
// ...
ABSessionInt->OnAMSDrainReceivedDelegates.RemoveAll(this);
// ...
}Compile the project and make sure there are no compile errors.
Handle session end
There are cases where the backend ends a session even when the server is still active. Here, we want the server to unregister itself when the session end notification fires.
Open the
MultiplayerDSEssentialsSubsystemAMS_Starter
Header file and add the following function declaration:private:
// ...
void OnV2SessionEnded(const FName SessionName);Implement the session end callback. Open the
MultiplayerDSEssentialsSubsystemAMS_Starter
Header file and add the following function declaration:void UMultiplayerDSEssentialsSubsystemAMS_Starter::OnV2SessionEnded(const FName SessionName)
{
UE_LOG_MultiplayerDSEssentials(Log, TEXT("Received AMS session ended notification; Shutting down the server now!"));
UnregisterServer(SessionName);
}Add
OnV2SessionEnded
underUMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize:
.void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
// ...
ABSessionInt->OnV2SessionEndedDelegates.AddUObject(this, &ThisClass::OnV2SessionEnded);
// ...
}Remove the delegate under the
UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
function:void UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
{
// ...
ABSessionInt->OnV2SessionEndedDelegates.RemoveAll(this);
}Compile the project and make sure there are no compile errors.
Activating your implementation
To make the game use your implementation, you need to activate the starter files.
- Open the Unreal Engine Editor.
- In the content browser window, go to
/Content/TutorialModules/Play/MultiplayerDSEssentials/
. - Open the data asset called
DA_MultiplayerDSEssentials
and enableIs Starter Mode Active
. - Save the data asset.
Resources
- The files used in this tutorial section are available in the Byte Wars GitHub repository.
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MultiplayerDSEssentials/MultiplayerDSEssentialsSubsystemAMS_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MultiplayerDSEssentials/MultiplayerDSEssentialsSubsystemAMS_Starter.cpp
- AccelByteWars/Source/AccelByteWars/Core/System/AccelByteWarsGameSession.cpp
- AccelByteWars/Source/AccelByteWars/Core/GameModes/AccelByteWarsInGameGameMode.cpp