Skip to content
UCIK Docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Host a listen server

Start gameplay from a lobby and connect players when the host is ready.

Keep these flows in your Game Instance Blueprint so they survive map travel. Configure Transport in Betide → Crossplay and package the gameplay map. The host must create a lobby; the joining flow below takes a lobby search result.

Host the lobby match

Host the lobby match
Preparing your graph…

Loading the interactive viewer.

Game module dependencies: UCIKCore, BetideCore.

#include "Engine/GameInstance.h"
#include "Lobbies/UCIKLobbySubsystem.h"
#include "UCIKCore.h"

void HostPartyMatch(UGameInstance &GameInstance)
{
    auto *Service = GameInstance.GetSubsystem<UUCIKLobbySubsystem>();
    if (!Service)
        return;

    TMap<FString, FString> Options;
    Service->HostLobbyListenServer(TEXT("GameLobby"), TEXT("/Game/Maps/Arena"), Options,
                                   FBetideCallback<FUCIK_Lobby>::CreateWeakLambda(
                                       &GameInstance, [](const TBetideResult<FUCIK_Lobby> &Result) {
                                           if (!Result.bOk)
                                           {
                                               UE_LOG(LogUCIK, Warning, TEXT("%s"),
                                                      *Result.Error.GetLogString());
                                               return;
                                           }
                                           UE_LOG(LogUCIK, Log, TEXT("Lobby match ready: %s"),
                                                  *Result.GetValue().Name.ToString());
                                       }));
}

Replace the map path with your packaged map. Only the lobby host should call this. The plugin opens the listen world, waits for its provider address, then advertises that the game is ready. A fixed Delay is not a readiness check.

Travel when the host is ready

Travel when the host is ready
Preparing your graph…

Loading the interactive viewer.

Game module dependencies: UCIKCore, BetideCore.

#include "Engine/GameInstance.h"
#include "Lobbies/UCIKLobbySubsystem.h"
#include "Travel/UCIKTravelSubsystem.h"
#include "UCIKCore.h"
#include "UCIKLobbyEventsSubsystem.h"

FDelegateHandle JoinAndWaitForMatch(UGameInstance &GameInstance, const FUCIK_Lobby &SelectedLobby)
{
    auto *Events = GameInstance.GetSubsystem<UUCIK_LobbyEventsSubsystem>();
    auto *Lobbies = GameInstance.GetSubsystem<UUCIKLobbySubsystem>();
    auto *Travel = GameInstance.GetSubsystem<UUCIKTravelSubsystem>();
    if (!Events || !Lobbies || !Travel)
        return {};

    const TWeakObjectPtr<UGameInstance> WeakGameInstance(&GameInstance);
    const auto Binding = MakeShared<FDelegateHandle>();
    const FUCIK_LobbyHandle ExpectedLobby = SelectedLobby.Primary;
    *Binding = Events->OnLobbyGameConnectionAvailableNative.AddWeakLambda(
        &GameInstance, [WeakGameInstance, Binding, ExpectedLobby](const FUCIK_Lobby &ReadyLobby) {
            if (!(ReadyLobby.Primary == ExpectedLobby) &&
                !ReadyLobby.Mirrors.Contains(ExpectedLobby))
                return;
            auto *Game = WeakGameInstance.Get();
            if (!Game)
                return;
            Game->GetSubsystem<UUCIK_LobbyEventsSubsystem>()
                ->OnLobbyGameConnectionAvailableNative.Remove(*Binding);
            Game->GetSubsystem<UUCIKTravelSubsystem>()->TravelToLobby(
                ReadyLobby,
                FBetideCallback<bool>::CreateWeakLambda(Game, [](const TBetideResult<bool> &Result) {
                    if (!Result.bOk)
                        UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
                    else
                        UE_LOG(LogUCIK, Log, TEXT("Travel requested"));
                }));
        });

    Lobbies->JoinLobby(
        SelectedLobby, false,
        FBetideCallback<FUCIK_Lobby>::CreateWeakLambda(
            &GameInstance, [WeakGameInstance, Binding](const TBetideResult<FUCIK_Lobby> &Result) {
                if (Result.bOk)
                {
                    UE_LOG(LogUCIK, Log, TEXT("Joined lobby; waiting for host"));
                    return;
                }
                if (auto *Game = WeakGameInstance.Get())
                    Game->GetSubsystem<UUCIK_LobbyEventsSubsystem>()
                        ->OnLobbyGameConnectionAvailableNative.Remove(*Binding);
                UE_LOG(LogUCIK, Warning, TEXT("%s"), *Result.Error.GetLogString());
            }));
    return *Binding;
}

void StopWaitingForMatch(UGameInstance &GameInstance, FDelegateHandle Binding)
{
    if (auto *Events = GameInstance.GetSubsystem<UUCIK_LobbyEventsSubsystem>())
        Events->OnLobbyGameConnectionAvailableNative.Remove(Binding);
}

Pass a search result to JoinAndWaitForMatch. The Blueprint stores it in PendingLobby (Crossplay Lobby) and ignores readiness events from other lobbies. Keep one join attempt active at a time. Call StopWaitingForMatch if the player leaves before the match starts. Turn off Party Follows Host when using this manual travel flow.

Host without a lobby

Host without a lobby
Preparing your graph…

Loading the interactive viewer.

Game module dependencies: UCIKCore, BetideCore.

#include "Engine/GameInstance.h"
#include "Travel/UCIKTravelSubsystem.h"
#include "UCIKCore.h"

void StartListenMatch(UGameInstance &GameInstance)
{
    auto *Travel = GameInstance.GetSubsystem<UUCIKTravelSubsystem>();
    if (!Travel)
        return;

    const FBetideError Error = Travel->SetGameNetDriverTransport({});
    if (Error.IsError())
    {
        UE_LOG(LogUCIK, Warning, TEXT("%s"), *Error.GetLogString());
        return;
    }
    Travel->HostListenServer(TEXT("/Game/Maps/Arena"), {},
                             FBetideCallback<FUCIK_ConnectInfo>::CreateWeakLambda(
                                 &GameInstance, [](const TBetideResult<FUCIK_ConnectInfo> &Result) {
                                     if (!Result.bOk)
                                         UE_LOG(LogUCIK, Warning, TEXT("%s"),
                                                *Result.Error.GetLogString());
                                     else
                                         UE_LOG(LogUCIK, Log, TEXT("Listen server ready"));
                                 }));
}

An empty Provider restores your configured transport for the next map load. Publish Connect Info through your session or matchmaking flow; use session travel for joined session results.

Connection checks

The selected transport must match the active GameNetDriver. Let UCIK configure and validate it before travel. Do not assemble P2P addresses or overwrite reserved lobby metadata.

From a client or standalone world, hosting opens the map with ?listen; from an authoritative server world, it uses server travel. A successful travel request is not proof that the remote client connected: handle Unreal network/travel failures and verify the second player reaches the match.

Direct-IP hosts need a reachable game port. Test internet joins on separate machines as well as local play.

Was this page helpful?