Keywords: UE4, Networking, Dedicated Server,ClientTravel, ServerTravel, UEngine::Browse

Notices

Difference between ServerTravel and ClientTravel
  • ServerTravel also informs the clients to move along with the server.
  • ClientTravel will be called locally for the client to load a new map. or connect to ip.
  • Browse can load local map via LoadMap ( not for multiplayer ) but there are more things happening in this method. I also want to know more about what it does and what it should be used for.
  • LoadMap should also load a map for a local client

Browse, LoadMap, ServerTravel and ClientTravel?
https://answers.unrealengine.com/questions/122565/browse-servertravel-and-clienttravel.html

What’s the different between absolute travel and relative travel?
https://answers.unrealengine.com/questions/101284/what-the-different-between-absolute-travel-and-rel.html

What is the difference between ServerTravel and OpenLevel?
https://answers.unrealengine.com/questions/55477/the-difference-of-the-two-functions.html

PlayerController->ClientTravel() (Connect to remote server)
https://answers.unrealengine.com/questions/29017/gamemode-multiplayer-c.html

Seamless and non-seamless travel
https://docs.unrealengine.com/latest/INT/Gameplay/Networking/Travelling

Cases

How to pass arguments from Client to Server

When execute PlayerController->ClientTravel, default URL is like this:

FString Address = TEXT("127.0.0.1:7777");

If want to pass arguments to Server when logining, URL format is like this:

FString Address = FString::Printf(TEXT("127.0.0.1:7777?Param1=%s?Param2=%s"), *Param1, *Param2);
How to parse arguments sent by Client on Server

Overrice function InitNewPlayer() in GameMode, then parse arguments like this:

FString AMyGameMode::InitNewPlayer(APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId, const FString& Options, const FString& Portal = TEXT(""))
{
    FString Param1 = UGameplayStatics::ParseOption(Options, TEXT("Param1"));
    FString Param2 = UGameplayStatics::ParseOption(Options, TEXT("Param2"));
}

Official Doc: Passing Arguments To Server During Connection
https://wiki.unrealengine.com/Passing_Arguments_To_Server_During_Connection

How does Server informs Clients which are connected to Server to travel map

Execute ClientTravel on Server:

void AMyGameModeBase::PostLogin(APlayerController* NewPlayer)
{
    Super::PostLogin(NewPlayer);
    
    FString URL = TEXT("/Game/Maps/TestMap");
    NewPlayer->ClientTravel(URL, TRAVEL_Absolute);
    
}

This way works right for travelling to new map, but Client would also disconnect to Server. It means that the NetMode of all Actors in Client would be set as NM_Standalone.

How to use ServerTravel

A common use case is starting a server in a lobby level. Clients connect to this lobby level and choose loadout and character, for example. When ready, the server triggers a ServerTravel, which transitions all clients into the main game level.

When ServerTravel is triggered, the server tells all clients to begin to ClientTravel to the map specified. If the ServerTravel is seamless then the client maintains its connection to the server. If it’s not seamless then all the clients disconnect from the server and reconnect once they have loaded the map. Internally, the server does a similar process: it loads in the new level, usually a game world for all the clients to play on, and begins accepting player spawn requests once ready.

Reference: Map travel
https://docs.improbable.io/unreal/alpha/content/map-travel#in-native-unreal-1

Enabling Seamless Travel

To enable seamless travel, you need to setup a transition map. This is configured through the UGameMapsSettings::TransitionMap property. By default this property is empty, and if your game leaves this property empty, an empty map will be created for the transition map.

The reason the transition map exists, is that there must always be a world loaded (which holds the map), so we can’t free the old map before loading the new one. Since maps can be very large, it would be a bad idea to have the old and new map in memory at the same time, so this is where the transition map comes in.

So now we can travel from the current map to the transition map, and then from there we can travel to the final map. Since the transition map is very small, it doesn’t add much extra overhead while it overlaps the current and final map.

Once you have the transition map setup, you set AGameModeBase::bUseSeamlessTravel to true, and from there seamless travel should work!

Origin:
https://docs.unrealengine.com/4.27/en-US/InteractiveExperiences/Networking/Travelling/

ClientTravel

See the usage of APlayerController->ClientTravel() in offical example Shooter Game.

Seamless travel

After seamless travel AGameModeBase::GenericPlayerInitialization(AController* Controller) and AGameModeBase::HandleStartingNewPlayer(), Gets called for the client.

Related
https://answers.unrealengine.com/questions/251940/post-login-event-not-firing-on-active-clients-afte.html

Travelling API in GameMode

ClientTravel:

APlayerController* AGameModeBase::ProcessClientTravel(FString& FURL, FGuid NextMapGuid, bool bSeamless, bool bAbsolute)
{
    // We call PreClientTravel directly on any local PlayerPawns (ie listen server)
    APlayerController* LocalPlayerController = nullptr;
    for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator)
    {
        if (APlayerController* PlayerController = Iterator->Get())
        {
            if (Cast<UNetConnection>(PlayerController->Player) != nullptr)
            {
                // Remote player
                PlayerController->ClientTravel(FURL, TRAVEL_Relative, bSeamless, NextMapGuid);
            }
            else
            {
                // Local player
                LocalPlayerController = PlayerController;
                PlayerController->PreClientTravel(FURL, bAbsolute ? TRAVEL_Absolute : TRAVEL_Relative, bSeamless);
            }
        }
    }

    return LocalPlayerController;
}

ServerTravel:

void AGameModeBase::ProcessServerTravel(const FString& URL, bool bAbsolute)
{
#if WITH_SERVER_CODE
    StartToLeaveMap();

    // Force an old style load screen if the server has been up for a long time so that TimeSeconds doesn't overflow and break everything
    bool bSeamless = (bUseSeamlessTravel && GetWorld()->TimeSeconds < 172800.0f); // 172800 seconds == 48 hours

    FString NextMap;
    if (URL.ToUpper().Contains(TEXT("?RESTART")))
    {
        NextMap = UWorld::RemovePIEPrefix(GetOutermost()->GetName());
    }
    else
    {
        int32 OptionStart = URL.Find(TEXT("?"));
        if (OptionStart == INDEX_NONE)
        {
            NextMap = URL;
        }
        else
        {
            NextMap = URL.Left(OptionStart);
        }
    }

    FGuid NextMapGuid = UEngine::GetPackageGuid(FName(*NextMap), GetWorld()->IsPlayInEditor());

    // Notify clients we're switching level and give them time to receive.
    FString URLMod = URL;
    APlayerController* LocalPlayer = ProcessClientTravel(URLMod, NextMapGuid, bSeamless, bAbsolute);

    UE_LOG(LogGameMode, Log, TEXT("ProcessServerTravel: %s"), *URL);
    UWorld* World = GetWorld();
    check(World);
    World->NextURL = URL;
    ENetMode NetMode = GetNetMode();

    if (bSeamless)
    {
        World->SeamlessTravel(World->NextURL, bAbsolute);
        World->NextURL = TEXT("");
    }
    // Switch immediately if not networking.
    else if (NetMode != NM_DedicatedServer && NetMode != NM_ListenServer)
    {
        World->NextSwitchCountdown = 0.0f;
    }
#endif // WITH_SERVER_CODE
}

Reference

Official Docs

Travelling in Multiplayer
https://docs.unrealengine.com/en-US/Gameplay/Networking/Travelling/index.html


山舞银蛇,原驰蜡象,欲与天公试比高。须晴日,看红装素裹,分外妖娆。----毛泽东《沁园春·雪》