Connection

App ID and Fusion Settings

Project-wide Fusion configuration can be found in Project Settings > Fusion Settings in the Unreal editor. The most important fields are AppId and AppVersion. The App ID identifies the project on the Photon Cloud and AppVersion segments players by client build so incompatible versions cannot share rooms.

Get an App ID for the project from the Photon Dashboard and paste it into the App ID field as shown in the Quick Start Guide.

Connecting to Photon

In Blueprints, the entry point is the latent node "Connect To Photon", which maps to ConnectToPhoton(FFusionConnectOptions, WorldContextObject) and returns a UFusionConnectToPhotonAsync with OnSuccess and OnFailure multicast pins. That node is a convenience wrapper built for Blueprint use; under the hood it drives RealtimeCore::Matchmaking::RealtimeClient::Connect.

Connect to Photon in Blueprint
Connect to Photon in Blueprint

When connecting through Blueprints, the latent node automatically sets PhotonMatchmaking::RealtimeClient on the UFusionOnlineSubsystem. Subsequent calls to check the connection status and join/create rooms can then be made. The other latent nodes rely on having the PhotonMatchmaking::RealtimeClient set on the UFusionOnlineSubsystem.

From C++, you call that matchmaking API directly and skip the latent node entirely.

C++

// Create a standalone matchmaking client. AppId / AppVersion / region mode come
// from Fusion Settings. It is NOT on the subsystem yet.
FFusionConnectOptions ConnectOpts;
ConnectOpts.RegionSelectionMode = EFusionRegionSelectionMode::Best;
UFusionRealtimeClient* RealtimeClient = UFusionRealtimeClient::CreateRealtimeClient(ConnectOpts);

RealtimeCore::Matchmaking::RealtimeClient* Client = RealtimeClient->GetClient();

RealtimeCore::Matchmaking::ConnectOptions RtOpts;
RtOpts.Auth.UserId = reinterpret_cast<const RealtimeCore::Common::CharType*>(
    StringCast<UTF8CHAR>(*FGuid::NewGuid().ToString()).Get());
// Leave RtOpts.ServerAddress empty for the Photon Cloud NameServer.

RealtimeCore::Matchmaking::Task Task = Client->Connect(RtOpts);

// For illustration only: Poll from Tick or a timer instead of blocking the game thread.
while (!Task.IsReady())
{
      RealtimeClient->Service();
      FPlatformProcess::Sleep(0.01f);
}

RealtimeCore::Matchmaking::Result Result = Task.Get();

The one hard requirement is that something calls Service() on the client every tick; that is what advances the connection and dispatches incoming events. While the client is standalone, you pump it yourself; once you hand it to the subsystem via UFusionOnlineSubsystem::SetRealtimeClient, UFusionOnlineSubsystem::WorldTick does it for you.

Joining or Creating a Room

Once connected, the subsystem exposes five room entry points. CreateRoom, JoinOrCreateRoom and JoinOrCreateRandomRoom all take an FFusionRoomOptions struct describing the room to create. JoinRoom(RoomName) and JoinRandomRoom join an existing room without creating one: the by-name variant requires the exact room name, and the random variant picks any open and visible room.

For Blueprint projects, it is then possible to join a room using one of the available join room actions.

JoinOrCreateRoom in Blueprint
JoinOrCreateRoom in Blueprint

C++

RealtimeCore::Matchmaking::RealtimeClient* Client = RealtimeClient->GetClient();
const auto RoomName = StringCast<UTF8CHAR>(TEXT("MyRoomName"));

RealtimeCore::Matchmaking::CreateRoomOptions RoomOpts;
RoomOpts.MaxPlayers = 4;

RealtimeCore::Common::Task<RealtimeCore::Common::Result<RealtimeCore::Matchmaking::MutableRoomView, RealtimeCore::Matchmaking::ErrorCode>> Task =
      Client->JoinOrCreateRoom(reinterpret_cast<const RealtimeCore::Common::CharType*>(RoomName.Get()), RoomOpts);

// For illustration only: Poll from Tick or a timer instead of blocking the game thread.
while (!Task.IsReady())
{
      RealtimeClient->Service();
      FPlatformProcess::Sleep(0.01f);
}

If you would rather not call Service() yourself, the inverse also works: assign the client to UFusionOnlineSubsystem up front so WorldTick services it from the start.

C++

// Member of your class: the subscriptions stay alive as long as it does.
RealtimeCore::Common::SubscriptionBag Subscriptions;

UFusionOnlineSubsystem* Subsystem = GetGameInstance()->GetSubsystem<UFusionOnlineSubsystem>();

UFusionRealtimeClient* RealtimeClient = UFusionRealtimeClient::CreateRealtimeClient(FFusionConnectOptions{});
// The subsystem holds the client from here on and calls Service() on it every frame.
Subsystem->SetRealtimeClient(RealtimeClient);

RealtimeCore::Matchmaking::ConnectOptions RtOpts;
RtOpts.Auth.UserId = reinterpret_cast<const RealtimeCore::Common::CharType*>(
StringCast<UTF8CHAR>(*FGuid::NewGuid().ToString()).Get());

RealtimeClient->PendingVoidTasks.push_back(RealtimeClient->GetClient()->Connect(RtOpts));

// Once connected (Subsystem->IsConnected()):
UFusionRealtimeClient*                     RealtimeClient = Subsystem->GetRealtimeClient();
RealtimeCore::Matchmaking::RealtimeClient* Client = RealtimeClient->GetClient();

Subscriptions += Client->OnRoomJoined.Subscribe([this, Subsystem, RealtimeClient]() {
Subsystem->StartFusionSession(RealtimeClient, GetWorld());
});

// Must be called before joining. Fusion needs its core client to exist before the join request is sent.
RealtimeClient->CreateFusionCoreClient();

RealtimeCore::Matchmaking::CreateRoomOptions RoomOpts;
RoomOpts.MaxPlayers = 4;

const auto RoomName = StringCast<UTF8CHAR>(TEXT("MyRoomName"));

// The subsystem drops the task once it is ready, so react to failure in OrElse.
RealtimeClient->PendingRoomTasks.push_back(
Client->JoinOrCreateRoom(reinterpret_cast<const RealtimeCore::Common::CharType*>(RoomName.Get()), RoomOpts)
.OrElse([](RealtimeCore::Matchmaking::Error Error) {
UE_LOG(LogTemp, Error, TEXT("JoinOrCreateRoom failed: %s"),
UTF8_TO_TCHAR(reinterpret_cast<const char*>(Error.Message.c_str())));
return RealtimeCore::Matchmaking::Result<RealtimeCore::Matchmaking::MutableRoomView>::Err(std::move(Error));
}));

The room entry points map one-to-one onto RealtimeCore::Matchmaking::RealtimeClient methods: JoinOrCreateRoom, JoinRandomOrCreateRoom, CreateRoom, JoinRoom(name) and JoinRandomRoom. The FFusionRoomOptions struct used by the Blueprint nodes is just a wrapper; FusionMatchmakingHelpers::CreatePhotonRoomOption converts it to the RealtimeCore::Matchmaking::CreateRoomOptions these methods take, so C++ code can build either one.

For Blueprints, the convenience node "Connect And Join Room" (ConnectAndJoinRoom) chains connect, join and Fusion start in a single action. The C++ equivalent is the snippet above.

Async Actions

Every connect and room call returns a UFusionRoomActionBase subclass, a Blueprint latent action that exposes OnSuccess and OnFailure multicast delegates as exec pins in Blueprints and as FMulticastDynamicDelegate members in C++. OnFailure carries an EFusionActionFailureCodes value describing the reason. These actions exist for Blueprint use; C++ code that wants to avoid them uses the RealtimeCore::Matchmaking::RealtimeClient directly, as shown in Connecting to Photon above.

Async class Subsystem method Wraps
UFusionConnectToPhotonAsync ConnectToPhoton Initial cloud connection
UFusionDisconnectFromPhotonAsync DisconnectFromPhoton Full disconnect from the cloud
UFusionJoinOrCreateRoomAsync JoinOrCreateRoom, JoinOrCreateRandomRoom Join a matching room or create one
UFusionCreateRoomAsync CreateRoom Create a new room unconditionally
UFusionJoinRoomAsync JoinRoom, JoinRandomRoom Join an existing room
UFusionLeaveRoomAsync LeaveRoom Leave the current room, stay connected
UFusionConnectAndJoinRoomAsync ConnectAndJoinRoom Connect plus join in one action

EFusionActionFailureCodes covers the practical failure cases: TimeOut, InvalidRegion, ClientAlreadyExists, Disconnected and the already-in-that-state codes Connecting, JoiningRoom and InRoom, which fire when the requested action is redundant because the client has already reached that state (for example, calling connect while already connected, or join/create while already in a room).

Connection Options and Regions

FFusionConnectOptions carries two fields. Region is a region code string such as "us" or "eu"; see Regions for the full list. RegionSelectionMode is an EFusionRegionSelectionMode enum that decides how the Region field is interpreted.

Mode Behavior
Default Use the first available region. The Region string is ignored.
Select Connect to the region named in the Region string.
Best Ping every available region, pick the lowest-RTT one, and cache the result.

C++

FFusionConnectOptions ConnectOpts;
ConnectOpts.Region = TEXT("us");
ConnectOpts.RegionSelectionMode = EFusionRegionSelectionMode::Select;

For production, ship with Best on first launch so the player lands on the lowest-RTT region without any UI. Persist the chosen region and switch to Select with that value on subsequent launches to skip the ping pass.

Room Options

FFusionRoomOptions describes the room being created or joined.

Field Type Effect
RoomName FString Unique name within the App ID. Required for invite-style flows; auto-generated when joining randomly.
MaxPlayers uint8 Maximum concurrent players in the room.
bIsOpen bool If false, the room rejects new joins. Set false to seal a match in progress.
bIsVisible bool If false, the room does not appear in random-join results. Invite-only rooms set this false.
EmptyTTL uint8 (seconds) How long the room survives after the last player leaves. Converted to ms internally.
PlayerTTL uint8 (seconds) How long a disconnected player stays in the room as inactive, keeping their slot so they can rejoin. 0 removes the player immediately. Converted to ms internally.
InitialWorld TSoftObjectPtr<UWorld> World to load when the local player joins. Skips a separate ChangeWorld call on the Master Client.

C++

FFusionRoomOptions RoomOpts;
RoomOpts.RoomName = TEXT("Match-42");
RoomOpts.MaxPlayers = 8;
RoomOpts.EmptyTTL = 0;
RoomOpts.PlayerTTL = 2;
RoomOpts.InitialWorld = LobbyWorldPtr;

Set InitialWorld whenever the room creator already knows the starting map. The local client loads that world automatically as it joins, and the Master Client does not need to issue a separate ChangeWorld call after OnSuccess.

Leaving the Room and Reconnecting

LeaveRoom(WorldContextObject) returns a UFusionLeaveRoomAsync, and after OnSuccess, the client is back in EFusionStatus::Connected. It can call JoinOrCreateRoom again without re-running ConnectToPhoton. For a full teardown of the Photon connection, use DisconnectFromPhoton, which returns the client to EFusionStatus::None.

Connection Status and Events

Status() returns the current EFusionStatus. The normal lifecycle traces None → Connecting → Connected → JoiningRoom → InRoom → LeavingRoom, with Disconnected and Error reachable from any non-terminal state. The transitions are introduced in Concepts.

Query Returns
IsConnected() True when the client has reached at least Connected.
IsInRoom() True when the client is fully in a room (InRoom).
IsJoiningOrInRoom() True for both JoiningRoom and InRoom — useful for "session is forming or formed" gates.
IsMasterClient() True when this client is the elected Master Client of the current room.
PlayerCount() Current number of players in the room.
GetLocalPlayerId() Stable int32 id of the local player for this session.
GetRtt() Round-trip time to the Photon relay, in milliseconds.
CurrentRoomInfo(Name, Players) Room name and current player count as out params.

Last updated on

Back to top