Synced objects and values

Each player's game runs its own copy of your scripts. Moving an object or changing a value changes it for that player only. To share it, the object gets a Network Id and one player, its owner, sends what the others follow.

The network id

Give the object a Network Id on its FL# Script component: any name, unique in the world, 256 characters at most. Synced transforms, synced state, ownership and network events need it. A script that tries without one is told in the log: 'Door' has no network id (set it on its FL# Script component).

The owner and the master

Networking.masterId, Networking.isMasterThe master of the room: the player in it for the longest time. When the master leaves, the next one takes over and every script gets OnMasterChanged(masterId).
Networking.GetOwner(go), Networking.IsOwner(go)The owner of an object: the player who took it last, or the master when nobody did. The objects of a player who leaves go to the master.
Networking.RequestOwnership(go)The local player takes the object.

Taking an object:

  1. The scripts of the object on this player's game hear OnOwnershipRequest(requesterId), then those on the owner's game; any of them returns false to refuse. The owner's game has 3 seconds to answer; then the object is given anyway.
  2. IsOwner is true at once, unless refused on this game. Refused by the owner, it goes back.
  3. Every script of the object, on every game, hears OnOwnershipChanged(newOwnerId).

A player who grabs an object that has a network id takes it the same way. Refused, the object stays in place and the log says Grab of 'Cup' refused by one of its scripts (OnOwnershipRequest): the object stays with player 3.

Sync a transform

csharp
protected override void Start() => Networking.SyncTransform(gameObject, 20f);

The owner sends the object's position, rotation and velocity that many times a second (60 at most; 0 stops), and the other players follow it. Call it on every player's game, in Start: each game needs to know the object is synced. On the players who do not own it, its rigidbody is kinematic: it follows the owner instead of falling on its own.

Networking.FlagDiscontinuity(go): the next transform sent is a jump (a respawn, a teleport); the others place the object at once instead of sliding it there.

Sync values: the synced state

The owner's script writes its values in OnSerialize, the others read them in OnDeserialize, in the same order.

csharp
using NodeWorld.Flux;

namespace MyWorld
{
    public class ClickCounter : FluxScript
    {
        public Text label;
        private int _count;

        protected override void Start()
        {
            Interaction.SetInteractable(gameObject, true);
            Show();
        }

        protected override void OnInteract()
        {
            if (!Networking.IsOwner(gameObject)) Networking.RequestOwnership(gameObject);
            _count++;
            RequestSerialization();
            Show();
        }

        protected override void OnSerialize(NetWriter writer) => writer.Write(_count);

        protected override void OnDeserialize(NetReader reader)
        {
            _count = reader.ReadInt();
            Show();
        }

        private void Show() => label.text = _count.ToString();
    }
}
  • RequestSerialization() sends the state at the end of the frame, once per frame at most, if this player owns the object. Asked while the ownership is on its way, it leaves once the object is granted, and is dropped if refused.
  • RequestSerialization(false) sends it unreliably: as often as you like, the latest wins. For a value that changes all the time (a slider being dragged).
  • OnPostSerialization(sent) tells whether the state left (false: this player is not the owner).
  • The server keeps the last state of each script: a player who arrives later gets it in OnDeserialize.
  • A state is 16 KB at most.

NetWriter.Write takes byte, bool, int, uint, long, float, double, string, Vector2, Vector3, Vector4, Quaternion, Color, and arrays of int, float, bool, string, byte and Vector3. NetReader has a Read… method for each. Reading past what was written throws Network data ended before what the script reads (written with another layout?).

The server's clock

Networking.serverTimeMilliseconds (an int, wraps around) and Networking.serverTime (seconds, a double) are the server's clock, the same for every player give or take their latency. Share a moment with it: "the video started at server time T" lets each player compute where it stands now (video players). In Play mode it is the editor's own clock.

On the server

The server is not a player: it owns nothing (RequestOwnership is refused there) and its scripts send no synced state. They hear OnOwnershipChanged, and the network events players send them (network events).

In Play mode you are alone and the master: a request is granted at the next frame, and nothing is sent.