Interest Management

How Interest Management Works

Interest management filters which client receives updates for which networked object. The goal is twofold: save bandwidth by not sending state to players who cannot see it, and hide state from players outside the relevant region (a stealth-game opponent should not learn a hidden player's position through replicated transforms).

Every replicated object carries a 64-bit interest key in the room. Every client maintains a set of subscribed keys. The room only delivers property updates and RPCs for objects whose key matches one of the client's subscribed keys. Two halves drive the system: the owner stamps keys on the objects it owns (the publisher side), and the client subscribes to the keys it wants (the subscriber side).

The basic property-replication flow is covered in Replicated Properties, and which client gets to stamp keys at all is decided by the ownership rules in Ownership.

Global Interest

The default interest key is 0, which means "global". FFusionInterestKey::IsGlobal() checks Key == 0. Objects with the global key are delivered to every client regardless of subscriptions — they bypass the interest filter entirely.

Objects fall back to global interest in two cases: no interest component is present, or the interest component is present but bEnabled is false. The fallback ensures that a project that has not yet adopted interest management still works — every object is global, every client receives everything.

Global interest is the right choice for small rooms (a 4-player co-op session does not benefit from filtering), lobby state that all players must see, and singletons such as the AGameStateBase that every client needs to observe.

Spatial-Hash Area of Interest

UFusionSpatialHashInterestComponent is the pre-built area-of-interest strategy. Add it to a PlayerController (or Pawn) and it does both halves of the work in one tick: subscribe the local client to the cells the player is currently near, and stamp the local client's owned root actors with their cell key.

The 64-bit key packs three 21-bit cell coordinates: (CellZ << 42) | (CellY << 21) | CellX. The packing is computed by ComputeKeyForPosition(FVector) on the component. Two clients computing the same world position into a key produce the same 64-bit value — the encoding is what lets the room match publishers to subscribers cheaply.

On a PlayerController (or Pawn) carrying the component, both roles run on the same actor: each tick, the component computes the keys of the cells around the local player and subscribes to them, and at the same time computes the cell key of every locally-owned root actor and stamps that key on it. Tune SubscribeRadius to control how many neighbour cells the local client subscribes to — see the next section.

Configuring Spatial-Hash AoI

Property Default Effect
bEnabled true When false, the component does nothing and owned actors fall back to global interest.
CellSize 3200 cm Edge length of a single cell. Smaller cells = finer filtering, larger memory pressure on the room.
WorldHalfExtent (1024, 1024, 1024) cells Half-extent of the world grid in cells. World must fit inside ±WorldHalfExtent * CellSize.
SubscribeRadius (1, 1, 1) cells Neighbour cells in each axis to subscribe to. (1,1,1) is a 3×3×3 = 27-cell volume around the player.

CellSize and WorldHalfExtent must match the server-side AOI configuration of the Photon Hive plugin or the keys computed on the client will not align with the keys the room indexes. Mismatches manifest as missing updates or ghost objects — the project deploys both sides in lock-step.

Shape SubscribeRadius to the game. The default (1, 1, 1) is a 27-cell volume centred on the player — appropriate for first-/third-person 3D play. A top-down or isometric game where vertical visibility is irrelevant uses something like (2, 0, 2) (a flat 5×1×5 = 25-cell slab) to widen horizontal interest without subscribing to overhead and below cells.

Custom Interest Strategies

The spatial-hash component is just one strategy. Any client-side system can drive the publisher half of interest management by calling UFusionClient::UpdateOwnedActorAreaInterestKeys each frame with a per-actor key-computation function. The function decides what key the owned actor gets — team affiliation, room region, gameplay state, whatever the game needs.

C++

void UMyInterestSubsystem::Tick(float DeltaTime)
{
    UFusionClient* Client = OnlineSubsystem->GetClient();
    Client->UpdateOwnedActorAreaInterestKeys(
        [this](const AActor* Actor) -> uint64
        {
            return ComputeKeyForActor(Actor);
        });
}

For the subscription side — deciding which keys the local client receives — the pattern is to write a replacement component modelled on UFusionSpatialHashInterestComponent's tick logic. The interest-key subscribe/unsubscribe surface on UFusionClient is what the spatial-hash component itself calls into; a custom component drives the same API with whatever subscription logic the game needs.

A custom strategy must agree with the server on the meaning of each key. Clients that disagree on what a given 64-bit value represents will see ghost objects (the server thinks key X matches, the client did not expect it) or missing updates (the client expected key X to match but the server stamped a different key). Coordinate the encoding with the server-side Photon Hive plugin before shipping.

Interaction with Always Relevant and Dormancy

Fusion interest keys are independent of Unreal's AActor::bAlwaysRelevant, bNetUseOwnerRelevancy, NetCullDistanceSquared, and NetDormancy. Those are properties of Unreal's native replication path. Fusion does not consume them — setting them has no effect on Fusion traffic. The full list of unsupported native mechanisms is in Compatibility with Built-in Netcode.

The Fusion equivalent of "always relevant" is the global interest key (0). Leave the object on the global key — typically by not attaching an interest component, or by setting bEnabled = false on the spatial-hash component for that actor's owner — and every client receives its updates.

The Fusion equivalent of dormancy is property-change detection plus bAutomaticallySendUpdates. An object whose properties never change does not generate traffic — there is no separate dormancy state machine to enter or exit. For a coarser pause-everything switch, use ToggleNetworkSend(false) on the UFusionActorComponent.

Enter and Exit Events

UFusionActorComponent broadcasts two events that track the local client's interest state for the actor. OnInterestEnter fires when the local client starts receiving updates for the object — the object entered an interest cell the client is subscribed to. OnInterestExit fires when the client stops receiving updates. Both are FFusionObjectStatusChange multicasts and are safe to bind from Blueprint.

These events fire in addition to OnObjectReady and OnObjectDestroyed. A remote object stays alive on the local client even when out of interest — it just stops receiving updates. It can enter and exit interest many times during its lifetime without being destroyed or re-spawned, so the events are the right hook for fade-in/fade-out visuals and per-tick work suspension.

C++

void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    FusionActorComponent->OnInterestEnter.AddDynamic(this, &AMyActor::FadeIn);
    FusionActorComponent->OnInterestExit.AddDynamic(this, &AMyActor::FadeOut);
}
Back to top