Skip to main content

Implementing the subsystem - Game server integration - (Unreal Engine module)

Last updated on March 12, 2025

Game server with AMS flow

Before you begin, let's understand how game server is managed by AccelByte Multiplayer Servers (AMS). Take a look at the following state diagram showing the states that the server will go through and how to transition between each of them:

Connect to AMS WebSocket
Send ready message
Players logged into server
Game over
Received drain signal
Received drain signal
Disconnect from AMS
shutdown
Created
Connected
Ready
Serving players
Disconnecting
Disconnected

When the game server starts, it connects to the AMS via WebSocket. After the connection has been established, the game server sends a message indicating that it is ready. Once the game server is ready, your backend service can claim it to serve game sessions for your game clients. If your game session is over, the game server must disconnect from the AMS WebSocket and shut itself down.

AMS also able to send drain event to your game server to remove unused servers. When this event happen, if your game server is not serving any game session (e.g. there is no player in your server, or the game is over) you should shutdown your game server to avoid unneeded cost.

Unwrap the subsystem

To follow this tutorial, a Game Instance Subsystem called AMSModuleSubsystem_Starter has been prepared for you. This class is available in the Resources section and consists of the following files:

  • Header file: /Source/AccelByteWars/TutorialModules/Play/AMSModule/AMSModuleSubsystem_Starter.cpp
  • CPP file: /Source/AccelByteWars/TutorialModules/Play/AMSModule/AMSModuleSubsystem_Starter.h

The AMSModuleSubsystem_Starter class has several functionalities provided.

  • Functions to bind and unbind some functions to Byte Wars core game's delegates.
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);
}
  • Functions that other object will use to interact with this class. The definition of these functions are currently empty, you will later define these functions.
public:
// ...
void RegisterServer(const FName SessionName);
void UnregisterServer(const FName SessionNam);
  • Functions to clear delegates. This will unbind its own delegate from other object that may have bound something to it.
private:
// ...
void UnbindConnectDelegates();
void UnbindAllDelegates();
void UAMSModuleSubsystem_Starter::UnbindConnectDelegates()
{
bIsRegistering = false;

OnConnectSuccessDelegate.Unbind();
OnConnectErrorDelegate.Unbind();
OnConnectClosedDelegate.Unbind();
}
void UAMSModuleSubsystem_Starter::UnbindAllDelegates()
{
UnbindConnectDelegates();
OnDrainReceivedDelegate.Unbind();
}
  • Variables to handle connection timeout, prevent multiple regsiter running at once, and session name that will be used for the in-game session, not the cloud session.
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;
  • Timer handles. You will use this later to handle connection timed out.
private:
// ...
FTimerHandle ConnectionTimerHandle;
FTimerHandle DisconnectionTimerHandle;
  • Macro to make sure the code will only be compile when compiling for game server.
#if UE_EDITOR || UE_SERVER
//...
#endif

Integrate AMS to game server

  1. Open the AMSModuleSubsystem_Starter Header file and add the following function declarations. These are the functions that the server need to actively trigger to send message to the AMS.

    private:
    void Connect();
    void Disconnect();
    void SendServerReady();
  2. Open the AMSModuleSubsystem_Starter CPP file and define the Connect function. This function does several things other than executing the WebSocket connect:

    • Bind all of the connect delegates, which will handle if the connect function failed, succeded, or closed.

    • Bind a handler to the drain signal.

    • Trigger a timeout timer which will handle if nothing happen after calling connect in a certain amount of time.

    • Calling the connect function.

      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();
      }
      }
  3. Define the Disconnect function which also does several things:

    • Unbind the drain and all connect delegates
    • Start the timeout timer which will handle if nothing happen after calling disconnect in a certain amount of time.
    • Calling the disconnect function.
    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();
    }
  4. Define the SendServerReady function. This will let AMS know that the server is ready to receive players.

    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();
    }
  5. Open the AMSModuleSubsystem_Starter Header file and add the following function declarations. These functions will handle what happen the game got responses from trying to connect to the AMS.

    private:
    // ...
    void OnConnectSuccess();
    void OnConnectError(const FString& ErrorMessage);
    void OnConnectClosed(int32 StatusCode, FString const& Reason, bool bWasClean);
  6. Open the AMSModuleSubsystem_Starter CPP file and define the OnConnectSuccess function. This will unbind the connect delegates and send server ready message immediately. You don't have to send server ready message immediately in this step in your game, if your game need time to set somethings up, you should call the setup here and send the server ready message afterward.

    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();
    }
  7. Define the OnConnectError function. This will handle what happen if the server failed, which is simply unbind the connect delegates. But, there's another handler that will handle what happens next which you will implement later.

    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();
    }
  8. Define the OnConnectClosed function. This will handle what happen when the connection is closed. Similar with previous function, this will simply unbind the connect delegates. Another handler will take over on what will happen next.

    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();
    }
  9. Go back to the AMSModuleSubsystem_Starter Header file and add the following function declaration.

    private:
    // ...
    void OnDisconnected(bool bIsSucceeded, const FString& ErrorMessage);
  10. Open the AMSModuleSubsystem_Starter CPP file and define the function. This will shut the server down.

    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);
    }
  11. Open the AMSModuleSubsystem_Starter Header file and add the following function declaration. As the name suggest, this is a handler when the game receives drain signal which you have bound in previous step.

    private:
    // ...
    void OnDrainReceived();
  12. Open the AMSModuleSubsystem_Starter CPP file and define the function. The ideal way to handle this is, if the server is currently serving players, ignore the drain signal. If not, shut the server down immediately. That is exactly what this function does.

    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."));
    }
  13. Open the AMSModuleSubsystem_Starter Header file and add the following function declarations. Notice that, in previous steps, there are mentions about timeout. These are the functions that will handle those timeouts.

    private:
    // ...
    void CheckConnection();
    void CheckDisconnection();
  14. Open the AMSModuleSubsystem_Starter CPP file and define the CheckConnection function. This function periodically checks whether the game server is connected to AMS. If the predefined time passes and the game server is still not connected, it will attempt to reconnect. This process will repeat for a predefined number of attempts. If the connection still fails, the function will treat it as a failure and call the OnConnectError function.

    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--;
    }
  15. Define the CheckDisconnection function. This function server similar purpose as CheckConnection, but instead of checking for connection, it checks for disconnection. This function also won't attempt to call the disconnect again if it fail. It will call the OnDisconnected immediately after the first timeout, which will shut the server down.

    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--;
    }
  16. Still in the CPP file, go to the RegisterServer function and replace the definition with the code below. This function does several things:

    • Prevent attempting to connect to AMS when it is already connected or currently trying to connect.
    • Reset the timeout timer.
    • Execute the connect to AMS function.
    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();
    }
  17. Go to the UnregisterServer function and replace the definition with the code below. Since the disconnect function already handle what happen if the game currently already not connected to AMS, this function will simply call Disconnect and reset the stored game session name.

    void UAMSModuleSubsystem_Starter::UnregisterServer(const FName SessionNam)
    {
    UE_LOG_AMS_MODULE(Log, TEXT("Unregister server from AMS."));

    CurrentSessionName = FName(FString());
    Disconnect();
    }
  18. Build your project and open it in the Unreal Engine Editor. In the Editor, go to /Content/TutorialModules/Play/AMSModule/. You will find a data asset called DA_AMSModule. Open it and enable the Is Starter Mode Active. Then, save the data asset. This will activate the widgets so you can navigate through them when you play the game.

    Activate starter mode

Resources