Skip to content

Commit 60fcd87

Browse files
NoelStephensUnityVic-Cooperr-machjabbacakesLagowiecDev
authored
update: adding documentation for PR-2906 (#1260)
Co-authored-by: Vic Cooper <[email protected]> Co-authored-by: Rémi MACH <[email protected]> Co-authored-by: Amy Reeve <[email protected]> Co-authored-by: LagowiecDev <[email protected]> Co-authored-by: amanda-butler-unity <[email protected]> Co-authored-by: Griffin of Innatical <[email protected]> Co-authored-by: Christopher Pope <[email protected]> Co-authored-by: Steve Diniro <[email protected]> Co-authored-by: s-omeilia-unity <[email protected]> Co-authored-by: Alex Martin <[email protected]> Co-authored-by: Monaxys <[email protected]> Co-authored-by: Flap27 <[email protected]> Co-authored-by: NRTnarathip <[email protected]>
1 parent 93ad200 commit 60fcd87

File tree

3 files changed

+237
-187
lines changed

3 files changed

+237
-187
lines changed
+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
id: networkbehavior-synchronize
3+
title: NetworkBehaviour synchronization
4+
---
5+
6+
[`NetworkBehaviour`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one `NetworkBehaviour` component.
7+
8+
You can use `NetworkBehaviour`s to synchronize settings before, during, and after spawning NetworkObjects.
9+
10+
For more information about spawning and despawning `NetworkBehaviour`s, refer to the [NetworkBehaviour spawning and despawning page](networkbehaviour.md).
11+
12+
## Pre-spawn, spawn, post-spawn and synchronization
13+
14+
The NetworkObject spawn process can become complicated when there are multiple `NetworkBehaviour` components attached to the same GameObject. Additionally, there can be times where you want to be able to handle pre- and post-spawn oriented tasks.
15+
16+
- Pre-spawn example: Instantiating a `NetworkVariable` with owner write permissions and assigning a value to that `NetworkVariable` on the server or host side.
17+
- Spawn example: Applying a local value or setting that may be used during post spawn by another local `NetworkBehaviour` component.
18+
- Post-spawn example: Accessing a `NetworkVariable` or other property that is set during the spawn process.
19+
20+
Below are the three virtual methods you can override within a `NetworkBehaviour`-derived class:
21+
22+
Method | Scope | Use case | Context
23+
---------------------------- | ------------------------ | ------------------------------------------------------ | -------------
24+
OnNetworkPreSpawn | NetworkObject | Pre-spawn initialization | Client and server
25+
OnNetworkSpawn | NetworkObject | During spawn initialization | Client and server
26+
OnNetworkPostSpawn | NetworkObject | Post-spawn actions | Client and server
27+
OnNetworkSessionSynchronized | All NetworkObjects | New client finished synchronizing | Client-side only
28+
OnInSceneObjectsSpawned | In-scene NetworkObjects | New client finished synchronizing or a scene is loaded | Client and server
29+
30+
In addition to the methods above, there are two special case convenience methods:
31+
32+
- `OnNetworkSessionSynchronized`: When scene management is enabled and a new client joins a session, the client starts synchronizing with the network session. During this period of time the client might need to load additional scenes as well as instantiate and spawn NetworkObjects. When a client has finished loading all scenes and all NetworkObjects are spawned, this method gets invoked on all `NetworkBehaviour`s associated with any spawned NetworkObjects. This can be useful if you want to write scripts that might require access to other spawned NetworkObjects and/or their `NetworkBehaviour` components. When this method is invoked, you are assured everything is spawned and ready to be accessed and/or to have messages sent from them. Remember that this is invoked on clients and is not invoked on a server or host.
33+
- `OnInSceneObjectsSpawned`: Sometimes you might want to have the same kind of assurance that any in-scene placed NetworkObjects have been spawned prior to a specific set of scripts being invoked. This method is invoked on in-scene placed NetworkObjects when:
34+
- A server or host first starts up after all in-scene placed NetworkObjects in the currently loaded scene(s) have been spawned.
35+
- A client finishes synchronizing.
36+
- On the server and client side after a scene has been loaded and all newly instantiated in-scene placed NetworkObjects have been spawned.
37+
38+
### Pre-spawn synchronization with `OnSynchronize`
39+
40+
There can be scenarios where you need to include additional configuration data or use a `NetworkBehaviour` to configure some non-netcode related component (or the like) before a `NetworkObject` is spawned. This can be particularly critical if you want specific settings applied before `NetworkBehaviour.OnNetworkSpawn` is invoked. When a client is synchronizing with an existing network session, this can become problematic as messaging requires a client to be fully synchronized before you know "it is safe" to send the message, and even if you send a message there is the latency involved in the whole process that might not be convenient and can require additional specialized code to account for this.
41+
42+
`NetworkBehaviour.OnSynchronize` allows you to write and read custom serialized data during the NetworkObject serialization process.
43+
44+
There are two cases where NetworkObject synchronization occurs:
45+
46+
- When dynamically spawning a NetworkObject.
47+
- When a client is being synchronized after connection approval
48+
- that is, Full synchronization of the NetworkObjects and scenes.
49+
50+
:::info
51+
If you aren't familiar with the [`INetworkSerializable` interface](../advanced-topics/serialization/inetworkserializable.md), then you might read up on that before proceeding, because `NetworkBehaviour.OnSynchronize` follows a similar usage pattern.
52+
:::
53+
54+
#### Order of operations when dynamically spawning
55+
56+
The following provides you with an outline of the order of operations that occur during NetworkObject serialization when dynamically spawned.
57+
58+
Server-side:
59+
60+
- `GameObject` with `NetworkObject` component is instantiated.
61+
- The `NetworkObject` is spawned.
62+
- For each associated `NetworkBehaviour` component, `NetworkBehaviour.OnNetworkSpawn` is invoked.
63+
- The `CreateObjectMessage` is generated.
64+
- `NetworkObject` state is serialized.
65+
- `NetworkVariable` state is serialized.
66+
- `NetworkBehaviour.OnSynchronize` is invoked for each `NetworkBehaviour` component.
67+
- If this method isn't overridden then nothing is written to the serialization buffer.
68+
- The `CreateObjectMessage` is sent to all clients that are observers of the `NetworkObject`.
69+
70+
71+
Client-side:
72+
- The `CreateObjectMessage` is received.
73+
- `GameObject` with `NetworkObject` component is instantiated.
74+
- `NetworkVariable` state is deserialized and applied.
75+
- `NetworkBehaviour.OnSynchronize` is invoked for each `NetworkBehaviour` component.
76+
- If this method isn't overridden then nothing is read from the serialization buffer.
77+
- The `NetworkObject` is spawned.
78+
- For each associated `NetworkBehaviour` component, `NetworkBehaviour.OnNetworkSpawn` is invoked.
79+
80+
#### Order of operations during full (late-join) client synchronization
81+
82+
Server-side:
83+
- The `SceneEventMessage` of type `SceneEventType.Synchronize` is created.
84+
- All spawned `NetworkObjects` that are visible to the client, already instantiated, and spawned are serialized.
85+
- `NetworkObject` state is serialized.
86+
- `NetworkVariable` state is serialized.
87+
- `NetworkBehaviour.OnSynchronize` is invoked for each `NetworkBehaviour` component.
88+
- If this method isn't overridden then nothing is written to the serialization buffer.
89+
- The `SceneEventMessage` is sent to the client.
90+
91+
Client-side:
92+
- The `SceneEventMessage` of type `SceneEventType.Synchronize` is received.
93+
- Scene information is deserialized and scenes are loaded (if not already).
94+
- In-scene placed `NetworkObject`s are instantiated when a scene is loaded.
95+
- All `NetworkObject` oriented synchronization information is deserialized.
96+
- Dynamically spawned `NetworkObject`s are instantiated and state is synchronized.
97+
- For each `NetworkObject` instance:
98+
- `NetworkVariable` state is deserialized and applied.
99+
- `NetworkBehaviour.OnSynchronize` is invoked.
100+
- If this method isn't overridden then nothing is read from the serialization buffer.
101+
- The `NetworkObject` is spawned.
102+
- For each associated `NetworkBehaviour` component, `NetworkBehaviour.OnNetworkSpawn` is invoked.
103+
104+
### `OnSynchronize` example
105+
106+
Now that you understand the general concept behind `NetworkBehaviour.OnSynchronize`, the question you might have is "when would you use such a thing"? `NetworkVariable`s can be useful to synchronize state, but they also are only updated every network tick and you might have some form of state that needs to be updated when it happens and not several frames later, so you decide to use RPCs. However, this becomes an issue when you want to synchronize late-joining clients as there is no way to synchronize late-joining clients based on RPC activity over the duration of a network session. This is one of many possible reasons one might want to use `NetworkBehaviour.OnSynchronize`.
107+
108+
The following example uses `NetworkBehaviour.OnSynchronize` to synchronize connecting (to-be-synchronized) clients and also uses an RPC to synchronize changes in state for already synchronized and connected clients:
109+
110+
```csharp
111+
using UnityEngine;
112+
using Unity.Netcode;
113+
114+
/// <summary>
115+
/// Simple RPC driven state that shows one
116+
/// form of NetworkBehaviour.OnSynchronize usage
117+
/// </summary>
118+
public class SimpleRpcState : NetworkBehaviour
119+
{
120+
private bool m_ToggleState;
121+
122+
/// <summary>
123+
/// Late joining clients will be synchronized
124+
/// to the most current m_ToggleState
125+
/// </summary>
126+
protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
127+
{
128+
serializer.SerializeValue(ref m_ToggleState);
129+
base.OnSynchronize(ref serializer);
130+
}
131+
132+
public void ToggleState(bool stateIsSet)
133+
{
134+
m_ToggleState = stateIsSet;
135+
}
136+
137+
/// <summary>
138+
/// Synchronizes connected clients with the
139+
/// server-side m_ToggleState
140+
/// </summary>
141+
/// <param name="stateIsSet"></param>
142+
[Rpc(SendTo.ClientsAndHost)]
143+
private void ToggleStateClientRpc(bool stateIsSet)
144+
{
145+
m_ToggleState = stateIsSet;
146+
}
147+
}
148+
```
149+
:::caution
150+
`NetworkBehaviour.OnSynchronize` is only invoked on the server side during the write part of serialization and only invoked on the client side during the read part of serialization. When running a host, `NetworkBehaviour.OnSynchronize` is still only invoked once (server-side) during the write part of serialization.
151+
:::
152+
153+
### Debugging `OnSynchronize` serialization
154+
155+
If your serialization code has a bug and throws an exception, then `NetworkBehaviour.OnSynchronize` has additional safety checking to handle a graceful recovery without completely breaking the rest of the synchronization serialization pipeline.
156+
157+
#### When writing
158+
159+
If user-code throws an exception during `NetworkBehaviour.OnSynchronize`, it catches the exception and if:
160+
- **LogLevel = Normal**: A warning message that includes the name of the `NetworkBehaviour` that threw an exception while writing will be logged and that part of the serialization for the given `NetworkBehaviour` is skipped.
161+
- **LogLevel = Developer**: It provides the same warning message as well as it logs an error with the exception message and stack trace.
162+
163+
After generating the log message(s), it rewinds the serialization stream to the point just before it invoked `NetworkBehaviour.OnSynchronize` and will continue serializing. Any data written before the exception occurred will be overwritten or dropped depending upon whether there are more `NetworkBehaviour` components to be serialized.
164+
165+
#### When reading
166+
167+
For exceptions this follows the exact same message logging pattern described above when writing. The distinct difference is that after it logs one or more messages to the console, it skips over only the serialization data written by the server-side when `NetworkBehaviour.OnSynchronize` was invoked and continues the deserialization process for any remaining `NetworkBehaviour` components.
168+
169+
However, there is an additional check to assure that the total expected bytes to read were actually read from the buffer. If the total number of bytes read does not equal the expected number of bytes to be read it will log a warning that includes the name of the `NetworkBehaviour` in question, the total bytes read, the expected bytes to be read, and lets you know this `NetworkBehaviour` is being skipped.
170+
171+
:::caution
172+
When using `NetworkBehaviour.OnSynchronize` you should be aware that you are increasing the synchronization payload size per instance. If you have 30 instances that each write 100 bytes of information you will have increased the total full client synchronization size by 3000 bytes.
173+
:::
174+
175+
## Serializing `NetworkBehaviour`s
176+
177+
`NetworkBehaviour`s require the use of specialized [`NetworkBehaviourReference`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviourReference.html) structures to be serialized and used with RPCs and `NetworkVariable`s.

0 commit comments

Comments
 (0)