Synchronized state in CloudBehaviours
Declare synchronized state, read it in your game, and control when Cloud Code pushes updates.
Read time 5 minutesLast updated 2 days ago
A public field or property on a CloudBehaviour is synchronized state. The member's value is stored in the cloud, and the client bindings keep an up-to-date copy. In a 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. To notify players that something happened, rather than share a current value, use a push channel. For a comparison, refer to Choose between a push channel and synchronized state.
MultiplayerSessionDeclare synchronized state
A public, non-static field synchronizes, and so does a public, non-static property with a setter. The setter can be private. The following example shows members that synchronize and members that don't:
[StateScope(Scope.MultiplayerSession)]public class MatchState : CloudBehaviour{ // These members synchronize. public int Round; public List<int> Scores; public int TurnNumber { get; set; } public int Seed { get; private set; } // This field stays in the cloud. private List<string> _deck;}
A field or a property without a setter, such as a computed property, doesn't synchronize. The build reports a warning for each one. Synchronization and persistence follow different rules. For more information about persistence, refer to Player and match persistence in CloudBehaviours.
readonlyValues serialize as JSON. For how the client bindings represent types that you define, refer to Model types crossing the client boundary.
Read synchronized state in your game
The generated client bindings expose each synchronized member as a property. Only the client bindings set these properties, when they apply state from the server. Don't assign them from your client bindings script. A value you assign doesn't change the server state, and the next update overwrites it.
Subscribe to the event to react to updates:
Synchronizedprotected virtual void OnEnable(){ EnableClient(); Synchronized.AddListener(() => turnLabel.text = $"Round {Round}");}
The client bindings raise after each applied update. An update comes from an RPC return, an incoming push, or the hydration that runs when the client's scope resolves. Hydration runs automatically, so a client that connects and does nothing else still holds the current server state.
SynchronizedControl synchronization manually
By default, is . 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, set to and call when you're ready:
AutoSynchronizetrueAutoSynchronizefalseSynchronizeAsync()public async Task ResolveRound(){ AutoSynchronize = false; Round++; Scores[0] += 10; await SynchronizeAsync();}
Cloud Code saves with the rest of the CloudBehaviour's state. After you set to , it stays for every later call in that scope until you set it back to .
AutoSynchronizeAutoSynchronizefalsefalsetrueThe calling client always receives the current state in the RPC return, regardless of .
AutoSynchronizeA -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.
PlayerModel 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, the Unity Editor generates a type with the same name in the client bindings. Your game can then work with the same data. The generated type is a data-only copy that keeps only the public fields and properties.
For example, the following inventory CloudBehaviour stores a list of items:
public class Item{ public string Name; public int Quantity = 1; // This data stays on the server. private string _dropTable; // This logic stays on the server. public bool IsStackable() => Quantity > 1;}[StateScope(Scope.Player)]public class Inventory : CloudBehaviour{ public List<Item> Items = new(); public void Add(Item item) => Items.Add(item);}
In your game, on the client bindings is a list of the following generated type:
ItemsItempublic class Item{ public string Name { get; set; } public int Quantity { get; set; }}
The generated type affects your game in the following ways:
Item- Your game reads and
Namedirectly, for example to display the inventory.Quantity - doesn't exist in your game. To use the same rule on both sides, put the rule in a shared rules library, or compute the result on the server and synchronize it.
IsStackable() - never reaches your game.
_dropTable - The generated type doesn't copy default values such as . An
= 1that your game creates, for example to pass toItem, starts withAddatQuantityunless you set it.0
A public property that can be read but not publicly assigned causes a compile-time error. Examples include a computed property and a property with a private setter. A public field also causes a compile-time error. To learn where to put game logic instead, refer to Game organization in CloudBehaviours.
readonlyUse , an array, or for a collection field. Don't use a custom collection type, because Cloud Code can't serialize a custom collection to JSON and deserialize it back. A custom collection type causes a compile-time error when you use it as an RPC parameter or return value, or when it derives from a .NET collection type.
List<T>Dictionary<TKey, TValue>To share a single value differently per player, refer to Per-player state views in CloudBehaviours.