Skip to main content

Unreal Engine Module - Run a Dedicated Server on AccelByte Multiplayer Server (AMS) - Use Online Subsystem to set up a server

Last updated on January 13, 2024

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 the RegisterServer to prevent the server sending an unnecessary HTTP call.
    • bUnregisterServerRunning: used to prevent the server calling UnregisterServer 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 to UMultiplayerDSEssentialsSubsystemAMS_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 the ABSessionInt.

    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.

  1. Unreal Engine has a built-in RegisterServer() function in its GameSession class. In the Byte Wars project, we have created a class in AGameSession (AccelByteWarsGameSession), which overrides the RegisterServer() to call the OnRegisterServerDelegates delegate. So, in this implementation, we will be binding our register server function to that delegate. For reference, the AccelByteWarsGameSession cpp file is located in /Source/AccelByteWars/Core/System/AccelByteWarsGameSession.cpp.

  2. 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);
  3. 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);
  4. 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));
    }
  5. 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;
    }
    }
  6. Finally, let's bind the RegisterServer to the OnRegisterServerDelegates delegate we mentioned earlier. Place it in the MultiplayerDSEssentialsSubsystemAMS_Starter class cpp file in this predefined function:

    void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    ...
    AAccelByteWarsGameSession::OnRegisterServerDelegates.AddUObject(this, &ThisClass::RegisterServer);
    ...
    }
  7. 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);
    }
  8. 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.

  1. First, make sure you have enable bManualRegisterServer=true in your SDK config.

  2. Open the MultiplayerDSEssentialsSubsystemAMS_Starter header file and declare the following function:

    private:
    void SendServerReady(const FName SessionName);
  3. 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);
  4. 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));
    }
  5. 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;
    }
    }
  6. 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 the OnRegisterServerDelegates delegate we mentioned earlier. Place it in the MultiplayerDSEssentialsSubsystemAMS_Starter class cpp file in this predefined function:

    void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    ...
    AAccelByteWarsGameSession::OnRegisterServerDelegates.AddUObject(this, &ThisClass::SendServerReady);
    ...
    }
  7. 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);
    }
  8. 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).

  1. Just like the register server functionality, we used GameSession to call the unregister server, but the built-in GameSession (AGameSession) does not have an unregister server or equivalent function. So, we created a new function called UnregisterServer in AccelByteWarsGameSession and triggered in AAccelByteWarsInGameGameMode::CloseGame. To implement the unregister function itself, you will need to bind the implementation to the AccelByteWarsGameSession::OnUnregisterServerDelegates delegate.

  2. 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);
  3. Add one more function declaration as the callback for UnregisterServer:

    private:
    void OnUnregisterServerComplete(const bool bSucceeded);
  4. Then, create the definition for the UnregisterServer. Open the MultiplayerDSEssentialsSubsystemAMS_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;
    }
  5. 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);
    }
  6. Next, let's bind UnregisterServer to the OnUnregisterServerDelegate delegate we mentioned earlier. Place the binding in the MultiplayerDSEssentialsSubsystemAMS_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);
    ...
    }
  7. 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.

  1. Open the MultiplayerDSEssentialsSubsystemAMS_Starter header file and add the following function declaration:

    private:
    void OnAMSDrainReceived();
  2. 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);
    }
  3. Then, add OnAMSDrainReceived under `UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize:.

    void UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize()
    {
    ...
    ABSessionInt->OnAMSDrainReceivedDelegates.AddUObject(this, &ThisClass::OnAMSDrainReceived);
    }
  4. Also, remove the delegate under the UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize() function.

    void UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
    {
    ...
    ABSessionInt->OnAMSDrainReceivedDelegates.RemoveAll(this);
    }
  5. Let's compile the project and make sure there are no compile errors.

  6. 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 enable Is Starter Mode Active.

    • Save the data asset.

      Unreal editor with Is Starter Mode Active selected in the DA_MultiplayerDSEssentials data asset

Congratulations! The AMS integration for your game server is completed.

Resources