Documentation

Cloud Code

Synchronized state in CloudBehaviours

Declare synchronized state, read it in your game, and control when Cloud Code pushes updates.
Read time 4 minutesLast updated 15 days ago

Important
This page describes an experimental feature that might change significantly before release. It's not recommended to use or rely on experimental features in production environments due to potential instability.
A public field or property on a CloudBehaviour is synchronized state. Its value lives in the cloud, and the client bindings keep a copy of it up to date. In a
MultiplayerSession
scope, every member of the session receives updates. Private members never reach a client, so secrets such as a card deck or a dice seed stay in the cloud.

Declare synchronized state

A public, non-static field synchronizes, and so does a public, non-static property with a public setter:
[StateScope(Scope.MultiplayerSession)]public class MatchState : CloudBehaviour{ public int Round; // Synchronizes public List<int> Scores; // Synchronizes public int TurnNumber { get; set; } // Synchronizes public int Seed { get; private set; } // Doesn't synchronize: no public setter private List<string> _deck; // Stays in the cloud}
A read-only member, such as a
readonly
field or a property without a public setter, doesn't synchronize, and the build reports a warning for it. Synchronization and persistence follow different rules. For more information about persistence, refer to Player and match persistence in CloudBehaviours.
Values serialize as JSON. Classes and enums that you define in the cloud assembly are automatically available to the client bindings, carrying only their public fields and properties.

Read synchronized state in your game

The generated client bindings expose each synchronized field as a read-only property. Subscribe to the
Synchronized
event to react to updates:
protected virtual void OnEnable(){ EnableClient(); Synchronized.AddListener(() => turnLabel.text = $"Round {matchStateClient.Round}");}
Synchronized
raises after each applied update: an RPC return, an incoming push, or the hydrate that runs when the client's scope resolves. A client that connects and does nothing else still holds current server state, because hydration runs automatically.

Control synchronization manually

AutoSynchronize
is enabled by default. After each RPC returns, Cloud Code pushes updated synchronized state to the other clients in the scope. To batch several changes into a single push, disable
AutoSynchronize
and call
SynchronizeAsync()
when you're ready:
public async Task ResolveRound(){ AutoSynchronize = false; Round++; Scores[0] += 10; await SynchronizeAsync();}
AutoSynchronize
is saved with the rest of the CloudBehaviour's state. Once you set it to
false
, it stays off for every later call in that scope until you set it back to
true
.
The calling client always receives current state in the RPC return, regardless of
AutoSynchronize
.
Note
A
Player
-scoped CloudBehaviour has no other clients to push to. State reaches that one client through RPC returns and hydration. A timer is the exception: a timer-triggered invocation has no RPC return, so Cloud Code pushes the updated state to the player. Refer to What a timer callback pushes. For scope-specific persistence guidance, refer to Player and match persistence in CloudBehaviours.

Model types crossing the client boundary

When a synchronized field, an RPC, or an event uses a class, struct, or enum that you define in your cloud assembly, Cloud Code generates a type with the same name in the client bindings, so your game can work with the same data. The generated type is a data-only copy: it keeps the public fields and properties, and drops everything else.
For example, an inventory CloudBehaviour stores a list of items:
public class Item{ public string Name; public int Quantity = 1; private string _dropTable; // Server-only data public bool IsStackable() => Quantity > 1; // Server-only logic}[StateScope(Scope.Player)]public class Inventory : CloudBehaviour{ public List<Item> Items = new(); public void Add(Item item) => Items.Add(item);}
In your game,
Items
on the client bindings is a list of this generated
Item
type:
public class Item{ public string Name { get; set; } public int Quantity { get; set; }}
In practice, this means:
  • Your game reads
    Name
    and
    Quantity
    directly, for example to display the inventory.
  • IsStackable()
    doesn't exist in your game. To use the same rule on both sides, put it in a shared rules library, or work out the answer on the server and synchronize it.
  • _dropTable
    never reaches your game.
  • Default values such as
    = 1
    aren't copied. An
    Item
    that your game creates, for example to pass to
    Add
    , starts with
    Quantity
    at
    0
    unless you set it.
A public property that can be read but not publicly assigned, such as a computed property or one with a private setter, causes a compile-time error, and so does a public
readonly
field. For where to put real game logic instead, refer to Game organization in CloudBehaviours.
Use
List<T>
, an array, or
Dictionary<TKey, TValue>
for a collection field. Don't use a custom collection type: Cloud Code can't serialize it to JSON and deserialize it back. As an RPC parameter or return value, or when it derives from a .NET collection type, it causes a compile-time error.
To share a single value differently per player, refer to Per-player state views in CloudBehaviours.

Additional resources