Unreal Engine Module - Running DS on Armada - Use Online Subsystem to set up a server
Dedicated server flow
Let's start by understanding the flow of how a game server is managed by Armada. Take a look at the graph below.
About the subsystem
In the Byte Wars module starter project, you will see a subsystem called the MultiplayerDSEssentialsSubsystem_Starter
. This class provides necessary declarations and definitions, so you can begin using them to implement DS functionalities right away.
The MultiplayerDSEssentialsSubsystem_Starter
class files can be found at the following:
- Header file can be found in
/Source/AccelByteWars/TutorialModules/Play/MultiplayerDSEssentials/MultiplayerDSEssentialsSubsystem_Starter.h
. - CPP file can be found in
/Source/AccelByteWars/TutorialModules/Play/MultiplayerDSEssentials/MultiplayerDSEssentialsSubsystem_Starter.cpp
.
Let's take a look at what we have provided in the class.
In the
SessionEssentialsOnlineSession_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
or not, to prevent the server sending unnecessary HTTP call.bUnregisterServerRunning
: similar as before, to prevent the server callingUnregisterServer
when it has been called and is currently waiting for the response.- Lastly,
ABSessionInt
, which is our interface to the OSS itself.
Now, navigate to
SessionEssentialsOnlineSession_Starter
class CPP file, and on to theUMultiplayerDSEssentialsSubsystem_Starter::Initialize
. 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 UMultiplayerDSEssentialsSubsystem_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 Armada. To do this, you just need to make sure you have set up your Server SDK correctly, and your game server will automatically perform login to Armada.
Register server
After successfully logging in, your game server needs to be registered to Armada. This allows Armada to recognize and manage your game server properly.
Unreal Engine have a built-in
RegisterServer()
function in itsGameSession
class. In the Byte Wars project, we have created a class ofAGameSession
, theAccelByteWarsGameSession
, which overrides theRegisterServer()
to callOnRegisterServerDelegates
delegate. So, in this implementation, we will be binding our register server function to that delegate. For reference, theAccelByteWarsGameSession
class 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 AccelByte OSS. First, open the
MultiplayerDSEssentialsSubsystem_Starter
class 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
completed.private:
void OnRegisterServerComplete(const bool bSucceeded);Next, create the function definition in the
MultiplayerDSEssentialsSubsystem_Starter
class CPP file. Then, add the following code to register your game server with Armada.void UMultiplayerDSEssentialsSubsystem_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 definition for the callback. We will simply call log and flag the DS as already registered so any more attempt to register will be canceled, saving HTTP call.
void UMultiplayerDSEssentialsSubsystem_Starter::OnRegisterServerComplete(const bool bSucceeded)
{
UE_LOG_MultiplayerDSEssentials(Log, TEXT("succeeded: %s"), *FString(bSucceeded ? "TRUE": "FALSE"))
if (bSucceeded)
{
bServerAlreadyRegister = true;
}
}Let's bind the
RegisterServer
to theOnRegisterServerDelegates
delegate we mentioned earlier. Place it in theMultiplayerDSEssentialsSubsystem_Starter
class CPP file in this predefined function.void UMultiplayerDSEssentialsSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
{
...
AAccelByteWarsGameSession::OnRegisterServerDelegates.AddUObject(this, &ThisClass::RegisterServer);
...
}Next, unbind the delegate when the
MultiplayerDSEssentialsSubsystem_Starter
is uninitialized. You can do it inside this predefined function.void UMultiplayerDSEssentialsSubsystem_Starter::Deinitialize()
{
...
AAccelByteWarsGameSession::OnRegisterServerDelegates.RemoveAll(this);
}There you have it. Register server to Armada implementation is completed. Please compile your project and make sure there are no errors.
Unregister and shutdown server
Once the game is over, you should unregister your game server from Armada and then shut it down. This prevents your servers from becoming zombie servers (session ended, but the server still active).
Just like the register server functionality, we used
GameSession
to unregister server. But, the built-inGameSession
, theAGameSession
, doesn't have 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
MultiplayerDSEssentialsSubsystem_Starter
class 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 theMultiplayerDSEssentialsSubsystem_Starter
class CPP file and add the following code.void UMultiplayerDSEssentialsSubsystem_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 definition for the callback function. We will be adding a logic to close the DS upon receiving the callback.
void UMultiplayerDSEssentialsSubsystem_Starter::OnUnregisterServerComplete(const bool bSucceeded)
{
UE_LOG_MultiplayerDSEssentials(Log, TEXT("succeeded: %s"), *FString(bSucceeded ? "TRUE": "FALSE"))
bUnregisterServerRunning = false;
FPlatformMisc::RequestExit(false);
}Next, bind the
UnregisterServer
to theOnUnregisterServerDelegate
delegate we mentioned earlier. Place the binding in theMultiplayerDSEssentialsSubsystem_Starter
class CPP file in this predefined function.void UMultiplayerDSEssentialsSubsystem_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.void UMultiplayerDSEssentialsSubsystem_Starter::Deinitialize()
{
...
AAccelByteWarsGameSession::OnRegisterServerDelegates.RemoveAll(this);
AAccelByteWarsGameSession::OnUnregisterServerDelegates.RemoveAll(this);
}Congratulations! Your implementation to unregister and shut down the game server is completed. Please compile your project again and make sure there are no errors.
Enabling your implementation
Build AccelByteWars project and open it with Unreal Editor once it's done building.
In the Unreal Editor, from the Content Browser, navigates to
/Content/TutorialModules/Play/MultiplayerDSEssentials/DA_MultiplayerDSEssentials.uasset
and enable theIs Starter Mode Active
. Save the Data Asset.There you have it. Your implementation will be used the next time you run the DS.
Resources
- The files used in this tutorial section are available in the Byte Wars GitHub repository.
- AccelByteWars/Content/TutorialModules/Play/MultiplayerDSEssentials/DA_MultiplayerDSEssentials.uasset
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MultiplayerDSEssentials/MultiplayerDSEssentialsSubsystem_Starter.h
- AccelByteWars/Source/AccelByteWars/TutorialModules/Play/MultiplayerDSEssentials/MultiplayerDSEssentialsSubsystem_Starter.cpp
- AccelByteWars/Source/AccelByteWars/Core/System/AccelByteWarsGameSession.cpp
- AccelByteWars/Source/AccelByteWars/Core/GameModes/AccelByteWarsInGameGameMode.cpp