Ownership

Ownership Modes

Authority determines which client is allowed to write a networked object's properties. Only the authority client's values are accepted by the server, all other clients receive read-only copies. Fusion has several ownership modes that the Ownership value can be set to on the UFusionActorComponent to control how authority is assigned and transferred.

Each mode is a different rule for who can own the object and what happens to it when the owner disconnects. Pick it at design time.

Mode Behavior When the owner leaves
Transaction Take-and-release transaction semantics: a client requests ownership via SetWantsOwner(true) and the current owner grants or denies it. See Transaction. Becomes unowned.
PlayerAttached Same request handshake as Transaction. Destroyed.
Dynamic Any client can claim ownership optimistically, without owner approval. Suited to objects that change ownership frequently (e.g. physics objects). See Dynamic. Becomes unowned.
MasterClient Always owned by the current Master Client. See Master Client. Re-targets the new Master Client.

Example mapping: PlayerAttached for player avatars and per-player inventory, Transaction for shared pickups the player explicitly picks up and drops, Dynamic for physics objects and projectiles that change ownership frequently, MasterClient for singletons and room-wide systems.

Setting Ownership Mode

The Ownership UPROPERTY on UFusionActorComponent is read once, when the actor is attached as a Fusion source. Set it in the C++ constructor or the BP defaults. It is not designed to flip at runtime.

C++

AInteractablePickup::AInteractablePickup()
{
    FusionActorComponent = CreateDefaultSubobject<UFusionActorComponent>(TEXT("Fusion"));
    FusionActorComponent->Ownership = EFusionObjectOwnerFlags::Transaction;
}

Requesting Ownership

UFusionOnlineSubsystem::SetWantsOwner(Actor, bWantsOwner) is the runtime entry point. Call it with bWantsOwner = true to claim ownership of an actor, or false to release. The method is BP-callable.

What the claim does depends on the ownership mode. For Transaction and PlayerAttached, it raises a request the current owner must grant or deny (see Transaction). For Dynamic, it sets a persistent intent the client claims by sending, with no owner approval (see Dynamic). For MasterClient the claim has no effect.

C++

OnlineSubsystem->SetWantsOwner(Actor, true);  // claim
OnlineSubsystem->SetWantsOwner(Actor, false); // release

The BP-pure inspectors on UFusionOnlineSubsystem answer the runtime "who owns this and can I write to it?" question: HasOwner(Actor), GetOwner(Actor), IsOwner(Actor), and CanModify(Actor). Use IsOwner and CanModify as the predicates for gating writes to replicated state. AActor::HasAuthority() works equally well for that gate. It is equivalent to CanModify(Actor) (see Ownership and HasAuthority).

Ownership Modes in Detail

Transaction

Transaction ownership transfers go through a request that the current owner answers. When a non-owner calls SetWantsOwner(Actor, true), the current owner's OnOwnershipRequested event fires with the requesting player id.

OnOwnershipRequested is a BlueprintAssignable multicast on UFusionActorComponent. It passes the requesting player id as an int32.

While nothing is bound to OnOwnershipRequested, every request is denied automatically. Binding a handler transfers that responsibility to you: the handler must answer every request by calling RespondToOwnershipRequest, passing true to grant or false to deny.

RespondToOwnershipRequest(int32 RequesterPlayerId, bool bGranted) is the BlueprintCallable answer on UFusionActorComponent. Pass true to grant the request or false to deny it.

C++

void AInteractablePickup::BeginPlay()
{
    Super::BeginPlay();
    FusionActorComponent->OnOwnershipRequested.AddDynamic(this, &AInteractablePickup::HandleOwnershipRequested);
}

void AInteractablePickup::HandleOwnershipRequested(int32 RequesterPlayerId)
{
    // Answer every request. A bound handler that stays silent leaves the requester waiting forever.
    const bool bGrant = CanYieldTo(RequesterPlayerId);
    FusionActorComponent->RespondToOwnershipRequest(RequesterPlayerId, bGrant);
}

The requester learns the outcome through events, not a return value. A granted request arrives as OnOwnerChanged followed by OnOwnerWasGiven. There is no positive "request granted" event. A denied request arrives as OnOwnershipDenied.

Leaving OnOwnershipRequested unbound denies every request automatically. That is the default for any actor that does not opt in to granting.

The request handshake only applies while the object has an owner. If a Transaction object is unowned, for example after its previous owner disconnected, SetWantsOwner(Actor, true) claims it directly. There is nobody to ask, so no OnOwnershipRequested fires anywhere, and ownership arrives as OnOwnerChanged followed by OnOwnerWasGiven.

PlayerAttached

Same transfer mechanics as Transaction, but with one key difference: when the owning player disconnects, the server automatically destroys the object. Use this for player avatars, player-owned inventory, or anything that should not outlive its owner.

Dynamic

Dynamic ownership does not use the request handshake. SetWantsOwner(Actor, true) sets an intent on the object. You do not need to re-assert it every frame to keep holding one you own. But the intent is cleared when you lose the object to another client (see below), so a client that keeps losing a contested object must re-assert it to stay in the running.

A client that wants a Dynamic object claims it when the object next sends an update, once a short per-object cooldown has elapsed. No current-owner approval is involved. The claim is optimistic.

Because the claim rides on the object's next send, how often this client sends the object governs how aggressively it takes ownership. UFusionActorComponent::SetLocalSendRate(int32) sets that rate for this client, and ResetLocalSendRate() restores the default.

SetLocalSendRate takes a send interval, not a frequency. A value of 1 sends every tick and claims fastest, 16 sends every sixteenth tick and claims slowly. Values of 0 or below are rejected with an error. Call ResetLocalSendRate() to restore the default.

The common pattern is to drop the local send rate to 1 while contesting a Dynamic object for a fast takeover, then raise it (for example 16) once you own it to save bandwidth while passively holding.

C++

// Contest: want ownership and send every tick for a fast claim.
OnlineSubsystem->SetWantsOwner(Actor, true);
FusionActorComponent->SetLocalSendRate(1);

// If another client wins the contest your intent is cleared (see below),
// so call SetWantsOwner(Actor, true) again to keep contesting.

// Once owned, relax to save bandwidth while holding.
FusionActorComponent->SetLocalSendRate(16);

If another client wins ownership of a Dynamic object you are contesting (even one you only claimed optimistically and never actually held), your want intent is automatically cleared. This stops two clients from fighting over the object indefinitely. With manual SetWantsOwner, the intent is one-shot per lost contest: call SetWantsOwner(Actor, true) again to re-contest. The AutoDynamicOwnershipRange helper re-asserts the intent each tick, so it recovers on its own.

ClearOwnerCooldown() is a BlueprintCallable on UFusionActorComponent. It clears the per-object cooldown so the next eligible send can re-claim immediately.

Master Client

Setting the Ownership value to MasterClient binds the object's owner to whichever player is the current Master Client. The resolved owner is recomputed on every master change, so IsOwner(Actor) and IsMasterClient() always agree for these actors.

When the current Master Client leaves the room, Photon promotes a new master automatically. Every MasterClient owned object re-targets the new master.

Do not call SetWantsOwner on MasterClient owned objects. The ownership rule for MasterClient is "whoever is master right now", not "whoever asked". Claim requests do not change the resolved owner. If the project needs ownership that any client can request and release, use Transaction or Dynamic instead.

Ownership and HasAuthority

Fusion sets each networked actor's ENetRole every tick, so AActor::HasAuthority() is a supported way to gate writes to Fusion-replicated state. It reads true on the client that may currently modify the object and false everywhere else, matching CanModify(Actor).

In Dynamic mode HasAuthority() is optimistic: it turns true the moment you claim, before the server confirms. If the claim loses the contest, the role reverts when the server's correction arrives, a round trip later (see Dynamic). Read it after OnObjectReady, not in BeginPlay: before Fusion's first update the role still holds Unreal's default value, so a map-placed actor reads ROLE_Authority and HasAuthority() true on every client.

C++

if (Actor->HasAuthority())
{
    // We hold write authority over this Fusion object, so it's safe to drive.
}

Auto Dynamic Ownership Range

AutoDynamicOwnershipRange on UFusionActorComponent is an optional helper that automates SetWantsOwner calls based on distance. It is a separate feature from the Dynamic ownership mode.

While enabled, the actor requests ownership whenever the local player is within AutoDynamicOwnershipRange and releases it again once the player moves beyond that distance.

C++

FusionActorComponent->Ownership = EFusionObjectOwnerFlags::Dynamic;
FusionActorComponent->AutoDynamicOwnershipRange = 500.0; // cm

The helper calls SetWantsOwner on whatever ownership mode the actor uses, and the mode decides what happens. A value of 0 disables the helper, leaving ownership transfer under explicit gameplay control through direct SetWantsOwner calls.

Ownership Change Events

OnOwnerChanged is a BlueprintAssignable multicast on UFusionActorComponent. It fires whenever the owner of the actor changes, including the local-to-remote and remote-to-local transitions.

OnOwnerWasGiven is the narrower companion event. It fires only when the local client has just become the owner. Use it as the hook for "I just took authority, start driving this actor".

C++

void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    FusionActorComponent->OnOwnerChanged.AddDynamic(this, &AMyActor::HandleOwnerChanged);
    FusionActorComponent->OnOwnerWasGiven.AddDynamic(this, &AMyActor::HandleBecameOwner);
}
Back to top