Skip to main content

Use Online Subsystem to set up a server - Dedicated servers with AccelByte Multiplayer Servers (AMS) - (Unreal Engine module)

Last updated on March 20, 2024

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 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:

  • 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.

    bool bServerAlreadyRegister;
    bool bUnregisterServerRunning;
    FOnlineSessionV2AccelBytePtr ABSessionInt;
    • bServerAlreadyRegister: used to indicate 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.
  • Navigate to and open MultiplayerDSEssentialsSubsystemAMS_Starter CPP file, and then to UMultiplayerDSEssentialsSubsystemAMS_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 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, which you have set up in the previous tutorial (Set up IAM).

info

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.

  1. Unreal Engine has a built-in RegisterServer() function in its GameSession class. In the Byte Wars project, a class has been created in AGameSession (AccelByteWarsGameSession), which overrides the RegisterServer() to call the OnRegisterServerDelegates delegate. In this implementation, you will bind the 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, you will implement registering servers using AGS OSS. 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. 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. 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;
    }
    }
  6. 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. 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. 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.

  1. First, make sure you have enabled 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. 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. 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 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 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. 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. 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).

  1. Just like the register server functionality, you used GameSession to call the unregister server, but the built-in GameSession (AGameSession) does not have an unregister server or equivalent function. So, a new function called UnregisterServer has been created 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. 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. 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. 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);
    }
  6. 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. Unbind the delegate. Just like before, you will call the unbind in the Deinitialize function:

    void UMultiplayerDSEssentialsSubsystemAMS_Starter::Deinitialize()
    {
    ...
    AAccelByteWarsGameSession::OnRegisterServerDelegates.RemoveAll(this);
    AAccelByteWarsGameSession::OnUnregisterServerDelegates.RemoveAll(this);
    }
  8. 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.

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

    private:
    void OnAMSDrainReceived();
  2. 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. Add OnAMSDrainReceived under UMultiplayerDSEssentialsSubsystemAMS_Starter::Initialize:.

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

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

  6. Set the Starter files to active:

    1. Open the Unreal Engine Editor.

    2. In the content browser window, go to /Content/TutorialModules/Play/MultiplayerDSEssentials/.

    3. Open the data asset called DA_MultiplayerDSEssentials and enable Is Starter Mode Active.

    4. Save the data asset.

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

Resources