Nebula

NetworkTransform

Replicate the position, rotation, and scale of an entity root or child transform.

Add NetworkTransform to replicate a Unity Transform. You can select axes, set change thresholds, choose local or world space, select worker or owner authority, configure interpolation, and enable compression.

Choose the components

ObjectComponents
Static object with replicated gameplay stateNetworkIdentity
Scripted movementIdentity and NetworkTransform
Worker-simulated physics propNetworkIdentity, Rigidbody, NetworkRigidbody, and NetworkTransform
Predicted characterNetworkIdentity, prediction behavior, and NetworkTransform
Moving containerNetworkIdentity, Container, and NetworkTransform on the root

Add NetworkTransform to each root or child that needs ongoing movement replication. A child component can animate a turret or other articulated part independently. NetworkRigidbody, PredictedBehaviour, and a Container on an entity's root require a visible NetworkTransform component.

An identity without NetworkTransform sends its initial placement and reliable container changes, but no ongoing movement. Spawn and worker-handover snapshots remain complete even when ongoing replication omits position, rotation, or scale. Turning off an axis reduces recurring bandwidth; it does not remove that value from simulation handover state.

Root updates use the batched spatial stream, which the gateway can read for distance-based delivery and late-join snapshots. Child updates use the sync channel. Both honor the selected axes, change thresholds, and interpolation settings.

Keep simulation and presentation consistent

Use worker authority for physics, predicted characters, and moving containers. A predicted owner applies reconciliation instead of remote transform interpolation. A moving container must synchronize all position and rotation axes so other workers can place its contents correctly. Run Nebula > Validate Project to check these combinations.

Do not disable an axis that remote collision or distance-based delivery needs. For example, a moving physics prop needs position updates even if its mesh is hidden. Disabling position leaves the gateway with its last known position.

Configure moving entities

Add NetworkTransform to existing moving prefabs and scene entities. Component requirements apply when you add new components; they do not migrate every existing asset. Leave static objects opted out, and enable scale only where it changes during play.

Use Identity.Motion.Velocity for scripted or predicted movement, or NetworkRigidbody.SetVelocity for physics.

Options

The inspector groups the options as the source does. Defaults are in the third column.

Axes to synchronize

FieldDefaultNotes
SyncPositionX / Y / ZtrueAxes left off are not sent; the receiver keeps its own value for them.
SyncRotAngleX / Y / ZtrueEuler axes. Ignored when UseQuaternionSynchronization is on (all three always travel).
SyncScaleX / Y / ZfalseScale is always local scale.

SyncVelocity defaults to true. It includes Identity.Motion.Velocity for root position extrapolation. Turn it off when interpolation alone is sufficient; physics handover still preserves momentum.

Thresholds

FieldDefaultNotes
PositionThreshold0.001Meters the position must move since the last send.
RotAngleThreshold0.01Degrees turned since the last send, measured exactly. Before protocol 30 the check used Quaternion.Angle, which reports no turn below about 0.16°, so a smaller threshold had no effect.
ScaleThreshold0.01Distance in scale space since the last send.

Thresholds compare against what was last sent, not the previous tick's value, so slow drift still accumulates into an update.

Space and delivery

FieldDefaultNotes
InLocalSpacefalseReplicate localPosition / localRotation relative to the parent instead of world values. Scale is local either way.
UseUnreliableDeltastrueSend ordinary updates on the sequenced channel. Off: reliable-ordered. Teleports and root recovery updates are reliable.

Nebula represents root position and rotation in the container frame. World-space child updates also travel in container-local coordinates; local-space child updates use their parent frame. Buffered samples retain their container reference so passengers follow a moving carrier. SetState and Teleport accept world values unless InLocalSpace is enabled.

Root transforms send only when selected values cross a threshold. Once a root has not changed for 30 ticks (NetworkTransform.SettleTicks), it sends one reliable recovery sample with the pose it came to rest at, and then stops sending. A root that keeps moving sends no recovery samples. Recovery bypasses distance filtering, so a lost or filtered final update cannot leave an object at its old pose. On an entity with an update interval, the root is captured only on its update ticks; a teleport still goes out on the next tick. Unreliable child transforms still send periodic keyframes. Reliable child updates carry complete selected state so late joiners receive the latest values.

Precision

FieldDefaultNotes
UseHalfFloatPrecisionfalsePositions, scales and uncompressed rotations as 16-bit halves.
UseQuaternionSynchronizationfalseSend the whole quaternion rather than the selected Euler angles. No gimbal issues; all three axes always.
UseQuaternionCompressionfalseWith quaternion synchronization: smallest-three, 4 bytes per rotation. Takes precedence over half floats for the rotation.
PreciseRotationtrueRoot only, with quaternion compression: 15 bits per component, 6 bytes per rotation and about 0.005° of error, instead of 10 bits, 4 bytes and up to about 0.16°. A slow turn below the coarse step otherwise holds for several updates and then steps. Clients older than protocol 30 are sent the 4-byte form.

Motion fidelity

FieldDefaultNotes
SyncAngularVelocitytrueRoot only: send the angular velocity, measured from the change of rotation since the previous update, as three f16 values in radians per second. Remote copies keep turning at it while they wait for a late update, and use it between updates (below). Adds 6 bytes to an update that carries a rotation.
VelocityAwareInterpolationtrueRoot only, buffered: between two updates, move along a cubic Hermite curve through both updates' velocities instead of a straight line, and turn through both updates' angular velocities. A turning or accelerating entity sent at 15 or 5 Hz then follows its path instead of turning a corner at each update. When the velocities do not explain the move (a teleport, a bounce, a stream without velocities), the copy falls back to the straight line, so it never overshoots. Inside a moving container (a ship, a turning planet) the position stays on the straight line, because velocities are in the scope's space; the rotation still curves. This is a receiver setting: each client reads it from its own copy of the prefab.

Interpolation (non-authoritative copies)

FieldDefaultNotes
InterpolatetrueOff: every received state is applied immediately.
InterpolationBufferedBuffered or SmoothDamp, see below.
SlerpPositionfalseBuffered only: Vector3.Slerp between samples for curved paths (an orbiting object).
PositionMaxInterpolationTime0.1SmoothDamp only: seconds to reach the newest position.
RotationMaxInterpolationTime0.1SmoothDamp only: seconds to reach the newest rotation.
ScaleMaxInterpolationTime0.1SmoothDamp only: seconds to reach the newest scale.

Authority

FieldDefaultNotes
AuthorityServerAuthorityMode.Server or AuthorityMode.Owner. Serialized on the base class NetworkSyncBehaviour; also settable in code.

Server versus owner authority

AuthorityMode lives on NetworkSyncBehaviour, the base NetworkTransform shares with NetworkAnimator.

Server (default): The authoritative worker produces the transform state. The worker samples root transforms after simulation and batches changed values. Child transforms use AuthorityTick and the component sync channel. Remote copies follow this state; a predicted owner uses its reconciliation state.

Owner: The owning client drives the transform:

On the owning client, NetworkSyncBehaviour.Update runs AuthorityTick once per tick interval (accumulated from Time.unscaledDeltaTime, never more than one tick's worth per frame). If anything changed it writes a chunk and sends it in a ServerRpc (RpcOwnerSyncState(byte[] chunk, bool full)). Every SyncKeyframeInterval-th send from that client is a keyframe.

The worker that controls the entity receives the RPC and applies the state without interpolation. It sends the same changes to other copies on its next tick. For a root transform, the worker also calculates NetworkIdentity.Motion.Velocity from consecutive owner positions and limits it to 100 meters per second.

The owner's own echo comes back through the gateway. ReadSyncState consumes the chunk and returns before applying anything when IsSyncAuthority is true, so the owner never fights its own input.

The properties involved:

PropertyMeaning
IsOwnerAuthoritativeAuthority == Owner and a client owns the entity (OwnerClientId != 0).
IsSyncAuthorityThis copy currently produces the state: IsOwner under owner authority, otherwise HasAuthority.
IsRelayingWorkerThis worker holds the entity but the owner drives it (protected).

Handover is unaffected by the mode. The worker still moves the entity between containers; the owner's RPCs simply route to the new worker through the gateway. On a gained authority the send counter resets so the new worker opens with a keyframe.

Server-driven entities use server authority

IsOwnerAuthoritative requires an owning client. A server-driven entity (spawned with SpawnServerDriven) with Authority = Owner behaves as Server, and the worker ignores any owner chunk that arrives for it.

Interpolation modes

Non-authoritative copies (remote entities on a client, ghosts on a worker) present the state from RemoteTick(renderTick). On a client that runs once per frame at NetworkTime.RenderTick, a few ticks behind the newest snapshot (NebulaConfig.InterpolationDelayTicks); on a worker it runs once per tick, one tick behind, before Physics.SyncTransforms so ghost colliders are in place for that tick's raycasts.

Buffered: Nebula stores 64 received states by tick. RemoteTick interpolates between the states before and after renderTick. When no newer state is available, child transforms hold the latest sample. Root transforms can extrapolate briefly using the supplied velocity when SyncVelocity is enabled, and keep turning at the supplied angular velocity when SyncAngularVelocity is enabled. With VelocityAwareInterpolation, a root follows a curve through the velocities of the two states around renderTick rather than the straight line between them.

On a client, an entity whose state arrives further apart than the render delay is drawn later than that, by its own gap. An entity updated every 6 ticks, or sent every 20th tick in a slow distance tier, would otherwise be drawn past its newest state for most of each gap: held there, or extrapolated by its velocity, until the next state pulls it into place. The client measures how far apart each entity's states arrive and draws that entity enough ticks further behind to have a state on each side of the render tick, so it interpolates across the gap. renderTick is then earlier for that entity, and every behavior on it is drawn at that tick.

  • An entity sent every tick or two gets no extra delay.
  • NebulaConfig.SlowUpdateMaxDelayTicks caps the extra delay (30 ticks by default; 0 turns it off).
  • A gap longer than a second counts as the entity resting, not as its rate.
  • When an entity's rate changes, its delay moves toward the new one at half real time, so it slows down or speeds up briefly instead of jumping.
  • NetworkIdentity.UpdateGap shows the measured gap (GapTicks) and the delay in use (DelayTicks).

SmoothDamp: Nebula presents the newest state without sampling between buffered ticks. Each RemoteTick moves toward the newest state. Configure the maximum interpolation time separately for position, scale, and rotation.

Regardless of mode, a state is applied immediately (and the buffer cleared) when it is a teleport, when Interpolate is off, when it arrived with tick 0 (the spawn snapshot a late joiner or fresh ghost receives), or on the relaying worker.

Root updates carry all selected fields when any selected value crosses its threshold. Child deltas carry the field groups that changed. Receivers merge these updates into complete buffered samples while preserving unsynchronized axes.

Teleport and SetState

Both are authority only (IsSyncAuthority); calling them on a spawned copy without authority logs a warning and does nothing.

// Move instantly; remotes snap instead of interpolating.
void Teleport(Vector3 position, Quaternion rotation, Vector3 scale);

// Set any subset (null leaves a part alone), in the configured space. teleport defaults to true.
void SetState(Vector3? position = null, Quaternion? rotation = null, Vector3? scale = null, bool teleport = true);

Nebula interprets position and rotation in the configured local or world space. A teleport sends the selected position, rotation, and scale axes in the next update. Remote root copies clear their buffered movement so they do not interpolate across the jump.

Call SetState(..., teleport: false) to move the transform through its normal change detection on the next AuthorityTick.

Hooks

Override these methods to inspect or change a TransformState:

public struct TransformState
{
    public uint Tick;
    public Vector3 Position;
    public Quaternion Rotation;
    public Vector3 Scale;
    public bool HasPosition, HasRotation, HasScale;   // fields without a Has* flag were not part of the update
    public bool Teleport;
    public bool InLocalSpace;
}

// Authority, just before a state is written. Modify it to send something other than the transform.
protected virtual void OnAuthorityPushTransformState(ref TransformState state) { }

// Non-authoritative copies, after a received state was applied or buffered.
protected virtual void OnNetworkTransformStateUpdated(ref TransformState state) { }

In OnAuthorityPushTransformState the position and rotation are in wire space (local, or container-local for world sync) and the Has* flags decide what goes on the wire, so clearing HasScale suppresses the scale for that send. In OnNetworkTransformStateUpdated the values have been converted back to stored space (world or local) and Tick is the tick they belong to (0 for a spawn snapshot).

Delivery and keyframes

For child transforms, SyncDelivery is Delivery.Sequenced when UseUnreliableDeltas is on, otherwise Delivery.ReliableOrdered. The worker sends one component message per delivery class per entity per tick, so a reliable animator and an unreliable child transform on the same entity travel separately. Root transforms use the batched spatial stream and reliable recovery described above.

Child keyframes (WriteSyncState(writer, full: true), every selected field) go out:

  • on the first send after this copy gained authority (a new epoch always opens with keyframes, which is what fresh ghosts and the gateway's late-joiner cache start from);
  • every NetworkIdentity.SyncKeyframeInterval ticks (30, half a second at 60 Hz) for an unreliable transform, whether or not it is dirty;
  • on every changed reliable child update, so the gateway always has a complete snapshot;
  • in the spawn snapshot (WriteSyncSnapshot).

Between keyframes an unreliable transform sends deltas; a lost delta shows as a held pose until the next delta or keyframe. Reliable transforms never lose a delta and pay for it in head-of-line blocking.

Compare with Netcode for GameObjects

NGONebula
NetworkTransform on the player rootKeep a root NetworkTransform for ongoing movement. Its axis switches control the batched root updates.
ClientNetworkTransformNetworkTransform with Authority = AuthorityMode.Owner.
AuthorityMode (NGO 2.x)Authority on NetworkSyncBehaviour, same two values.
OnIsServerAuthoritative() overrideSet Authority instead.
SyncPositionX/Y/Z, SyncRotAngleX/Y/Z, SyncScaleX/Y/ZSame names.
PositionThreshold, RotAngleThreshold, ScaleThresholdSame names.
InLocalSpaceSame. World values travel container-local on the wire.
InterpolateSame.
PositionInterpolationType and related fieldsOne Interpolation field: Buffered or SmoothDamp.
PositionMaxInterpolationTime, RotationMaxInterpolationTime, ScaleMaxInterpolationTimeSame names; used by SmoothDamp only.
SlerpPositionSame.
UseHalfFloatPrecision, UseQuaternionSynchronization, UseQuaternionCompressionSame names.
UseUnreliableDeltasUnreliable ordinary updates; one root recovery sample 30 ticks after the root stops changing, and periodic child keyframes.
Teleport(pos, rot, scale), SetState(pos?, rot?, scale?, teleport)Same signatures.
OnAuthorityPushTransformState(ref NetworkTransformState)OnAuthorityPushTransformState(ref TransformState).
OnNetworkTransformStateUpdated(ref NetworkTransformState)OnNetworkTransformStateUpdated(ref TransformState).
TickSyncChildrenNo counterpart: every behavior's chunk already rides the same per-entity per-tick message.
SwitchTransformSpaceWhenParentedNo counterpart: an entity's parent only changes with its container, and world values are already carried in container space and resolved with the same tick's container index.
CanCommitToTransformIsSyncAuthority.

On this page