Unreal Engine Module - Run a Dedicated Server on AccelByte Multiplayer Server (AMS) - Use Online Subsystem to set up a server
Understand the dedicated server flow
First, let's start by understanding the flow of how a game server is managed by AccelByte Multiplayer Servers (AMS). Please read more about the AMS dedicated server states in the AMS Integrate dedicated servers with 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:
- The
header
file can be found in/Source/AccelByteWars/TutorialModules/Play/MultiplayerDSEssentials/MultiplayerDSEssentialsSubsystemAMS_Starter.h
. - The
cpp
file can be found in/Source/AccelByteWars/TutorialModules/Play/MultiplayerDSEssentials/MultiplayerDSEssentialsSubsystemAMS_Starter.cpp
.
Let's take a look at what we have provided in the class.
In the
MultiplayerDSEssentialsSubsystemAMS_Starter
class header file, you will see three variable declarations.bool bServerAlreadyRegister;
bool bUnregisterServerRunning;
FOnlineSessionV2AccelBytePtr ABSessionInt;bServerAlreadyRegister
: used to indicates 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.
Now, navigate to and open
MultiplayerDSEssentialsSubsystemAMS_Starter
cpp
file, and then toUMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize
inside of it. Notice that we have implemented a way to get the interface itself. This allows you to interact with the OSS right away using theABSessionInt
.void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// TODO: Bind delegates
ABSessionInt = StaticCastSharedPtr<FOnlineSessionV2AccelByte>(Online::GetSessionInterface());
ensure(ABSessionInt);
}
Server login
Your game server needs to log in to access AMS. To do this, you don't need to perform additional steps; just make sure you have done the Setup a dedicated server IAM client, and your game server will automatically perform the login to AMS.
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, we have created a class inAGameSession
(AccelByteWarsGameSession
), which overrides theRegisterServer()
to call theOnRegisterServerDelegates
delegate. So, in this implementation, we will be binding our 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, let's create a register server implementation using AGS OSS. First, open the
MultiplayerDSEssentialsSubsystemAMS_Starter
header file and declare the following function:private:
void RegisterServer(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);Next, 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(FName SessionName)
{
UE_LOG_MultiplayerDSEssentials(Verbose, TEXT("called"))
// safety
if (!ABSessionInt)
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("Session interface null"))
OnRegisterServerComplete(false);
return;
}
if (!IsRunningDedicatedServer())
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("Is not DS"));
OnRegisterServerComplete(false);
return;
}
if (bServerAlreadyRegister)
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("Already registered"));
OnRegisterServerComplete(false);
return;
}
ABSessionInt->RegisterServer(SessionName, FOnRegisterServerComplete::CreateUObject(
this, &ThisClass::OnRegisterServerComplete));
}Create a definition for the callback. We 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;
}
}Finally, let's bind the
RegisterServer
to theOnRegisterServerDelegates
delegate we mentioned earlier. Place it in theMultiplayerDSEssentialsSubsystemAMS_Starter
classcpp
file in this predefined function:void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
...
AAccelByteWarsGameSession::OnRegisterServerDelegates.AddUObject(this, &ThisClass::RegisterServer);
...
}Next, 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);
}Please compile your project and make sure there are no errors.
There you have it! Register server to AMS implementation is completed.
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 some stuff before the server is ready to handle incoming players, for example, 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 enable
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);Next, 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 null"));
OnSendServerReadyComplete(false);
return;
}
if (!IsRunningDedicatedServer())
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("Is not DS"));
OnSendServerReadyComplete(false);
return;
}
if (bServerAlreadyRegister)
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("Already registered and ready"));
OnSendServerReadyComplete(false);
return;
}
// Registering the server manually by setting it as ready.
ABSessionInt->SendServerReady(SessionName, FOnRegisterServerComplete::CreateUObject(this, &ThisClass::OnSendServerReadyComplete));
}Then, 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 ? "TRUE" : "FALSE"))
if (bSucceeded)
{
bServerAlreadyRegister = true;
}
}Since Byte Wars is a simple game, the game server doesn't need to handle additional stuff before it is ready to welcome 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 asycn process is completed, etc. All right, let's bind the
SendServerReady
to theOnRegisterServerDelegates
delegate we mentioned earlier. Place it in theMultiplayerDSEssentialsSubsystemAMS_Starter
classcpp
file in this predefined function:void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
...
AAccelByteWarsGameSession::OnRegisterServerDelegates.AddUObject(this, &ThisClass::SendServerReady);
...
}Next, 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);
}Please compile your project and make sure there are no errors.
There you have it! Send server ready to AMS implementation is completed.
Unregister and shut down server
Once the game is over, you should unregister your game server from AMS and then 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, we used
GameSession
to call the unregister server, but the built-inGameSession
(AGameSession
) does not have an unregister server or equivalent function. So, we created a new function calledUnregisterServer
inAccelByteWarsGameSession
and triggered inAAccelByteWarsInGameGameMode::CloseGame
. To implement the unregister function itself, you will need to bind the implementation to theAccelByteWarsGameSession::OnUnregisterServerDelegates
delegate.It's time to implement the unregister server functionality. Open the
MultiplayerDSEssentialsSubsystemAMS_Starter
header file and add the following function declaration:private:
void UnregisterServer(FName SessionName);Add one more function declaration as the callback for
UnregisterServer
:private:
void OnUnregisterServerComplete(const bool bSucceeded);Then, 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"))
// safety
if (!ABSessionInt)
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("Session interface null"))
OnUnregisterServerComplete(false);
return;
}
if (!IsRunningDedicatedServer())
{
UE_LOG_MultiplayerDSEssentials(Warning, TEXT("Is not DS"));
OnUnregisterServerComplete(false);
return;
}
ABSessionInt->UnregisterServer(SessionName, FOnUnregisterServerComplete::CreateUObject(
this, &ThisClass::OnUnregisterServerComplete));
bUnregisterServerRunning = true;
}Create a definition for the callback function. We will be adding a 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);
}Next, let's 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::OnRegisterServerDelegates.AddUObject(this, &ThisClass::RegisterServer);
AAccelByteWarsGameSession::OnUnregisterServerDelegates.AddUObject(this, &ThisClass::UnregisterServer);
...
}Onward to the unbinding of the delegate. Just like before, we will call the unbind in the
Deinitialize
function, then compile the project:void UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
{
...
AAccelByteWarsGameSession::OnRegisterServerDelegates.RemoveAll(this);
AAccelByteWarsGameSession::OnUnregisterServerDelegates.RemoveAll(this);
}
Congratulations! The drain handling for the game server is completed.
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 action and then terminate itself.
Open the
MultiplayerDSEssentialsSubsystemAMS_Starter
header file and add the following function declaration:private:
void OnAMSDrainReceived();It's time to implement the unregister server functionality. 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);
}Then, add
OnAMSDrainReceived
under `UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize:.void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize()
{
...
ABSessionInt->OnAMSDrainReceivedDelegates.AddUObject(this, &ThisClass::OnAMSDrainReceived);
}Also, remove the delegate under the
UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
function.void UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
{
...
ABSessionInt->OnAMSDrainReceivedDelegates.RemoveAll(this);
}Let's compile the project and make sure there are no compile errors.
Finally, to see the starter files in action, you need to follow these steps.
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.
Congratulations! The AMS integration for your game server is completed.
Resources
- The files used in this tutorial section are available in the ByteWars 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
- AccelByte Multiplayer Servers (AMS): Integrate dedicated servers with SDK