Handle Game Server Communication #

Now that we’ve created our initial assets, we can start building out the PragmaGameServer with the necessary functionality to communicate with the Pragma Platform.

In this tutorial we’ll work with the PragmaGameServer.h header file and PragmaGameServer.cpp source file generated in the previous topic. Specifically, we’ll define the following methods, along with any required helper methods and variables:

  • Start(): initializes the server and begins broadcasting capacity to the Pragma backend
  • InitConnectionDetails(): ensures that we have the hostname and port for our game server
  • ReportCapacity(): starts report capacity polling
  • Shutdown(): shuts down the server (closes the application)
  • GameServerVersion(): sets the game server version

Set up the header file #

  1. In your PragmaGameServer.h header file, ensure that your includes section looks like the following:

    #include "CoreMinimal.h"
    #include "UObject/NoExportTypes.h"
    #include "Dto/PragmaGameInstanceRpcDto.h"
    #include "PragmaRuntime.h"
    #include "PragmaPlayer.h"
    #include "PragmaGameServerSubsystem.h"
    #include "PragmaGameServer.generated.h"
    
  2. Below the include statements, add the following:

    class UPragmaSession;
    
    UCLASS(Config=Game)
    
  3. Declare the Start() and Shutdown() functions in the UPragmaGameServer class under public.

    void Start();
    void ShutdownServer();
    
  4. Start() depends on several variables. Add the following under the protected access modifier:

    protected:
    	UPROPERTY()
    	UPragmaGameServerSubsystem* Subsystem;
    
    	Pragma::FServerPtr Server;
    	Pragma::FRuntimePtr Runtime;
    

    These variables hold the Pragma Game Server Subsystem and pointers to the Pragma Server and Runtime objects.

  5. Declare InitConnectionDetails() and ReportCapacity() under the private access modifier:

    private:
    	void InitConnectionDetails();
    	void ReportCapacity();
    
  6. InitConnectionDetails() depends on the HostConnectionDetails variables. Add the following under the private access modifier:

    private:
    	FPragma_GameInstance_HostConnectionDetails HostConnectionDetails;
    
  7. ReportCapacity() depends on several events, variables, and helper methods related to game start data, server IDs, server pools, server versions, and timeouts. Let’s declare those:

    public:
    DECLARE_EVENT_OneParam(UPragmaGameServer, FGameStartDataReceivedEvent, FPragma_GameInstance_GameStart /* GameStartData */)
    FGameStartDataReceivedEvent OnGameStartDataReceived;
    
    protected:
    	// How long to wait for a game to be allocated before shutting down the server with a failure.
    	// -1 means it will never time out. Override with -PragmaReportCapacityTimeout=1234.
    	UPROPERTY(Config)
    	float ReportCapacityTimeout{10.f};
    
    	UPROPERTY(Config)
    	FString TestGameVersion{ "GameServerVersion1" };
    
    private:
    	const FString& GameServerVersion();
    	FString ServerId;
    	FString ServerPoolId;
    

    The game server version can be set via override, commandline parameter, or be hardcoded. Pragma Engine can enforce game server version compatibility; for this tutorial, we’re hardcoding the game server version to GameServerVersion1.

  8. Declare the following private variables that represent connection information:

    private:
    	FString PRAGMA_ADDRESS = "127.0.0.1";
    	int32 PRAGMA_PORT = 10100;
    	FString SOCIAL_TOKEN = "";
    	FString GAME_TOKEN = "";
    

    Don’t worry about the tokens being empty. We’ll create our tokens in a later step.

Build PragmaGameServer functionality #

Now, let’s define functionality for the above functions in the PragmaGameServer.cpp file.

  1. Define Start():

    void UPragmaGameServer::Start()
    {
    	InitConnectionDetails();
    
    	auto* World = GetWorld();
    	checkf(World, TEXT("PragmaGameServer -- Must initialize UPragmaGameServer with an outer object that has a UWorld."));
    	auto* GameInstance = World->GetGameInstance();
    	check(GameInstance);
    	if (!GameInstance->IsDedicatedServerInstance())
    	{
    		PRAGMA_LOG(Verbose, "PragmaGameServer --  NOT running because we are not a dedicated server.");
    		return;
    	}
    	PRAGMA_LOG(Log, "PragmaGameServer -- starting...");
    
    	Subsystem = GameInstance->GetSubsystem<UPragmaGameServerSubsystem>();
    	Runtime = Subsystem->Runtime();
    	Server = Subsystem->Server();
    
    	Runtime->Config().PartnerBackendAddress = "http://" + PRAGMA_ADDRESS + ":" + FString::FromInt(PRAGMA_PORT);
    	Runtime->Config().PartnerSessionSocialAuthToken = SOCIAL_TOKEN;
    	Runtime->Config().PartnerSessionGameAuthToken = GAME_TOKEN;
    
    	Runtime->Server()->Connect(Pragma::FServer::FConnectedDelegate::CreateLambda([this](const TPragmaResult<>& Result)
    		{
    			if (Result.IsFailure())
    			{
    				PRAGMA_LOG(Log, "Connect failed");
    				return;
    			}
    			ReportCapacity();
    		}));
    
    	if (!FParse::Value(FCommandLine::Get(), *ServerIdSourceCliParam, ServerId) || ServerId.IsEmpty())
    	{
    		ServerId = FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens);
    		PRAGMA_LOG(Error, "PragmaGameServer -- No ServerId specified with configured command line param '%s'. A new ServerId has been generated: %s", *ServerIdSourceCliParam, *ServerId);
    	}
    }
    

    After ensuring that we have the connection details for the game server, Start() checks to ensure we are a game server. It then initializes the Pragma SDK and connects to the Pragma Engine backend. Finally, it checks that the game server has been assigned an ID. If no ID has been assigned, it generates one.

  2. Define InitConnectionDetails():

    void UPragmaGameServer::InitConnectionDetails()
    {
    	if (!FParse::Value(FCommandLine::Get(), *HostnameCliParam, HostConnectionDetails.Hostname))
    	{
    		PRAGMA_LOG(Error, "PragmaGameServer -- Failed to get Hostname from cli parameter '%s' and no hostname was configured in ini. This is likely a problem with backend configuration. Please contact your Pragma representative with this error message.", *HostnameCliParam);
    		ShutdownServer();
    		return;
    	}
    	checkf(GetWorld(), TEXT("PragmaGameServer -- needs to be created with an outer class that has access to World."));
    	HostConnectionDetails.Port = GetWorld()->URL.Port;
    	PRAGMA_LOG(Log, "PragmaGameServer -- Game server is located at: %s:%d", *HostConnectionDetails.Hostname, HostConnectionDetails.Port)
    }
    

    InitConnectionDetails() ensures that we have the hostname and port for our game server. This information is required for the game clients to connect to the game server. Our game server provides this information to Pragma, which then provides the connection details to the game clients when the game server is ready to accept clients.

  3. Define ReportCapacity():

    void UPragmaGameServer::ReportCapacity()
    {
    	FParse::Value(FCommandLine::Get(), TEXT("-PragmaReportCapacityTimeout="), ReportCapacityTimeout);
    	FParse::Value(FCommandLine::Get(), TEXT("-PragmaServerPoolId="), ServerPoolId);
    
    	Server->MatchApi().OnGameStart.AddWeakLambda(this, [this](FPragma_GameInstance_GameStart GameStart)
    	{
    		OnGameStartDataReceived.Broadcast(MoveTemp(GameStart));
    	});
    
    	Server->MatchApi().OnGameStartFailed.AddWeakLambda(this,[this](FPragmaError Error)
    	{
    		PRAGMA_LOG(Error, "PragmaGameServer -- ReportCapacity failed with error: %s", *Error.ErrorCode());
    		ShutdownServer();
    	});
    
    	Server->MatchApi().StartReportCapacityPolling(ServerId, ServerPoolId, ReportCapacityTimeout);
    }
    

    ReportCapacity() is called when the game server successfully connects to the Pragma backend. It starts report capacity polling, which is the game server reporting to the pragma backend how many matches it is currently running and how many it can handle. It also reports with its unique ID and its version. This report occurs every second.

  4. Define ShutdownServer():

    void UPragmaGameServer::ShutdownServer()
    {
    	FGenericPlatformMisc::RequestExit(false);
    }
    
  5. Define GameServerVersion():

    const FString& UPragmaGameServer::GameServerVersion()
    {
    	FParse::Value(FCommandLine::Get(), TEXT("-PragmaGameServerVersion="), TestGameVersion);
    	return TestGameVersion;
    }
    

    GameServerVersion() sets the game server version. In this tutorial, we set the version in the header file.

In the next section, we’ll build out the game mode base files.