From 00007ded112d502fd5551217bc667e75554cb285 Mon Sep 17 00:00:00 2001 From: Amy Reeve Date: Thu, 17 Oct 2024 15:46:23 +0100 Subject: [PATCH 1/2] Removing reference to removed method in object pooling --- docs/advanced-topics/object-pooling.md | 2 +- versioned_docs/version-1.10.0/advanced-topics/object-pooling.md | 2 +- versioned_docs/version-1.11.0/advanced-topics/object-pooling.md | 2 +- versioned_docs/version-1.3.0/advanced-topics/object-pooling.md | 2 +- versioned_docs/version-1.4.0/advanced-topics/object-pooling.md | 2 +- versioned_docs/version-1.5.2/advanced-topics/object-pooling.md | 2 +- versioned_docs/version-1.6.0/advanced-topics/object-pooling.md | 2 +- versioned_docs/version-1.7.1/advanced-topics/object-pooling.md | 2 +- versioned_docs/version-1.8.1/advanced-topics/object-pooling.md | 2 +- versioned_docs/version-1.9.1/advanced-topics/object-pooling.md | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/advanced-topics/object-pooling.md b/docs/advanced-topics/object-pooling.md index 2b17d7dda..4e8dac44a 100644 --- a/docs/advanced-topics/object-pooling.md +++ b/docs/advanced-topics/object-pooling.md @@ -25,4 +25,4 @@ The following example is from the Boss Room Sample. It shows how object pooling https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Infrastructure/NetworkObjectPool.cs ``` -Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. In the `InitializePool` method, called in `OnNetworkSpawn`, it initialises the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. +Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. The `RegisterPrefabInternal` method, called in `OnNetworkSpawn`, initializes the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. diff --git a/versioned_docs/version-1.10.0/advanced-topics/object-pooling.md b/versioned_docs/version-1.10.0/advanced-topics/object-pooling.md index a6f45541d..83741000a 100644 --- a/versioned_docs/version-1.10.0/advanced-topics/object-pooling.md +++ b/versioned_docs/version-1.10.0/advanced-topics/object-pooling.md @@ -25,4 +25,4 @@ The following example is from the Boss Room Sample. It shows how object pooling https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Infrastructure/NetworkObjectPool.cs ``` -Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. In the `InitializePool` method, called in `OnNetworkSpawn`, it initialises the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. +Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. The `RegisterPrefabInternal` method, called in `OnNetworkSpawn`, initializes the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. diff --git a/versioned_docs/version-1.11.0/advanced-topics/object-pooling.md b/versioned_docs/version-1.11.0/advanced-topics/object-pooling.md index a6f45541d..83741000a 100644 --- a/versioned_docs/version-1.11.0/advanced-topics/object-pooling.md +++ b/versioned_docs/version-1.11.0/advanced-topics/object-pooling.md @@ -25,4 +25,4 @@ The following example is from the Boss Room Sample. It shows how object pooling https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Infrastructure/NetworkObjectPool.cs ``` -Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. In the `InitializePool` method, called in `OnNetworkSpawn`, it initialises the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. +Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. The `RegisterPrefabInternal` method, called in `OnNetworkSpawn`, initializes the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. diff --git a/versioned_docs/version-1.3.0/advanced-topics/object-pooling.md b/versioned_docs/version-1.3.0/advanced-topics/object-pooling.md index f35eff8b9..cd3c63ca4 100644 --- a/versioned_docs/version-1.3.0/advanced-topics/object-pooling.md +++ b/versioned_docs/version-1.3.0/advanced-topics/object-pooling.md @@ -25,4 +25,4 @@ The following example is from the Boss Room Sample. It shows how object pooling https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.1.0/Assets/Scripts/Infrastructure/NetworkObjectPool.cs ``` -Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. In the `InitializePool` method, called in `OnNetworkSpawn`, it initialises the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. +Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. The `RegisterPrefabInternal` method, called in `OnNetworkSpawn`, initializes the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. diff --git a/versioned_docs/version-1.4.0/advanced-topics/object-pooling.md b/versioned_docs/version-1.4.0/advanced-topics/object-pooling.md index f35eff8b9..cd3c63ca4 100644 --- a/versioned_docs/version-1.4.0/advanced-topics/object-pooling.md +++ b/versioned_docs/version-1.4.0/advanced-topics/object-pooling.md @@ -25,4 +25,4 @@ The following example is from the Boss Room Sample. It shows how object pooling https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.1.0/Assets/Scripts/Infrastructure/NetworkObjectPool.cs ``` -Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. In the `InitializePool` method, called in `OnNetworkSpawn`, it initialises the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. +Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. The `RegisterPrefabInternal` method, called in `OnNetworkSpawn`, initializes the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. diff --git a/versioned_docs/version-1.5.2/advanced-topics/object-pooling.md b/versioned_docs/version-1.5.2/advanced-topics/object-pooling.md index 29f054b82..002b1e8ec 100644 --- a/versioned_docs/version-1.5.2/advanced-topics/object-pooling.md +++ b/versioned_docs/version-1.5.2/advanced-topics/object-pooling.md @@ -25,4 +25,4 @@ The following example is from the Boss Room Sample. It shows how object pooling https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Infrastructure/NetworkObjectPool.cs ``` -Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. In the `InitializePool` method, called in `OnNetworkSpawn`, it initialises the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. +Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. The `RegisterPrefabInternal` method, called in `OnNetworkSpawn`, initializes the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. diff --git a/versioned_docs/version-1.6.0/advanced-topics/object-pooling.md b/versioned_docs/version-1.6.0/advanced-topics/object-pooling.md index 29f054b82..002b1e8ec 100644 --- a/versioned_docs/version-1.6.0/advanced-topics/object-pooling.md +++ b/versioned_docs/version-1.6.0/advanced-topics/object-pooling.md @@ -25,4 +25,4 @@ The following example is from the Boss Room Sample. It shows how object pooling https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Infrastructure/NetworkObjectPool.cs ``` -Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. In the `InitializePool` method, called in `OnNetworkSpawn`, it initialises the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. +Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. The `RegisterPrefabInternal` method, called in `OnNetworkSpawn`, initializes the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. diff --git a/versioned_docs/version-1.7.1/advanced-topics/object-pooling.md b/versioned_docs/version-1.7.1/advanced-topics/object-pooling.md index 29f054b82..002b1e8ec 100644 --- a/versioned_docs/version-1.7.1/advanced-topics/object-pooling.md +++ b/versioned_docs/version-1.7.1/advanced-topics/object-pooling.md @@ -25,4 +25,4 @@ The following example is from the Boss Room Sample. It shows how object pooling https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Infrastructure/NetworkObjectPool.cs ``` -Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. In the `InitializePool` method, called in `OnNetworkSpawn`, it initialises the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. +Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. The `RegisterPrefabInternal` method, called in `OnNetworkSpawn`, initializes the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. diff --git a/versioned_docs/version-1.8.1/advanced-topics/object-pooling.md b/versioned_docs/version-1.8.1/advanced-topics/object-pooling.md index a6f45541d..83741000a 100644 --- a/versioned_docs/version-1.8.1/advanced-topics/object-pooling.md +++ b/versioned_docs/version-1.8.1/advanced-topics/object-pooling.md @@ -25,4 +25,4 @@ The following example is from the Boss Room Sample. It shows how object pooling https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Infrastructure/NetworkObjectPool.cs ``` -Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. In the `InitializePool` method, called in `OnNetworkSpawn`, it initialises the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. +Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. The `RegisterPrefabInternal` method, called in `OnNetworkSpawn`, initializes the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. diff --git a/versioned_docs/version-1.9.1/advanced-topics/object-pooling.md b/versioned_docs/version-1.9.1/advanced-topics/object-pooling.md index a6f45541d..83741000a 100644 --- a/versioned_docs/version-1.9.1/advanced-topics/object-pooling.md +++ b/versioned_docs/version-1.9.1/advanced-topics/object-pooling.md @@ -25,4 +25,4 @@ The following example is from the Boss Room Sample. It shows how object pooling https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Infrastructure/NetworkObjectPool.cs ``` -Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. In the `InitializePool` method, called in `OnNetworkSpawn`, it initialises the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. +Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. The `RegisterPrefabInternal` method, called in `OnNetworkSpawn`, initializes the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. From 306be642b55be2064c188f680cff7aa0f5b02517 Mon Sep 17 00:00:00 2001 From: Amy Reeve Date: Mon, 21 Oct 2024 16:24:01 +0100 Subject: [PATCH 2/2] NGO 2.1.1 (#1367) --- docs/components/networkmanager.md | 19 +- docusaurus.config.js | 6 +- versioned_docs/version-2.0.0/about.md | 65 ++ .../advanced-topics/bufferserializer.md | 21 + .../advanced-topics/client-anticipation.md | 125 ++++ .../advanced-topics/connection-events.md | 35 + .../advanced-topics/custom-serialization.md | 97 +++ .../fastbufferwriter-fastbufferreader.md | 163 +++++ .../inscene_parenting_player.md | 92 +++ .../message-system/clientrpc.md | 132 ++++ .../message-system/custom-messages.md | 302 ++++++++ .../deprecation-of-return-values.md | 51 ++ .../message-system/execution-table.md | 101 +++ .../message-system/reliabilty.md | 41 ++ .../message-system/rpc-compatibility.md | 98 +++ .../message-system/rpc-params.md | 108 +++ .../advanced-topics/message-system/rpc.md | 235 ++++++ .../message-system/serverrpc.md | 170 +++++ .../advanced-topics/messaging-system.md | 70 ++ .../network-update-loop-system/index.md | 53 ++ .../network-update-loop-reference.md | 22 + .../networkobject-parenting.md | 228 ++++++ .../advanced-topics/networktime-ticks.md | 202 ++++++ .../advanced-topics/object-pooling.md | 28 + .../version-2.0.0/advanced-topics/physics.md | 70 ++ .../advanced-topics/reconnecting-mid-game.md | 84 +++ .../serialization/cprimitives.md | 21 + .../serialization/enum-types.md | 33 + .../serialization/fixedstrings.md | 16 + .../serialization/inetworkserializable.md | 199 ++++++ .../inetworkserializebymemcpy.md | 29 + .../networkobject-serialization.md | 95 +++ .../serialization/serialization-arrays.md | 87 +++ .../serialization/serialization-intro.md | 16 + .../serialization/unity-primitives.md | 19 + .../advanced-topics/session-management.md | 43 ++ .../advanced-topics/transports.md | 35 + .../advanced-topics/ways-to-synchronize.md | 48 ++ .../basics/connection-approval.md | 235 ++++++ .../basics/custom-networkvariables.md | 251 +++++++ .../basics/deferred-despawning.md | 233 ++++++ .../version-2.0.0/basics/logging.md | 25 + .../version-2.0.0/basics/maxnumberplayers.md | 35 + .../basics/networkbehaviour-synchronize.md | 179 +++++ .../version-2.0.0/basics/networkbehaviour.md | 138 ++++ .../version-2.0.0/basics/networkobject.md | 127 ++++ .../version-2.0.0/basics/networkvariable.md | 632 ++++++++++++++++ .../version-2.0.0/basics/object-spawning.md | 347 +++++++++ .../version-2.0.0/basics/object-visibility.md | 120 ++++ .../version-2.0.0/basics/ownership.md | 93 +++ .../version-2.0.0/basics/playerobjects.md | 102 +++ .../version-2.0.0/basics/race-conditions.md | 40 ++ .../client-synchronization-mode.md | 55 ++ .../scenemanagement/custom-management.md | 67 ++ .../inscene-placed-networkobjects.md | 281 ++++++++ .../basics/scenemanagement/scene-events.md | 281 ++++++++ .../scene-management-overview.md | 20 + .../scenemanagement/timing-considerations.md | 162 +++++ .../using-networkscenemanager.md | 374 ++++++++++ .../basics/spawning-synchronization.md | 126 ++++ .../codemonkey-videos.md | 63 ++ .../community-contributions/dapper-videos.md | 33 + .../community-contributions/dilmer-videos.md | 98 +++ .../components/networkanimator.md | 149 ++++ .../components/networkmanager.md | 249 +++++++ .../components/networktransform.md | 222 ++++++ .../installation/installation.md | 55 ++ .../installation/migratingfromUNet.md | 676 ++++++++++++++++++ .../installation/migratingfrommlapi.md | 159 ++++ .../learn/bitesize/bitesize-clientdriven.md | 68 ++ .../learn/bitesize/bitesize-dynamicprefabs.md | 307 ++++++++ .../learn/bitesize/bitesize-introduction.md | 44 ++ .../learn/bitesize/bitesize-invaders.md | 129 ++++ .../learn/bitesize/bitesize-spaceshooter.md | 69 ++ .../learn/bitesize/bitesize-usecases.md | 43 ++ .../learn/bossroom/architecture.md | 270 +++++++ .../learn/bossroom/bossroom-actions.md | 52 ++ .../bossroom/getting-started-boss-room.md | 286 ++++++++ .../learn/bossroom/networkobject-parenting.md | 23 + .../learn/bossroom/networkrigidbody.md | 12 + .../learn/bossroom/optimizing-bossroom.md | 189 +++++ .../learn/bossroom/spawn-networkobjects.md | 27 + .../learn/clientside-interpolation.md | 29 + .../learn/dealing-with-latency.md | 393 ++++++++++ .../distributed-authority-quick-start.md | 305 ++++++++ versioned_docs/version-2.0.0/learn/faq.md | 101 +++ .../version-2.0.0/learn/lagandpacketloss.md | 56 ++ .../learn/listenserverhostarchitecture.md | 87 +++ .../version-2.0.0/learn/rpcnetvarexamples.md | 72 ++ .../version-2.0.0/learn/rpcvnetvar.md | 114 +++ .../learn/sample-dedicated-server.md | 201 ++++++ .../learn/ticks-and-update-rates.md | 39 + versioned_docs/version-2.0.0/relay/relay.md | 182 +++++ .../release-notes/ngo-changelog.md | 28 + .../release-notes/bitesize-changelog.md | 11 + .../release-notes/bossroom-changelog.md | 20 + .../terms-concepts/client-server.md | 26 + .../terms-concepts/distributed-authority.md | 52 ++ .../terms-concepts/mutliplayer-terms.md | 10 + .../terms-concepts/network-topologies.md | 50 ++ .../troubleshooting/error-messages.md | 31 + .../troubleshooting/troubleshooting.md | 43 ++ .../tutorials/command-line-helper.md | 185 +++++ .../tutorials/get-started-with-ngo.md | 587 +++++++++++++++ .../tutorials/goldenpath_series/gp_intro.md | 31 + .../goldenpath_series/gp_module_one.md | 458 ++++++++++++ .../goldenpath_series/gp_module_two.md | 239 +++++++ .../version-2.0.0/tutorials/helloworld.md | 312 ++++++++ ..._tricks_for_debugging_multiplayer_games.md | 180 +++++ .../testing_client_connection_management.md | 60 ++ .../tutorials/testing/testing_locally.md | 40 ++ .../testing_with_artificial_conditions.md | 226 ++++++ .../version-2.0.0-sidebars.json | 613 ++++++++++++++++ versions.json | 1 + 114 files changed, 15182 insertions(+), 5 deletions(-) create mode 100644 versioned_docs/version-2.0.0/about.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/bufferserializer.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/client-anticipation.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/connection-events.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/custom-serialization.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/fastbufferwriter-fastbufferreader.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/inscene_parenting_player.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/message-system/clientrpc.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/message-system/custom-messages.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/message-system/deprecation-of-return-values.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/message-system/execution-table.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/message-system/reliabilty.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/message-system/rpc-compatibility.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/message-system/rpc-params.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/message-system/rpc.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/message-system/serverrpc.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/messaging-system.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/network-update-loop-system/index.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/network-update-loop-system/network-update-loop-reference.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/networkobject-parenting.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/networktime-ticks.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/object-pooling.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/physics.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/reconnecting-mid-game.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/serialization/cprimitives.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/serialization/enum-types.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/serialization/fixedstrings.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/serialization/inetworkserializable.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/serialization/inetworkserializebymemcpy.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/serialization/networkobject-serialization.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/serialization/serialization-arrays.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/serialization/serialization-intro.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/serialization/unity-primitives.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/session-management.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/transports.md create mode 100644 versioned_docs/version-2.0.0/advanced-topics/ways-to-synchronize.md create mode 100644 versioned_docs/version-2.0.0/basics/connection-approval.md create mode 100644 versioned_docs/version-2.0.0/basics/custom-networkvariables.md create mode 100644 versioned_docs/version-2.0.0/basics/deferred-despawning.md create mode 100644 versioned_docs/version-2.0.0/basics/logging.md create mode 100644 versioned_docs/version-2.0.0/basics/maxnumberplayers.md create mode 100644 versioned_docs/version-2.0.0/basics/networkbehaviour-synchronize.md create mode 100644 versioned_docs/version-2.0.0/basics/networkbehaviour.md create mode 100644 versioned_docs/version-2.0.0/basics/networkobject.md create mode 100644 versioned_docs/version-2.0.0/basics/networkvariable.md create mode 100644 versioned_docs/version-2.0.0/basics/object-spawning.md create mode 100644 versioned_docs/version-2.0.0/basics/object-visibility.md create mode 100644 versioned_docs/version-2.0.0/basics/ownership.md create mode 100644 versioned_docs/version-2.0.0/basics/playerobjects.md create mode 100644 versioned_docs/version-2.0.0/basics/race-conditions.md create mode 100644 versioned_docs/version-2.0.0/basics/scenemanagement/client-synchronization-mode.md create mode 100644 versioned_docs/version-2.0.0/basics/scenemanagement/custom-management.md create mode 100644 versioned_docs/version-2.0.0/basics/scenemanagement/inscene-placed-networkobjects.md create mode 100644 versioned_docs/version-2.0.0/basics/scenemanagement/scene-events.md create mode 100644 versioned_docs/version-2.0.0/basics/scenemanagement/scene-management-overview.md create mode 100644 versioned_docs/version-2.0.0/basics/scenemanagement/timing-considerations.md create mode 100644 versioned_docs/version-2.0.0/basics/scenemanagement/using-networkscenemanager.md create mode 100644 versioned_docs/version-2.0.0/basics/spawning-synchronization.md create mode 100644 versioned_docs/version-2.0.0/community-contributions/codemonkey-videos.md create mode 100644 versioned_docs/version-2.0.0/community-contributions/dapper-videos.md create mode 100644 versioned_docs/version-2.0.0/community-contributions/dilmer-videos.md create mode 100644 versioned_docs/version-2.0.0/components/networkanimator.md create mode 100644 versioned_docs/version-2.0.0/components/networkmanager.md create mode 100644 versioned_docs/version-2.0.0/components/networktransform.md create mode 100644 versioned_docs/version-2.0.0/installation/installation.md create mode 100644 versioned_docs/version-2.0.0/installation/migratingfromUNet.md create mode 100644 versioned_docs/version-2.0.0/installation/migratingfrommlapi.md create mode 100644 versioned_docs/version-2.0.0/learn/bitesize/bitesize-clientdriven.md create mode 100644 versioned_docs/version-2.0.0/learn/bitesize/bitesize-dynamicprefabs.md create mode 100644 versioned_docs/version-2.0.0/learn/bitesize/bitesize-introduction.md create mode 100644 versioned_docs/version-2.0.0/learn/bitesize/bitesize-invaders.md create mode 100644 versioned_docs/version-2.0.0/learn/bitesize/bitesize-spaceshooter.md create mode 100644 versioned_docs/version-2.0.0/learn/bitesize/bitesize-usecases.md create mode 100644 versioned_docs/version-2.0.0/learn/bossroom/architecture.md create mode 100644 versioned_docs/version-2.0.0/learn/bossroom/bossroom-actions.md create mode 100644 versioned_docs/version-2.0.0/learn/bossroom/getting-started-boss-room.md create mode 100644 versioned_docs/version-2.0.0/learn/bossroom/networkobject-parenting.md create mode 100644 versioned_docs/version-2.0.0/learn/bossroom/networkrigidbody.md create mode 100644 versioned_docs/version-2.0.0/learn/bossroom/optimizing-bossroom.md create mode 100644 versioned_docs/version-2.0.0/learn/bossroom/spawn-networkobjects.md create mode 100644 versioned_docs/version-2.0.0/learn/clientside-interpolation.md create mode 100644 versioned_docs/version-2.0.0/learn/dealing-with-latency.md create mode 100644 versioned_docs/version-2.0.0/learn/distributed-authority-quick-start.md create mode 100644 versioned_docs/version-2.0.0/learn/faq.md create mode 100644 versioned_docs/version-2.0.0/learn/lagandpacketloss.md create mode 100644 versioned_docs/version-2.0.0/learn/listenserverhostarchitecture.md create mode 100644 versioned_docs/version-2.0.0/learn/rpcnetvarexamples.md create mode 100644 versioned_docs/version-2.0.0/learn/rpcvnetvar.md create mode 100644 versioned_docs/version-2.0.0/learn/sample-dedicated-server.md create mode 100644 versioned_docs/version-2.0.0/learn/ticks-and-update-rates.md create mode 100644 versioned_docs/version-2.0.0/relay/relay.md create mode 100644 versioned_docs/version-2.0.0/release-notes/ngo-changelog.md create mode 100644 versioned_docs/version-2.0.0/samples/release-notes/bitesize-changelog.md create mode 100644 versioned_docs/version-2.0.0/samples/release-notes/bossroom-changelog.md create mode 100644 versioned_docs/version-2.0.0/terms-concepts/client-server.md create mode 100644 versioned_docs/version-2.0.0/terms-concepts/distributed-authority.md create mode 100644 versioned_docs/version-2.0.0/terms-concepts/mutliplayer-terms.md create mode 100644 versioned_docs/version-2.0.0/terms-concepts/network-topologies.md create mode 100644 versioned_docs/version-2.0.0/troubleshooting/error-messages.md create mode 100644 versioned_docs/version-2.0.0/troubleshooting/troubleshooting.md create mode 100644 versioned_docs/version-2.0.0/tutorials/command-line-helper.md create mode 100644 versioned_docs/version-2.0.0/tutorials/get-started-with-ngo.md create mode 100644 versioned_docs/version-2.0.0/tutorials/goldenpath_series/gp_intro.md create mode 100644 versioned_docs/version-2.0.0/tutorials/goldenpath_series/gp_module_one.md create mode 100644 versioned_docs/version-2.0.0/tutorials/goldenpath_series/gp_module_two.md create mode 100644 versioned_docs/version-2.0.0/tutorials/helloworld.md create mode 100644 versioned_docs/version-2.0.0/tutorials/testing/techniques_and_tricks_for_debugging_multiplayer_games.md create mode 100644 versioned_docs/version-2.0.0/tutorials/testing/testing_client_connection_management.md create mode 100644 versioned_docs/version-2.0.0/tutorials/testing/testing_locally.md create mode 100644 versioned_docs/version-2.0.0/tutorials/testing/testing_with_artificial_conditions.md create mode 100644 versioned_sidebars/version-2.0.0-sidebars.json diff --git a/docs/components/networkmanager.md b/docs/components/networkmanager.md index 8eb91f548..044aaaa24 100644 --- a/docs/components/networkmanager.md +++ b/docs/components/networkmanager.md @@ -5,7 +5,7 @@ title: NetworkManager The `NetworkManager` is a required Netcode for GameObjects component that has all of your project's netcode-related settings. Think of it as the central netcode hub for your netcode-enabled project. -### `NetworkManager` Inspector properties +## `NetworkManager` Inspector properties - **LogLevel**: Sets the network logging level - **PlayerPrefab**: When a prefab is assigned, the prefab will be instantiated as the PlayerObject and assigned to the newly connected and authorized client. For more information about player prefabs, refer to [PlayerObjects and player prefabs](../basics/playerobjects.md). @@ -22,12 +22,19 @@ The `NetworkManager` is a required Netcode for GameObjects component that has al - **Enable Scene Management**: When checked, Netcode for GameObjects will handle scene management and client synchronization for you. When not checked, you will have to create your own scene management scripts and handle client synchronization. - **Load Scene Time Out**: When Enable Scene Management is checked, this specifies the period of time the `NetworkSceneManager` will wait while a scene is being loaded asynchronously before `NetworkSceneManager` considers the load/unload scene event to have failed/timed out. +## Starting and stopping `NetworkManager` + +There are two static event notifications available when [starting](#starting-a-server-host-or-client) or [stopping](#disconnecting-and-shutting-down) `NetworkManager`. If you need to know when a `NetworkManager` has been instantiated or when it's in the process of being destroyed, there are two event notifications you can subscribe to: + +- The `NetworkManager.OnInstantiated` notification is triggered when a new `NetworkManager` instance has been instantiated. +- The `NetworkManager.OnDestroying` notification is triggered when an existing `NetworkManager` instance is being destroyed. + ### `NetworkManager` sub-systems -`NetworkManager` is also where you can find references to other Netcode related management systems:
+`NetworkManager` is also where you can find references to other Netcode-related management systems: :::caution -All `NetworkManager` sub-systems are instantiated once the `NetworkManager` is started (that is, `NetworkManager.IsListening == true`). A good general "rule of thumb" is to not attempt to access the below sub-systems before starting the `NetworkManager`, otherwise they won't yet be initialized. +All `NetworkManager` sub-systems are instantiated once the `NetworkManager` is started (either when `NetworkManager.IsListening == true` or after you've received a `NetworkManager.OnInstantiated` notification). Don't attempt to access the following sub-systems before starting the `NetworkManager`, otherwise they won't yet be initialized. ::: - [NetworkManager.PrefabHandler](../advanced-topics/object-pooling.md): This provides access to the NetworkPrefabHandler that is used for NetworkObject pools and to have more control overriding network prefabs. @@ -59,7 +66,7 @@ Don't start a `NetworkManager` within any `NetworkBehaviour` component's method; - [Connection Approval](../basics/connection-approval) ::: -## Connecting +### Connecting When starting a client, the `NetworkManager` uses the IP and the Port provided in your `Transport` component for connecting. While you can set the IP address in the editor, many times you might want to be able to set the IP address and port during runtime. @@ -247,3 +254,7 @@ public class ConnectionNotificationManager : MonoBehaviour } } ``` + +## Additional resources + +- [`NetworkManager` API documentation](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html) diff --git a/docusaurus.config.js b/docusaurus.config.js index a0d9ba46b..14310676e 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -237,9 +237,13 @@ module.exports = { lastVersion: "current", versions: { current: { - label: "2.0.0", + label: "2.1.1", path: "current", }, + "2.0.0": { + label: "2.0.0", + path: "2.0.0", + }, "1.11.0": { label: "1.11.0", path: "1.11.0", diff --git a/versioned_docs/version-2.0.0/about.md b/versioned_docs/version-2.0.0/about.md new file mode 100644 index 000000000..f28e3e238 --- /dev/null +++ b/versioned_docs/version-2.0.0/about.md @@ -0,0 +1,65 @@ +--- +id: about +title: About Netcode for GameObjects +description: Overview of Unity's Netcode for GameObjects for your multiplayer networking needs. +--- + +Netcode for GameObjects is a high-level networking library built for Unity for you to abstract networking logic. You can send GameObjects and world data across a networking session to many players at once. With Netcode for GameObjects, you can focus on building your game instead of low-level protocols and networking frameworks. + +To learn more about Netcode for GameObjects functionality and capabilities, explore the content below: + + + +
+ +| Getting Started | Get Started Project | Education and Samples | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Install Unity Netcode](installation/installation.md)
[Migration from UNet to Netcode](installation/migratingfromUNet.md)
[Upgrade to Unity Netcode Package](installation/migratingfrommlapi.md) | [Get started with Netcode](../docs/tutorials/get-started-with-ngo.md) | [Boss Room](learn/bossroom/getting-started-boss-room.md)
[Bite Size Samples](learn/bitesize/bitesize-introduction.md)
[Dilmer's Tutorials](community-contributions/dilmer-videos.md) | + +
+ +
+ +| Core Concepts | Debugging | Terminology and FAQs | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Networking](basics/connection-approval.md)
[Components](components/networkmanager.md)
[Objects](basics/object-spawning.md)
[Messaging System](advanced-topics/messaging-system.md)
[Serialization](advanced-topics/serialization/serialization-intro.md)
[Scenes](basics/scenemanagement/scene-management-overview.md) | [Logging](basics/logging.md)
[Troubleshooting](troubleshooting/troubleshooting.md)
[Error Messages](troubleshooting/error-messages.md) | [High Level Terminology](terms-concepts/mutliplayer-terms.md)
[Multiplayer Game Architecture](terms-concepts/network-topologies.md)
[FAQs](learn/faq.md) | + +
+ +For more information, check out [Release Notes](release-notes/ngo-changelog.md) and [APIs](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/index.html). + +## Before you begin + +Netcode for GameObjects supports the following versions: + +- Unity 6.0+ and later +- Mono and IL2CPP [Scripting Backends](https://docs.unity3d.com/Manual/scripting-backends.html) + +Netcode for GameObjects supports the following platforms: + +- Windows, macOS, and Linux +- iOS and Android +- XR platforms running on Windows, Android, and iOS operating systems +- Most [**closed platforms**](https://unity.com/platform-installation), such as consoles. Contact us for more information about specific closed platforms. + - When working with consoles (such as PlayStation, Xbox, or Nintendo Switch), there may be Netcode-specific policies you should be aware of while testing and before launching your game live. Refer to the console's internal documentation for more information. This content is typically protected by NDA. +- WebGL (requires Netcode for GameObjects 1.2.0+ and Unity Transport 2.0.0+) diff --git a/versioned_docs/version-2.0.0/advanced-topics/bufferserializer.md b/versioned_docs/version-2.0.0/advanced-topics/bufferserializer.md new file mode 100644 index 000000000..c9151271c --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/bufferserializer.md @@ -0,0 +1,21 @@ +--- +id: bufferserializer +title: BufferSerializer +sidebar_label: BufferSerializer +--- + +`BufferSerializer` is the bi-directional serializer primarily used for serializing within [`INetworkSerializable`](inetworkserializable.md) types. It wraps [`FastBufferWriter` and `FastBufferReader`](fastbufferwriter-fastbufferreader.md) to provide high performance serialization, but has a couple of differences to make it more user-friendly: + +- Rather than writing separate methods for serializing and deserializing, `BufferSerializer` allows writing a single method that can handle both operations, which reduces the possibility of a mismatch between the two +- `BufferSerializer` does bound checking on every read and write by default, making it easier to avoid mistakes around manual bounds checking required by `FastBufferWriter` and `FastBufferReader` + +These aren't without downsides, however: + +- `BufferSerializer` has to operate on an existing mutable value due to its bi-directional nature, which means values like `List.Count` have to be stored to a local variable before writing. +- `BufferSerializer` is slightly slower than `FastBufferReader` and `FastBufferWriter` due to both the extra pass-through method calls and the mandatory bounds checking on each write. +- `BufferSerializer` don't support any form of packed reads and writes. + +However, when those downsides are unreasonable, `BufferSerializer` offers two ways to perform more optimal serialization for either performance or bandwidth usage: + +- For performance, you can use `PreCheck(int amount)` followed by `SerializeValuePreChecked()` to perform bounds checking for multiple fields at once. +- For both performance and bandwidth usage, you can obtain the wrapped underlying reader/writer via `serializer.GetFastBufferReader()` when `serializer.IsReader` is `true`, and `serializer.GetFastBufferWriter()` when `serializer.IsWriter` is `true`. These provide micro-performance improvements by removing a level of indirection, and also give you a type you can use with `BytePacker` and `ByteUnpacker`. diff --git a/versioned_docs/version-2.0.0/advanced-topics/client-anticipation.md b/versioned_docs/version-2.0.0/advanced-topics/client-anticipation.md new file mode 100644 index 000000000..3c3420e1c --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/client-anticipation.md @@ -0,0 +1,125 @@ +--- +id: client-anticipation +title: Client anticipation +--- + +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +:::info Client-server only + +Client anticipation is only relevant for games using a [client-server topology](../terms-concepts/client-server.md). + +::: +Netcode for GameObjects doesn't support full client-side prediction and reconciliation, but it does support client anticipation: a simplified model that lacks the full rollback-and-replay prediction loop, but still provides a mechanism for anticipating the server result of an action and then correcting if you anticipated incorrectly. + +Client anticipation uses `AnticipatedNetworkVariable` and `AnticipatedNetworkTransform`. By calling their various `Anticipate` methods, you can set a visual value that differs from the server value and then react to updates from the server in different ways depending on how you have configured the `StaleDataHandling` property. Additionally, both include a [`Smooth` method](#smoothing-for-corrections) to support interpolation, either when receiving updated values from the server that don't match the anticipated value, or when receiving updates from other clients' actions that weren't anticipated at all. + +## Overview + +Games with a server-authoritative architecture often face the problem of making the game feel responsive despite [latency](../learn/ladandpacketloss.md). For example, when a user wants to change the color of an object from green to blue they click a button in the UI, an RPC is sent to the server, and the server changes the object to blue. From the client's perspective, the object doesn't change to blue until the server responds to that message, resulting in a perceived delay for the user. + +
+ +
+ +Client anticipation solves this problem by allowing a separation between the visual value and the authoritative value of an object. In this example, with anticipation, when the button is pressed to change the color from green to blue, the client *anticipates* the result of the command by visually changing the object to green while it waits for an update from the server: + +
+ +
+ +This creates a responsive feel to the gameplay, allowing the client player to feel as if things are responding immediately to their input and concealing latency. + +## The anticipated value + +Both `AnticipatedNetworkVariable` and `AnticipatedNetworkTransform` separate their values into two concepts: the anticipated (or visual) value and the authoritative value. These are exposed slightly differently between the two: + +- On `AnticipatedNetworkVariable`, the anticipated value is stored as `variable.Value`, while the authoritative value is stored as `variable.AuthoritativeValue`. To change the anticipated value on the client, call `variable.Anticipate(newValue)`, which sets the anticipated value to the newly provided value. On the server, calling `variable.Anticipate(newValue)` changes both the anticipated and authoritative values, enabling you to use the exact same code on the client and server. Likewise, `variable.AuthoritativeValue = newValue` also updates both values on the server, while this value is read-only on the client. +- On `AnticipatedNetworkTransform`, the anticipated value is stored in both `gameObject.transform` and `anticipatedNetworkTransform.AnticipatedState`, both of which are read-only on the client, while the authoritative value is stored as `anticipatedNetworkTransform.AuthoritativeState`. If you only update the transform's values on the client, they are overwritten when the next server update comes in. To perform anticipation on the transform, you have to call `anticipatedNetworkTransform.AnticipateMove()`, `anticipatedNetworkTransform.AnticipateRotate()`, `anticipatedNetworkTransform.AnticipateScale()`, or `anticipatedNetworkTransform.AnticipateState()` to update all three at once. As with `AnticipatedNetworkVariable`, calling any of these on the server updates both the anticipated and authoritative values. + +## `StaleDataHandling` + +Anticipation systems need to be able to handle stale data. Stale data refers to updates from the server that represent actions that happened before your last request, and are actually going to be overwritten by that request. + +Expanding the example above to include a second client that's also trying to change the color of the same object highlights this problem. If client A tries to change the object to blue, and then client B tries to change it to red, client A sees a delayed switch to blue, followed by a switch to red (which is fine because this is actually what happened). Client B, however, clicks the button to change it to red, then sees it change to blue, followed by a change to red. + +
+ +
+ +With client anticipation, this scenario plays out differently: client A anticipates the change to blue, so it happens immediately, and then later sees the object change to red (which, again, is fine). Client B also sees the object change to red immediately, but because a change to blue is already in progress, that overwrites client B's anticipated value, causing it to flicker briefly to blue from client A's request before changing back to red again from client B's request. + +
+ +
+ +To address this, Netcode for GameObjects's client anticipation includes a feature called `StaleDataHandling`. Stale data is determined based on assumptions about causation - it assumes that, when you make an anticipation on the client side based on player input, an RPC is sent to the server at the same time requesting it to make the same change. It uses a continuously incrementing `AnticipationCounter` to track when the server has received and responded to the batch of requests that was sent on the same frame as the variable was anticipated. If an update for a variable arrives before the server has processed that message, the anticipation system regards that data as being stale. + +There are two ways you can respond to stale data, which are determined by the `StaleDataHandling` value on each `AnticipatedNetworkVariable` and `AnticipatedNetworkTransform`: + +- StaleDataHandling.Ignore +- StaleDataHandling.Reanticipate + +### StaleDataHandling.Ignore +If `StaleDataHandling` is set to `StaleDataHandling.Ignore`, stale data doesn't roll back the value of the variable or transform to the server value and doesn't trigger the [`OnReanticipate` event](#onreanticipate-event). `ShouldReanticipate` remains false in the event something else triggers the callback. The authoritative value is still updated, however, and for `AnticipatedNetworkVariable`, the `OnAuthoritativeValueUpdated` callback is still called. The result for our example is that, for client B, the change to blue is recognized as being sequenced before its change to red, and is thus ignored, eliminating the flickering. This is the default behavior for `AnticipatedNetworkVariable`. + +
+ +
+ +### StaleDataHandling.Reanticipate +If `StaleDataHandling` is set to `StaleDataHandling.Reanticipate`, stale data is treated the same way as any other server data updates. The value is rolled back, `ShouldReanticipate` is set to true, and the [`OnReanticipate` event](#onreanticipate-event) fires. In typical client prediction systems, this generally involves replaying the player's input from the time of the incoming data to now, which results in re-performing the switch to red. + +
+ +
+ +The `OnReanticipate` event can also be used for other purposes, such as "forward simulation" of an AI to anticipate a new position based on latency. This is considered advanced functionality, however, and implementing it is up to users. + +## `OnReanticipate` event + +`NetworkBehaviour` has a virtual method called `OnReanticipate`. When server data is received for an `AnticipatedNetworkVariable` or `AnticipatedNetworkTransform`, it's rolled back immediately, setting its anticipated state. During each frame in which a server update for any `AnticipatedNetworkVariable` or `AnticipatedNetworkTransform` is received (after **all** such operations have been performed and **all** objects are rolled back to their server state), each NetworkObject that had any rollbacks calls the `OnReanticipate` method on **all** of its `NetworkBehaviour`s. + +If you need to do any reanticipation to update the anticipated state of any of these variables or transforms, this method is where you will do it. `OnReanticipate` takes as its only parameter a `double` providing the amount of time, in seconds, that the object has been rolled back (which corresponds to the round-trip time of the current batch of responses received from the server). This value can be used to calculate the difference between what the server value is, and what the anticipated client value should be, and apply that change. + +However, note that not all variables and transforms on that object may have received updates, so not all of them will have been rolled back to a previous state. Before doing any reanticipation on any given variable or transform, you should check that variable/transform's `ShouldReanticipate` property. If this is `false`, then it still contains the most recent client anticipated value and no work is needed. + +### Global `OnReanticipate` + +In addition to the `NetworkBehaviour`'s `OnReanticipate` method, `NetworkManager` also has a callback that can be subscribed to for global reanticipation. This is useful if you need to run your reanticipation in a more global way, such as if you need to run it step-wise (say, anticipating one frame at a time) and need all objects to complete one step before any of them begin the second one. This callback receives the same `lastRoundTripTime` value as the `NetworkBehaviour` method, and is called after all of the `NetworkBehaviour` methods have been called. + +:::note + +If you want to implement a full client-side prediction model in your game, the global OnReanticipate callback is likely the ideal place to incorporate your rollback and replay logic. The details of implementing this, however, are left up to users. Implementing a full, production-ready prediction loop is a complex topic and recommended for advanced users only. + +::: + +## Smoothing for corrections + +Most anticipations will be correct and the server will update the state to the same value. Sometimes, however, the server will have a different result, since latency can cause the server state to change before it receives your command in ways that affect the result. When this happens, by default, the value will be snapped to the new server value. + +This can result in an undesirable player experience, causing things that normally move smoothly to become choppy. When this happens, you can smooth out the result by using the `Smooth()` function on both `AnticipatedNetworkVariable` and `AnticipatedNetworkTransform`. + +`Smooth()` takes a starting position (usually the previous anticipated value), a final position (usually the new anticipated value or the current authoritative value), and a duration over which to perform the smoothing. For `AnticipatedNetworkVariable`, because the type is generic, it also requires a delegate to use to perform the smoothing operation (which takes a from value, a to value, and a percent value, and in many cases may just be `Mathf.Lerp`). + +For convenience, you can use `PreviousAnticipatedValue` and `PreviousAnticipatedState` to access the value most recently passed into `Anticipate()` for `AnticipatedNetworkVariable` and `AnticipatedNetworkTransform` respectively. Note, however, that if you call `Anticipate()` or its related transform methods prior to doing the smoothing, they will update the previous anticipated value, so you may need to store the previous anticipated value in a local variable before doing your reanticipation. + +### Server-side smoothing + +Even though these concepts are mostly for client-side use, there are cases where you might want to perform server-side smoothing as well. For example, when running in host mode, the host player sees the movement of other players. Due to latency and jitter, input updates from remote clients may not arrive every frame, which may result in choppy movement of an `AnticipatedNetworkTransform`, for example. + +To address this, you can also use the `Smooth()` function at any time on the server side. The server doesn't do anticipation, so it will not get any `OnReanticipate()` events, but you can, for example, call `Smooth()` each time you receive an input from the client to smooth the motion between the previous position and the new one. + +One important distinction between client-side smoothing and server-side smoothing for `AnticipatedNetworkTransform`, however, is that client-side smoothing smooths the actual motion of the transform, while server-side smoothing only smooths the visual depiction of the motion. Which is to say, `AnticipatedState` is updated, but `transform` is not, so all game logic and collision detection is done based on the actual position, and the rendering of the object will be slightly behind the actual position of the object. diff --git a/versioned_docs/version-2.0.0/advanced-topics/connection-events.md b/versioned_docs/version-2.0.0/advanced-topics/connection-events.md new file mode 100644 index 000000000..2c67b9bc3 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/connection-events.md @@ -0,0 +1,35 @@ +--- +id: connection-events +title: Connection events + +--- + +When you need to react to connection or disconnection events for yourself or other clients, you can use `NetworkManager.OnConnectionEvent` as a unified source of information about changes in the network. Connection events are session-mode agnostic and work in both [client-server](../terms-concepts/client-server.md) and [distributed authority](../terms-concepts/distributed-authority.md) contexts. + +`OnConnectionEvent` receives a `ConnectionEventData` struct detailing the relevant information about the connection state change: + +```csharp +public enum ConnectionEvent +{ + ClientConnected, + PeerConnected, + ClientDisconnected, + PeerDisconnected +} + +public struct ConnectionEventData +{ + public ConnectionEvent EventType; + public ulong ClientId; + public NativeArray PeerClientIds; +} +``` + +There are four types of event you can receive. The events are the same for both clients and servers/hosts, but they indicate slightly different things depending on the context. + +|Event |Server or host |Client | +|---|---|---| +|`ConnectionEvent.ClientConnected` |Indicates that a new client has connected. The ID of the newly connected client is `ClientId`. `PeerClientIds` is uninitialized and shouldn't be used.|Indicates that the local client has completed its connection to the server. The ID of the client is `LocalClientId`, and `PeerClientIds` contains a list of client IDs of other clients currently connected to the server.| +|`ConnectionEvent.PeerConnected` |Not applicable for servers. For host clients running in host mode, works the same as for clients.|Indicates that another client has connected to the server. The ID of the newly connected client is `ClientId`. `PeerClientIds` is uninitialized and shouldn't be used. | +|`ConnectionEvent.ClientDisconnected`|Indicates that a client has disconnected. The ID of the disconnected client is `ClientId`. `PeerClientIds` is uninitialized and shouldn't be used.|Indicates that the local client has disconnected from the server. The ID of the client is `LocalClientId`. `PeerClientIds` is uninitialized and shouldn't be used. | +|`ConnectionEvent.PeerDisconnected` |Not applicable for servers. For host clients running in host mode, works the same as for clients.| Indicates that another client has disconnected from the server. The ID of the disconnected client is `ClientId`. `PeerClientIds` is uninitialized and shouldn't be used.| diff --git a/versioned_docs/version-2.0.0/advanced-topics/custom-serialization.md b/versioned_docs/version-2.0.0/advanced-topics/custom-serialization.md new file mode 100644 index 000000000..3d8a96765 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/custom-serialization.md @@ -0,0 +1,97 @@ +--- +id: custom-serialization +title: Custom serialization +--- + +Netcode uses a default serialization pipeline when using `RPC`s, `NetworkVariable`s, or any other Netcode-related tasks that require serialization. The serialization pipeline looks like this: + +`` +Custom Types => Built In Types => INetworkSerializable +`` + +That is, when Netcode first gets hold of a type, it will check for any custom types that the user have registered for serialization, after that it will check if it's a built in type, such as a Vector3, float etc. These are handled by default. If not, it will check if the type inherits `INetworkSerializable`, if it does, it will call it's write methods. + +By default, any type that satisfies the `unmanaged` generic constraint can be automatically serialized as RPC parameters. This includes all basic types (bool, byte, int, float, enum, etc) as well as any structs that has only these basic types. + +With this flow, you can provide support for serializing any unsupported types, and with the API provided, it can even be done with types that you haven't defined yourself, those who are behind a 3rd party wall, such as .NET types. However, the way custom serialization is implemented for RPCs and NetworkVariables is slightly different. + +### Serialize a type in a Remote Procedure Call (RPC) + +:::note +From versioln 1.7.0 Remote Procedure Calls (RPCs) can also use the Network Variable flow, but NetworkVariables can't use the RPC flow. The RPC flow is more efficient when RPCs serialize the type. Unity selects the RPC flow if you implement both the RPC and Network variable flows. When a type is used by both NetworkVariables and RPCs you can use the NetworkVariable flow to lower maintenance requirements. +::: + +To register a custom type, or override an already handled type, you need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()`: + +```csharp +// Tells the Netcode how to serialize and deserialize Url in the future. +// The class name doesn't matter here. +public static class SerializationExtensions +{ + public static void ReadValueSafe(this FastBufferReader reader, out Url url) + { + reader.ReadValueSafe(out string val); + url = new Url(val); + } + + public static void WriteValueSafe(this FastBufferWriter writer, in Url url) + { + writer.WriteValueSafe(url.Value); + } +} +``` + +The code generation for RPCs will automatically pick up and use these functions, and they'll become available via `FastBufferWriter` and `FastBufferReader` directly. + +You can also optionally use the same method to add support for `BufferSerializer.SerializeValue()`, if you wish, which will make this type readily available within [`INetworkSerializable`](/advanced-topics/serialization/inetworkserializable.md) types: + +```csharp +// The class name doesn't matter here. +public static class SerializationExtensions +{ + public static void SerializeValue(this BufferSerializer serializer, ref Url url) where TReaderWriter: IReaderWriter + { + if (serializer.IsReader) + { + url = new Url(); + } + serializer.SerializeValue(ref url.Value); + } +} +``` + +Additionally, you can also add extensions for `FastBufferReader.ReadValue()`, `FastBufferWriter.WriteValue()`, and `BufferSerializer.SerializeValuePreChecked()` to provide more optimal implementations for manual serialization using `FastBufferReader.TryBeginRead()`, `FastBufferWriter.TryBeginWrite()`, and `BufferSerializer.PreCheck()`, respectively. However, none of these will be used for serializing RPCs - only `ReadValueSafe` and `WriteValueSafe` are used. + +### For NetworkVariable + +`NetworkVariable` goes through a slightly different pipeline than `RPC`s and relies on a different process for determining how to serialize its types. As a result, making a custom type available to the `RPC` pipeline doesn't automatically make it available to the `NetworkVariable` pipeline, and vice-versa. The same method can be used for both, but currently, `NetworkVariable` requires an additional runtime step to make it aware of the methods. + +To add custom serialization support in `NetworkVariable`, follow the steps from the "For RPCs" section to write extension methods for `FastBufferReader` and `FastBufferWriter`; then, somewhere in your application startup (before any `NetworkVariable`s using the affected types will be serialized) add the following: + +```csharp +UserNetworkVariableSerialization.WriteValue = SerializationExtensions.WriteValueSafe; +UserNetworkVariableSerialization.ReadValue = SerializationExtensions.ReadValueSafe; +``` + +You can also use lambda expressions here: + +```csharp +UserNetworkVariableSerialization.WriteValue = (FastBufferWriter writer, in Url url) => +{ + writer.WriteValueSafe(url.Value); +}; + +UserNetworkVariableSerialization.ReadValue = (FastBufferReader reader, out Url url) +{ + reader.ReadValueSafe(out string val); + url = new Url(val); +}; +``` + +When you create an extension method in `NetworkVariable` you need to implement the following values: + +- `WriteValue` +- `ReadValue` +- `DuplicateValue` + +`DuplicateValue` returns a complete deep copy of the value that `NetworkVariable` compares to a previous value to check whether or not that values has changed. This avoids reserializing it over the network every frame when it hasn't changed. diff --git a/versioned_docs/version-2.0.0/advanced-topics/fastbufferwriter-fastbufferreader.md b/versioned_docs/version-2.0.0/advanced-topics/fastbufferwriter-fastbufferreader.md new file mode 100644 index 000000000..5b7fd43e1 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/fastbufferwriter-fastbufferreader.md @@ -0,0 +1,163 @@ +--- +id: fastbufferwriter-fastbufferreader +title: FastBufferWriter and FastBufferReader +sidebar_label: FastBufferWriter and FastBufferReader +--- +The serialization and deserialization is done via `FastBufferWriter` and `FastBufferReader`. These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. + +There's a trade-off of CPU usage vs bandwidth in using this: Writing individual fields is slower (especially when it includes operations on unaligned memory), but allows the buffer to be filled more efficiently, both because it avoids padding for alignment in structs, and because it allows you to use `BytePacker.WriteValuePacked()`/`ByteUnpacker.ReadValuePacked()` and `BytePacker.WriteValueBitPacked()`/`ByteUnpacker.ReadValueBitPacked()`. The difference between these two is that the BitPacked variants pack more efficiently, but they reduce the valid range of values. See the section below for details on packing. + + +**Example** + +```csharp +struct ExampleStruct +{ + private float f; + private bool b; + private int i; + + void Serialize(FastBufferWriter writer); +} +``` + +In this example struct, `Serialize` can be implemented in two ways: + +```csharp +void Serialize(FastBufferWriter writer) +{ + if(!writer.TryBeginWrite(sizeof(float) + sizeof(bool) + sizeof(i))) + { + throw new OverflowException("Not enough space in the buffer"); + } + writer.WriteValue(f); + writer.WriteValue(b); + writer.WriteValue(i); +} +``` + +```csharp +void Serialize(FastBufferWriter writer) +{ + if(!writer.TryBeginWrite(sizeof(ExampleStruct))) + { + throw new OverflowException("Not enough space in the buffer"); + } + writer.WriteValue(this); +} +``` +This creates efficiently packed data in the message, and can be further optimized by using `BytePacker.WriteValuePacked()` and `BytePacker.WriteValueBitPacked()`, but it has two downsides: +- First, it involves more method calls and more instructions, making it slower. +- Second, that it creates a greater opportunity for the serialize and deserialize code to become misaligned, since they must contain the same operations in the same order. + +You can also use a hybrid approach if you have a few values that will need to be packed and several that won't: + +```C# +struct ExampleStruct +{ + struct Embedded + { + private byte a; + private byte b; + private byte c; + private byte d; + } + public Embedded embedded; + public float f; + public short i; + + void Serialize(FastBufferWriter writer) + { + writer.WriteValue(embedded); + BytePacker.WriteValuePacked(writer, f); + BytePacker.WriteValuePacked(writer, i); + } +} +``` + +This allows the four bytes of the embedded struct to be rapidly serialized as a single action, then adds the compacted data at the end, resulting in better bandwidth usage than serializing the whole struct as-is, but better performance than serializing it one byte at a time. + + +## FastBufferWriter and FastBufferReader + +`FastBufferWriter` and `FastBufferReader` are replacements for the old `NetworkWriter` and `NetworkReader`. For those familiar with the old classes, there are some key differences: + +- `FastBufferWriter` uses `WriteValue()` as the name of the method for all types *except* [`INetworkSerializable`](serialization/inetworkserializable) types, which are serialized through `WriteNetworkSerializable()` +- `FastBufferReader` similarly uses `ReadValue()` for all types except INetworkSerializable (which is read through `ReadNetworkSerializable`), with the output changed from a return value to an `out` parameter to allow for method overload resolution to pick the correct value. +- `FastBufferWriter` and `FastBufferReader` outsource packed writes and reads to `BytePacker` and `ByteUnpacker`, respectively. +- `FastBufferWriter` and `FastBufferReader` are **structs**, not **classes**. This means they can be constructed and destructed without GC allocations. +- `FastBufferWriter` and `FastBufferReader` both use the same allocation scheme as Native Containers, allowing the internal buffers to be created and resized without creating any garbage and with the use of `Allocator.Temp` or `Allocator.TempJob`. +- `FastBufferReader` can be instantiated using `Allocator.None` to operate on an existing buffer with no allocations and no copies. +- Neither `FastBufferReader` nor `FastBufferWriter` inherits from nor has a `Stream`. +- `FastBufferReader` and `FastBufferWriter` are heavily optimized for speed, using aggressive inlining and unsafe code to achieve the fastest possible buffer storage and retrieval. +- `FastBufferReader` and `FastBufferWriter` use unsafe typecasts and `UnsafeUtility.MemCpy` operations on `byte*` values, achieving native memory copy performance with no need to iterate or do bitwise shifts and masks. +- `FastBufferReader` and `FastBufferWriter` are intended to make data easier to debug - one such thing to support will be a `#define MLAPI_FAST_BUFFER_UNPACK_ALL` that will disable all packing operations to make the buffers for messages that use them easier to read. +- `FastBufferReader` and `FastBufferWriter` don't support runtime type discovery - there is no `WriteObject` or `ReadObject` implementation. All types must be known at compile time. This is to avoid garbage and boxing allocations. + +A core benefit of `NativeArray` is that it offers access to the allocation scheme of `Allocator.TempJob`. This uses a special type of allocation that is nearly as fast as stack allocation and involves no GC overhead, while being able to persist for a few frames. In general they're rarely if ever needed for more than a frame, but this does provide a efficient option for creating buffers as needed, which avoids the need to use a pool for them. The only downside is that buffers created this way must be manually disposed after use, as they're not garbage collected. + +## Creating and Disposing FastBufferWriters and FastBufferReaders + +To create your own `FastBufferWriter`s and `FastBufferReader`s, it's important to note that struct default/parameterless constructors can't be removed or overridden, but `FastBufferWriter` and `FastBufferReader` require constructor behavior to be functional. + +`FastBufferWriter` always owns its internal buffer and must be constructed with an initial size, an allocator, and a maximum size. If the maximum size isn't provided or is less than or equal to the initial size, the `FastBufferWriter` can't expand. + +`FastBufferReader` can be constructed to either own its buffer or reference an existing one via `Allocator.None`. Not all types are compatible with `Allocator.None` - only `byte*`, `NativeArray`, and `FastBufferWriter` input types can provide `Allocator.None`. You can obtain a `byte*` from a `byte[]` using the following method: + +```c# +byte[] byteArray; +fixed(byte* bytePtr = byteArray) +{ + // use bytePtr here +} +``` + +It's important to note with `Allocator.None` that the `FastBufferReader` will be directly referencing a position in memory, which means the `FastBufferReader` must not live longer than the input buffer it references - and if the input buffer is a `byte[]`, the `FastBufferReader` must not live longer than the `fixed()` statement, because outside of that statement, the garbage collector is free to move that memory, which will cause random and unpredictable errors. + +Regardless which allocator you use (including `Allocator.None`), `FastBufferWriter` and `FastBufferReader` must always have `Dispose()` called on them when you're done with them. The best practice is to use them within `using` blocks. + +## Bounds Checking + +For performance reasons, by default, `FastBufferReader` and `FastBufferWriter` **don't do bounds checking** on each write. Rather, they require the use of specific bounds checking functions - `TryBeginRead(int amount)` and `TryBeginWrite(int amount)`, respectively. This improves performance by allowing you to verify the space exists for the multiple values in a single call, rather than doing that check on every single operation. + +:::info +**In editor mode and development builds**, calling these functions records a watermark point, and any attempt to read or write past the watermark point will throw an exception. This ensures these functions are used properly, while avoiding the performance cost of per-operation checking in production builds. In production builds, attempting to read or write past the end of the buffer will cause undefined behavior, likely program instability and/or crashes. +::: + +For convenience, every `WriteValue()` and `ReadValue()` method has an equivalent `WriteValueSafe()` and `ReadValueSafe()` that does bounds checking for you, throwing `OverflowException` if the boundary is exceeded. Additionally, some methods, such as arrays (where the amount of data being read can't be known until the size value is read) and [`INetworkSerializable`](inetworkserializable.md) values (where the size can't be predicted outside the implementation) will always do bounds checking internally. + +## Bitwise Reading and Writing + +Writing values in sizes measured in bits rather than bytes comes with a cost +- First, it comes with a cost of having to track bitwise lengths and convert them to bytewise lenghts. +- Second, it comes with a cost of having to remember to add padding after your bitwise writes and reads to ensure the next bytewise write or read functions correctly, and to make sure the buffer length includes any trailing bits. + +To address that, `FastBufferReader` and `FastBufferWriter` don't, themselves, have bitwise operations. When needed, however, you can create a `BitWriter` or `BitReader` instance, which is used ensure that no unaligned bits are left at the end - from the perspective of `FastBufferReader` and `FastBufferWriter`, only bytes are allowed. `BitWriter` and `BitReader` operate directly on the underlying buffer, so calling non-bitwise operations within a bitwise context is an error (and will raise an exception in non-production builds). + +```csharp +FastBufferWriter writer = new FastBufferWriter(256, Allocator.TempJob); +using(var bitWriter = writer.EnterBitwiseContext()) +{ + bitWriter.WriteBit(a); + bitWriter.WriteBits(b, 5); +} // Dispose automatically adds 2 more 0 bits to pad to the next byte. +``` + +## Packing + +Packing values is done using the utility classes `BytePacker` and `ByteUnpacker`. These generally offer two different ways of packing values: + +- `BytePacker.WriteValuePacked()`/`ByteUnpacker.ReadValuePacked()` are the most versatile. They can write any range of values that fit into the type, and also have special built-in methods for many common Unity types that can automatically pack the values contained within. + +- `BytePacker.WriteValueBitPacked()`/`ByteUnpacker.ReadValueBitPacked()` offer tighter/more optimal packing (the data in the buffer will never exceed `sizeof(type)`, which can happen with large values using `WriteValuePacked()`, and will usually be one byte smaller than with `WriteValuePacked()` except for values <= 240, which will be one byte with both methods), but come with the limitations that they can only be used on integral types, and they use some bits of the type to encode length information, meaning that they reduce the usable size of the type. The sizes allowed by these functions are as follows: + + | Type | Usable Size | + | ------ | ------------------------------------------------------------ | + | short | 14 bits + sign bit (-16,384 to 16,383) | + | ushort | 15 bits (0 to 32,767) | + | int | 29 bits + sign bit (-536,870,912 to 536,870,911) | + | uint | 30 bits (0 to 1,073,741,824) | + | long | 60 bits + sign bit (-1,152,921,504,606,846,976 to 1,152,921,504,606,846,975) | + | ulong | 61 bits (0 to 2,305,843,009,213,693,952) | + + diff --git a/versioned_docs/version-2.0.0/advanced-topics/inscene_parenting_player.md b/versioned_docs/version-2.0.0/advanced-topics/inscene_parenting_player.md new file mode 100644 index 000000000..54a92836b --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/inscene_parenting_player.md @@ -0,0 +1,92 @@ +--- +id: inscene-parenting-players +title: Real world In-scene NetworkObject parenting of players solution +description: In-scene NetworkObject parenting of players Solution +--- + + +We received the following issue in Github. + +## Issue: + +When a player Prefab has a script that dynamically adds a parent to its transform, the client can't join a game hosted by another client. [You can see orignal issue here](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues/1211) + +Steps to reproduce the behavior: + +1. Set up basic networking game with at least one `GameObject` in a scene that isn't the player. +1. Add a script to the player Prefab that adds parenting to its transform via `gameObject.transform.SetParent()` in the `Start()` method. +1. Launch one instance of the game as Host. +1. Launch another instance and try to join as Client. + +## Solution: + + +If you want to do this when a player has first connected and all `NetworkObjects` (in-scene placed and already dynamically spawned by the server-host) have been fully synchronized with the client then we would recommend using the `NetworkManager.SceneManager.OnSceneEvent` to trap for the `SynchronizeComplete` event. + +Here is an example script that we recommend using to achieve this: + +```csharp +using Unity.Netcode; + +public class ParentPlayerToInSceneNetworkObject : NetworkBehaviour +{ + public override void OnNetworkSpawn() + { + if (IsServer) + { + // Server subscribes to the NetworkSceneManager.OnSceneEvent event + NetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + + // Server player is parented under this NetworkObject + SetPlayerParent(NetworkManager.LocalClientId); + } + } + + private void SetPlayerParent(ulong clientId) + { + if (IsSpawned && IsServer) + { + // As long as the client (player) is in the connected clients list + if (NetworkManager.ConnectedClients.ContainsKey(clientId)) + { + // Set the player as a child of this in-scene placed NetworkObject + // We parent in local space by setting the WorldPositionStays value to false + NetworkManager.ConnectedClients[clientId].PlayerObject.TrySetParent(NetworkObject, false); + } + } + } + + private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) + { + // OnSceneEvent is useful for many things + switch (sceneEvent.SceneEventType) + { + // The SceneEventType event tells the server that a client-player has: + // 1.) Connected and Spawned + // 2.) Loaded all scenes that were loaded on the server at the time of connecting + // 3.) Synchronized (instantiated and spawned) all NetworkObjects in the network session + case SceneEventType.SynchronizeComplete: + { + // As long as we aren't the server-player + if (sceneEvent.ClientId != NetworkManager.LocalClientId) + { + // Set the newly joined and synchronized client-player as a child of this in-scene placed NetworkObject + SetPlayerParent(sceneEvent.ClientId); + } + break; + } + } + } +} +``` + +You should place this script on your in-scene placed NetworkObject (that is, the first `GameObject`) and do the parenting from it to avoid any timing issues of when it's spawned or the like. It only runs the script on the server-host side since parenting is server authoritative. + + +:::note +Remove any parenting code you might have had from your player Prefab before using the above script. Depending upon your project's goals, you might be parenting all players under the same in-scene placed NetworkObject or you might intend to have each player parenting unique. If you want each player to be parented under a unique in-scene placed NetworkObject then you will need to have the same number of in-scene placed NetworkObjects as your maximum allowed players per game session. The above example will only parent all players under the same in-scene placed NetworkObject. You can extend the above example by migrating the scene event code into an in-scene placed NetworkObject that manages the parenting of players (i,e. name it something like `PlayerSpawnManager`) as they connect, make the `SetPlayerParent` method public, and add all in-scene placed NetworkObjects to a public list of GameObjects that the `PlayerSpawnManager` will reference and assign player's to as they connect while also freeing in-scene placed NetworkObjects as players disconnect during a game session. +::: + +:::important +Netcode for GameObjects v1.2 has a [known issue](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/pull/2396) with parenting dynamically spawned `NetworkObjects` under in-scene placed `NetworkObjects`. This will be fixed in the next update. +::: diff --git a/versioned_docs/version-2.0.0/advanced-topics/message-system/clientrpc.md b/versioned_docs/version-2.0.0/advanced-topics/message-system/clientrpc.md new file mode 100644 index 000000000..f750d7af4 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/message-system/clientrpc.md @@ -0,0 +1,132 @@ +--- +id: clientrpc +title: ClientRpc +--- +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +:::warning +ClientRpc and ServerRpc are legacy features of Netcode for GameObjects and have been replaced with the universal RPC attribute. This documentation is for legacy use. For current projects, use [Rpc](rpc.md) instead. +::: + + +Servers can invoke a ClientRpc to execute on all clients. + +
+ +
+ + + +## Declaring a ClientRpc + +You can declare a ClientRpc by marking a method with the `[ClientRpc]` attribute and including the `ClientRpc` suffix in the method name. + +```csharp +[ClientRpc] +void PongClientRpc(int somenumber, string sometext) { /* ... */ } +``` + +## Invoking a ClientRpc + +You can invoke a ClientRpc by invoking the function directly with parameters: + +```csharp +void Update() +{ + if (Input.GetKeyDown(KeyCode.P)) + { + PongClientRpc(Time.frameCount, "hello, world"); // Server -> Client + } +} +``` + +You must mark client RPC methods with the `[ClientRpc]` attribute and use the `ClientRpc` method suffix; failing to do so results in an error message. + +```csharp +// Error: Invalid, missing 'ClientRpc' suffix in the method name +[ClientRpc] +void Pong(int somenumber, string sometext) { /* ... */ } + +// Error: Invalid, missing [ClientRpc] attribute on the method +void PongClientRpc(int somenumber, string sometext) { /* ... */ } +``` + +The `[ClientRpc]` attribute and matching `...ClientRpc` suffix in the method name help to assure context clarity in scripts that invoke them and are used by Netcode during the ILPostProcessor pass. The ILPostProcessor pass replaces all call-site locations where the RPC method is invoked, with additional code generated specific to the RPC to assure that the RPC message is generated and sent to the appropriate destinations/targets as opposed to just locally invoking the method. + +```csharp +Pong(somenumber, sometext); // Is this an RPC call? + +PongRpc(somenumber, sometext); // Is this a ServerRpc call or ClientRpc call? + +PongClientRpc(somenumber, sometext); // This is clearly a ClientRpc call +``` + +## To send to one client, use ClientRpcSendParameters + +The following code provides an example of using ClientRpcSendParameters, which sends a ClientRpc to a specific client connection. The default Netcode for GameObjects behavior is to broadcast to every single client. + +```csharp +private void DoSomethingServerSide(int clientId) +{ + // If isn't the Server/Host then we should early return here! + if (!IsServer) return; + + + // NOTE! In case you know a list of ClientId's ahead of time, that does not need change, + // Then please consider caching this (as a member variable), to avoid Allocating Memory every time you run this function + ClientRpcParams clientRpcParams = new ClientRpcParams + { + Send = new ClientRpcSendParams + { + TargetClientIds = new ulong[]{clientId} + } + }; + + // Let's imagine that you need to compute a Random integer and want to send that to a client + const int maxValue = 4; + int randomInteger = Random.Range(0, maxValue); + DoSomethingClientRpc(randomInteger, clientRpcParams); +} + +[ClientRpc] +private void DoSomethingClientRpc(int randomInteger, ClientRpcParams clientRpcParams = default) +{ + if (IsOwner) return; + + // Run your client-side logic here!! + Debug.LogFormat("GameObject: {0} has received a randomInteger with value: {1}", gameObject.name, randomInteger); +} +``` + +
+ +
Server can invoke a client RPC on a Network Object. The RPC will be placed in the local queue and then sent to a selection of clients (by default this selection is "all clients"). When received by a client, RPC will be executed on the client's version of the same Network Object.
+
+ +## Invoking a client RPC from the host + +The host is both a client and a server. If a host invokes a client RPC, it triggers the on all clients, including the host. + + +
+ +
Hosts can invoke client RPCs on `NetworkObjects`. If broadcasting to all clients, the RPC will be immediately invoked on the host and placed in the local queue. At the end of the frame, the client RPC will be sent to the remote clients. When a remote client receives the client RPC it's executed on the client's local cloned instance of the same NetworkObject.
+
+ +:::warning +When running as a host, Netcode for GameObjects invokes RPCs immediately within the same stack as the method invoking the RPC. Since a host is both considered a server and a client, you should avoid design patterns where a ClientRpc invokes a ServerRpc that invokes the same ClientRpc as this can end up in a stack overflow (infinite recursion). +::: + +See the [Boss Room RPC Examples](../../learn/bossroom/bossroom-actions.md). + + +## Also see + +* [ServerRpc](serverrpc.md) +* [RPC parameters](rpc-params.md) diff --git a/versioned_docs/version-2.0.0/advanced-topics/message-system/custom-messages.md b/versioned_docs/version-2.0.0/advanced-topics/message-system/custom-messages.md new file mode 100644 index 000000000..8a5741d15 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/message-system/custom-messages.md @@ -0,0 +1,302 @@ +--- +id: custom-messages +title: Custom messages +description: A brief explanation of Custom Messages use in Netcode for GameObjects (Netcode) covering Named and Unnamed messages. +--- +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +If you don't want to use the Netcode for GameObjects (Netcode) messaging system, you don't have to. You can use a thin layer called "Custom Messages" to implement your own messaging behavior or add custom targeting. They're unbound to any GameObject. You can use Custom messages with [RPC messages](../messaging-system.md). + +There are two types of custom messages: +- Unnamed +- Named + +## Unnamed Messages +You can think about unnamed messages as if you are sending information over a single unique channel. There's only one receiver handler per unnamed message, which can help when building a custom messaging system where you can define your own message headers. Netcode for GameObjects handles delivering and receiving custom unnamed messages; you determine what kind of information you want to transmit over the channel. + +### Unnamed Message Example +Below is a basic example of how you might implement your own messaging system using unnamed messages: +```csharp +using UnityEngine; +using Unity.Collections; +using Unity.Netcode; + +/// +/// Using an unnamed message to send a string message +/// defined +/// further down below. +/// +public class UnnamedStringMessageHandler : CustomUnnamedMessageHandler +{ + /// + /// We override this method to define the unique message type + /// identifier for this child derived class + /// + protected override byte MessageType() + { + // As an example, we can define message type of 1 for string messages + return 1; + } + + public override void OnNetworkSpawn() + { + // For this example, we always want to invoke the base + base.OnNetworkSpawn(); + + if (IsServer) + { + // Server broadcasts to all clients when a new client connects + // (just for example purposes) + NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback; + } + else + { + // Clients send a greeting string message to the server + SendUnnamedMessage("I am a client connecting!"); + } + } + + public override void OnNetworkDespawn() + { + // For this example, we always want to invoke the base + base.OnNetworkDespawn(); + + // Whether server or not, unregister this. + NetworkManager.OnClientDisconnectCallback -= OnClientConnectedCallback; + } + + private void OnClientConnectedCallback(ulong clientId) + { + // Server broadcasts a welcome string message to all clients that + // a new client has joined. + SendUnnamedMessage($"Everyone welcome the newly joined client ({clientId})!"); + } + + /// + /// For this example, we override this message to handle receiving the string + /// message. + /// + protected override void OnReceivedUnnamedMessage(ulong clientId, FastBufferReader reader) + { + var stringMessage = string.Empty; + reader.ReadValueSafe(out stringMessage); + if (IsServer) + { + Debug.Log($"Server received unnamed message of type ({MessageType()}) from client " + + $"({clientId}) that contained the string: \"{stringMessage}\""); + + // As an example, we can also broadcast the client message to everyone + SendUnnamedMessage($"Newly connected client sent this greeting: \"{stringMessage}\""); + } + else + { + Debug.Log(stringMessage); + } + } + + /// + /// For this example, we will send a string as the payload. + /// + /// IMPORTANT NOTE: You can construct your own header to be + /// written for custom message types, this example just uses + /// the message type value as the "header". This provides us + /// with the ability to have "different types" of unnamed + /// messages. + /// + public override void SendUnnamedMessage(string dataToSend) + { + var writer = new FastBufferWriter(1100, Allocator.Temp); + var customMessagingManager = NetworkManager.CustomMessagingManager; + // Tip: Placing the writer within a using scope assures it will + // be disposed upon leaving the using scope + using (writer) + { + // Write our message type + writer.WriteValueSafe(MessageType()); + + // Write our string message + writer.WriteValueSafe(dataToSend); + if (IsServer) + { + // This is a server-only method that will broadcast the unnamed message. + // Caution: Invoking this method on a client will throw an exception! + customMessagingManager.SendUnnamedMessageToAll(writer); + } + else + { + // This method can be used by a client or server (client to server or server to client) + customMessagingManager.SendUnnamedMessage(NetworkManager.ServerClientId, writer); + } + } + } +} + +/// +/// A templated class to handle sending different data types +/// per unique unnamed message type/child derived class. +/// +public class CustomUnnamedMessageHandler : NetworkBehaviour +{ + /// + /// Since there is no unique way to identify unnamed messages, + /// adding a message type identifier to the message itself is + /// one way to handle know: + /// "what kind of unnamed message was received?" + /// + protected virtual byte MessageType() + { + // The default unnamed message type + return 0; + } + + /// + /// For most cases, you want to register once your NetworkBehaviour's + /// NetworkObject (typically in-scene placed) is spawned. + /// + public override void OnNetworkSpawn() + { + // Both the server-host and client(s) will always subscribe to the + // the unnamed message received event + NetworkManager.CustomMessagingManager.OnUnnamedMessage += ReceiveMessage; + } + + public override void OnNetworkDespawn() + { + // Unsubscribe when the associated NetworkObject is despawned. + NetworkManager.CustomMessagingManager.OnUnnamedMessage -= ReceiveMessage; + } + + /// + /// This method needs to be overridden to handle reading a unique message type + /// (that is, derived class) + /// + protected virtual void OnReceivedUnnamedMessage(ulong clientId, FastBufferReader reader) + { + } + + /// + /// For this unnamed message example, we always read the message type + /// value to determine if it should be handled by this instance in the + /// event it's a child of the CustomUnnamedMessageHandler class. + /// + private void ReceiveMessage(ulong clientId, FastBufferReader reader) + { + var messageType = (byte)0; + // Read the message type value that is written first when we send + // this unnamed message. + reader.ReadValueSafe(out messageType); + // Example purposes only, you might handle this in a more optimal way + if (messageType == MessageType()) + { + OnReceivedUnnamedMessage(clientId, reader); + } + } + + /// + /// For simplicity, the default does nothing + /// + /// + public virtual void SendUnnamedMessage(T dataToSend) + { + + } +} +``` +## Named Messages +If you don't want to handle the complexity of creating your own messaging system, Netcode for GameObjects also provides you with the option to use custom named messages. Custom named messages use the message name as the unique identifier (it creates a hash value from the name and links that to a received named message callback). + +:::tip +If you aren't quite sure if you need to incorporate the complexity of message identification and handling like you do with custom unnamed messages, you can always start with custom named messages and then if, later, you find you need sub-message types for a specific custom named message then you can always incorporate a type identifier (like you would with unnamed messages) into the named message payload itself. +::: + +### Name Message Example +Below is a basic example of implementing a custom named message: +```csharp +using System; +using UnityEngine; +using Unity.Collections; +using Unity.Netcode; +public class CustomNamedMessageHandler : NetworkBehaviour +{ + [Tooltip("The name identifier used for this custom message handler.")] + public string MessageName = "MyCustomNamedMessage"; + + /// + /// For most cases, you want to register once your NetworkBehaviour's + /// NetworkObject (typically in-scene placed) is spawned. + /// + public override void OnNetworkSpawn() + { + // Both the server-host and client(s) register the custom named message. + NetworkManager.CustomMessagingManager.RegisterNamedMessageHandler(MessageName, ReceiveMessage); + + if (IsServer) + { + // Server broadcasts to all clients when a new client connects (just for example purposes) + NetworkManager.OnClientConnectedCallback += OnClientConnectedCallback; + } + else + { + // Clients send a unique Guid to the server + SendMessage(Guid.NewGuid()); + } + } + + private void OnClientConnectedCallback(ulong obj) + { + SendMessage(Guid.NewGuid()); + } + + public override void OnNetworkDespawn() + { + // De-register when the associated NetworkObject is despawned. + NetworkManager.CustomMessagingManager.UnregisterNamedMessageHandler(MessageName); + // Whether server or not, unregister this. + NetworkManager.OnClientDisconnectCallback -= OnClientConnectedCallback; + } + + /// + /// Invoked when a custom message of type + /// + private void ReceiveMessage(ulong senderId, FastBufferReader messagePayload) + { + var receivedMessageContent = new ForceNetworkSerializeByMemcpy(new Guid()); + messagePayload.ReadValueSafe(out receivedMessageContent); + if (IsServer) + { + Debug.Log($"Sever received GUID ({receivedMessageContent.Value}) from client ({senderId})"); + } + else + { + Debug.Log($"Client received GUID ({receivedMessageContent.Value}) from the server."); + } + } + + /// + /// Invoke this with a Guid by a client or server-host to send a + /// custom named message. + /// + public void SendMessage(Guid inGameIdentifier) + { + var messageContent = new ForceNetworkSerializeByMemcpy(inGameIdentifier); + var writer = new FastBufferWriter(1100, Allocator.Temp); + var customMessagingManager = NetworkManager.CustomMessagingManager; + using (writer) + { + writer.WriteValueSafe(messageContent); + if (IsServer) + { + // This is a server-only method that will broadcast the named message. + // Caution: Invoking this method on a client will throw an exception! + customMessagingManager.SendNamedMessageToAll(MessageName, writer); + } + else + { + // This is a client or server method that sends a named message to one target destination + // (client to server or server to client) + customMessagingManager.SendNamedMessage(MessageName, NetworkManager.ServerClientId, writer); + } + } + } +} +``` diff --git a/versioned_docs/version-2.0.0/advanced-topics/message-system/deprecation-of-return-values.md b/versioned_docs/version-2.0.0/advanced-topics/message-system/deprecation-of-return-values.md new file mode 100644 index 000000000..9063a912b --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/message-system/deprecation-of-return-values.md @@ -0,0 +1,51 @@ +--- +id: deprecation-of-return-values +title: Deprecation of return values +--- +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +Netcode for GameObjects used to support RPC return values on convenience RPCs. + +Example: + +```csharp +public IEnumerator MyRpcCoroutine() +{ + RpcResponse response = InvokeServerRpc(MyRpcWithReturnValue, Random.Range(0f, 100f), Random.Range(0f, 100f)); + + while (!response.IsDone) + { + yield return null; + } + + Debug.LogFormat("The final result was {0}!", response.Value); +} + +[ServerRPC] +public float MyRpcWithReturnValue(float x, float y) +{ + return x * y; +} + +``` + +Netcode for GameObjects no longer supports this feature. To achieve the same functionality, use a combination of the ServerRpc and ClientRpc methods. The following code demonstrates this method: + +```csharp +void MyRpcInvoker() +{ + MyRpcWithReturnValueRequestServerRpc(Random.Range(0f, 100f)), Random.Range(0f, 100f))); +} + +[Rpc(SendTo.Server)] +void MyRpcWithReturnValueRequestServerRpc(float x, float y) +{ + MyRpcWithReturnValueResponseClientRpc(x * y); +} + +[Rpc(SendTo.ClientsAndHost)] +void MyRpcWithReturnValueResponseClientRpc(float result) +{ + Debug.LogFormat("The final result was {0}!", result); +} +``` diff --git a/versioned_docs/version-2.0.0/advanced-topics/message-system/execution-table.md b/versioned_docs/version-2.0.0/advanced-topics/message-system/execution-table.md new file mode 100644 index 000000000..cd5940465 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/message-system/execution-table.md @@ -0,0 +1,101 @@ +--- +id: execution-table +title: Execution table +--- +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +:::warning +ClientRpc and ServerRpc are legacy features of Netcode for GameObjects and have been replaced with the universal RPC attribute. This documentation is for legacy use. For current projects, use [Rpc](rpc.md) instead. +::: + +The following table details the execution of `ServerRpc` and `ClientRpc` functions: + +| Function | Server | Client | Host (Server+Client) | +|---|:---:|:---:|:---:| +| ServerRpc Send | | | | +| ServerRpc Execute | | | | +| ClientRpc Send | | | | +| ClientRpc Execute | | | | + +An RPC function typically doesn't execute its body immediately since the function call is a stand-in for a network transmission. Since the host is both a client and a server, local RPCs targeting the host-server or host-client are invoked immediately. As such, avoid nesting RPCs when running in host mode as a ServerRpc method that invokes a ClientRpc method that invokes the same ServerRpc method (and repeat...) can cause a stack overflow. + +Structure of a typical `ServerRpc`: + +```csharp +[ServerRpc] +void MyServerRpc(int somenumber, string somestring) +{ + // Network Send Block (framework-code) + // Network Return Block (framework-code) + // RPC Method Body (user-code) +} +``` + +Pseudo-code sample of a `ServerRpc`: + +```csharp +[ServerRpc] +void MyServerRpc(int somenumber, string somestring) +{ + // --- begin: injected framework-code + if (NetworkSend()) + { + // this block will be executed if: + // - called from user-code on client + // - called from user-code on host + + var writer = NetworkCreateWriter(); + writer.WriteInt32(1234567890); // RPC method signature hash + writer.WriteInt32(somenumber); + writer.WriteChars(somestring); + NetworkSendRpc(writer); + } + + if (NetworkReturn()) + { + // this block will be executed if: + // - called from user-code + // - called from framework-code on client + + return; + } + // --- end: injected framework-code + + print($"MyServerRpc: {somenumber}"); +} +``` + +
+ +
Client can invoke a server RPC on a Network Object. The RPC will be placed in the local queue and then sent to the server, where it will be executed on the server version of the same Network Object.
+
+ +
+ +
Clients can invoke server RPCs on Client Hosts the same way they can invoke server RPCs on the regular servers: the RPC will be placed in the local queue and then sent to the Client Host, where it will be executed on the Client Host's version of the same Network Object.
+
+ + +
+ +
When a server RPC is invoked by the Client Host, the RPC will be placed in a local queue and then executed on the Client Host after a short delay. The same happens for pure servers.
+
+ +
+ +
+ +
+ +
Client Hosts can invoke Client RPCs on Network Objects. The RPC will be placed in the local queue and then, after a short delay the client RPC will be executed on the Client Host, and sent to the other clients. When client RPC is received by the client - it's executed on the Client's version of the same Network Object.
+
diff --git a/versioned_docs/version-2.0.0/advanced-topics/message-system/reliabilty.md b/versioned_docs/version-2.0.0/advanced-topics/message-system/reliabilty.md new file mode 100644 index 000000000..cc4e94ad6 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/message-system/reliabilty.md @@ -0,0 +1,41 @@ +--- +id: reliability +title: Reliability +--- +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +## Introduction +RPCs **are reliable by default**. This means they're guaranteed to be received and executed on the remote side. However, sometimes developers might want to opt-out reliability, which is often the case for non-critical events such as particle effects, sounds effects etc. +:::caution +Packet reliability can be a two edged sword (pro and con): +- **The Pro**: Under bad network conditions a reliable packet is guaranteed to be received by the target remote side. +- **The Con**: Because the sender will continue trying to send a reliable packet if it does not get a "packet received" response, under bad network conditions too many reliable packets being sent can cause additional bandwidth overhead. +::: + +### Reliable and Unreliable RPC Examples: +Reliability configuration can be specified for `Rpc` methods at compile-time: + +```csharp + +[Rpc(SendTo.Server)] +void MyReliableServerRpc() { /* ... */ } + +[Rpc(SendTo.Server, Delivery = RpcDelivery.Unreliable)] +void MyUnreliableServerRpc() { /* ... */ } +``` + +Reliable RPCs will be received on the remote end in the same order they are sent, but this in-order guarantee only applies to RPCs on the same NetworkObject. Different `NetworkObjects` might have reliable RPCs called but executed in different order compared to each other. To put more simply, **in-order reliable RPC execution is guaranteed per NetworkObject basis only**. If you determine an RPC is being updated often (that is, several times per second), it _might_ be better suited as an unreliable RPC. +:::caution +When testing unreliable RPCs on a local network, the chance of an unreliable packet being dropped is reduced greatly (sometimes never). As such, you might want to use [`UnityTransport`'s Simulator Pipeline](https://docs-multiplayer.unity3d.com/transport/current/pipelines#simulator-pipeline) to simulate poor network conditions to better determine how dropped unreliable RPC messages impacts your project. +::: + +:::tip +Sometimes you might find out that you are sending parameters that are both critical and non-critical. Under this scenario you might benefit by splitting the RPC into two or more RPCs with the less critical parameters being sent unreliably (possibly at the same or higher frequency) while the more critical parameters are sent reliably (possibly at the same or lower frequency). +::: + +### Additional RPC Facts +- An RPC call made without an active connection won't be automatically added to the send queue and will be dropped/ignored. +- Both reliable and unreliable RPC calls have to be made when there is an active network connection established between a client and the server. _The caveat to this rule is if you are running a host since a host is both a server and a client:_ + - _A host can send RPC messages from the "host-server" to the "host-client" and vice versa._ +- Reliable RPC calls made during connection will be dropped if the sender disconnects before the RPC is sent. + diff --git a/versioned_docs/version-2.0.0/advanced-topics/message-system/rpc-compatibility.md b/versioned_docs/version-2.0.0/advanced-topics/message-system/rpc-compatibility.md new file mode 100644 index 000000000..9d26293dd --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/message-system/rpc-compatibility.md @@ -0,0 +1,98 @@ +--- +id: rpc-compatibility +title: RPC migration and compatibility +--- +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +This section provides information on compatibility and support for Unity Netcode for GameObjects (Netcode) features compared to previous Netcode versions. See the [Release Notes](../../../../releases/introduction) for more information. + +## Cross-Compatibility + +Learn more about standard RPC API's cross-compatibility only, not the framework as a whole. A method decorated with an RPC attribute will be statically registered with its assembly-scoped method signature hash. + +A typical assembly-scoped method signature sample: + +``` +Game.dll / System.Void Shooter::PingRpc(System.Int32) +``` + +where: + +- `Game.dll` is the Assembly +- `/` is a Separator +- `System.Void Shooter::PingRpc(System.Int32)` is the Method signature: + + - `System.Void` is the Return type + - `Shooter` is the Enclosing type + - `::` is the Scope resolution operator + - `PingRpc` is the Method name + - `(System.Int32)` is the Parameter type (no param names) + +An RPC signature will be turned into a 32-bit integer using [xxHash](https://cyan4973.github.io/xxHash/) (XXH32) non-cryptographic hash algorithm. + +The RPC signature hash changes when any of the following variables change: +* Assembly. +* Enclosing type. +* Method name. +* Method parameter type. + +However, the RPC signature hash doesn't change when the names of the parameters for an existing RPC are the only variables that change. + +When the RPC signature changes, it directs to a different invocation code path that has different serialization code. This means that the RPC method with the new signature doesn't invoke previous versions of that RPC method (for example, an RPC method from an older build). + +| Compatibility | | Description | +| -- | :--: | -- | +| Cross-Build Compatibility | | As long as the RPC method signature is kept the same, it will be compatible between different builds. | +| Cross-Version Compatibility | | As long as the RPC method signature is kept the same, it will be compatible between different versions. | +| Cross-Project Compatibility | | The exact same RPC method signature can be defined in different projects. This is because the project name or project-specific token isn't part of RPC signature. Cross-project RPC methods aren't compatible with each other. | + +## Deprecation of return values + +Netcode supports RPC return values on convenience RPCs. + +Example: + +```csharp +public IEnumerator MyRpcCoroutine() +{ + RpcResponse response = InvokeServerRpc(MyRpcWithReturnValue, Random.Range(0f, 100f), Random.Range(0f, 100f)); + + while (!response.IsDone) + { + yield return null; + } + + Debug.LogFormat("The final result was {0}!", response.Value); +} + +[ServerRPC] +public float MyRpcWithReturnValue(float x, float y) +{ + return x * y; +} + +``` + +To achieve similar functionality, use the following: + +```csharp +void MyRpcInvoker() +{ + MyRpcWithReturnValueRequestServerRpc(Random.Range(0f, 100f), Random.Range(0f, 100f)); +} + +[Rpc(SendTo.Server)] +void MyRpcWithReturnValueRequestServerRpc(float x, float y) +{ + MyRpcWithReturnValueResponseClientRpc(x * y); +} + +[Rpc(SendTo.ClientsAndHost)] +void MyRpcWithReturnValueResponseClientRpc(float result) +{ + Debug.LogFormat("The final result was {0}!", result); +} +``` + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import Link from '@docusaurus/Link'; \ No newline at end of file diff --git a/versioned_docs/version-2.0.0/advanced-topics/message-system/rpc-params.md b/versioned_docs/version-2.0.0/advanced-topics/message-system/rpc-params.md new file mode 100644 index 000000000..2f8efe31f --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/message-system/rpc-params.md @@ -0,0 +1,108 @@ +--- +id: rpc-params +title: RPC params +--- +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +You can configure the `Rpc`, `ServerRpc` and `ClientRpc` methods in the following ways: +* Use the `[Rpc]`, `[ServerRpc]` and `[ClientRpc]` attributes at compile-time. +* Use `ServerRpcParams` and `ClientRpcParams` at runtime. + +Developers can put, `RpcParams`, `ServerRpcParams`, or `ClientRpcParams` as the last parameter (optionally, as appropriate for the type of RPC). The `RpcParams`, `ServerRpcParams`, and `ClientRpcParams` types contain both send and receive properties that are populated relative to the context of usage. + + +## Rpc Params + +See the following for `Rpc` params: + +```csharp +// Both Rpc methods below are fine, `ServerRpcParams` is completely optional + +[Rpc(SendTo.Server)] +void AbcdServerRpc(int somenumber) { /* ... */ } + +[Rpc(SendTo.Server)] +void XyzwServerRpc(int somenumber, RpcParams rpcParams = default) { /* ... */ } +``` + +For those accustomed to the legacy `ServerRpc` and `ClientRpc` parameters, there are a few differences with `Rpc` params: + +- `RpcParams` defines implicit conversion operators for `BaseRpcTarget` and `LocalDeferMode`: + - `BaseRpcTarget`: Used to change the destination of an RPC at runtime. + - `LocalDeferMode`: Used to change how an RPC is handled locally. + - `Defer`: Invokes the RPC locally on the next frame. + - `SendImmediate` (default): Immediately invokes the RPC (at the current call stack) locally. + +- Changing destination of an RPC is done using the properties and methods of `RpcTarget` (each `NetworkBehaviour` contains a reference to the shared `RpcTarget` object, as does `NetworkManager`.) This allows conveniently selecting various common targets (Server, NotServer, Owner, NotOwner, etc), as well as custom client lists using `RpcTarget.Single()` (to send to one client ID), `RpcTarget.Group()` (to send to multiple client IDs), and`RpcTarget.Not()` (to send to everyone except for the specified client ID or list of client IDs) +- `RpcParams` do not allow overriding the destination at runtime unless either the default target is `SendTo.SpecifiedInParams` or `AllowTargetOverride = true` is passed to the attribute. + +```csharp +[Rpc(SendTo.Server)] +void ServerRpc(RpcParams rpcParams = default) { /* ... */ } + +[Rpc(SendTo.SpecifiedInParams)] +void RuntimeSpecifiedRpc(RpcParams rpcParams = default) { /* ... */ } + +[Rpc(SendTo.Server, AllowTargetOverride = true)] +void OverridableServerRpc(RpcParams rpcParams = default) { /* ... */ } + +// Throws an exception: SendTo.Server cannot be overriden for ServerRpc +ServerRpc(RpcTarget.NotServer); +ServerRpc(RpcTarget.Single(someClientId, RpcTargetUse.Temp)); + +// Works +RuntimeSpecifiedRpc(RpcTarget.NotServer); +RuntimeSpecifiedRpc(RpcTarget.Single(someClientId, RpcTargetUse.Temp)); + +// Works +OverridableServerRpc(RpcTarget.NotServer); +OverridableServerRpc(RpcTarget.Single(someClientId, RpcTargetUse.Temp)); +``` + +:::note +For various reasons, `BaseRpcTarget` is not intended to be extended by user code. If you have a custom use case that requires a special target, we recommend creating a wrapper method that returns `RpcTarget.Single()` or `RpcTarget.Group()`, which should be able to satisfy most if not all of your needs. +::: + +## ServerRpc Params + +:::warning +ClientRpc and ServerRpc are legacy features of Netcode for GameObjects, and have been supplanted by the universal Rpc attribute. This documentation is provided for legacy use, but we recommend all current projects use [Rpc](rpc.md) instead. +::: + +See the following for `ServerRpc` params: + +``` csharp +// Both ServerRpc methods below are fine, `ServerRpcParams` is completely optional + +[ServerRpc] +void AbcdServerRpc(int somenumber) { /* ... */ } + +[ServerRpc] +void XyzwServerRpc(int somenumber, ServerRpcParams serverRpcParams = default) { /* ... */ } +``` + +[ServerRpcParams Documentation](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.ServerRpcParams.html) + +## ClientRpc Params + +:::warning +ClientRpc and ServerRpc are legacy features of Netcode for GameObjects, and have been supplanted by the universal Rpc attribute. This documentation is provided for legacy use, but we recommend all current projects use [Rpc](rpc.md) instead. +::: + +See the following for `ClientRpc` params: + +```csharp +// Both ClientRpc methods below are fine, `ClientRpcParams` is completely optional + +[ClientRpc] +void AbcdClientRpc(int framekey) { /* ... */ } + +[ClientRpc] +void XyzwClientRpc(int framekey, ClientRpcParams clientRpcParams = default) { /* ... */ } +``` + +[ClientRpcParams Documentation](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.ClientRpcParams.html) + +:::tip +`ClientRpcSendParams`'s `TargetClientIds` property is a `ulong[]` which means everytime you try to specify a subset of target clients or even a single client target, you will have to allocate a `new ulong[]`. This pattern can quickly lead into lots of heap allocations and pressure GC which would cause GC spikes at runtime. We suggest developers cache their `ulong[]` variables or use an array pool to cycle `ulong[]` instances so that it would cause less heap allocations. +::: diff --git a/versioned_docs/version-2.0.0/advanced-topics/message-system/rpc.md b/versioned_docs/version-2.0.0/advanced-topics/message-system/rpc.md new file mode 100644 index 000000000..9f60c73cd --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/message-system/rpc.md @@ -0,0 +1,235 @@ +--- +id: rpc +title: RPC +--- +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +Any process can communicate with any other process by sending a remote procedure call (RPC). As of Netcode for GameObjects version 1.8.0, the `Rpc` attribute encompasses server to client RPCs, client to server RPCs, and client to client RPCs. The `Rpc` attribute is session-mode agnostic and can be used in both [client-server](../../terms-concepts/client-server.md) and [distributed authority](../../terms-concepts/distributed-authority.md) contexts. + +
+ +
+ +
+ +
+ +## Declaring an RPC + +Declare an RPC by marking a method with the `[Rpc]` attribute and including the `Rpc` suffix in the method name. RPCs have a number of possible targets that can be declared at both runtime and compile time, but a default must be passed to the `[Rpc]` attribute. For example, to create an RPC that will be executed on a server, declare it like this: + +```csharp +[Rpc(SendTo.Server)] +public void PingRpc(int pingCount) { /* ... */ } + +[Rpc(SendTo.NotServer)] +void PongRpc(int pingCount, string message) { /* ... */ } +``` + +## Invoking an RPC + +Invoke an RPC at [compile time](#compile-time-targets) or [runtime](#runtime-targets) by invoking the function directly with parameters: + +```csharp +[Rpc(SendTo.Server)] +public void PingRpc(int pingCount) +{ + // Server -> Clients because PongRpc sends to NotServer + // Note: This will send to all clients. + // Sending to the specific client that requested the pong will be discussed in the next section. + PongRpc(pingCount, "PONG!"); +} + +[Rpc(SendTo.NotServer)] +void PongRpc(int pingCount, string message) +{ + Debug.Log($"Received pong from server for ping {pingCount} and message {message}"); +} + +void Update() +{ + if (IsClient && Input.GetKeyDown(KeyCode.P)) + { + // Client -> Server because PingRpc sends to Server + PingRpc(); + } +} +``` + +You must mark client RPC methods with the `[Rpc]` attribute and use the `Rpc` method suffix. Failing to do so produces an error message. + +```csharp +// Error: Invalid, missing 'Rpc' suffix in the method name +[Rpc(SendTo.Everyone)] +void Pong(int somenumber, string sometext) { /* ... */ } +``` + +The `[Rpc]` attribute and matching `...Rpc` suffix in the method name ensure context clarity in scripts that invoke them and are used by Netcode during the ILPostProcessor pass. The ILPostProcessor pass replaces all call-site locations where the RPC method is invoked with additional code, specifically generated for the RPC, to ensure that the RPC message is generated and sent to the appropriate targets rather than just locally invoking the method. + +```csharp +Pong(somenumber, sometext); // Is this a local function or a remote function? + +PongRpc(somenumber, sometext); // This is clearly an remote function +``` + +## RPC targets + +### Compile-time targets + +There are several default compile-time targets you can choose from. Some of these are only available in [client-server contexts](../../terms-concepts/client-server.md). + +While client-to-client RPCs are supported, it's important to note that there are no direct connections between clients. When sending a client-to-client RPC, the RPC is sent to the server (or distributed authority service) that acts as a proxy and forwards the same RPC message to all destination clients. If the targeted destination clients includes the local client, the local client will still execute the RPC immediately (or on the next frame locally, if deferred). + +| Target | Available contexts| Description | +| ---------------------------- | :----------------------------------------------------------- | --- | +| `SendTo.Server` |Client-server only| Send to the server, regardless of ownership. Executes locally if invoked on the server. | +| `SendTo.NotServer` |Client-server only| Send to everyone _but_ the server, filtered to the current observer list. Won't send to a server running in host mode - it's still treated as a server. If you want to send to servers when they are host, but not when they are dedicated server, use `SendTo.ClientsAndHost`.

Executes locally if invoked on a client. Doesn't execute locally if invoked on a server running in host mode. | +|`SendTo.Authority`|Client-server and distributed authority|Send to the NetworkObject's current authority. Executes locally if the local process has authority.| +|`SendTo.NotAuthority`|Client-server and distributed authority|Send to everyone but the current authority, filtered to the current observer list. Executes locally if the local process is not the owner.| +| `SendTo.Owner` |Client-server and distributed authority| Send to the NetworkObject's current owner. Executes locally if the local process is the owner. | +| `SendTo.NotOwner` |Client-server and distributed authority| Send to everyone but the current owner, filtered to the current observer list. Executes locally if the local process is not the owner.

In host mode, the host receives this message twice: once on the host client, once on the host server. | +| `SendTo.Me` |Client-server and distributed authority| Execute this RPC locally.

Normally this is no different from a standard function call.

Using the `DeferLocal` parameter of the attribute or the `LocalDeferMode` override in `RpcSendParams`, this can allow an RPC to be processed on localhost with a one-frame delay as if it were sent over the network. | +| `SendTo.NotMe` |Client-server and distributed authority| Send this RPC to everyone but the local machine, filtered to the current observer list. | +| `SendTo.Everyone` |Client-server and distributed authority| Send this RPC to everyone, filtered to the current observer list. Executes locally. | +| `SendTo.ClientsAndHost` |Client-server and distributed authority| Send this RPC to all clients, including the host, if a host exists. If the server is running in host mode, this is the same as `SendTo.Everyone`. If the server is running in dedicated server mode, this is the same as `SendTo.NotServer`. | +| `SendTo.SpecifiedInParams` |Client-server and distributed authority| This RPC cannot be sent without passing in a target in `RpcSendParams`. | + +### Runtime targets + +By default, most targets are hard-coded at compile time: owner always sends to owner, server always sends to server, and so on. There are two cases where you can to specify a different set of targets at runtime: + +- If the target is `SendTo.SpecifiedInParams`, you must pass a target in at runtime to send the RPC. +- If the `Rpc` parameter is declared with `AllowTargetOverride = true`, then you can optionally pass a target in at runtime to send the RPC to a different target than the compile-time default. + +To send to a different target, you must define an additional parameter of type `RpcParameters` as the *last* parameter in your RPC method, and then pass your runtime targets in with that parameter. This gets special handling in the ILPostProcessor pass: the last parameter isn't serialized and sent across the network. Instead, it's read on the sending side to check for overrides to its default sending behavior using the `RpcParameters.Send` field (while `RpcParameters.Receive` will be ignored), and on the receiving side, is be populated with `RpcParameters.Receive` to provide information about the receiving context (while `RpcParameters.Send` is left uninitialized). + +`RpcParameters` can be implicitly constructed from runtime send targets, making it simple to pass these targets in as parameters. + +#### Built-in targets + +`NetworkManager` and `NetworkBehaviour` both provide reference to a field called `RpcTarget`, which provides references to the various override targets you can pass in. The list mirrors the list of targets provided in the `SendTo` enum, and has the same behavior as described in the table above. + +The `RpcTarget` object is shared by all `NetworkBehaviours` attached to a given `NetworkManager`, so you can get access to this via any `NetworkBehaviour` or via the `NetworkManager` object itself. + +```csharp +public class SomeNetworkBehaviour : NetworkBehaviour +{ + [Rpc(SendTo.Server, AllowTargetOverride = true)] + public void SomeRpc(int serializedParameter1, float serializedParameter2, RpcParams rpcParams) + { + + } + + // Sends SomeRpc() to the owner instead of the server + public void SendSomeRpcToOwner(int param1, float param2) + { + SomeRpc(param1, param2, RpcTarget.Owner); + } +} + +public class SomeOtherClass +{ + public SomeNetworkBehaviour Behaviour; + + public SendSomeRpcToOwnerOnBehaviour(int param1, float param2) + { + // Since this method is not within a NetworkBehaviour, RpcTarget.Owner must be accessed through + // the Behaviour object via Behaviour.RpcTarget.Owner. + Behaviour.SomeRpc(param1, param2, Behaviour.RpcTarget.Owner); + } +} +``` + +#### Custom targets + +If you need to send to a specific client ID or list of client IDs that are not covered by any of the existing named targets, you can use the following methods: + +```csharp +// Send to a specific single client ID. +public BaseRpcTarget Single(ulong clientId, RpcTargetUse use) { /* ... */ } + +// Send to everyone EXCEPT a specific single client ID. +public BaseRpcTarget Not(ulong excludedClientId, RpcTargetUse use) { /* ... */ } + +// Sends to a group of client IDs. +public BaseRpcTarget Group(NativeArray clientIds, RpcTargetUse use) { /* ... */ } +public BaseRpcTarget Group(NativeList clientIds, RpcTargetUse use) { /* ... */ } +public BaseRpcTarget Group(ulong[] clientIds, RpcTargetUse use) { /* ... */ } +public BaseRpcTarget Group(T clientIds, RpcTargetUse use) where T : IEnumerable +{ /* ... */ } + +// Sends to everyone EXCEPT a group of client IDs. +public BaseRpcTarget Not(NativeArray excludedClientIds, RpcTargetUse use) { /* ... */ } +public BaseRpcTarget Not(NativeList excludedClientIds, RpcTargetUse use) { /* ... */ } +public BaseRpcTarget Not(ulong[] excludedClientIds, RpcTargetUse use) { /* ... */ } +public BaseRpcTarget Not(T excludedClientIds, RpcTargetUse use) where T : IEnumerable +{ /* ... */ } +``` + +Each of these includes an `RpcTargetUse` parameter. This parameter controls the use of a particular performance optimization in situations where it can be used. Because `BaseRpcTarget` is a managed type, allocating a new one is expensive, as it puts pressure on the garbage collector. + +If you're only creating the `BaseRpcTarget` instance for one-time use to pass it to an RPC and then discard it, `RpcTargetUse.Temp` can be used to avoid these allocations. It does this by making use of a persistent cached `BaseRpcTarget` object that it populates with new data. The downside of this is that, the next time you call any of these methods, that data may be overwritten - so if you are creating a target that you plan to use for a longer duration of time, `RpcTargetUse.Persistent` will tell the code to allocate a new one for you that it will never overwrite. + +In general, in cases where you are using `RpcTargetUse.Temp` with the group functions, it's recommended to also use the `NativeArray` or `NativeList` versions. `NativeArray` and `NativeList` can be allocated with `Allocator.Temp` very efficiently with no pressure on the garbage collector; on the other hand, the `ulong[]` and `IEnumerable` will add garbage collection pressure (even if the `IEnumerable` version is invoked using a `struct` type, it will still cause a boxing allocation), so it's recommended to avoid using those frequently. + +#### Completing the ping example + +As noted in the comments above, the earlier [ping/pong example](#invoking-an-rpc) sends its pong to all connected clients instead of just the one that requested it. To complete this example, we can use `RpcParameters` along with `SendTo.SpecifiedInParams` to make the example work as you might expect a ping to work: + +```csharp +[Rpc(SendTo.Server)] +public void PingRpc(int pingCount, RpcParams rpcParams) +{ + // Here we use RpcParams for incoming purposes - fetching the sender ID the RPC came from + // That sender ID can be passed in to the PongRpc to send this back to that client and ONLY that client + + PongRpc(pingCount, "PONG!", RpcTarget.Single(rpcParams.Receive.SenderClientId, RpcTargetUse.Temp)); +} + +[Rpc(SendTo.SpecifiedInParams)] +void PongRpc(int pingCount, string message, RpcParams rpcParams) +{ + // We do not use rpcParams within this method's body, but that is okay! + // The params passed in are used by the generated code to ensure that this sends only + // to the one client it should go to + + Debug.Log($"Received pong from server for ping {pingCount} and message {message}"); +} + +void Update() +{ + if (IsClient && Input.GetKeyDown(KeyCode.P)) + { + PingRpc(); + } +} +``` + +## Other RPC parameters + +There are a few other parameters that can be passed to either the `Rpc` attribute at compile-time or the `RpcSendParams` object at runtime. + +### `RpcAttribute` parameters + +| Parameter | Description | +| ----------------------- | ------------------------------------------------------------ | +| `Delivery` | Controls whether the delivery is reliable (default) or unreliable.

Options: `RpcDelivery.Reliable` or `RpcDelivery.Unreliable`
Default: `RpcDelivery.Reliable` | +| `RequireOwnership` | If `true`, this RPC throws an exception if invoked by a player that does not own the object. This is in effect for server-to-client, client-to-server, and client-to-client RPCs - i.e., a server-to-client RPC will still fail if the server is not the object's owner.

Default: `false` | +| `DeferLocal` | If `true`, RPCs that execute locally will be deferred until the start of the next frame, as if they had been sent over the network. (They will not actually be sent over the network, but will be treated as if they were.) This is useful for mutually recursive RPCs on hosts, where sending back and forth between the server and the "host client" will cause a stack overflow if each RPC is executed instantly; simulating the flow of RPCs between remote client and server enables this flow to work the same in both contexts.

Default: `false` | +| `AllowTargetOverride` | By default, any `SendTo` value other than `SendTo.SpecifiedInParams` is a hard-coded value that cannot be changed. Setting this to `true` allows you to provide an alternate target at runtime, while using the `SendTo` value as a fallback if no runtime value is provided. | + +### `RpcSendParams` parameters + +| Parameter | Description | +| ------------------ | ------------------------------------------------------------ | +| `Target` | Runtime override destination for the RPC. (See above for more details.) Populating this value will throw an exception unless either the `SendTo` value for the RPC is `SendTo.SpecifiedInParams`, or `AllowTargetOverride` is `true`.

Default: `null` | +| `LocalDeferMode` | Overrides the `DeferLocal` value. `DeferLocalMode.Defer` causes this particular invocation of this RPC to be deferred until the next frame even if `DeferLocal` is `false`, while `DeferLocalMode.SendImmediate` causes the RPC to be executed immediately on the local machine even if `DeferLocal` is `true`. `DeferLocalMode.Default` does whatever the `DeferLocal` value in the attribute is configured to do.

Options: `DeferLocalMode.Default`, `DeferLocalMode.Defer`, `DeferLocalMode.SendImmediate`
Default: `DeferLocalMode.Default` | + +## Additional resources + +* [RPC parameters](rpc-params.md) +* [Boss Room RPC Examples](../../learn/bossroom/bossroom-actions.md) diff --git a/versioned_docs/version-2.0.0/advanced-topics/message-system/serverrpc.md b/versioned_docs/version-2.0.0/advanced-topics/message-system/serverrpc.md new file mode 100644 index 000000000..d3a70b40a --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/message-system/serverrpc.md @@ -0,0 +1,170 @@ +--- +id: serverrpc +title: ServerRpc +--- +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +:::warning +ClientRpc and ServerRpc are legacy features of Netcode for GameObjects and have been replaced with the universal RPC attribute. This documentation is for legacy use. For current projects, use [Rpc](rpc.md) instead. +::: + +## Introduction +A `ServerRpc` provides you with the ability to send information from a client to a server just like you would invoke a method from within a class. A `ServerRpc` is a remote procedure call (RPC) that can be only invoked by a client and will always be received and executed on the server/host. + +## Declaring a ServerRpc +You can declare a `ServerRpc` by: +1. Creating a method that ends with the `ServerRpc` suffix within a `NetworkBehaviour` derived class. +2. Adding the `[ServerRpc]` attribute above the method + +### Example of declaring a ServerRpc: +```csharp +public class SomeNetworkBehaviour : NetworkBehaviour +{ + [ServerRpc] + public void PingServerRpc(int pingCount) + { + + } +} +``` +The above example uses the default [ServerRpc] attribute settings which only allows a client owner (client that owns NetworkObject associated with the `NetworkBehaviour` containing the `ServerRpc` method) invocation rights. Any client that isn't the owner won't be allowed to invoke the `ServerRpc`. + +## ServerRpc Ownership And ServerRpcParams +There are times where you might want any client to have `ServerRpc` invocation rights. You can easily accomplish this by setting the `ServerRpc` attribute's `RequireOwnership` parameter to false like in the example below: +```csharp +[ServerRpc(RequireOwnership = false)] +public void MyGlobalServerRpc(ServerRpcParams serverRpcParams = default) +{ + var clientId = serverRpcParams.Receive.SenderClientId; + if (NetworkManager.ConnectedClients.ContainsKey(clientId)) + { + var client = NetworkManager.ConnectedClients[clientId]; + // Do things for this client + } +} + +public override void OnNetworkSpawn() +{ + MyGlobalServerRpc(); // serverRpcParams will be filled in automatically +} +``` +In the above example, you will also notice that `MyGlobalServerRpc` takes a single parameter of type [`ServerRpcParams`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.ServerRpcParams.html). This parameter type is optional, but it can be useful to identify **which client** was requesting the server invoke the RPC. The `ServerRpcParams.Receive.SenderClientId` property is automatically set upon the server receiving the `ServerRpc` request and used to get the server-side `NetworkClient` instance of the client (sender). +:::important Best Practice +Using the `ServerRpcParams.Receive.SenderClientId` property is considered the best practice to identify which client was invoking the `ServerRpc`. It isn't recommended to send the client identifier via an additional `ulong` parameter added to the `ServerRpc`:
+```csharp +[ServerRpc(RequireOwnership = false)] +public void MyGlobalServerRpc(ulong clientId) // This is considered a bad practice (Not Recommended) +{ + if (NetworkManager.ConnectedClients.ContainsKey(clientId)) + { + var client = NetworkManager.ConnectedClients[clientId]; + // Do things for this client + } +} +``` +The primary reason, especially when `RequireOwnership == false`, is that it can introduce potential security issues. The secondary reason is that this value is already automatically provided to you via `ServerRpcParams` without the additional `ulong` parameter bandwidth overhead you would incur by sending the client identifier as a `ServerRpc` parameter. +::: + +Now, taking the best practices example into consideration, you might want to have other valid parameters added to your `ServerRpc`. When adding additional parameters other than the `ServerRpcParams` parameter, you **must** declare `ServerRpcParams` as the **last** parameter of the `ServerRpc`: +```csharp +[ServerRpc(RequireOwnership = false)] +public void PlayerShootGunServerRpc(Vector3 lookWorldPosition, ServerRpcParams serverRpcParams = default) +{ + var clientId = serverRpcParams.Receive.SenderClientId; + if (NetworkManager.ConnectedClients.ContainsKey(clientId)) + { + var client = NetworkManager.ConnectedClients[clientId]; + var castRay = new Ray(client.PlayerObject.transform.position, lookWorldPosition); + RaycastHit rayCastHit; + if (Physics.Raycast(castRay, out rayCastHit, 100.0f)) + { + // Handle shooting something + } + } +} +``` +Looking at the above example, we can see the client invoking the `PlayerShootGunServerRpc` method passes in a world position based on perhaps a screen space crosshair position to world space position, the `ServerRpcParams`, and the `ServerRpc` doesn't require ownership. + +:::tip Alternate Owner Example +Of course, if your project's design was such that a weapon changes ownership when it's picked up by a player, then you would only allow owners to invoke the method and would only need the one `Vector3` parameter like in the example below: +```csharp +[ServerRpc] +public void PlayerOwnerShootGunServerRpc(Vector3 lookWorldPosition) +{ + if (NetworkManager.ConnectedClients.ContainsKey(OwnerClientId)) + { + var client = NetworkManager.ConnectedClients[OwnerClientId]; + var castRay = new Ray(client.PlayerObject.transform.position, lookWorldPosition); + RaycastHit rayCastHit; + if (Physics.Raycast(castRay, out rayCastHit, 100.0f)) + { + // Handle shooting something + } + } +} +``` +::: + +## Invoking a ServerRpc +From the example below that uses the `PlayerOwnerShootGunServerRpc` method, you can see that you would invoke it just like any other method: + +```csharp +private void Update() +{ + if (!IsSpawned || !IsOwner) + { + return; + } + + if (Input.GetKeyDown(KeyCode.Space)) + { + var point = new Vector3(); + var currentEvent = Event.current; + var mousePos = new Vector2(); + + // Get the mouse position from Event. + // Note that the y position from Event is inverted. + mousePos.x = currentEvent.mousePosition.x; + mousePos.y = Camera.current.pixelHeight - currentEvent.mousePosition.y; + + point = Camera.current.ScreenToWorldPoint(new Vector3(mousePos.x, + mousePos.y, Camera.current.nearClipPlane)); + + PlayerOwnerShootGunServerRpc(point); + } +} +``` + +### ServerRpc Timing +The following are a few timing diagrams to help provide additional visual context when invoking a `ServerRpc`. + +
+ +
A Client can invoke a server RPC on a NetworkObject. The RPC will be placed in the local queue and then sent to the server at the end of the frame. Upon receiving the server RPC, it's executed on the Server's instance of the same NetworkObject.
+
+ +
+ +
Clients can invoke server RPCs on Hosts exactly like they can on a Server: the RPC will be placed in the local queue and sent to the Host at the end of the frame. Upon receiving the server RPC, it will be executed on the Host's instance of the same NetworkObject.
+
+ +
+ +
When a server RPC is invoked by a Host, the RPC is immediately executed.
+
+ +:::warning +When running as a host, RPCs are invoked immediately within the same stack as the method invoking the RPC. Since a host is both considered a server and a client, you should avoid design patterns where a ClientRpc invokes a ServerRpc that invokes the same ClientRpc as this can end up in a stack overflow (that is, infinite recursion). +::: + +## See Also + +* [ClientRpc](clientrpc.md) +* [RPC Params](rpc-params.md) +* See [examples](../../learn/bossroom/bossroom-actions) of how these were used in Boss Room. diff --git a/versioned_docs/version-2.0.0/advanced-topics/messaging-system.md b/versioned_docs/version-2.0.0/advanced-topics/messaging-system.md new file mode 100644 index 000000000..4b3d6b99c --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/messaging-system.md @@ -0,0 +1,70 @@ +--- +id: messaging-system +title: Sending events with RPCs +--- +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +Netcode for GameObjects has two parts to its messaging system: [remote procedure calls (RPCs)](message-system/rpc.md) and [custom messages](message-system/custom-messages.md). Both types have sub-types that change their behavior, functionality, and performance. RPCs as implemented in Netcode for GameObjects are session-mode agnostic, and work in both [client-server](../terms-concepts/client-server.md) and [distributed authority](../terms-concepts/distributed-authority.md) contexts. + +This page provides an introduction to RPCs. For more details, refer to the pages listed in the [RPCs in Netcode for GameObjects section](#rpcs-in-netcode-for-gameobjects) + +## Remote procedure calls (RPCs) + +RPCs are a standard software industry concept. They're a way to call methods on objects that aren't in the same executable. + +
+ +
Client can invoke a server RPC on a NetworkObject. The RPC is placed in the local queue and then sent to the server, where it's executed on the server version of the same NetworkObject.
+
+ +When calling an RPC from a client, the SDK takes note of the object, component, method, and any parameters for that RPC and sends that information over the network. The server or distributed authority service receives that information, finds the specified object, finds the specified method, and calls it on the specified object with the received parameters. + +Netcode for GameObjects includes multiple RPC variations that you can use to execute logic with various remote targets. + +
+ +
Server can invoke a client RPC on a NetworkObject. The RPC is placed in the local queue and then sent to a selection of clients (by default this selection is all clients). When received by a client, RPCs are executed on the client's version of the same NetworkObject.
+
+ +### RPCs in Netcode for GameObjects + +Refer to the following pages for more information about how RPCs are implemented in Netcode for GameObjects. + +- [Remote procedure calls (RPCs)](message-system/rpc.md) +- [Reliability](message-system/reliabilty.md) +- [RPC parameters](message-system/rpc-params.md) + - [Serialization types and RPCs](message-system/../serialization/serialization-intro.md) + +There's also some additional design advice on RPCs and some usage examples on the following pages: + +- [RPC vs NetworkVariable](../learn/rpcvnetvar.md) +- [RPC vs NetworkVariables Examples](../learn/rpcnetvarexamples.md) + +:::note Migration and compatibility +Refer to [RPC migration and compatibility](message-system/rpc-compatibility.md) for more information on updates, cross-compatibility, and deprecated methods for Unity RPC. +::: + +## RPC method calls + +A typical SDK user (Unity developer) can declare multiple RPCs under a `NetworkBehaviour` and inbound/outbound RPC calls will be replicated as a part of its replication in a network frame. + +A method turned into an RPC is no longer a regular method; it will have its own implications on direct calls and in the network pipeline. + +### RPC usage checklist + +To use RPCs, make sure: + +- `[Rpc]` attributes are on your method +- Your method name ends with `Rpc` (for example, `DoSomethingRpc()`) +- Your method is declared in a class that inherits from `NetworkBehaviour` + - Your GameObject has a NetworkObject component attached + +## Serialization types and RPCs + +Instances of serializable types are passed into an RPC as parameters and are serialized and replicated to the remote side. + +Refer to the [serialization page](serialization/serialization-intro.md) for more information. diff --git a/versioned_docs/version-2.0.0/advanced-topics/network-update-loop-system/index.md b/versioned_docs/version-2.0.0/advanced-topics/network-update-loop-system/index.md new file mode 100644 index 000000000..32af3c0e7 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/network-update-loop-system/index.md @@ -0,0 +1,53 @@ +--- +id: about-network-update-loop +title: About NetworkUpdateLoop +--- + +Often there is a need to update netcode systems like RPC queue, transport IO, and others outside the standard `MonoBehaviour` event cycle. + +The Network Update Loop infrastructure utilizes Unity's low-level Player Loop API allowing for registering `INetworkUpdateSystems` with `NetworkUpdate()` methods to be executed at specific `NetworkUpdateStages` which may be either before or after `MonoBehaviour`-driven game logic execution. + +Typically you will interact with `NetworkUpdateLoop` for registration and `INetworkUpdateSystem` for implementation. Systems such as network tick and future features (such as network variable snapshotting) will rely on this pipeline. + +## Registration + +`NetworkUpdateLoop` exposes four methods for registration: + +| Method | Registers | +| -- | -- | +| `void RegisterNetworkUpdate(INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage)` | Registers an `INetworkUpdateSystem` to be executed on the specified `NetworkUpdateStage` | +| `void RegisterAllNetworkUpdates(INetworkUpdateSystem updateSystem)` | Registers an `INetworkUpdateSystem` to be executed on all `NetworkUpdateStage`s | +| `void UnregisterNetworkUpdate(INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage)` | Unregisters an `INetworkUpdateSystem` from the specified `NetworkUpdateStage` | +| `void UnregisterAllNetworkUpdates(INetworkUpdateSystem updateSystem)` | Unregisters an `INetworkUpdateSystem` from all `NetworkUpdateStage`s | + +## Update Stages + +After injection, the player loops follows these stages. The player loop executes the `Initialization` stage and that invokes `NetworkUpdateLoop`'s `RunNetworkInitialization` method which iterates over registered `INetworkUpdateSystems` in `m_Initialization_Array` and calls `INetworkUpdateSystem.NetworkUpdate(UpdateStage)` on them. + +In all `NetworkUpdateStages`, it iterates over an array and calls the `NetworkUpdate` method over `INetworkUpdateSystem` interface, and the pattern is repeated. + + B --> C --> D --> E --> F --> G +`}/> + +| Stage | Method | +| -- | -- | +| `Initialization` | `RunNetworkInitialization` | +| `EarlyUpdate` | `RunNetworkEarlyUpdate`
`ScriptRunDelayedStartupFrame`
Other systems | +| `FixedUpdate` | `RunNetworkFixedUpdate`
`ScriptRunBehaviourFixedUpdate`
Other systems | +| `PreUpdate` | `RunNetworkPreUpdate`
`PhysicsUpdate`
`Physics2DUpdate`
Other systems | +| `Update` | `RunNetworkUpdate`
`ScriptRunBehaviourUpdate`
Other systems | +| `PreLateUpdate` | `RunNetworkPreLateUpdate`
`ScriptRunBehaviourLateUpdate` | +| `PostLateUpdate` | `PlayerSendFrameComplete`
`RunNetworkPostLateUpdate`
Other systems | + +## References + +See [Network Update Loop Reference](network-update-loop-reference.md) for process flow diagrams and code. diff --git a/versioned_docs/version-2.0.0/advanced-topics/network-update-loop-system/network-update-loop-reference.md b/versioned_docs/version-2.0.0/advanced-topics/network-update-loop-system/network-update-loop-reference.md new file mode 100644 index 000000000..488fcda58 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/network-update-loop-system/network-update-loop-reference.md @@ -0,0 +1,22 @@ +--- +id: network-update-loop-reference +title: NetworkUpdateLoop reference +--- + +The following diagrams provide insight into the Network Update Loop process and APIs. + +## Injecting NetworkUpdateLoop Systems Into PlayerLoop + +
+ +![Injecting NetworkUpdateLoop Systems Into PlayerLoop](/img/injecting-networkupdatesloop.svg) + +
+ +## NetworkUpdateLoop Running INetworkUpdateSystem Updates + +
+ +![NetworkUpdateLoop Running INetworkUpdateSystem Updates](/img/runninginetworkupdatesystemupdates.svg) + +
diff --git a/versioned_docs/version-2.0.0/advanced-topics/networkobject-parenting.md b/versioned_docs/version-2.0.0/advanced-topics/networkobject-parenting.md new file mode 100644 index 000000000..66bae036d --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/networkobject-parenting.md @@ -0,0 +1,228 @@ +--- +id: networkobject-parenting +title: NetworkObject parenting +description: A NetworkObject parenting solution within Netcode for GameObjects (Netcode) to help developers with synchronizing transform parent-child relationships of NetworkObject components. +--- + +### Overview + +If you aren't completely familiar with transform parenting in Unity, then it's highly recommended to [review over the existing Unity documentation](https://docs.unity3d.com/Manual/class-Transform.html) before reading further to properly synchronize all connected clients with any change in a GameObject component's transform parented status, Netcode for GameObjects requires that the parent and child GameObject components have NetworkObject components attached to them. + +### Parenting rules + +- Setting the parent of a child's `Transform` directly (that is, `transform.parent = childTransform;`) always uses the default `WorldPositionStays` value of `true`. + - It's recommended to always use the `NetworkObject.TrySetParent` method when parenting if you plan on changing the `WorldPositionStays` default value. + - Likewise, it's also recommended to use the `NetworkObject.TryRemoveParent` method to remove a parent from a child. +- When a server parents a spawned NetworkObject component under another spawned NetworkObject component during a Netcode game session this parent child relationship replicates across the network to all connected and future late joining clients. +- If, while editing a scene, you place an in-scene placed NetworkObject component under a GameObject component that doesn't have a NetworkObject component attached to it, Netcode for GameObjects preserves that parenting relationship. + - During runtime, this parent-child hierarchy remains true unless the user code removes the GameObject parent from the child NetworkObject component. + - **Note**: Once removed, Netcode for GameObjects won't allow you to re-parent the NetworkObject component back under the same or another GameObject component that with no NetworkObject component attached to it. +- You can perform the same parenting actions with in-scene placed NetworkObjects as you can with dynamically spawned NetworkObject components. + - Unlike network prefabs that don't allow in-Editor nested NetworkObject component children, in-scene placed NetworkObjects can have multiple generations of in-editor nested NetworkObject component children. + - You can parent dynamically spawned NetworkObject components under in-scene placed NetworkObject components and vice versa. +- To adjust a child's transform values when parenting or when removing a parent: + - Override the `NetworkBehaviour.OnNetworkObjectParentChanged` virtual method within a NetworkBehaviour component attached to the child NetworkObject component. + - When `OnNetworkObjectParentChanged` is invoked, on the server side, adjust the child's transform values within the overridden method. + - Netcode for GameObjects will then synchronize all clients with the child's parenting and transform changes. + +:::tip +When a NetworkObject is parented, Netcode for GameObjects synchronizes both the parenting information along with the child's transform values. Netcode for GameObjects uses the `WorldPositionStays` setting to decide whether to synchronize the local or world space transform values of the child NetworkObject component. This means that a NetworkObject component doesn't require you to include a NetworkTransform component if it never moves around, rotates, or changes its scale when it isn't parented. This can be beneficial for world items a player might pickup (parent the item under the player) and the item in question needs to adjustment relative to the player when it's parented or the parent is removed (dropped). This helps to reduce the item's over-all bandwidth and processing resources consumption. +::: + +### OnNetworkObjectParentChanged + +[`NetworkBehaviour.OnNetworkObjectParentChanged`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_OnNetworkObjectParentChanged_Unity_Netcode_NetworkObject_) is a virtual method you can override to be notified when a NetworkObject component's parent has changed. The [`MonoBehaviour.OnTransformParentChanged()`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnTransformParentChanged.html) method is used by NetworkObject component to catch `transform.parent` changes and notify its associated NetworkBehaviour components. + +```csharp +/// +/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed +/// +virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { } +``` + +:::caution Multi-generation children and scale +If you are dealing with more than one generation of nested children where each parent and child have scale values other than `Vector3.one`, then mixing the `WorldPositionStays` value when parenting and removing a parent will impact how the final scale is calculated! If you want to keep the same values before parenting when removing a parent from a child, then you need to use the same `WorldPositionStays` value used when the child was parented. +::: + +### Only a server (or a host) can parent NetworkObjects + +Similar to [Ownership](../basics/networkobject#ownership), only the server (or host) can control NetworkObject component parenting. + +:::tip +If you run into a situation where your client must trigger parenting a NetworkObject component, one solution is for the client to send an RPC to the server. Upon receiving the RPC message, the server then handles parenting the NetworkObject component. +::: + +### Only parent under a NetworkObject Or nothing (root or null) + +You can only parent a NetworkObject under another NetworkObject. The only exception is if you don't want the NetworkObject to have a parent. In this case, you can remove the NetworkObject's parent by invoking `NetworkObject.TryRemoveParent`. If this operation succeeds, the parent of the child will be set to `null` (root of the scene hierarchy). + +### Only spawned NetworkObjects can be parented + +A NetworkObject component can only be parented if it's spawned and can only be parented under another spawned NetworkObject component. This also means that NetworkObject component parenting can only occur during a network session (netcode enabled game session). Think of NetworkObject component parenting as a Netcode event. In order for it to happen, you must have, at minimum, a server or host instance started and listening. + +### Invalid parenting actions are reverted + +If an invalid/unsupported NetworkObject component parenting action occurs, the attempted parenting action reverts back to the NetworkObject component's original parenting state. + +**For example:** +If you had a NetworkObject component whose current parent was root and tried to parent it in an invalid way (such as under a GameObject without a NetworkObject component), it logs a warning message and the NetworkObject component reverts back to having root as its parent. + +### In-scene object parenting and player objects + +If you plan on parenting in-scene placed NetworkObject components with a player NetworkObject component when it's initially spawned, you need to wait until the client finishes synchronizing with the server first. Because you can only perform parenting on the server side, ensure you perform the parenting action only when the server has received the `NetworkSceneManager` generated `SceneEventType.SynchronizeComplete` message from the client that owns the player NetworkObject component to be parented (as a child or parent). +:::info For more information + +- [Real World In-scene NetworkObject Parenting of Players Solution](inscene_parenting_player.md)
+- [Scene Event Notifications](../basics/scenemanagement/scene-events#scene-event-notifications)
+- [In-Scene NetworkObjects](../basics/scenemanagement/inscene-placed-networkobjects.md) + ::: + +### WorldPositionStays usage + +When using the `NetworkObject.TrySetParent` or `NetworkObject.TryRemoveParent` methods, the `WorldPositionStays` parameter is synchronized with currently connected and late joining clients. When removing a child from its parent, use the same `WorldPositionStays` value that you used to parent the child. More specifically, when `WorldPositionStays` is false, this applies. However, if you're using the default value of `true`, this isn't required (because it's the default). + +When the `WorldPositionStays` parameter in `NetworkObject.TrySetParent` is the default value of `true`, this will preserve the world space values of the child NetworkObject relative to the parent. However, sometimes you might want to only preserve the local space values (pick up an object that only has some initial changes to the child's transform when parented). Through a combination of `NetworkObject.TrySetParent` and `NetworkBehaviour.OnNetworkObjectParentChanged` you can accomplish this without the need for a NetworkTransform component. To better understand how this works, it's important to understand the order of operations for both of these two methods: + +**Server-Side** + +- `NetworkObject.TrySetParent` invokes `NetworkBehaviour.OnNetworkObjectParentChanged` + - You can make adjustments to the child's position, rotation, and scale in the overridden `OnNetworkObjectParentChanged` method. +- The ParentSyncMessage is generated, the transform values are added to the message, and the message is then sent. + - ParentSyncMessage includes the child's position, rotation, and scale + - Currently connected or late joining clients will be synchronized with the parenting and the child's associated transform values + +**When to use a NetworkTransform**
+If you plan on the child NetworkObject component moving around, rotating, or scaling independently (when parented or not) then you will still want to use a NetworkTransform component. +If you only plan on making a one time change to the child NetworkObject component's transform when parented or having a parent removed, then the child doesn't need a NetworkTransform component. + +:::info For More Information + +- [Learn More About WorldPositionStays](https://docs.unity3d.com/ScriptReference/Transform.SetParent.html) + ::: + +### Network prefabs, parenting, and NetworkTransform components + +Because the NetworkTransform component synchronizes the transform of a GameObject component (with a NetworkObject component attached to it), it can become tricky to understand the parent-child transform relationship and how that translates when synchronizing late joining clients. Currently, a network prefab can only have one NetworkObject component within the root GameObject component of the prefab. However, you can have a complex hierarchy of GameObject components nested under the root GameObject component and each child GameObject component can have a NetworkBehaviour component attached to it. Because a NetworkTransform component synchronizes the transform of the GameObject component it's attached to, you might be tempted to setup a network prefab like this: + +``` +Network Prefab Root (GameObject with NetworkObject and NetworkTransform components attached to it) + ├─ Child #1 (GameObject with NetworkTransform component attached to it) + │ + └─ Child #2 (GameObject with NetworkTransform component attached to it) +``` + +While this won't give you any warnings and, depending upon the transform settings of Child #1 & #2, it might appear to work properly (because it synchronizes clients), it's important to understand how the child GameObject component (with no NetworkObject component attached to it) and parent GameObject component (that does have a NetworkObject component attached to it), synchronize when a client connects to an already in-progress network session (late joins or late joining client). If Child #1 or Child #2 have had changes to their respective GameObject component's transform before a client joining, then upon a client late joining the two child GameObject component's transforms won't synchronize during the initial synchronization period because they don't have NetworkObject components attached to them: + +``` +Network Prefab Root (Late joining client is synchronized with GameObject's current transform state) + ├─ Child #1 (Late joining client *isn't synchronized* with GameObject's current transform state) + │ + └─ Child #2 (Late joining client *isn't synchronized* with GameObject's current transform state) +``` + +This is important to understand because the NetworkTransform component initializes itself, during `NetworkTransform.OnNetworkSpawn`, with the GameObject component's current transform state. Just below, in the parenting examples, there are some valid and invalid parenting rules. As such, take these rules into consideration when using NetworkTransform components if you plan on using a complex parent-child hierarchy. Also make sure to organize your project's assets where any children that have NetworkTransform components attached to them also have NetworkObject components attached to them to avoid late-joining client synchronization issues. + +## Parenting examples + +### Simple example: + +For this example, assume you have the following initial scene hierarchy before implementing parenting: + +``` +Sun +Tree +Camera +Player (GameObject->NetworkObject) +Vehicle (GameObject->NetworkObject) +``` + +Both the player and vehicle NetworkObject components are spawned and the player moves towards the vehicle and wants to get into the vehicle. The player's client sends perhaps a **use object** RPC command to the server. In turn, the server then parents the player under the vehicle and changes the player's model pose to sitting. Because both NetworkObject components are spawned and the server is receiving an RPC to perform the parenting action, the parenting action performed by the server is valid and the player is then parented under the vehicle as shown below: + +``` +Sun +Tree +Camera +Vehicle (GameObject->NetworkObject) + └─ Player (GameObject->NetworkObject) +``` + +### Mildly complex invalid example: + +``` +Sun +Tree +Camera +Player (GameObject->NetworkObject) +Vehicle (GameObject->NetworkObject) + ├─ Seat1 (GameObject) + └─ Seat2 (GameObject) +``` + +In the above example, the vehicle has two GameObject components nested under the vehicle's root GameObject to represent the two available seats. If you tried to parent the player under Seat1: + +``` +Sun +Tree +Camera +Vehicle (GameObject->NetworkObject) + ├─Seat1 (GameObject) + │ └─Player (GameObject->NetworkObject) + └─Seat2 (GameObject) +``` + +This is an invalid parenting and will revert. + +### Mildly complex valid example: + +To resolve the earlier invalid parenting issue, you need to add a NetworkObject component to the seats. This means you need to: + +1. Spawn the vehicle and the seats: + +``` +Sun +Tree +Camera +Player (GameObject->NetworkObject) +Vehicle (GameObject->NetworkObject) +Seat1 (GameObject->NetworkObject) +Seat2 (GameObject->NetworkObject) +``` + +2. After spawning the vehicle and seats, parent the seats under the vehicle. + +``` +Sun +Tree +Camera +Player (GameObject->NetworkObject) +Vehicle (GameObject->NetworkObject) + ├─ Seat1 (GameObject->NetworkObject) + └─ Seat2 (GameObject->NetworkObject) +``` + +3. Finally, some time later a player wants to get into the vehicle and the player is parented under Seat1: + +``` +Sun +Tree +Camera +Vehicle (GameObject->NetworkObject) + ├─Seat1 (GameObject->NetworkObject) + │ └─Player (GameObject->NetworkObject) + └─Seat2 (GameObject->NetworkObject) +``` + +### Parenting & transform synchronization + +It's important to understand that without the use of a NetworkTransform component clients are only synchronized with the transform values when: + +- A client is being synchronized with the NetworkObject component in question: + - During the client's first synchronization after a client has their connection approved. + - When a server spawns a new NetworkObject component. +- A NetworkObject component has been parented (or a parent removed). +- The server can override the `NetworkBehaviour.OnNetworkObjectParentChanged` method and adjust the transform values when that's invoked. + - These transform changes will be synchronized with clients via the `ParentSyncMessage` + +:::note Optional auto synchronized parenting +The Auto Object Parent Sync property of a NetworkObject component, enabled by default, allows you to disable automatic parent change synchronization in the event you want to implement your own parenting solution for one or more NetworkObjects. It's important to understand that disabling the Auto Object Parent Sync option of a NetworkObject component will treat the NetworkObject component's transform synchronization with clients as if its parent is the hierarchy root (null). +::: diff --git a/versioned_docs/version-2.0.0/advanced-topics/networktime-ticks.md b/versioned_docs/version-2.0.0/advanced-topics/networktime-ticks.md new file mode 100644 index 000000000..1bbab4556 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/networktime-ticks.md @@ -0,0 +1,202 @@ +--- +id: networktime-ticks +title: NetworkTime and ticks +sidebar_label: NetworkTime and ticks +--- + +## LocalTime and ServerTime + +Why are there two different time values and which one should be used? + +Netcode for GameObjects (Netcode) uses a star topology. That means all communications happen between the clients and the server/host and never between clients directly. Messages take time to transmit over the network. That's why `RPCs` and `NetworkVariable` won't happen immediately on other machines. `NetworkTime` allows to use time while considering those transmission delays. + +- `LocalTime` on a client is ahead of the server. If a server RPC is sent at `LocalTime` from a client it will roughly arrive at `ServerTime` on the server. +- `ServerTime` on clients is behind the server. If a client RPC is sent at `ServerTime` from the server to clients it will roughly arrive at `ServerTime` on the clients. + + + +>Server: Delay when sending message + Note over Server: Message arrives at ServerTime. + Note over Server: On server: ServerTime == LocalTime. + Note over Server: Send message to clients at LocalTime. + Server->>Receiver: Delay when sending message + Note over Receiver: Message arrives at ServerTime. +`}/> + + + + +`LocalTime` +- Use for player objects with client authority. +- Use if just a general time value is needed. + +`ServerTime`: +- For player objects with server authority (For example, by sending inputs to the server via RPCs) +- In sync with position updates of `NetworkTransform` for all `NetworkObjects` where the client isn't authoritative over the transform. +- For everything on non client controlled `NetworkObjects`. + +## Examples + +### Example 1: Using network time to synchronize environments + +Many games have environmental objects which move in a fixed pattern. By using network time these objects can be moved without having to synchronize their positions with a `NetworkTransform`. + +For instance the following code can be used to create a moving elevator platform for a client authoritative game: + +```csharp +using Unity.Netcode; +using UnityEngine; + +public class MovingPlatform : MonoBehaviour +{ + public void Update() + { + // Move up and down by 5 meters and change direction every 3 seconds. + var positionY = Mathf.PingPong(NetworkManager.Singleton.LocalTime.TimeAsFloat / 3f, 1f) * 5f; + transform.position = new Vector3(0, positionY, 0); + } +} +``` + +### Example 2: Using network time to create a synced event + +Most of the time aligning an effect precisely to time isn't needed. But in some cases for important effects or gameplay events it can help to improve consistency especially for clients with bad network connections. + +```csharp +using System.Collections; +using Unity.Netcode; +using UnityEngine; +using UnityEngine.Assertions; + +public class SyncedEventExample : NetworkBehaviour +{ + public GameObject ParticleEffect; + + // Called by the client to create a synced particle event at its own position. + public void ClientCreateSyncedEffect() + { + Assert.IsTrue(IsOwner); + var time = NetworkManager.LocalTime.Time; + CreateSyncedEffectServerRpc(time); + StartCoroutine(WaitAndSpawnSyncedEffect(0)); // Create the effect immediately locally. + } + + private IEnumerator WaitAndSpawnSyncedEffect(float timeToWait) + { + // Note sometimes the timeToWait will be negative on the server or the receiving clients if a message got delayed by the network for a long time. This usually happens only in rare cases. Custom logic can be implemented to deal with that scenario. + if (timeToWait > 0) + { + yield return new WaitForSeconds(timeToWait); + } + + Instantiate(ParticleEffect, transform.position, Quaternion.identity); + } + + [Rpc(SendTo.Server)] + private void CreateSyncedEffectServerRpc(double time) + { + CreateSyncedEffectClientRpc(time); // Call a client RPC to also create the effect on each client. + var timeToWait = time - NetworkManager.ServerTime.Time; + StartCoroutine(WaitAndSpawnSyncedEffect((float)timeToWait)); // Create the effect on the server but wait for the right time. + } + + [Rpc(SendTo.ClientsAndHost)] + private void CreateSyncedEffectClientRpc(double time) + { + // The owner already created the effect so skip them. + if (IsOwner == false) + { + var timeToWait = time - NetworkManager.ServerTime.Time; + StartCoroutine(WaitAndSpawnSyncedEffect((float)timeToWait)); // Create the effect on the client but wait for the right time. + } + } +} +``` + +>Server: CreateSyncedEffectServerRpc + Server->>Receiver: CreateSyncedEffectClientRpc + Note over Server: ServerTime = 9.95 #38; timeToWait = 0.05 + Note over Server: StartCoroutine(WaitAndSpawnSyncedEffect(0.05)) + Server->>Server: WaitForSeconds(0.05); + Note over Server: Instantiate effect at ServerTime = 10.0 + Note over Receiver: ServerTime = 9.93 #38; timeToWait = 0.07 + Note over Receiver: StartCoroutine(WaitAndSpawnSyncedEffect(0.07)) + Receiver->>Receiver: WaitForSeconds(0.07); + Note over Receiver: Instantiate effect at ServerTime = 10.0 +`}/> + + + +:::note +Some components such as `NetworkTransform` add additional buffering. When trying to align an RPC event like in this example, an additional delay would need to be added. +::: + +## Network Ticks + +Network ticks are run at a fixed rate. The 'Tick Rate' field on the `NetworkManager` can be used to set the tick rate. + +What does changing the network tick affect? Changes to `NetworkVariables` aren't sent immediately. Instead during each network tick changes to `NetworkVariables` are collected and sent out to other peers. + +To run custom code once per network tick (before `NetworkVariable` changes are collected) the `Tick` event on the `NetworkTickSystem` can be used. +```cs +public override void OnNetworkSpawn() +{ + NetworkManager.NetworkTickSystem.Tick += Tick; +} + +private void Tick() +{ + Debug.Log($"Tick: {NetworkManager.LocalTime.Tick}"); +} + +public override void OnNetworkDespawn() // don't forget to unsubscribe +{ + NetworkManager.NetworkTickSystem.Tick -= Tick; +} +``` + +:::tip +When using `FixedUpdate` or physics in your game, set the network tick rate to the same rate as the fixed update rate. The `FixedUpdate` rate can be changed in `Edit > Project Settings > Time > Fixed Timestep` +::: + +## Network FixedTime + +`Network FixedTime` can be used to get a time value representing the time during a network tick. This works similar to `FixedUpdate` where `Time.fixedTime` represents the time during the `FixedUpdate`. + +```cs +public void Update() +{ + double time = NetworkManager.Singleton.LocalTime.Time; // time during this Update + double fixedTime = NetworkManager.Singleton.LocalTime.FixedTime; // time during the previous network tick +} +``` + +## NetworkTime Precision + +Network time values are calculated using double precisions. This allows time to stay accurate on long running servers. For game servers which run sessions for a long time (multiple hours or days) don't convert this value in a float and always use doubles for time related calculations. + +For games with short play sessions casting the time to float is safe or `TimeAsFloat` can be used. + +## NetworkTimeSystem Configuration + +:::caution +The properties of the `NetworkTimeSystem` should be left untouched on the server/host. Changing the values on the client is sufficient to change the behavior of the time system. +::: + +The way network time gets calculated can be configured in the `NetworkTimeSystem` if needed. Refer to the [API docs](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkTimeSystem.html) for information about the properties which can be modified. All properties can be safely adjusted at runtime. For instance, buffer values can be increased for a player with a bad connection. + + diff --git a/versioned_docs/version-2.0.0/advanced-topics/object-pooling.md b/versioned_docs/version-2.0.0/advanced-topics/object-pooling.md new file mode 100644 index 000000000..4e8dac44a --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/object-pooling.md @@ -0,0 +1,28 @@ +--- +id: object-pooling +title: Object pooling +--- + +Netcode for GameObjects (Netcode) provides built-in support for Object Pooling, which allows you to override the default Netcode destroy and spawn handlers with your own logic. This allows you to store destroyed network objects in a pool to reuse later. This is useful for often used objects, such as projectiles, and is a way to increase the application's overall performance. By pre-instantiating and reusing the instances of those objects, object pooling removes the need to create or destroy objects at runtime, which can save a lot of work for the CPU. This means that instead of creating or destroying the same object over and over again, it's simply deactivated after use, then, when another object is needed, the pool recycles one of the deactivated objects and reactivates it. + +See [Introduction to Object Pooling](https://learn.unity.com/tutorial/introduction-to-object-pooling) to learn more about the importance of pooling objects. + +## NetworkPrefabInstanceHandler + +You can register your own spawn handlers by including the `INetworkPrefabInstanceHandler` interface and registering with the `NetworkPrefabHandler`. +```csharp + public interface INetworkPrefabInstanceHandler + { + NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation); + void Destroy(NetworkObject networkObject); + } +``` +Netcode will use the `Instantiate` and `Destroy` methods in place of default spawn handlers for the NetworkObject used during spawning and despawning. Because the message to instantiate a new NetworkObject originates from a Host or Server, both won't have the Instantiate method invoked. All clients (excluding a Host) will have the instantiate method invoked if the `INetworkPrefabInstanceHandler` implementation is registered with `NetworkPrefabHandler` (`NetworkManager.PrefabHandler`) and a Host or Server spawns the registered/associated NetworkObject. + +The following example is from the Boss Room Sample. It shows how object pooling is used to handle the different projectile objects. In that example, the class `NetworkObjectPool` is the data structure containing the pooled objects and the class `PooledPrefabInstanceHandler` is the handler implementing `INetworkPrefabInstanceHandler`. + +```csharp reference +https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Infrastructure/NetworkObjectPool.cs +``` + +Let's have a look at `NetworkObjectPool` first. `PooledPrefabsList` has a list of prefabs to handle, with an initial number of instances to spawn for each. The `RegisterPrefabInternal` method, called in `OnNetworkSpawn`, initializes the different pools for each Prefab as `ObjectPool`s inside the `m_PooledObjects` dictionary. It also instantiates the handlers for each Prefab and registers them. To use these objects, a user then needs to obtain it via the `GetNetworkObject` method before spawning it, then return the object to the pool after use with `ReturnNetworkObject` before despawning it. This only needs to be done on the server, as the `PooledPrefabInstanceHandler` will handle it on the client(s) when the network object's `Spawn` or `Despawn` method is called, via its `Instantiate` and `Destroy` methods. Inside those methods, the `PooledPrefabInstanceHandler` simply calls the pool to get the corresponding object, or to return it. diff --git a/versioned_docs/version-2.0.0/advanced-topics/physics.md b/versioned_docs/version-2.0.0/advanced-topics/physics.md new file mode 100644 index 000000000..403d69c62 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/physics.md @@ -0,0 +1,70 @@ +--- +id: physics +title: Physics +Description: Brief explanation on using Physics in Netcode for GameObjects +--- + +There are many different ways to manage physics simulation in multiplayer games. Netcode for GameObjects (Netcode) has a built in approach which allows for server-authoritative physics where the physics simulation only runs on the server. To enable network physics, add a `NetworkRigidbody` component to your object. + +## NetworkRigidbody + +NetworkRigidbody is a component that sets the Rigidbody of the GameObject into kinematic mode on all non-authoritative instances (except the instance that has authority). Authority is determined by the NetworkTransform component (required) attached to the same GameObject as the NetworkRigidbody. Whether the NetworkTransform is server authoritative (default) or owner authoritative, the NetworkRigidBody authority model will mirror it. That way, the physics simulation runs on the authoritative instance, and the resulting positions synchronize on the non-authoritative instances, each with their RigidBody being Kinematic, without any interference. + +To use NetworkRigidbody, add a Rigidbody, NetworkTransform, and NetworkRigidbody component to your NetworkObject. + +Some collision events aren't fired when using `NetworkRigidbody`. +- On the `server`, all collision and trigger events (such as `OnCollisionEnter`) fire as expected and you can access (and change) values of the `Rigidbody` (such as velocity). +- On the `clients`, the `Rigidbody` is kinematic. Trigger events still fire but collision events won't fire when colliding with other networked `Rigidbody` instances. + +:::tip + +If there is a need for a gameplay event to happen on a collision, you can listen to `OnCollisionEnter` function on the server and synchronize the event via `Rpc(SendTo.ClientsAndHost)` to all clients. + +::: + +## NetworkRigidbody2D + +`NetworkRigidbody2D` works in the same way as NetworkRigidbody but for 2D physics (`Rigidbody2D`) instead. + +## NetworkRigidbody and ClientNetworkTransform + +You can use NetworkRigidbody with the [`ClientNetworkTransform`](../components/networktransform.md#clientnetworktransform) package sample to allow the owner client of a NetworkObject to move it authoritatively. In this mode, collisions only result in realistic dynamic collisions if the object is colliding with other NetworkObjects (owned by the same client). + +:::note + +Add the ClientNetworkTransform component to your GameObject first. Otherwise the NetworkRigidbody automatically adds a regular NetworkTransform. + +::: + +## Physics and latency + +A common issue with physics in multiplayer games is lag and how objects update on basically different timelines. For example, a player would be on a timeline that's offset by the network latency relative to your server's objects. One way to prepare for this is to test your game with artificial lag. You might catch some weird delayed collisions that would otherwise make it into production. + +The ClientDriven [bitesize sample](../learn/bitesize/bitesize-clientdriven.md) addresses this by manually adding forces server-side to offer a buffer before an actual collision, but it still feels wobbly at times. However, this isn't really a solution. + +The best way to address the issue of physics latency is to create a custom `NetworkTransform` with a custom physics-based interpolator. You can also use the [Network Simulator tool](../../tools/network-simulator.md) to spot issues with latency. + +## Message processing vs. applying changes to state (timing considerations) + +When handling the synchronization of changes to certain physics properties, it's important to understand the order of operations involved in message processing relative to the update stages that occur within a single frame. The stages occur in this order: + +- Initialization _(Awake and Start are invoked here)_ +- EarlyUpdate _(Inbound messages are processed here)_ +- FixedUpdate _(Physics simulation is run and results)_ +- PreUpdate _(NetworkTime and Tick is updated)_ +- Update _(NetworkBehaviours/Components are updated)_ +- PreLateUpdate: _(Useful for handling post-update tasks prior to processing and sending pending outbound messages)_ +- LateUpdate: _(Useful for changes to camera, detecting input, and handling other post-update tasks)_ +- PostLateUpdate: _(Dirty NetworkVariables processed and pending outbound messages are sent)_ + +From this list of update stages, the `EarlyUpdate` and `FixedUpdate` stages have the most impact on NetworkVariableDeltaMessage and RpcMessages processing. Inbound messages are processed during the `EarlyUpdate` stage, which means that Rpc methods and NetworkVariable.OnValueChanged callbacks are invoked at that point in time during any given frame. Taking this into consideration, there are certain scenarios where making changes to a Rigidbody could yield undesirable results. + +### Rigidbody interpolation example + +While `NetworkTransform` offers interpolation as a way to smooth between delta state updates, it doesn't get applied to the authoritative instance. You can use `Rigidbody.interpolation` for your authoritative instance while maintaining a strict server-authoritative motion model. + +To have a client control their owned objects, you can use either [RPCs](message-system/rpc.md) or [NetworkVariables](../basics/networkvariables.md) on the client-side. However, this often results in the host-client's updates working as expected, but with slight jitter when a client sends updates. You might be scanning for key or device input during the `Update` to `LateUpdate` stages. Any input from the host player is applied after the `FixedUpdate` stage (i.e. physics simulation for the frame has already run), but input from client players is sent via a message and processed, with a half RTT delay, on the host side (or processed 1 network tick + half RTT if using NetworkVariables). Because of when messages are processed, client input updates run the risk of being processed during the `EarlyUpdate` stage which occurs just before the current frame's `FixedUpdate` stage. + +To avoid this kind of scenario, it's recommended that you apply any changes received via messages to a Rigidbody _after_ the FixedUpdate has run for the current frame. If you [look at how `NetworkTransform` handles its changes to transform state](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/a2c6f7da5be5af077427eef9c1598fa741585b82/com.unity.netcode.gameobjects/Components/NetworkTransform.cs#L3028), you can see that state updates are applied during the `Update` stage, but are received during the `EarlyUpdate` stage. Following this kind of pattern when synchronizing changes to a Rigidbody via messages will help you to avoid unexpected results in your Netcode for GameObjects project. + +The best way to address the issue of physics latency is to create a custom `NetworkTransform` with a custom physics-based interpolator. You can also use the [Network Simulator tool](../../tools/network-simulator.md) to spot issues with latency. diff --git a/versioned_docs/version-2.0.0/advanced-topics/reconnecting-mid-game.md b/versioned_docs/version-2.0.0/advanced-topics/reconnecting-mid-game.md new file mode 100644 index 000000000..40664ea19 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/reconnecting-mid-game.md @@ -0,0 +1,84 @@ +--- +id: reconnecting-mid-game +title: Reconnecting mid-game +created: 2023-01-24T17:55:53-06:00 +updated: 2023-01-24T18:28:58-06:00 +--- + +In a multiplayer game, clients might get disconnected from the server for a variety of reasons (such as network issues or application/device crashes). For those reasons, you might want to allow your players to reconnect to the game. + +# Considerations + +Review the following considerations before allowing clients to reconnect in the middle of an active game: + +- Clients might have specific state and in-game data associated with them. If you want them to reconnect with that same state and data, implement a mechanism to keep the association of state and data with the client for when they reconnect. Refer to [Session Management](session-management.md) for more information. +- Depending on your game's requirements, make sure the client's state is reset and ready to connect before attempting reconnection. This might require resetting some external services. +- When using [scene management](../basics/scenemanagement/scene-management-overview.md) and multiple additive scenes, there is a specific case to keep in mind. During the synchronization process, which launches when connecting to a game, if the client's active main scene is the same as the server's, it won't start a scene load in single mode for that scene. Instead, it loads all additive scenes loaded on the server. This means that if the client has additive scenes loaded, it won't unload them like it would if the client's main scene was different than the server's. + + - For example, if during a game, the server loads **main scene A**, then additively loads scenes **B** and **C**, the client has all three loaded. If the client disconnects and reconnects without changing scenes, the scene synchronization process recognizes that **main scene A** is already loaded on the client, and then proceeds to load the server's additive scenes. In that case, the client loads the scenes **B** and **C** a second time and then has two copies of those scenes loaded. + + However, if, while the client is disconnected, the server loads or unloads a scene additively, there is also a mismatch between the scenes loaded on the client and on the server. For example, if the server unloads scene **C** and loads scene **D**, the client loads scene **D** when synchronizing, but doesn't unload scene C, so the client has loaded scenes **A**, **B** (twice), **C**, and **D**, while the server only has loaded scenes **A**, **B**, and **D**. + + - If you want to avoid this behavior, you can + - Load a different scene in single mode on the client when disconnecting + - Unload additive scenes on the client when disconnecting + - Unload additive scenes on the client when reconnecting + - Use the `NetworkSceneManager.VerifySceneBeforeLoading` callback to prevent loading scenes already loaded on the client. However, this won't handle unloading scenes that were unloaded by the server between the time the client disconnected and reconnected. + +If you want to avoid this behavior, you can: + +- Load a different scene in single mode on the client when disconnecting + +- Unload additive scenes on the client when disconnecting + +- Unload additive scenes on the client when reconnecting + +- Use the `NetworkSceneManager.VerifySceneBeforeLoading` callback to prevent loading scenes already loaded on the client. However, this won't handle unloading scenes that the server unloaded between the time the client disconnected and reconnected. + +# Automatic reconnection + +For a smoother experience for your players, clients can automatically try to reconnect to a game when they lose a connection unexpectedly. + +To implement automatic reconnection: + +- Define what triggers the reconnection process and when it should start. + + - For example, you can use the `NetworkManager.OnClientDisconnectCallback` callback, or some other unique event depending on your game. + +- Ensure the `NetworkManager` shuts down before attempting any reconnection. + + - You can use the `NetworkManager.ShutdownInProgress` property to manage this. + +- Add additional checks to ensure automatic reconnection doesn't trigger under the wrong conditions. + + - Examples + + - A client purposefully quits a game + + - The server shuts down as expected when the game session ends + + - A client gets kicked from a game + + - A client is denied during connection approval + +Depending on your game, you might want to add the following features as well: + +- Include multiple reconnection attempts in case of failure. You need to define the number of attempts, ensure that `NetworkManager` properly shuts down between each try, and reset the client's state (if needed). + +- Offer an option for players to cancel the reconnection process. This might be useful when there are a lot of reconnection attempts or when each try lasts a long duration. + +## Automatic reconnection example + +Check out the [Boss Room sample](../learn/bossroom/getting-started-boss-room.md) for an example implementation of automatic reconnection. + +The entry point for this feature is in [this class](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/ConnectionManagement/ConnectionState/ClientReconnectingState.cs). Boss Room's implementation uses a state inside a state machine that starts a coroutine on entering it ( `ReconnectCoroutine`) that attempts to reconnect a few times sequentially, until it either succeeds, surpasses the defined maximum number of attempts, or is cancelled. (Check out `OnClientConnected`, `OnClientDisconnect`, `OnDisconnectReasonReceived`, and `OnUserRequestedShutdown`.) + +The reconnecting state is entered when a client disconnects unexpectedly. (Check out `OnClientDisconnect` in [this class](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/ConnectionManagement/ConnectionState/ClientConnectedState.cs)) + +:::note + +This sample connects with [Lobby](https://docs.unity.com/lobby/unity-lobby-service-overview.html) and [Relay](https://docs.unity.com/relay/get-started.html) services, so the client must make sure it has left the lobby before each reconnection try. + +For more information about how Boss Room leverages Lobby and Relay, refer to [Getting Started with Boss Room](../learn/bossroom/getting-started-boss-room.md#register-the-project-with-unity-gaming-services-ugs) + +::: diff --git a/versioned_docs/version-2.0.0/advanced-topics/serialization/cprimitives.md b/versioned_docs/version-2.0.0/advanced-topics/serialization/cprimitives.md new file mode 100644 index 000000000..794468570 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/serialization/cprimitives.md @@ -0,0 +1,21 @@ +--- +id: cprimitives +title: C# primitives +--- + +C# primitive types are serialized by built-in serialization code. These types include `bool`, `char`, `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, and `string`. + +You can also serialize [arrays](serialization-arrays.md) of C# primitive types, with the exception of arrays of `strings` (`string[]`) for [performance reasons](serialization-arrays.md#performance-considerations). + +```csharp +[Rpc(SendTo.Server)] +void FooServerRpc(int somenumber, string sometext) { /* ... */ } + +void Update() +{ + if (Input.GetKeyDown(KeyCode.P)) + { + FooServerRpc(Time.frameCount, "hello, world"); // Client -> Server + } +} +``` diff --git a/versioned_docs/version-2.0.0/advanced-topics/serialization/enum-types.md b/versioned_docs/version-2.0.0/advanced-topics/serialization/enum-types.md new file mode 100644 index 000000000..90ca1fe22 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/serialization/enum-types.md @@ -0,0 +1,33 @@ +--- +id: enum_types +title: Enum types +--- + +A user-defined enum type will be serialized by built-in serialization code (with underlying integer type). + +```csharp +enum SmallEnum : byte +{ + A, + B, + C +} + +enum NormalEnum // default -> int +{ + X, + Y, + Z +} + +[Rpc(SendTo.Server)] +void ConfigServerRpc(SmallEnum smallEnum, NormalEnum normalEnum) { /* ... */ } + +void Update() +{ + if (Input.GetKeyDown(KeyCode.P)) + { + ConfigServerRpc(SmallEnum.A, NormalEnum.X); // Client -> Server + } +} +``` diff --git a/versioned_docs/version-2.0.0/advanced-topics/serialization/fixedstrings.md b/versioned_docs/version-2.0.0/advanced-topics/serialization/fixedstrings.md new file mode 100644 index 000000000..15391926b --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/serialization/fixedstrings.md @@ -0,0 +1,16 @@ +--- +id: fixedstrings +title: Fixed strings +--- + +Netcode's serialization system natively supports Unity's Fixed String types (`FixedString32`, `FixedString64`, `FixedString128`, `FixedString512`, and `FixedString4096`). The serialization system intelligently understands these fixed string types and ensures that only the amount of the string in use is serialized, even for the larger types. This native support ensures Netcode uses no more bandwidth than is necessary. + +```csharp +[Rpc(SendTo.Server)] +void SetPlayerNameServerRpc(FixedString32 playerName) { /* ... */ } + +void SetPlayerName(string name) +{ + SetPlayerNameServerRpc(new FixedString32(name)); +} +``` diff --git a/versioned_docs/version-2.0.0/advanced-topics/serialization/inetworkserializable.md b/versioned_docs/version-2.0.0/advanced-topics/serialization/inetworkserializable.md new file mode 100644 index 000000000..8764ccc47 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/serialization/inetworkserializable.md @@ -0,0 +1,199 @@ +--- +id: inetworkserializable +title: INetworkSerializable +sidebar_label: INetworkSerializable +created: 2023-01-24T18:19:59-06:00 +updated: 2023-01-24T18:19:59-06:00 +--- + +You can use the `INetworkSerializable` interface to define custom serializable types. + +```csharp +struct MyComplexStruct : INetworkSerializable +{ + public Vector3 Position; + public Quaternion Rotation; + + // INetworkSerializable + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref Position); + serializer.SerializeValue(ref Rotation); + } + // ~INetworkSerializable +} +``` + +Types implementing `INetworkSerializable` are supported by `NetworkSerializer`, `RPC` s and `NetworkVariable` s. + +```csharp + +[Rpc(SendTo.Server)] +void MyServerRpc(MyComplexStruct myStruct) { /* ... */ } + +void Update() +{ + if (Input.GetKeyDown(KeyCode.P)) + { + MyServerRpc( + new MyComplexStruct + { + Position = transform.position, + Rotation = transform.rotation + }); // Client -> Server + } +} +``` + +## Nested serial types + +Nested serial types will be `null` unless you initialize following one of these methods: + +* Manually before calling `SerializeValue` if `serializer.IsReader` (or something like that). + +* Initialize in the default constructor. + +This is by design. You may see the values as null until initialized. The serializer isn't deserializing them, the `null` value is applied before it can be serialized. + +## Conditional Serialization + +As you have more control over serialization of a struct, you might implement conditional serialization at runtime. + +The following example explores a more advanced use case. + +### Example: Move + +```csharp + +public struct MyMoveStruct : INetworkSerializable +{ + public Vector3 Position; + public Quaternion Rotation; + + public bool SyncVelocity; + public Vector3 LinearVelocity; + public Vector3 AngularVelocity; + + void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + // Position & Rotation + serializer.SerializeValue(ref Position); + serializer.SerializeValue(ref Rotation); + + // LinearVelocity & AngularVelocity + serializer.SerializeValue(ref SyncVelocity); + if (SyncVelocity) + { + serializer.SerializeValue(ref LinearVelocity); + serializer.SerializeValue(ref AngularVelocity); + } + } +} +``` + +**Reading:** + +* (De)serialize `Position` back from the stream. + +* (De)serialize `Rotation` back from the stream. + +* (De)serialize `SyncVelocity` back from the stream. + +* Check if `SyncVelocity` is set to true, if so: + + * (De)serialize `LinearVelocity` back from the stream. + + * (De)serialize `AngularVelocity` back from the stream. + +**Writing:** + +* Serialize `Position` into the stream. + +* Serialize `Rotation` into the stream. + +* Serialize `SyncVelocity` into the stream. + +* Check if `SyncVelocity` is set to true, if so: + + * Serialize `LinearVelocity` into the stream. + + * Serialize `AngularVelocity` into the stream. + +* If the `SyncVelocity` flag is set to true, serialize both the `LinearVelocity` and `AngularVelocity` into the stream. + +* When the `SyncVelocity` flag is set to `false`, leave `LinearVelocity` and `AngularVelocity` with default values. + +### Recursive Nested Serialization + +It's possible to recursively serialize nested members with `INetworkSerializable` interface down in the hierarchy tree. + +Review the following example: + +```csharp + +public struct MyStructA : INetworkSerializable +{ + public Vector3 Position; + public Quaternion Rotation; + + void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref Position); + serializer.SerializeValue(ref Rotation); + } +} + +public struct MyStructB : INetworkSerializable +{ + public int SomeNumber; + public string SomeText; + public MyStructA StructA; + + void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref SomeNumber); + serializer.SerializeValue(ref SomeText); + StructA.NetworkSerialize(serializer); + } +} +``` + +If you were to serialize `MyStructA` alone, it would serialize `Position` and `Rotation` into the stream using `NetworkSerializer`. + +However, if you were to serialize `MyStructB`, it would serialize `SomeNumber` and `SomeText` into the stream, then serialize `StructA` by calling `MyStructA` 's `void NetworkSerialize(NetworkSerializer)` method, which serializes `Position` and `Rotation` into the same stream. + +:::note + +Technically, there is no hard-limit on the number of `INetworkSerializable` fields you can serialize down the tree hierarchy. In practice, consider memory and bandwidth boundaries for best performance. + +::: + +:::tip + +You can conditionally serialize in recursive nested serialization scenario and make use of both features. + +::: + +:::caution + +While you can have nested `INetworkSerializable` implementations (an `INetworkSerializable` implementation with `INetworkSerializable` implementations as properties) like demonstrated in the example above, you can't have derived children of an `INetworkSerializable` implementation.
+**Unsupported Example** + +```csharp +/// This isn't supported. +public struct MyStructB : MyStructA +{ + public int SomeNumber; + public string SomeText; + + void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + serializer.SerializeValue(ref SomeNumber); + serializer.SerializeValue(ref SomeText); + serializer.SerializeValue(ref Position); + serializer.SerializeValue(ref Rotation); + } +} +``` + +::: diff --git a/versioned_docs/version-2.0.0/advanced-topics/serialization/inetworkserializebymemcpy.md b/versioned_docs/version-2.0.0/advanced-topics/serialization/inetworkserializebymemcpy.md new file mode 100644 index 000000000..c6bf366f3 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/serialization/inetworkserializebymemcpy.md @@ -0,0 +1,29 @@ +--- +id: inetworkserializebymemcpy +title: INetworkSerializeByMemcpy +sidebar_label: INetworkSerializeByMemcpy +--- + +The `INetworkSerializeByMemcpy` interface is used to mark an unmanaged struct type as being trivially serializable over the network by directly copying the whole struct, byte-for-byte, as it appears in memory, into and out of the buffer. This can offer some benefits for performance compared to serializing one field at a time, especially if the struct has many fields in it, but it may be less efficient from a bandwidth-usage perspective, as fields will often be padded for memory alignment and you won't be able to "pack" any of the fields to optimize for space usage. + +The interface itself has no methods in it - it's an empty interface that satisfies a constraint on methods that perform this type of serialization, primarily there to ensure that memcpy serialization isn't performed by accident on structs that don't support it. + +```csharp +public struct MyStruct : INetworkSerializeByMemcpy +{ + public int A; + public int B; + public float C; + public bool D; +} +``` + +If you have a type you wish to serialize that you know is compatible with this method of serialization, but don't have access to modify the struct to add this interface, you can wrap your values in `ForceNetworkSerializeByMemcpy` to enable it to be serialized this way. This works in both `RPC`s and `NetworkVariables`, as well as in other contexts such as `BufferSerializer<>`, `FastBufferReader`, and `FastBufferWriter`. + +```csharp +public NetworkVariable> GuidVar; +``` + +:::caution +Take care with using `INetworkSerializeByMemcpy`, and especially `ForceNetworkSerializeByMemcpy`, because not all unmanaged structs are actually compatible with this type of serialization. Anything that includes pointer types (including Native Collections like `NativeArray<>`) won't function correctly when serialized this way, and will likely cause memory corruption or crashes on the receiving side. +::: diff --git a/versioned_docs/version-2.0.0/advanced-topics/serialization/networkobject-serialization.md b/versioned_docs/version-2.0.0/advanced-topics/serialization/networkobject-serialization.md new file mode 100644 index 000000000..56c74cac9 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/serialization/networkobject-serialization.md @@ -0,0 +1,95 @@ +--- +id: networkobject-serialization +title: NetworkObject and NetworkBehaviour +description: Brief explanation on using NetworkObject and NetworkBehaviour in Network for GameObjects +--- + +`GameObjects`, `NetworkObjects` and `NetworkBehaviour` aren't serializable types so they can't be used in `RPCs` or `NetworkVariables` by default. + +There are two convenience wrappers which can be used to send a reference to a NetworkObject or a `NetworkBehaviour` over RPCs or `NetworkVariables`. + +## NetworkObjectReference + +`NetworkObjectReference` can be used to serialize a reference to a NetworkObject. It can only be used on already spawned `NetworkObjects`. + +Here is an example of using NetworkObject reference to send a target NetworkObject over an RPC: +```csharp +public class Weapon : NetworkBehaviour +{ + public void ShootTarget(GameObject target) + { + var targetObject = target.GetComponent(); + ShootTargetServerRpc(targetObject); + } + + [Rpc(SendTo.Server)] + public void ShootTargetServerRpc(NetworkObjectReference target) + { + if (target.TryGet(out NetworkObject targetObject)) + { + // deal damage or something to target object. + } + else + { + // Target not found on server, likely because it already has been destroyed/despawned. + } + } +} +``` + +### Implicit Operators + +There are also implicit operators which convert from/to `NetworkObject/GameObject` which can be used to simplify code. For instance the above example can also be written in the following way: +```csharp +public class Weapon : NetworkBehaviour +{ + public void ShootTarget(GameObject target) + { + ShootTargetServerRpc(target); + } + + [Rpc(SendTo.Server)] + public void ShootTargetServerRpc(NetworkObjectReference target) + { + NetworkObject targetObject = target; + } +} +``` +:::note +The implicit conversion to NetworkObject / `GameObject` will result in `Null` if the reference can't be found. +::: + +## NetworkBehaviourReference + +`NetworkBehaviourReference` works similar to `NetworkObjectReference` but is used to reference a specific `NetworkBehaviour` component on a spawned NetworkObject. + +```cs +public class Health : NetworkBehaviour +{ + public NetworkVariable Health = new NetworkVariable(); +} + +public class Weapon : NetworkBehaviour +{ + public void ShootTarget(GameObject target) + { + var health = target.GetComponent(); + ShootTargetServerRpc(health, 10); + } + + [Rpc(SendTo.Server)] + public void ShootTargetServerRpc(NetworkBehaviourReference health, int damage) + { + if (health.TryGet(out Health healthComponent)) + { + healthComponent.Health.Value -= damage; + } + } +} +``` + +## How NetworkObjectReference & NetworkBehaviourReference work + +`NetworkObjectReference` and `NetworkBehaviourReference` are convenience wrappers which serialize the id of a NetworkObject when being sent and on the receiving end retrieve the corresponding ` ` with that id. `NetworkBehaviourReference` sends an additional index which is used to find the right `NetworkBehaviour` on the NetworkObject. + +Both of them are structs implementing the `INetworkSerializable` interface. diff --git a/versioned_docs/version-2.0.0/advanced-topics/serialization/serialization-arrays.md b/versioned_docs/version-2.0.0/advanced-topics/serialization/serialization-arrays.md new file mode 100644 index 000000000..4a386b740 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/serialization/serialization-arrays.md @@ -0,0 +1,87 @@ +--- +id: arrays +title: Arrays and native containers +--- + +Netcode for GameObjects has built-in serialization code for arrays of [C# value-type primitives](cprimitives.md), like `int[]`, and [Unity primitive types](unity-primitives.md). Any arrays of types that aren't handled by the built-in serialization code, such as `string[]`, need to be handled using a container class or structure that implements the [`INetworkSerializable`](inetworkserializable.md) interface. + +## Performance considerations + +Sending arrays and strings over the network has performance implications. An array incurs a garbage collected allocation, and a string also incurs a garbage collected allocation, so an array of strings results in an allocation for every element in the array, plus one more for the array itself. + +For this reason, arrays of strings (`string[]`) aren't supported by the built-in serialization code. Instead, it's recommended to use `NativeArray` or `NativeList`, because they're more efficient and don't incur garbage collected memory allocation. Refer to [`NativeArray`](#nativearrayt) and [`NativeList`](#nativelistt) below for more details. + +## Built-in primitive types example + +Using built-in primitive types is fairly straightforward: + +```csharp +[Rpc(SendTo.Server)] +void HelloServerRpc(int[] scores, Color[] colors) { /* ... */ } +``` + +## INetworkSerializable implementation example + +There are many ways to handle sending an array of managed types. The following example is a simple `string` container class that implements `INetworkSerializable` and can be used as an array of "StringContainers": + +```csharp +[Rpc(SendTo.ClientsAndHost)] +void SendMessagesClientRpc(StringContainer[] messages) +{ + foreach (var stringContainer in stringContainers) + { + Debug.Log($"{stringContainer.SomeText}"); + } +} + +public class StringContainer : INetworkSerializable +{ + public string SomeText; + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + if (serializer.IsWriter) + { + serializer.GetFastBufferWriter().WriteValueSafe(SomeText); + } + else + { + serializer.GetFastBufferReader().ReadValueSafe(out SomeText); + } + } +} +``` + +## Native containers + +Netcode for GameObjects supports `NativeArray` and `NativeList` native containers with built-in serialization, RPCs, and NetworkVariables. However, you can't nest either of these containers without causing a crash. + +A few examples of nesting that will cause a crash: + +* `NativeArray>` +* `NativeList>` +* `NativeArray>` +* `NativeList>` + +### `NativeArray` + +To serialize a `NativeArray` container, use `serializer.SerializeValue(ref Array)`. + +### `NativeList` + +To serialize a `NativeList` container, you must: +1. Ensure your assemblies reference `Collections`. +2. You must add `UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT` to your **Scriptiong Define Symbols** list. + 1. From the Unity Editor top bar menu, go to **Edit** > **Project Settings...** > **Player**. + 2. Select the **Other Settings** dropdown. + 3. Scroll to **Script Compilation** > **Scripting Define Symbols**. + 4. Add `UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT`. +3. Use `serializer.SerializeValue(ref List)` as your serialization syntax. + +:::important +When using `NativeLists` within `INetworkSerializable`, the list `ref` value must be a valid, initialized `NativeList`. + +NetworkVariables are similar that the value must be initialized before it can receive updates. +For example, `public NetworkVariable> ByteListVar = new NetworkVariable>{Value = new NativeList(Allocator.Persistent)};`. + +RPCs do this automatically. +::: diff --git a/versioned_docs/version-2.0.0/advanced-topics/serialization/serialization-intro.md b/versioned_docs/version-2.0.0/advanced-topics/serialization/serialization-intro.md new file mode 100644 index 000000000..e81443336 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/serialization/serialization-intro.md @@ -0,0 +1,16 @@ +--- +id: serialization-intro +title: About serialization +--- +Multiplayer framework has built-in serialization support for C# and Unity primitive types out-of-the-box, also with ability to further extend network serialization for user defined types implementing `INetworkSerializable` interface. + +See the following sections: + +* [C# Primitives](cprimitives.md) +* [Unity Primitives](unity-primitives.md) +* [Enum Types](enum-types.md) +* [Arrays](serialization-arrays.md) +* [Fixed Strings](fixedstrings.md) +* [INetworkSerializable](inetworkserializable.md) +* [INetworkSerializeByMemcpy](inetworkserializebymemcpy.md) +* [NetworkObjects & NetworkBehaviours](networkobject-serialization.md) diff --git a/versioned_docs/version-2.0.0/advanced-topics/serialization/unity-primitives.md b/versioned_docs/version-2.0.0/advanced-topics/serialization/unity-primitives.md new file mode 100644 index 000000000..a2bfaef84 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/serialization/unity-primitives.md @@ -0,0 +1,19 @@ +--- +id: unity-primitives +title: Unity primitives +--- + +Unity Primitive `Color`, `Color32`, `Vector2`, `Vector3`, `Vector4`, `Quaternion`, `Ray`, `Ray2D` types will be serialized by built-in serialization code. + +```csharp +[Rpc(SendTo.ClientsAndHost)] +void BarClientRpc(Color somecolor) { /* ... */ } + +void Update() +{ + if (Input.GetKeyDown(KeyCode.P)) + { + BarClientRpc(Color.red); // Server -> Client + } +} +``` diff --git a/versioned_docs/version-2.0.0/advanced-topics/session-management.md b/versioned_docs/version-2.0.0/advanced-topics/session-management.md new file mode 100644 index 000000000..3d8a43447 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/session-management.md @@ -0,0 +1,43 @@ +--- +id: session-management +title: Session management +created: 2023-01-24T17:55:53-06:00 +updated: 2023-01-24T18:28:58-06:00 +--- + +You can use session management to keep data when a player disconnects and accurately reassign it to the player when they reconnect. + +# Linking data to players + +To reassign data to the correct player when they reconnect, you need to link this data to the player. + +The **clientId** generated by Netcode for GameObjects (Netcode) can't be used, because it generates when a player connects and is disposed of when they disconnect. That ID may then be assigned to a different player if they connect to the server after the first one disconnected. + +To properly link data to a specific player, we need an ID that isn't tied to the current connection, but persists through time and is unique to that player. Some options include +* A login system with unique player accounts + +* A Globally Unique Identifier (GUID). For example, a GUID generated via `System.Guid.NewGuid()` and then saved to the `PlayerPrefs` client side. + +With this unique identifier, you can map each player's data (that's needed when reconnecting), such as the current state of their character (last known position, health, and the like). You then ensure this data is up to date and kept host side after a player disconnects to repopulate that player's data when they reconnect. + +You can also decide to clear all data when a session completes or add a timeout to purge the data after a specific amount of time. + +# Reconnection + +The best way to reconnect players depends on your game. For example, if you use a [Player Object](../basics/networkobject.md#player-objects), a new `Default Player Prefab` automatically spawns when a player connects to the game (including when they reconnect). You can use the player's earlier saved session data to update that object so that it returns to the same state before disconnecting. In those cases, you would need to keep all the important data that you want to restore and map it to the player using your identification system. You can save this data when a player disconnects or update it periodically. You can then use the `OnNetworkSpawn` event on the Player Object's `NetworkBehavior`(s) to get this data and apply it where needed. + +In cases where we don't use the Player Object approach and instead manually attribute client ownership to NetworkObject(s), we can keep the objects that a player owns when they disconnect, and set the reconnected player as their new owner. To accomplish this, the only data we would need to keep would be the mapping between those objects and their owning player's identifier, then when a player reconnects we can use this mapping to set them as the new owner. This mapping can be as simple as a dictionary mapping the player identifier with the `NetworkObjectId`(s) of the NetworkObject(s) they own. Then, in the `OnClientConnectedCallback` from the `NetworkManager`, the server can set the ownership of these objects. + +Here is an example from the Boss Room sample, showing some simple session management. The game uses the Player Object approach and a GUID to identify unique players. + +```csharp reference +https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/SessionManager.cs +``` + +This class allows Boss Room to handle player session data, represented by a struct `T` implementing the `ISessionPlayerData` interface, by providing mechanisms to initialize, get and edit that data, and to associate it to a specific player. It also handles the clearing of data that's no longer used and the reinitialization of data between sessions. + +In this case, since game sessions are quite short, the session data is only cleared for disconnected players when a session ends, or if a player leaves before a session starts. This makes sure that if a player disconnects during a session and then reconnects during the next session, the game treats it as a new connection. The definition of when a session ends and when a session starts might vary from game to game, but in Boss Room, a session starts after character selection and ends when the players enter the post-game scene. In other cases, one might want to add a timeout to session data and clear it after a specified time instead. + +This code is in Boss Room's utilities package so it can be easily reused. You can add this package via the `Package Manager` window in the Unity Editor by selecting `add from Git URL` and adding the following URL: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git?path=/Packages/com.unity.multiplayer.samples.coop#main" + +Or you can directly add this line to your `manifest.json` file: "com.unity.multiplayer.samples.coop": "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git?path=/Packages/com.unity.multiplayer.samples.coop#main" diff --git a/versioned_docs/version-2.0.0/advanced-topics/transports.md b/versioned_docs/version-2.0.0/advanced-topics/transports.md new file mode 100644 index 000000000..26d284c69 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/transports.md @@ -0,0 +1,35 @@ +--- +id: transports +title: Transports +description: A transport collects messages from your application and transmits them safely over the network. It ensures that all packets arrive and in order, if needed. +--- + +Unity Netcode for GameObjects (Netcode) uses Unity Transport by default and supports UNet Transport (deprecated) up to Unity 2022.2 version. + +## So what is a transport layer? + +The transport layer establishes communication between your application and different hosts in a network. + +A transport layer can provide: +* *Connection-oriented communication* to ensure a robust connection before exchanging data with a handshake protocol. +* *Maintain order delivery* for your packets to fix any discrepancies from the network layer in case of packet drops or device interruption. +* *Ensure data integrity* by requesting retransmission of missing or corrupted data through using checksums. +* *Control the data flow* in a network connection to avoid buffer overflow or underflow--causing unnecessary network performance issues. +* *Manage network congestion* by mediating flow rates and node overloads. +* *Adjust data streams* to transmit as byte streams or packets. + +## Unity Transport package + +Netcode's default transport Unity Transport is an entire transport layer that you can use to add multiplayer and network features to your project with or without Netcode. See the Transport [documentation](../../../transport/current/about) for more information and how to [install](../../../transport/current/install). + +## Unity's UNet Transport Layer API + +UNet is a deprecated solution that is no longer supported after Unity 2022.2. Unity Transport Package is the default transport for Netcode for GameObjects. We recommend transitioning to Unity Transport as soon as possible. + +### Community Transports or Writing Your Own + +You can use any of the community contributed custom transport implementations or write your own. + +The community transports are interchangeable transport layers for Netcode and can be installed with the Unity Package Manager. After installation, the transport package will appear in the **Select Transport** dropdown of the `NetworkManager`. Check out the [Netcode community contributed transports](https://github.com/Unity-Technologies/multiplayer-community-contributions/tree/main/Transports) for more information. + +To start writing your own and contributing to the community, check out the [Netcode community contribution repository](https://github.com/Unity-Technologies/multiplayer-community-contributions) for starting points and how to add your content. diff --git a/versioned_docs/version-2.0.0/advanced-topics/ways-to-synchronize.md b/versioned_docs/version-2.0.0/advanced-topics/ways-to-synchronize.md new file mode 100644 index 000000000..294218868 --- /dev/null +++ b/versioned_docs/version-2.0.0/advanced-topics/ways-to-synchronize.md @@ -0,0 +1,48 @@ +--- +id: ways-synchronize +title: Synchronizing states and events +--- + +Netcode for GameObjects has three options for synchronizing game states and events: + +- Messaging system + - [Remote procedure calls (RPCs)](message-system/rpc.md) + - [Custom messages](message-system/custom-messages.md) +- [NetworkVariables](../basics/networkvariable.md) + - Handled by the internal messaging system + +While each of these options can be used to synchronize states or events, they all have specific use cases and limitations. + +## Messaging system + +The Netcode for GameObjects messaging system allows you to send and receive messages or events. The system supports the serialization of most primitive value `type`s as well as any classes and/or structures that implement the `INetworkSerializable` interface. + +### Remote procedure calls (RPCs) + +RPCs are a way of sending an event notification as well as a way of handling direct communication between a server and a client, or between clients and the [distributed authority service](../terms-concepts/distributed-authority.md). This is sometimes useful when the ownership scope of the `NetworkBehavior`, that the remote procedure call is declared within, belongs to the server but you still want one or more clients to be able to communicate with the associated NetworkObject. + +Usage examples: + +- An RPC with `SendTo.Server` can be used by a client to notify the server that the player is trying to use a world object (such as a door or vehicle). +- An RPC with `SendTo.SpecifiedInParams` can be used by a server to notify a specific client of a special reconnection key or some other player-specific information that doesn't require its state to be synchronized with all current and any future late-joining client(s). + +There are many RPC methods, as outlined on the [RPC page](message-system/rpc.md#rpc-targets). The most commonly used are: + +- `Rpc(SendTo.Server)`: A remote procedure call received by and executed on the server side. +- `Rpc(SendTo.NotServer)`: A remote procedure call received by and executed on the client side. Note that this does NOT execute on the host, as the host is also the server. +- `Rpc(SendTo.ClientsAndHost)`: A remote procedure call received by and executed on the client side. If the server is running in host mode, this RPC will also be executed on the server (the host client). +- `Rpc(SendTo.SpecifiedInParams)`: A remote procedure call that will be sent to a list of client IDs provided as parameters at runtime. By default, other `SendTo` values cannot have any client IDs passed to them to change where they are being sent, but this can also be changed by passing `AllowTargetOverride = true` to the `Rpc` attribute. + +RPCs have no limitations on who can send them: the server can invoke an RPC with `SendTo.Server`, a client can invoke an RPC with `SendTo.NotServer`, and so on. If an RPC is invoked in a way that would cause it to be received by the same process that invoked it, it will be executed immediately in that process by default. Passing `DeferOverride = true` to the `Rpc` attribute will change this behavior and the RPC will be invoked at the start of the next frame. + +Refer to the [RPC page](../advanced-topics/message-system/rpc.md) for more details. + +### Custom messages + +Custom messages provide you with the ability to create your own message type. Refer to the [custom messages page](../advanced-topics/message-system/custom-messages.md) for more details. + +## NetworkVariables + +A NetworkVariable is most commonly used to synchronize state between both connected and late-joining clients. The `NetworkVariable` system only supports non-nullable value `type`s, but also provides support for `INetworkSerializable` implementations as well. You can create your own `NetworkVariable` class by deriving from the `NetworkVariableBase` abstract class. If you want something to always be synchronized with current and late-joining clients, then it's likely a good `NetworkVariable` candidate. + +Refer to the [NetworkVariable page](../basics/networkvariable.md) for more details. diff --git a/versioned_docs/version-2.0.0/basics/connection-approval.md b/versioned_docs/version-2.0.0/basics/connection-approval.md new file mode 100644 index 000000000..0e2bee126 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/connection-approval.md @@ -0,0 +1,235 @@ +--- +id: connection-approval +title: Connection approval +--- + +Connection approval allows you to decide for every new client connection attempt whether to allow or deny the client to connect. + +You can also send additional payload data via connection approval to initialize the client. For instance you can use this data to have a client specify a custom player Prefab (see example below). + +You have to enable ConnectionApproval in the NetworkManager Inspector or by setting `NetworkManager.NetworkConfig.ConnectionApproval = true` in a script before starting the host/server. If connection approval is not enabled, the `NetworkManager.ConnectionApprovalCallback` is not invoked. + +In both cases clients also have to pass internal authentication, to confirm that their NetworkConfig matches that of the server. + +## Server-side connection approval example + +```csharp +using Unity.Netcode; + +private void Setup() +{ + NetworkManager.Singleton.ConnectionApprovalCallback = ApprovalCheck; + NetworkManager.Singleton.StartHost(); +} + +private void ApprovalCheck(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response) +{ + // The client identifier to be authenticated + var clientId = request.ClientNetworkId; + + // Additional connection data defined by user code + var connectionData = request.Payload; + + // Your approval logic determines the following values + response.Approved = true; + response.CreatePlayerObject = true; + + // The Prefab hash value of the NetworkPrefab, if null the default NetworkManager player Prefab is used + response.PlayerPrefabHash = null; + + // Position to spawn the player object (if null it uses default of Vector3.zero) + response.Position = Vector3.zero; + + // Rotation to spawn the player object (if null it uses the default of Quaternion.identity) + response.Rotation = Quaternion.identity; + + // If response.Approved is false, you can provide a message that explains the reason why via ConnectionApprovalResponse.Reason + // On the client-side, NetworkManager.DisconnectReason will be populated with this message via DisconnectReasonMessage + response.Reason = "Some reason for not approving the client"; + + // If additional approval steps are needed, set this to true until the additional steps are complete + // once it transitions from true to false the connection approval response will be processed. + response.Pending = false; +} +``` + +## `NetworkManager.ConnectionApprovalRequest` + +This class represents the client-to-server request which has: + +- **ClientNetworkId**: the connecting client identifier +- **Payload**: any additional user-defined connection data + +## `NetworkManager.ConnectionApprovalResponse` + +This is how the connection approval response is formed by server-side specific user code in the handler assigned to `NetworkManager.ConnectionApprovalCallback`. On the server side, this class has all the connection approval response information required to either allow or reject a player attempting to connect. It also has the following properties: + +- **Approved**: When `true`, the player is approved. When `false`, the player is denied. +- **CreatePlayerObject**: When `true`, the server spawns a player Prefab for the connecting client. When `false`, the connecting client will have no player Prefab spawned. +- **PlayerPrefabHash**: The type of player Prefab to use for the authorized player (if this is `null`, it uses the default NetworkManager-defined player Prefab) +- **Position** and **Rotation**: The position and rotation of the player when spawned. +- **Pending**: Provides the ability to mark the approval as pending to delay the authorization until other user-specific code finishes the approval process. +- **Reason**: If `Approved` is `false`, you can populate this with a string-based message (or JSON) to send the reason the client wasn't approved. + +In earlier versions of Netcode for GameObjects, you had to provide a callback to invoke within the connection approval handler method. It's now only necessary to set the appropriate properties of the `NetworkManager.ConnectionApprovalResponse` class. Part of this update allows you to set your `ConnectionApprovalResponse` to `Pending`, providing extra time to process any other tasks involved with the player approval process. + +## Sending an approval declined reason (`NetworkManager.ConnectionApprovalResponse.Reason`) + +If you need to deny a player from connecting for any reason (such as if they reached the maximum number of connections or provided an invalid authorization), the `NetworkManager.ConnectionApprovalResponse` structure provides you with the optional `NetworkManager.ConnectionApprovalResponse.Reason` property. + +When `NetworkManager.ConnectionApprovalResponse.Approved` is false and `NetworkManager.ConnectionApprovalResponse.Reason` is populated with the reason for denying the player's request to connect, the server sends the client a `DisconnectReasonMessage`. Upon the client side receiving the `DisconnectReasonMessage`, the `NetworkManager.DisconnectReason` property populates with the `NetworkManager.ConnectionApprovalResponse.Reason` message. The example below shows how this works: + + +```csharp +using UnityEngine; +using Unity.Netcode; + +/// +/// Connection Approval Handler Component +/// +/// +/// This should be placed on the same GameObject as the NetworkManager. +/// It automatically declines the client connection for example purposes. +/// +public class ConnectionApprovalHandler : MonoBehaviour +{ + private NetworkManager m_NetworkManager; + + private void Start() + { + m_NetworkManager = GetComponent(); + if (m_NetworkManager != null) + { + m_NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; + m_NetworkManager.ConnectionApprovalCallback = ApprovalCheck; + } + } + + private void ApprovalCheck(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response) + { + response.Approved = false; + response.Reason = "Testing the declined approval message"; + } + + private void OnClientDisconnectCallback(ulong obj) + { + if (!m_NetworkManager.IsServer && m_NetworkManager.DisconnectReason != string.Empty) + { + Debug.Log($"Approval Declined Reason: {m_NetworkManager.DisconnectReason}"); + } + } +} +``` + + +## Connection data (`NetworkManager.ConnectionApprovalRequest.Payload`) + +The `ConnectionApprovalRequest.Payload` parameter takes any custom data the client should send to the server. Usually, this data is some sort of ticket, room password, or similar that helps the server decide whether to approve a connection. The `connectionData` is specified on the client-side in the `NetworkingConfig` (supplied when connecting). The `NetworkConfig.ConnectionData`, automatically sent with the connection request message to the server, should be populated on the client-side before calling `NetworkManager.StartClient`. + +Example: + +```csharp +using Unity.Netcode; + +NetworkManager.Singleton.NetworkConfig.ConnectionData = System.Text.Encoding.ASCII.GetBytes("room password"); +NetworkManager.Singleton.StartClient(); +``` + +The `Payload`, defined by the client-side `NetworkConfig.ConnectionData`, is sent to the server as part of the `Payload` of the connection request message that's used on the server-side to decide if the client is approved or not. The connection data is optional and you can use the connection approval process to assign player's unique Prefabs and facilitates the ability to spawn players at various locations (without requiring the client to send any form of connection data). + +## Timeout + +Netcode for GameObjects uses a callback system to allow for external validation. For example, you might have an authentication ticket sent as the `ConnectionData` that you want to validate against the owning servers. This can take some time, so you want to set the `NetworkManager.ConnectionApprovalResponse.Pending` to true while the server (or other third-party authentication service) validates the incoming ticket. + +If the third-party authentication process takes longer than the time specified by the `NetworkConfig.ClientConnectionBufferTimeout`, then the connection is dropped. The timer for this starts when the server is notified of the new inbound client connection. You can set the **Client Connection Buffer Timeout** value via the `NetworkManager` in the Inspector view or with the `NetworkManager.NetworkConfig.ClientConnectionBufferTimeout` property. + +## Security + +If you enable connection approval, the server silently ignores any messages sent before a connection is set up. + +### Connection data security + +:::warning + +Netcode for GameObjects doesn't encrypt or authenticate any of the raw information sent over connection approval. To prevent man-in-the-middle attacks, you should avoid sending authentication tokens (such as authentication tickets or user passwords) over connection approval without additional safety precautions. + +The examples on this page illustrate how Netcode for GameObjects doesn't protect any connection data, and don't show how to incorporate encryption, authentication, or some other method of data security. + +::: + +## Changing the player Prefab + +There might be times when you want to specify an alternate player Prefab to use for a player connecting. The connection approval process provides you with the ability to do this. + +### Modify or create an in-scene placed connection approval component + +```csharp + public class ClientConnectionHandler : NetworkBehaviour + { + public List AlternatePlayerPrefabs; + + public void SetClientPlayerPrefab(int index) + { + if (index > AlternatePlayerPrefabs.Count) + { + Debug.LogError($"Trying to assign player Prefab index of {index} when there are only {AlternatePlayerPrefabs.Count} entries!"); + return; + } + if (NetworkManager.IsListening || IsSpawned) + { + Debug.LogError("This needs to be set this before connecting!"); + return; + } + NetworkManager.NetworkConfig.ConnectionData = System.BitConverter.GetBytes(index); + } + + public override void OnNetworkSpawn() + { + if (IsServer) + { + NetworkManager.ConnectionApprovalCallback = ConnectionApprovalCallback; + } + } + + private void ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response) + { + var playerPrefabIndex = System.BitConverter.ToInt32(request.Payload); + if (AlternatePlayerPrefabs.Count > playerPrefabIndex) + { + response.PlayerPrefabHash = AlternatePlayerPrefabs[playerPrefabIndex]; + } + else + { + Debug.LogError($"Client provided player Prefab index of {playerPrefabIndex} when there are only {AlternatePlayerPrefabs.Count} entries!"); + return; + } + // Continue filling out the response + } + } + +``` + +The above example creates a list of unsigned integers to store an alternate player Prefab GlobalObjectIdHash values (`AlternatePlayerPrefabs`). For example purposes, this code sample adds a public method that a client can invoke to set their selected player Prefab's index that's relative to `AlternatePlayerPrefabs` (you can do this in some other component). The general idea for this approach is that the client provides the server with the alternate player Prefab index that the player wishes to use. + +The server assigns the `ConnectionApprovalCallback` when it spawns the in-scene placed NetworkObject that the `ClientConnectionHandler` is attached to. When the server handles a connection request, it grabs the alternate player Prefab index from the request's `Payload` field and then obtains the GlobalObjectIdHash value from the `AlternatePlayerPrefabs` list and assigns that to the `response.PlayerPrefabHash`. + +### Copy the alternate player Prefab's GlobalObjectIdHash value + +![Copy-GlobalObjectIdHash](/img/CopyGlobalObjectIdHash.png) + +To populate the `AlternatePlayerPrefabs` list: + +- Open the scene containing the in-scene placed NetworkObject that the `ConnectionApprovalCallback` is attached to. +- Find each alternate player Prefab you wish to add to the list, select the Prefab (but don't open it), and copy the **GlobalObjectIdHash** value by right-clicking and selecting **Copy**. +- Paste the copied **GlobalObjectIdHash** value into a new list item entry in the **AlternatePlayerPrefabs** list. + +### Assign client's selected player Prefab index + +This part is up to your project's design. The example includes a method that clients can invoke, but that also requires the client to have that scene loaded. You can choose to have a ScriptableObject that has the list of alternate player Prefab GlobalObjectIdHash values and share that between components (this would require you to change the `AlternatePlayerPrefabs` to a reference of the `ScriptableObject`). The general idea is to have the client populate the `NetworkConfig.ConnectionData` before starting. + +:::tip +An alternate way to handle this is by using a generic player Prefab that's used as the parent to the actual player's character and allowing the player to select their player once they're connected. This involves dynamically spawning the player's selected character with the client as the owner and parenting that under the player's generic player Prefab instance.
+Suggested Reading:
+[NetworkObject Parenting](../advanced-topics/networkobject-parenting.md)
+[Session Management](../advanced-topics/session-management.md)
+::: diff --git a/versioned_docs/version-2.0.0/basics/custom-networkvariables.md b/versioned_docs/version-2.0.0/basics/custom-networkvariables.md new file mode 100644 index 000000000..0226ecc09 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/custom-networkvariables.md @@ -0,0 +1,251 @@ +--- +id: custom-networkvariables +title: Custom NetworkVariables +--- + +In addition to the standard [`NetworkVariable`s](networkvariable.md) available in Netcode for GameObjects, you can also create custom `NetworkVariable`s for advanced implementations. The `NetworkVariable` and `NetworkList` classes were created as `NetworkVariableBase` class implementation examples. While the `NetworkVariable` class is considered production ready, you might run into scenarios where you have a more advanced implementation in mind. In this case, you can create your own custom implementation. + +To create your own `NetworkVariableBase`-derived container, you should: + +1. Create a class deriving from `NetworkVariableBase`. +1. Override the following methods: + - `void WriteField(FastBufferWriter writer)` + - `void ReadField(FastBufferReader reader)` + - `void WriteDelta(FastBufferWriter writer)` + - `void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)` +1. Depending on your custom `NetworkVariableBase` container, you can look at `NetworkVariable` or `NetworkList` to see how those two examples were implemented. + +## NetworkVariable serialization + +The way you read and write `NetworkVariable`s changes depending on the type you use. + +* Known, non-generic types: Use `FastBufferReader.ReadValue` to read from and `FastBufferWriter.WriteValue` to write to the `NetworkVariable` value. +* Integer types: This type gives you the option to use `BytePacker` and `ByteUnpacker` to compress the `NetworkVariable` value. This process can save bandwidth but adds CPU processing time. +* Generic types: Use serializers that Unity generates based on types discovered during a compile-time code generation process. This means you need to tell Unity's code generation algorithm which types to generate serializers for. To tell Unity which types to serialize, use the following methods: + * Use `GenerateSerializationForTypeAttribute` to serialize hard-coded types. + * Use `GenerateSerializationForGenericParameterAttribute` to serialize generic types. + +### Serialize a hard-coded type + +The following code example uses `GenerateSerializationForTypeAttribute` to generate serialization for a specific hard-coded type: + +```csharp +[GenerateSerializationForType(typeof(Foo))] +public class MyNetworkVariableTypeUsingFoo : NetworkVariableBase {} +``` + +You can call a type that you know the name of with the `FastBufferReader` or `FastBufferWriter` methods. These methods don't work for generic types because the name of the type is unknown. + +### Serialize a generic type + +The following code example uses `GenerateSerializationForGenericParameterAttribute` to generate serialization for a specific generic parameter in your `NetworkVariable` type: + +```csharp +[GenerateSerializationForGenericParameter(0)] +public class MyNetworkVariableType : NetworkVariableBase {} +``` + +This attribute accepts an integer that indicates which parameter in the type to generate serialization for. This value is 0-indexed, which means that the first type is 0, the second type is 1, and so on. + +The following code example places the attribute more than once on one class to generate serialization for multiple types, in this case,`TFirstType` and `TSecondType: + +```csharp +[GenerateSerializationForGenericParameter(0)] +[GenerateSerializationForGenericParameter(1)] +public class MyNetworkVariableType : NetworkVariableBase {} +``` + + +The `GenerateSerializationForGenericParameterAttribute` and `GenerateSerializationForTypeAttribute` attributes make Unity's code generation create the following methods: + +```csharp +NetworkVariableSerialization.Write(FastBufferWriter writer, ref T value); +NetworkVariableSerialization.Read(FastBufferWriter writer, ref T value); +NetworkVariableSerialization.Duplicate(in T value, ref T duplicatedValue); +NetworkVariableSerialization.AreEqual(in T a, in T b); +``` + +For dynamically-allocated types with a value that isn't `null` (for example, managed types and collections like `NativeArray` and `NativeList`) call `Read` to read the value in the existing object and write data into it directly (in-place). This avoids more allocations. + +You can use `AreEqual` to determine if a value is different from the value that `Duplicate` cached. This avoids sending the same value multiple times. You can also use the previous value that `Duplicate` cached to calculate deltas to use in `ReadDelta` and `WriteDelta`. + +The type you use must be serializable according to the [support types list above](#supported-types). Each type needs its own serializer instantiated, so this step tells the codegen which types to create serializers for. Unity's code generator assumes that all `NetworkVariable` types exist as fields inside `NetworkBehaviour` types. This means that Unity only inspects fields inside `NetworkBehaviour` types to identify the types to create serializers for. + +## Custom NetworkVariable example + +This example shows a custom `NetworkVariable` type to help you understand how you might implement such a type. In the current version of Netcode for GameObjects, this example is possible without using a custom `NetworkVariable` type; however, for more complex situations that aren't natively supported, this basic example should help inform you of how to approach the implementation: + + ```csharp + /// Using MyCustomNetworkVariable within a NetworkBehaviour + public class TestMyCustomNetworkVariable : NetworkBehaviour + { + public MyCustomNetworkVariable CustomNetworkVariable = new MyCustomNetworkVariable(); + public MyCustomGenericNetworkVariable CustomGenericNetworkVariable = new MyCustomGenericNetworkVariable(); + public override void OnNetworkSpawn() + { + if (IsServer) + { + for (int i = 0; i < 4; i++) + { + var someData = new SomeData(); + someData.SomeFloatData = (float)i; + someData.SomeIntData = i; + someData.SomeListOfValues.Add((ulong)i + 1000000); + someData.SomeListOfValues.Add((ulong)i + 2000000); + someData.SomeListOfValues.Add((ulong)i + 3000000); + CustomNetworkVariable.SomeDataToSynchronize.Add(someData); + CustomNetworkVariable.SetDirty(true); + + CustomGenericNetworkVariable.SomeDataToSynchronize.Add(i); + CustomGenericNetworkVariable.SetDirty(true); + } + } + } + } + + /// Bare minimum example of NetworkVariableBase derived class + [Serializable] + public class MyCustomNetworkVariable : NetworkVariableBase + { + /// Managed list of class instances + public List SomeDataToSynchronize = new List(); + + /// + /// Writes the complete state of the variable to the writer + /// + /// The stream to write the state to + public override void WriteField(FastBufferWriter writer) + { + // Serialize the data we need to synchronize + writer.WriteValueSafe(SomeDataToSynchronize.Count); + foreach (var dataEntry in SomeDataToSynchronize) + { + writer.WriteValueSafe(dataEntry.SomeIntData); + writer.WriteValueSafe(dataEntry.SomeFloatData); + writer.WriteValueSafe(dataEntry.SomeListOfValues.Count); + foreach (var valueItem in dataEntry.SomeListOfValues) + { + writer.WriteValueSafe(valueItem); + } + } + } + + /// + /// Reads the complete state from the reader and applies it + /// + /// The stream to read the state from + public override void ReadField(FastBufferReader reader) + { + // De-Serialize the data being synchronized + var itemsToUpdate = (int)0; + reader.ReadValueSafe(out itemsToUpdate); + SomeDataToSynchronize.Clear(); + for (int i = 0; i < itemsToUpdate; i++) + { + var newEntry = new SomeData(); + reader.ReadValueSafe(out newEntry.SomeIntData); + reader.ReadValueSafe(out newEntry.SomeFloatData); + var itemsCount = (int)0; + var tempValue = (ulong)0; + reader.ReadValueSafe(out itemsCount); + newEntry.SomeListOfValues.Clear(); + for (int j = 0; j < itemsCount; j++) + { + reader.ReadValueSafe(out tempValue); + newEntry.SomeListOfValues.Add(tempValue); + } + SomeDataToSynchronize.Add(newEntry); + } + } + + public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) + { + // Do nothing for this example + } + + public override void WriteDelta(FastBufferWriter writer) + { + // Do nothing for this example + } + } + + /// Bare minimum example of generic NetworkVariableBase derived class + [Serializable] + [GenerateSerializationForGenericParameter(0)] + public class MyCustomGenericNetworkVariable : NetworkVariableBase + { + /// Managed list of class instances + public List SomeDataToSynchronize = new List(); + + /// + /// Writes the complete state of the variable to the writer + /// + /// The stream to write the state to + public override void WriteField(FastBufferWriter writer) + { + // Serialize the data we need to synchronize + writer.WriteValueSafe(SomeDataToSynchronize.Count); + for (var i = 0; i < SomeDataToSynchronize.Count; ++i) + { + var dataEntry = SomeDataToSynchronize[i]; + // NetworkVariableSerialization is used for serializing generic types + NetworkVariableSerialization.Write(writer, ref dataEntry); + } + } + + /// + /// Reads the complete state from the reader and applies it + /// + /// The stream to read the state from + public override void ReadField(FastBufferReader reader) + { + // De-Serialize the data being synchronized + var itemsToUpdate = (int)0; + reader.ReadValueSafe(out itemsToUpdate); + SomeDataToSynchronize.Clear(); + for (int i = 0; i < itemsToUpdate; i++) + { + T newEntry = default; + // NetworkVariableSerialization is used for serializing generic types + NetworkVariableSerialization.Read(reader, ref newEntry); + SomeDataToSynchronize.Add(newEntry); + } + } + + public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) + { + // Do nothing for this example + } + + public override void WriteDelta(FastBufferWriter writer) + { + // Do nothing for this example + } + } + + /// Example managed class used as the item type in the + /// MyCustomNetworkVariable.SomeDataToSynchronize list + [Serializable] + public class SomeData + { + public int SomeIntData = default; + public float SomeFloatData = default; + public List SomeListOfValues = new List(); + } + ``` + +While the above example isn't the recommended way to synchronize a list where the number or order of elements in the list often changes, it's an example of how you can define your own rules using `NetworkVariableBase`. + +You can test the code above by: +- Using the above code with a project that includes Netcode for GameObjects v1.0 (or higher). +- Adding the `TestMyCustomNetworkVariable` component to an in-scene placed NetworkObject. +- Creating a stand alone build and running that as a host or server. +- Running the same scene within the Editor and connecting as a client. + - Once connected, you can then select the `GameObject` with the attached NetworkObject and `TestMyCustomNetworkVariable` components so it appears in the inspector view. There you can verify the `TestMyCustomNetworkVariable.CustomNetworkVariable` property was synchronized with the client (like in the screenshot below): + ![ScreenShot](/img/MyCustomNetworkVariableInspectorView.png) + +:::caution +You can't nest `NetworkVariable`s inside other `NetworkVariable` classes. This is because Netcode for GameObjects performs a code generation step to define serialization callbacks for each type it finds in a `NetworkVariable`. The code generation step looks for variables as fields of `NetworkBehaviour` types; it misses any `NetworkVariable`s declared anywhere else. + +Instead of nesting `NetworkVariable`s inside other `NetworkVariable` classes, declare `NetworkVariable` or `NetworkList` properties within the same `NetworkBehaviour` within which you have declared your custom `NetworkVariableBase` implementation. +::: diff --git a/versioned_docs/version-2.0.0/basics/deferred-despawning.md b/versioned_docs/version-2.0.0/basics/deferred-despawning.md new file mode 100644 index 000000000..35d639704 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/deferred-despawning.md @@ -0,0 +1,233 @@ +--- +id: deferred-despawning +title: Deferred despawning +--- + +:::info Distributed authority only + +Deferred despawning is only available for games using a [distributed authority topology](../terms-concepts/distributed-authority.md). + +::: + +In addition to the visual latency issues described on the [spawning synchronization page](spawning-synchronization.md), networked games also experience latency issues when despawning objects. In [client-server](../terms-concepts/client-server.md) contexts, these problems are typically addressed using client prediction models, but in a distributed authority context there is a simpler alternative solution: deferred despawning. + +## Why deferred despawning + +Issues with despawning occur when objects are despawned too early. The client that owns the projectile detects a collision, triggers any local animations, and despawns the projectile locally. For the player being hit, they see the projectile despawn before it reaches them and animations can appear to trigger at the wrong point in time. This can be seen in the diagram below. + +![Without deferred despawning](/img/without-deferred-despawning.jpg) + +Deferred despawning allows you to add a tick-based delay to a NetworkObject prior to despawning it locally. The deferred despawn tick value is then added to the `DestroyObjectMessage` so that other clients, upon receiving the message, defer processing the destroy message until the specified tick value has been reached. The results of this deferred despawn adjustment can be seen in the diagram below. + +![With deferred despawning](/img/with-deferred-despawning.jpg) + +Deferred despawning allows non-owner instances of an object to complete their interpolation towards the last transform state update before despawning the object. Even though non-owner clients can be visually latent by several network ticks behind the authority, all clients end up with the same visual experience since NetworkTransform deltas are tick synchronized. + +## Implementing deferred despawning + +Implementing deferred despawning is a three-step process, as described in the diagram below. + +![Implementing deferred despawning](/img/implementing-deferred-despawning.jpg) + +The process above is broken down into two steps that are performed on the authority instance, and one step performed on the non-authority instance(s). + +|Authority instance|Non-authority instance(s)| +|---|---| +|1. Invoke the `NetworkObject.DeferDespawn` method while providing the number of ticks to offset despawning by on non-authority instance(s), relative to the local client's current known network tick.
2. If overridden, handle any changes to state (i.e. NetworkVariables) when `NetworkBehaviour.OnDeferringDespawn` is invoked.|1. Use any updated states to synchronize the visual portion of a deferred despawn (for example, starting a particle system to represent the point of impact).| + +### Deferred despawning example + +Below is a basic example demonstrating how deferred despawning can be used to visually synchronize a projectile's impact with explosion effects: + +``` +public class ExplodingProjectile : NetworkBehaviour +{ + // The explosion network prefab + public GameObject ExplosionPrefab; + public int DespawnTickOffset = 4; + private ExplosionFx m_SpawnedExplosion; + + + // Permission are always owner write and everyone read in distributed authority + // Authority assigns this value after spawning the ExplosionFx within the overridden + // OnDeferringDespawn method prior to the local ExplodingPrrojectile is despawned + // locally. + private NetworkVariable m_ExplosionFx = new NetworkVariable(); + + + public override void OnNetworkSpawn() + { + if (!HasAuthority) + { + m_ExplosionFx.OnValueChanged += OnExplosionFxChanged; + } + + + base.OnNetworkSpawn(); + } + + + private void OnCollisionEnter(Collision collision) + { + if (!IsSpawned || !HasAuthority) + { + return; + } + // Typically you would want to check what you hit + // to make sure you want to "explode" the projectile + // first. This example assumes that a check was performed. + + + // Start with the projectile's position + var explodePoint = transform.position; + if (collision.contacts.Length > 0 ) + { + // Example purposes, just use the first contact + explodePoint = collision.contacts[0].point; + } + HandleExplosion(explodePoint); + } + + + private void HandleExplosion(Vector3 explodePoint) + { + // Example purposes only, we recommend using object pools + // using an INetworkPrefabInstanceHandler implementation + // and registering that with the NetworkManager.PrefabHandler. + var instance = Instantiate(ExplosionPrefab); + + // position the explosion + instance.transform.position = explodePoint; + + + // Assign this for later use (example purposes) + m_SpawnedExplosion = instance.GetComponent(); + + // Spawn the explosion + var instanceObj = instance.GetComponent(); + instanceObj.Spawn(); + + + // Defer the despawning of the projectile instance + NetworkObject.DeferDespawn(DespawnTickOffset); + + // The local instance should be despawned at this point + } + + + /// + /// Invoked on the authority side when it is deferring the + /// despawning of the NetworkObject. + /// + /// + /// This is a good time to set any NetworkVariable states as + /// they will be sent to clients prior to the defer despawn message. + /// + /// the final future network tick + /// non-authority instances will despawn the clone instance. + public override void OnDeferringDespawn(int despawnTick) + { + // This lets the non-authority instances know what ExplosionFx instance is + // associated with this NetworkObject. + m_ExplosionFx = new NetworkVariable(m_SpawnedExplosion); + + + // Apply any other state updates here + + + base.OnDeferringDespawn(despawnTick); + } + + + /// + /// Non-authority registers for this and acquires the ExplosionFX + /// of the network prefab spawned. + /// + private void OnExplosionFxChanged(NetworkBehaviourReference previous, NetworkBehaviourReference current) + { + // If possible, get the ExplosionFx component + current.TryGet(out m_SpawnedExplosion); + } + + + public override void OnNetworkDespawn() + { + // When non-authority instances finally despawn, + // the explosion FX will begin playing. + if (!HasAuthority) + { + if (m_SpawnedExplosion) + { + m_SpawnedExplosion.SetParticlePlayingState(true); + } + m_ExplosionFx.OnValueChanged -= OnExplosionFxChanged; + } + base.OnNetworkDespawn(); + } +} +``` + +The pseudo script above excludes the motion of the projectile, but makes the assumption that authority is moving the projectile and that when the projectile impacts a valid object, the `OnCollisionEnter` method will be invoked. + +Authority instance|Non-authority instance(s)| +|---|---| +|1. Move until collision (excluded from the above example)
2. Upon collision: instantiate ExplosionFX, position ExplosionFX, acquire the ExplosionFX component, spawn the ExplosionFX, defer despawning the projectile
3. When deferring despawning the projectile, assign the NetworkBehaviourReference to the ExplosionFX
4. Despawn locally (happens automatically at end of the DeferDespawn)|1. When spawned, register changes to the m_ExplosionFX NetworkVariable
2. Continue to interpolate towards last updated position (handled by NetworkTransform)
3. When m_ExplosionFX NetworkVariable changes, assign the local m_SpawnExplosion ExplosionFX component of the explosion associated with the current projectile instance (to be used when despawned)
4.When despawned, start the ExplosionFX particle system via m_SpawnExplosion| + +The non-authority instance still spawns the ExplosionFX NetworkObject at the same relative time frame as the authority, however it doesn't start its particle system until the local non-authority projectile instance is despawned. Since the projectile has had its despawn deferred until a future network tick, the non-authority instance interpolates up to its last updated position from the authority side and then shortly (in milliseconds) after is despawned and the explosion particle system started. + +:::info Example purposes only + +The spawning explosion FX example above is only for example purposes and would be less bandwidth and processor intensive if you used a local particle FX pool that you pull from and began playing on both the authority and non-authority sides when the object in question is despawned. The same deferred despawn principles would be used without the need to spawn an additional object. However, providing the spawned explosion example also covers other scenarios where spawning is required. + +::: + +For example purposes, the below script is all that you would need to control starting and stopping the explosion particle system: + +``` +public class ExplosionFx : NetworkBehaviour +{ + public ParticleSystem ParticleSystem; + + + private void OnEnable() + { + // In the event a pool is used, it is always good to + // make sure the particle system is not playing + SetParticlePlayingState(); + } + + + public override void OnNetworkSpawn() + { + // Authority starts the particle system (locally) when spawned + if (HasAuthority) + { + SetParticlePlayingState(true); + } + base.OnNetworkSpawn(); + } + + + /// + /// Used to start/stop the particle system + /// Non-Authority starts this when its associated projectile + /// is finally despawned + /// + public void SetParticlePlayingState(bool isPlaying = false) + { + if (ParticleSystem) + { + if(isPlaying) + { + ParticleSystem.Play(); + } + else + { + ParticleSystem.Stop(); + } + } + } +} +``` + +Note that the authority automatically starts the particle system upon being spawned. Non-authority instances are started by their paired projectile when despawned at the end of the deferred despawn defined network tick. diff --git a/versioned_docs/version-2.0.0/basics/logging.md b/versioned_docs/version-2.0.0/basics/logging.md new file mode 100644 index 000000000..97fb23eb0 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/logging.md @@ -0,0 +1,25 @@ +--- +id: logging +title: Logging +sidebar_label: Logging +--- + + Netcode for GameObjects (Netcode) has built in support for logging which can be great for working with development builds, playtesting and more. If used in production, it should be noted that logs can be forged and do take up some bandwidth depending on the log sizes. + +The logging functionality can be accessed with the NetworkLog API. If a `NetworkLog` is called on the server, the log won't be sent across the network but will instead just be logged locally with the `ServerClientId` as the sender. An example log would be `[MLAPI_SERVER Sender=0] Hello World!`. If the NetworkLog API is instead accessed from a client, it will first log locally on the sending client but will also be logged on the server. + +## Examples + +```csharp +if (IsServer) +{ + // This won't send any network packets but will log it locally on the server + NetworkLog.LogInfoServer("Hello World!"); +} + +if (IsClient) +{ + // This will log locally and send the log to the server to be logged there aswell + NetworkLog.LogInfoServer("Hello World!"); +} +``` \ No newline at end of file diff --git a/versioned_docs/version-2.0.0/basics/maxnumberplayers.md b/versioned_docs/version-2.0.0/basics/maxnumberplayers.md new file mode 100644 index 000000000..94188169a --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/maxnumberplayers.md @@ -0,0 +1,35 @@ +--- +id: max-players +title: Limiting the maximum number of players +created: 2023-01-24T18:08:36-06:00 +updated: 2023-01-24T18:59:55-06:00 +--- + +Netcode for GameObjects provides a way to customize the [connection approval process](connection-approval.md) that can reject incoming connections based on any number of user-specific reasons. + +Boss Room provides one example of how to handle limiting the number of players through the connection approval process: + +```csharp reference +https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/ConnectionManagement/ConnectionState/HostingState.cs + +``` + +The code below shows an example of an over-capacity check that would prevent more than a certain pre-defined number of players from connecting. + + +```csharp +if( m_Portal.NetManager.ConnectedClientsIds.Count >= CharSelectData.k_MaxLobbyPlayers ) +{ + return ConnectStatus.ServerFull; +} +``` + +:::tip​ + +In connection approval delegate, Netcode for GameObjects doesn't support sending anything more than a Boolean back. + +Boss Room shows a way to offer meaningful error code to the client by invoking a client RPC in the same channel that Netcode for GameObjects uses for its connection callback. + +::: + +When using Relay, ensure the maximum number of peer connections allowed by the host satisfies the logic implemented in the connection approval delegate. diff --git a/versioned_docs/version-2.0.0/basics/networkbehaviour-synchronize.md b/versioned_docs/version-2.0.0/basics/networkbehaviour-synchronize.md new file mode 100644 index 000000000..f37141e95 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/networkbehaviour-synchronize.md @@ -0,0 +1,179 @@ +--- +id: networkbehavior-synchronize +title: NetworkBehaviour synchronization +--- + +[`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. + +You can use `NetworkBehaviour`s to synchronize settings before, during, and after spawning NetworkObjects. + +For more information about spawning and despawning `NetworkBehaviour`s, refer to the [NetworkBehaviour spawning and despawning page](networkbehaviour.md). + +## Pre-spawn, spawn, post-spawn and synchronization + +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. + +- Pre-spawn example: Instantiating a `NetworkVariable` with owner write permissions and assigning a value to that `NetworkVariable` on the server or host side. +- Spawn example: Applying a local value or setting that may be used during post spawn by another local `NetworkBehaviour` component. +- Post-spawn example: Accessing a `NetworkVariable` or other property that is set during the spawn process. + +Below are the available virtual methods you can override within a `NetworkBehaviour`-derived class: + +Method | Scope | Use case | Context +---------------------------- | ------------------------ | ------------------------------------------------------ | ------------- +`OnNetworkPreSpawn` | NetworkObject | Pre-spawn initialization | Client and server +`OnNetworkSpawn` | NetworkObject | During spawn initialization | Client and server +`OnNetworkPostSpawn` | NetworkObject | Post-spawn actions | Client and server +`OnNetworkSessionSynchronized` | All NetworkObjects | New client finished synchronizing | Client-side only +`OnInSceneObjectsSpawned` | In-scene NetworkObjects | New client finished synchronizing or a scene is loaded | Client and server + + +`OnNetworkSessionSynchronized` and `OnInSceneObjectsSpawned` are two special case convenience methods: + +- `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 that everything is spawned and ready to be accessed and/or to have messages sent from them. Remember that this method is only invoked on clients and isn't invoked on a server or host. +- `OnInSceneObjectsSpawned`: This method ensures 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: + - A server or host first starts up after all in-scene placed NetworkObjects in the currently loaded scene(s) have been spawned. + - A client finishes synchronizing. + - On the server and client side after a scene has been loaded and all newly instantiated in-scene placed NetworkObjects have been spawned. + +### Pre-spawn synchronization with `OnSynchronize` + +There can be scenarios where you need to include additional configuration data or use a `NetworkBehaviour` to configure some non-netcode related component 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 latency involved that might require additional specialized code to compensate for. + +`NetworkBehaviour.OnSynchronize` allows you to write and read custom serialized data during the NetworkObject serialization process. + +There are two cases where NetworkObject synchronization occurs: + +- When dynamically spawning a NetworkObject. +- When a client is being synchronized after connection approval (that is, full synchronization of the NetworkObjects and scenes). + +:::info + +The [`INetworkSerializable` interface](../advanced-topics/serialization/inetworkserializable.md) is similar to `NetworkBehaviour.OnSynchronize`, and can be a useful reference point to understand first. +::: + +#### Order of operations when dynamically spawning + +The following provides you with an outline of the order of operations that occur during NetworkObject serialization when dynamically spawned. + +Server-side: + +- `GameObject` with NetworkObject component is instantiated. +- The `NetworkObject` is spawned. + - For each associated `NetworkBehaviour` component, `NetworkBehaviour.OnNetworkSpawn` is invoked. +- The `CreateObjectMessage` is generated. + - NetworkObject state is serialized. + - `NetworkVariable` state is serialized. + - `NetworkBehaviour.OnSynchronize` is invoked for each `NetworkBehaviour` component. + - If this method isn't overridden then nothing is written to the serialization buffer. +- The `CreateObjectMessage` is sent to all clients that are observers of the NetworkObject. + + +Client-side: + +- The `CreateObjectMessage` is received. + - `GameObject` with NetworkObject component is instantiated. + - `NetworkVariable` state is deserialized and applied. + - `NetworkBehaviour.OnSynchronize` is invoked for each `NetworkBehaviour` component. + - If this method isn't overridden then nothing is read from the serialization buffer. +- The `NetworkObject` is spawned. + - For each associated `NetworkBehaviour` component, `NetworkBehaviour.OnNetworkSpawn` is invoked. + +#### Order of operations during full (late-join) client synchronization + +Server-side: +- The `SceneEventMessage` of type `SceneEventType.Synchronize` is created. + - All spawned `NetworkObjects` that are visible to the client, already instantiated, and spawned are serialized. + - `NetworkObject` state is serialized. + - `NetworkVariable` state is serialized. + - `NetworkBehaviour.OnSynchronize` is invoked for each `NetworkBehaviour` component. + - If this method isn't overridden then nothing is written to the serialization buffer. +- The `SceneEventMessage` is sent to the client. + +Client-side: +- The `SceneEventMessage` of type `SceneEventType.Synchronize` is received. +- Scene information is deserialized and scenes are loaded (if not already). + - In-scene placed NetworkObjects are instantiated when a scene is loaded. +- All NetworkObject-oriented synchronization information is deserialized. + - Dynamically spawned NetworkObjects are instantiated and state is synchronized. + - For each NetworkObject instance: + - `NetworkVariable` state is deserialized and applied. + - `NetworkBehaviour.OnSynchronize` is invoked. + - If this method isn't overridden then nothing is read from the serialization buffer. + - The `NetworkObject` is spawned. + - For each associated `NetworkBehaviour` component, `NetworkBehaviour.OnNetworkSpawn` is invoked. + +### `OnSynchronize` example + +Understanding when to use `OnSynchronize` is key to making the most of the method. `NetworkVariable`s can be useful to synchronize state, but they're 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`. + +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: + +```csharp +using UnityEngine; +using Unity.Netcode; + +/// +/// Simple RPC driven state that shows one +/// form of NetworkBehaviour.OnSynchronize usage +/// +public class SimpleRpcState : NetworkBehaviour +{ + private bool m_ToggleState; + + /// + /// Late joining clients will be synchronized + /// to the most current m_ToggleState + /// + protected override void OnSynchronize(ref BufferSerializer serializer) + { + serializer.SerializeValue(ref m_ToggleState); + base.OnSynchronize(ref serializer); + } + + public void ToggleState(bool stateIsSet) + { + m_ToggleState = stateIsSet; + } + + /// + /// Synchronizes connected clients with the + /// server-side m_ToggleState + /// + /// + [Rpc(SendTo.ClientsAndHost)] + private void ToggleStateClientRpc(bool stateIsSet) + { + m_ToggleState = stateIsSet; + } +} +``` +:::caution +`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. +::: + +### Debugging `OnSynchronize` serialization + +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. + +#### When writing + +If user-code throws an exception during `NetworkBehaviour.OnSynchronize`, it catches the exception and if: +- **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. +- **LogLevel = Developer**: It provides the same warning message as well as it logs an error with the exception message and stack trace. + +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. + +#### When reading + +For exceptions this follows the exact same message logging pattern described above when writing. The difference is that after `NetworkBehaviour.OnSynchronize` 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. + +However, there is an additional check to ensure 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. + +:::caution +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. +::: + +## Serializing `NetworkBehaviour`s + +`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. diff --git a/versioned_docs/version-2.0.0/basics/networkbehaviour.md b/versioned_docs/version-2.0.0/basics/networkbehaviour.md new file mode 100644 index 000000000..7f007cfff --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/networkbehaviour.md @@ -0,0 +1,138 @@ +--- +id: networkbehavior +title: NetworkBehaviour spawning and despawning +--- + +[`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. + +`NetworkBehaviour`s can use `NetworkVariable`s and RPCs to synchronize states and send messages over the network. When you call an RPC function in a `NetworkBehaviour`, the function isn't called locally. Instead, a message is sent containing your parameters, the `networkId` of the NetworkObject associated with the same GameObject (or child) that the `NetworkBehaviour` is assigned to, and the index of the NetworkObject-relative `NetworkBehaviour` (NetworkObjects can have several `NetworkBehaviours`, the index communicates which one). + +For more information about serializing and synchronizing `NetworkBehaviour`s, refer to the [NetworkBehaviour synchronization page](networkbehaviour-synchronize.md). + +## `NetworkBehaviour` requirements + +A `NetworkBehaviour` requires a NetworkObject component on the same relative GameObject or on a parent of the GameObject with the `NetworkBehaviour` component assigned to it. If you add a `NetworkBehaviour` to a GameObject that doesn't have a NetworkObject (or any parent), then Netcode for GameObjects automatically adds a NetworkObject component to the `GameObject` in which the `NetworkBehaviour` was added. + +It's important that the `NetworkBehaviour`s on each NetworkObject remain the same for the server and any client connected. When using multiple projects, this becomes especially important so the server doesn't try to call a client RPC on a `NetworkBehaviour` that might not exist on a specific client type (or set a `NetworkVariable` that doesn't exist, and so on). + +:::warning Disabling automatic NetworkObject addition +You can optionally disable the automatic addition of NetworkObjects to GameObjects in the Editor using the **Multiplayer** > **Netcode for GameObjects** project settings. However, this is only recommended for advanced users who understand the implications and have alternative strategies in place to ensure that GameObjects with `NetworkBehaviour`s always have an associated NetworkObject. +::: + +## Spawning + +`OnNetworkSpawn` is invoked on each `NetworkBehaviour` associated with a NetworkObject when it's spawned. This is where all netcode-related initialization should occur. + +You can still use `Awake` and `Start` to do things like finding components and assigning them to local properties, but if `NetworkBehaviour.IsSpawned` is false then don't expect netcode-distinguishing properties (like `IsClient`, `IsServer`, `IsHost`, for example) to be accurate within `Awake` and `Start` methods. + +For reference purposes, below is a table of when `NetworkBehaviour.OnNetworkSpawn` is invoked relative to the NetworkObject type: + +Dynamically spawned | In-scene placed +------------------- | --------------- +`Awake` | `Awake` +`OnNetworkSpawn` | `Start` +`Start` | `OnNetworkSpawn` + +For more information about `NetworkBehaviour` methods and when they are invoked, see the [Pre-Spawn and MonoBehaviour Methods](networkbehaviour.md#pre-spawn-and-monobehaviour-methods) section. + +### Disabling `NetworkBehaviour`s when spawning + +If you want to disable a specific `NetworkBehaviour` but still want it to be included in the NetworkObject spawn process (so you can still enable it at a later time), you can disable the individual `NetworkBehaviour` instead of the entire `GameObject`. + +`NetworkBehaviour` components that are disabled by default and are attached to in-scene placed NetworkObjects behave like `NetworkBehaviour` components that are attached to dynamically spawned NetworkObjects when it comes to the order of operations for the `NetworkBehaviour.Start` and `NetworkBehaviour.OnNetworkSpawn` methods. Since in-scene placed NetworkObjects are spawned when the scene is loaded, a `NetworkBehaviour` component (that is disabled by default) will have its `NetworkBehaviour.OnNetworkSpawn` method invoked before the `NetworkBehaviour.Start` method, since `NetworkBehaviour.Start` is invoked when a disabled `NetworkBehaviour` component is enabled. + +Dynamically spawned | In-scene placed (disabled `NetworkBehaviour` components) +------------------- | --------------- +`Awake` | `Awake` +`OnNetworkSpawn` | `OnNetworkSpawn` +`Start` | `Start` (invoked when disabled `NetworkBehaviour` components are enabled) + +:::warning Parenting, inactive GameObjects, and `NetworkBehaviour` components +If you have child GameObjects that are not active in the hierarchy but are nested under an active GameObject with an attached NetworkObject component, then the inactive child GameObjects will not be included when the NetworkObject is spawned. This applies for the duration of the NetworkObject's spawned lifetime. If you want all child `NetworkBehaviour` components to be included in the spawn process, then make sure their respective GameObjects are active in the hierarchy before spawning the NetworkObject. Alternatively, you can just disable the NetworkBehaviour component(s) individually while leaving their associated GameObject active. + +It's recommended to disable a `NetworkBehaviour` component rather than the GameObject itself. +::: + +### Dynamically spawned NetworkObjects + +For dynamically spawned NetworkObjects (instantiating a network prefab during runtime) the `OnNetworkSpawn` method is invoked before the `Start` method is invoked. This means that finding and assigning components to a local property within the `Start` method exclusively will result in that property not being set in a `NetworkBehaviour` component's `OnNetworkSpawn` method when the NetworkObject is dynamically spawned. To circumvent this issue, you can have a common method that initializes the components and is invoked both during the `Start` method and the `OnNetworkSpawned` method like the code example below: + +```csharp +public class MyNetworkBehaviour : NetworkBehaviour +{ + private MeshRenderer m_MeshRenderer; + private void Start() + { + Initialize(); + } + + private void Initialize() + { + if (m_MeshRenderer == null) + { + m_MeshRenderer = FindObjectOfType(); + } + } + + public override void OnNetworkSpawn() + { + Initialize(); + // Do things with m_MeshRenderer + + base.OnNetworkSpawn(); + } +} +``` + +### In-scene placed NetworkObjects + +For in-scene placed NetworkObjects, the `OnNetworkSpawn` method is invoked after the `Start` method, because the SceneManager scene loading process controls when NetworkObjects are instantiated. The previous code example shows how you can design a `NetworkBehaviour` that ensures both in-scene placed and dynamically spawned NetworkObjects will have assigned the required properties before attempting to access them. Of course, you can always make the decision to have in-scene placed `NetworkObjects` contain unique components to that of dynamically spawned `NetworkObjects`. It all depends upon what usage pattern works best for your project. + +For more information about in-scene placed NetworkObjects, refer to the [in-scene placed NetworkObjects page](scenemanagement/inscene-placed-networkobjects.md). + +## Despawning + +`NetworkBehaviour.OnNetworkDespawn` is invoked on each `NetworkBehaviour` associated with a NetworkObject when it's despawned. This is where all netcode cleanup code should occur, but isn't to be confused with destroying ([see below](#destroying)). When a `NetworkBehaviour` component is destroyed, it means the associated GameObject, and all attached components, are in the middle of being destroyed. Regarding the order of operations, `NetworkBehaviour.OnNetworkDespawn` is always invoked before `NetworkBehaviour.OnDestroy`. + +### Destroying + +Each `NetworkBehaviour` has a virtual `OnDestroy` method that can be overridden to handle clean up that needs to occur when you know the `NetworkBehaviour` is being destroyed. + +If you override the virtual `OnDestroy` method it's important to always invoke the base like such: + +```csharp + public override void OnDestroy() + { + // Clean up your NetworkBehaviour + + // Always invoked the base + base.OnDestroy(); + } +``` + +`NetworkBehaviour` handles other destroy clean up tasks and requires that you invoke the base `OnDestroy` method to operate properly. + +## Pre-spawn and `MonoBehaviour` methods + +Since `NetworkBehaviour` is derived from `MonoBehaviour`, the `NetworkBehaviour.OnNetworkSpawn` method is treated similar to the `Awake`, `Start`, `FixedUpdate`, `Update`, and `LateUpdate` `MonoBehaviour` methods. Different methods are invoked depending on whether the GameObject is active in the hierarchy. + +- When active: `Awake`, `Start`, `FixedUpdate`, `Update`, and `LateUpdate` are invoked. +- When not active: `Awake`, `Start`, `FixedUpdate`, `Update`, and `LateUpdate` are not invoked. + +For more information about execution order, refer to [Order of execution for event functions](https://docs.unity3d.com/Manual/ExecutionOrder.html) in the main Unity Manual. + +The unique behavior of `OnNetworkSpawn`, compared to the previously listed methods, is that it's not invoked until the associated GameObject is active in the hierarchy and its associated NetworkObject is spawned. + +Additionally, the `FixedUpdate`, `Update`, and `LateUpdate` methods, if defined and the GameObject is active in the hierarchy, will still be invoked on `NetworkBehaviour`s even when they're not yet spawned. If you want portions or all of your update methods to only execute when the associated NetworkObject component is spawned, you can use the `NetworkBehaviour.IsSpawned` flag to determine the spawned status like the below example: + +```csharp +private void Update() +{ + // If the NetworkObject is not yet spawned, exit early. + if (!IsSpawned) + { + return; + } + // Netcode specific logic executed when spawned. +} +``` diff --git a/versioned_docs/version-2.0.0/basics/networkobject.md b/versioned_docs/version-2.0.0/basics/networkobject.md new file mode 100644 index 000000000..a2c49ea2b --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/networkobject.md @@ -0,0 +1,127 @@ +--- +id: networkobject +title: NetworkObject +--- + +A NetworkObject is a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) with a NetworkObject component and at least one [NetworkBehaviour](networkbehaviour.md) component, which enables the GameObject to respond to and interact with netcode. NetworkObjects are session-mode agnostic and used in both [client-server](../terms-concepts/client-server.md) and [distributed authority](../terms-concepts/distributed-authority.md) contexts. + +Netcode for GameObjects' high level components, [the RPC system](../advanced-topics/messaging-system.md), [object spawning](../object-spawning), and [`NetworkVariable`](networkvariable.md)s all rely on there being at least two Netcode components added to a GameObject: + + 1. NetworkObject + 2. [`NetworkBehaviour`](networkbehaviour.md) + +NetworkObjects require the use of specialized [`NetworkObjectReference`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObjectReference.html) structures before you can serialize and use them with RPCs and `NetworkVariable`s + +Netcode for GameObjects also has [PlayerObjects](playerobjects.md), an optional feature that you can use to assign a NetworkObject to a specific client. + +## Using NetworkObjects + +To replicate any Netcode-aware properties or send/receive RPCs, a GameObject must have a NetworkObject component and at least one NetworkBehaviour component. Any Netcode-related component, such as a NetworkTransform or a NetworkBehaviour with one or more NetworkVariables or RPCs, requires a NetworkObject component on the same relative GameObject (or on a parent of the GameObject in question). + +When spawning a NetworkObject, the `NetworkObject.GlobalObjectIdHash` value initially identifies the associated network prefab asset that clients instantiate to create a client-local clone. After instantiated locally, each NetworkObject is assigned a NetworkObjectId that's used to associate NetworkObjects across the network. For example, one peer can say "Send this RPC to the object with the NetworkObjectId 103," and everyone knows what object it's referring to. A NetworkObject is spawned on a client when it's instantiated and assigned a unique NetworkObjectId. + +You can use [NetworkBehaviours](networkbehaviour.md) to add your own custom Netcode logic to the associated NetworkObject. + +### Component order + +The order of components on a networked GameObject matters. When adding netcode components to a GameObject, ensure that the NetworkObject component is ordered before any NetworkBehaviour components. + +The order in which NetworkBehaviour components are presented in the **Inspector** view is the order in which each associated `NetworkBehaviour.OnNetworkSpawn` method is invoked. Any properties that are set during `NetworkBehaviour.OnNetworkSpawn` are set in the order that each NetworkBehaviour's `OnNetworkSpawned` method is invoked. + +#### Avoiding execution order issues + +You can avoid execution order issues in any NetworkBehaviour component scripts that have dependencies on other NetworkBehaviour components associated with the same NetworkObject by placing those scripts in an overridden `NetworkBehaviour.OnNetworkPostSpawn` method. The `NetworkBehaviour.OnNetworkPostSpawn` method is invoked on each NetworkBehaviour component after all NetworkBehaviour components associated with the same NetworkObject component have had their `NetworkBehaviour.OnNetworkSpawn` methods invoked (but they will still be invoked in the same execution order defined by their relative position to the NetworkObject component when viewed within the Unity Editor **Inspector** view). + +## Ownership + +Either the server (default) or any connected and approved client owns each NetworkObject. By default, Netcode for GameObjects is [server-authoritative](../terms-concepts/client-server.md), which means only the server can spawn and despawn NetworkObjects, but you can also build [distributed authority](../terms-concepts/distributed-authority.md) games where clients have the authority to spawn and despawn NetworkObjects as well. + +If you're creating a client-server game and you want a client to control more than one NetworkObject, use the following ownership methods. + +The default `NetworkObject.Spawn` method assumes server-side ownership: + +```csharp +GetComponent().Spawn(); +``` + +To spawn NetworkObjects with ownership use the following: + +```csharp +GetComponent().SpawnWithOwnership(clientId); +``` + +To change ownership, use the `ChangeOwnership` method: + +```csharp +GetComponent().ChangeOwnership(clientId); +``` + +To give ownership back to the server use the `RemoveOwnership` method: + +```csharp +GetComponent().RemoveOwnership(); +``` + +To see if the local client is the owner of a NetworkObject, you can check the [`NetworkBehaviour.IsOwner`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.IsOwner.html) property. + +To see if the server owns the NetworkObject, you can check the [`NetworkBehaviour.IsOwnedByServer`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.IsOwnedByServer.html) property. + +:::note + +When you want to despawn and destroy the owner but you don't want to destroy a specific NetworkObject along with the owner, you can set the `NetworkObject.DontDestroyWithOwner` property to `true` which ensures that the owned NetworkObject isn't destroyed with the owner. + +::: + +## Network prefabs + +Network prefabs are registered to a `NetworkPrefabsList` object (a type of `ScriptableObject`). By default, a default prefabs list containing every network prefab in your project. + +However, when you want to limit which prefabs are available (for example, to reduce memory usage), you can disable this behavior in **Project Settings** > **Netcode For GameObjects** > **Project Settings**. You can also manually create a `NetworkPrefabsList` by right-clicking in the assets view and selecting **Create** > **Netcode** > **Network Prefabs List** and adding your prefabs to it. That prefab list can then be assigned to a `NetworkManager` to allow that `NetworkManager` to create those prefabs. + +:::warning + +You can only have one NetworkObject at the root of a prefab. Don't create prefabs with nested `NetworkObjects`. + +::: + +## Spawning with (or without) observers + +![image](/img/SpawnWithObservers.png) + +The `NetworkObject.SpawnWithObservers` property (default is true) enables you to spawn a NetworkObject with no initial observers. This is the recommended alternative to using `NetworkObject.CheckObjectVisibility` when you just want it to be applied globally to all clients (only when spawning an instance of the NetworkObject in question). If you want more precise per-client control then `NetworkObject.CheckObjectVisibility` is recommended. `NetworkObject.SpawnWithObservers` is only applied upon the initial server-side spawning and once spawned it has no impact on object visibility. + +## Transform synchronization + +![image](/img/NetworkObject-TransformSynchronization.png) + +There are times when you want to use a NetworkObject for something that doesn't require the synchronization of its transform. You might have an [in-scene placed NetworkObject](./scenemanagement/inscene-placed-networkobjects.md) that's only used to manage game state and it doesn't make sense to incur the initial client synchronization cost of synchronizing its transform. To prevent a NetworkObject from initially synchronizing its transform when spawned, deselect the **Synchronize Transform** property. This property is enabled by default. + +:::caution +If you're planning to use a NetworkTransform, then you always want to make sure the **Synchronize Transform** property is enabled. +::: + +## Active scene synchronization + +![image](/img/ActiveSceneMigration.png) + +When a GameObject is instantiated, it gets instantiated in the current active scene. However, sometimes you might find that you want to change the currently active scene and would like specific NetworkObject instances to automatically migrate to the newly assigned active scene. While you could keep a list or table of the NetworkObject instances and write the code/logic to migrate them into a newly assigned active scene, this can be time consuming and become complicated depending on project size and complexity. The alternate and recommended way to handle this is by enabling the **Active Scene Synchronization** property of each NetworkObject you want to automatically migrate into any newly assigned scene. This property defaults to disabled. + +Refer to the [NetworkSceneManager active scene synchronization](../scenemanagement/using-networkscenemanager#active-scene-synchronization) page for more details. + +## Scene migration synchronization + +![image](/img/SceneMigrationSynchronization.png) + +Similar to [`NetworkObject.ActiveSceneSynchronization`](#active-scene-synchronization), [`NetworkObject.SceneMigrationSynchronization`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_SceneMigrationSynchronization) automatically synchronizes client-side NetworkObject instances that are migrated to a scene via [`SceneManager.MoveGameObjectToScene`](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.MoveGameObjectToScene.html) on the host or server side. This can be useful if you have a specific scene you wish to migrate NetworkObject instances to that is not the currently active scene. + +`NetworkObject.ActiveSceneSynchronization` can be used with `NetworkObject.SceneMigrationSynchronization` as long as you take into consideration that if you migrate a NetworkObject into a non-active scene via `SceneManager.MoveGameObjectToScene` and later change the active scene, then the NetworkObject instance will be automatically migrated to the newly set active scene. + +:::warning +Scene migration synchronization is enabled by default. For NetworkObjects that don't require it, such as those that generate static environmental objects like trees, it's recommended to disable scene migration synchronization to avoid additional processing overheads. +::: + +## Additional resources + +- [PlayerObjects and player prefabs](playerobjects.md) +- [NetworkBehaviour](networkbehaviour.md) +- [NetworkVariable](networkvariable.md) diff --git a/versioned_docs/version-2.0.0/basics/networkvariable.md b/versioned_docs/version-2.0.0/basics/networkvariable.md new file mode 100644 index 000000000..61a73dc7d --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/networkvariable.md @@ -0,0 +1,632 @@ +--- +id: networkvariable +title: NetworkVariables +--- + +`NetworkVariable`s are a way of synchronizing properties between servers and clients in a persistent manner, unlike [RPCs](../advanced-topics/message-system/rpc.md) and [custom messages](../advanced-topics/message-system/custom-messages.md), which are one-off, point-in-time communications that aren't shared with any clients not connected at the time of sending. `NetworkVariable`s are session-mode agnostic and can be used with either a [client-server](../terms-concepts/client-server.md) or [distributed authority](../terms-concepts/distributed-authority.md) network topology. + +`NetworkVariable` is a wrapper of the stored value of type `T`, so you need to use the `NetworkVariable.Value` property to access the actual value being synchronized. A `NetworkVariable` is synchronized with: + +* New clients joining the game (also referred to as late-joining clients) + * When the associated NetworkObject of a `NetworkBehaviour` with `NetworkVariable` properties is spawned, any `NetworkVariable`'s current state (`Value`) is automatically synchronized on the client side. +* Connected clients + * When a `NetworkVariable` value changes, all connected clients that subscribed to the `NetworkVariable.OnValueChanged` event (prior to the value being changed) are notified of the change. Two parameters are passed to any `NetworkVariable.OnValueChanged` subscribed callback method: + - First parameter (Previous): The previous value before the value was changed + - Second parameter (Current): The newly changed `NetworkVariable.Value` + +You can use `NetworkVariable` [permissions](#permissions) to control read and write access to `NetworkVariable`s. You can also create [custom `NetworkVariable`s](custom-networkvariables.md). + +## Setting up NetworkVariables + +### Defining a NetworkVariable + +- A `NetworkVariable` property must be defined within a `NetworkBehaviour`-derived class attached to a `GameObject`. + - The `GameObject` or a parent `GameObject` must also have a NetworkObject component attached to it. +- A `NetworkVariable`'s value can only be set: + - When initializing the property (either when it's declared or within the Awake method). + - While the associated NetworkObject is spawned (upon being spawned or any time while it's still spawned). + +### Initializing a NetworkVariable + +When a client first connects, it's synchronized with the current value of the `NetworkVariable`. Typically, clients should register for `NetworkVariable.OnValueChanged` within the `OnNetworkSpawn` method. A `NetworkBehaviour`'s `Start` and `OnNetworkSpawn` methods are invoked based on the type of NetworkObject the `NetworkBehaviour` is associated with: + +- In-scene placed: Since the instantiation occurs via the scene loading mechanism(s), the `Start` method is invoked before `OnNetworkSpawn`. +- Dynamically spawned: Since `OnNetworkSpawn` is invoked immediately (that is, within the same relative call-stack) after instantiation, the `Start` method is invoked after `OnNetworkSpawn`. + +Typically, these methods are invoked at least one frame after the NetworkObject and associated `NetworkBehaviour` components are instantiated. The table below lists the event order for dynamically spawned and in-scene placed objects respectively. + +Dynamically spawned | In-scene placed +------------------- | --------------- +Awake | Awake +OnNetworkSpawn | Start +Start | OnNetworkSpawn + +You should only set the value of a `NetworkVariable` when first initializing it or if it's spawned. It isn't recommended to set a `NetworkVariable` when the associated NetworkObject isn't spawned. + +:::tip +If you need to initialize other components or objects based on a `NetworkVariable`'s initial synchronized state, then you can use a common method that's invoked on the client side within the `NetworkVariable.OnValueChanged` callback (if assigned) and `NetworkBehaviour.OnNetworkSpawn` method. +::: + +## Supported types + +`NetworkVariable` supports the following types: + +* C# unmanaged [primitive types](../advanced-topics/serialization/cprimitives.md) (which are serialized by direct `memcpy` into/out of the buffer): `bool`, `byte`, `sbyte`, `char`, `decimal`, `double`, `float`, `int`, `uint`, `long`, `ulong`, `short`, and `ushort`. +* Unity unmanaged [built-in types](../advanced-topics/serialization/unity-primitives.md) (which are serialized by direct `memcpy` into/out of the buffer): `Vector2`, `Vector3`, `Vector2Int`, `Vector3Int`, `Vector4`, `Quaternion`, `Color`, `Color32`, `Ray`, `Ray2D`. +* Any [`enum`](../advanced-topics/serialization/enum-types.md) types (which are serialized by direct `memcpy` into/out of the buffer). +* Any type (managed or unmanaged) that implements [`INetworkSerializable`](../advanced-topics/serialization/inetworkserializable.md) (which are serialized by calling their `NetworkSerialize` method). **On the reading side, these values are deserialized in-place, meaning the existing instance is reused and any non-serialized values are left in their current state.** +* Any unmanaged struct type that implements [`INetworkSerializeByMemcpy`](../advanced-topics/serialization/inetworkserializebymemcpy.md) (which are serialized by direct `memcpy `of the entire struct into/out of the buffer). +* Unity [fixed string](../advanced-topics/serialization/fixedstrings.md) types: `FixedString32Bytes`, `FixedString64Bytes`, `FixedString128Bytes`, `FixedString512Bytes`, and `FixedString4096Bytes` (which are serialized intelligently, only sending the used part across the network and adjusting the length of the string on the other side to fit the received data). + +For any types that don't fit within this list, including managed types and unmanaged types with pointers: you can provide delegates informing the serialization system of how to serialize and deserialize your values. For more information, refer to [Custom Serialization](../advanced-topics/custom-serialization.md). A limitation of custom serialization is that, unlike `INetworkSerializable` types, types using custom serialization aren't able to be read in-place, so managed types will, by necessity, incur a garbage collection allocation (which can cause performance issues) on every update. + +### Managing garbage collection + +Although `NetworkVariable` supports both managed and [unmanaged](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters#unmanaged-constraint) types, managed types come with additional overhead. + +Netcode for GameObjects has been designed to minimize garbage collection allocations for managed `INetworkSerializable` types (for example, a new value is only allocated if the value changes from `null` to non-`null`). However, the ability of a type to be `null` adds additional overhead both in logic (checking for nulls before serializing) and bandwidth (every serialization carries an additional byte indicating whether the value is `null`). + +Additionally, any type that has a managed type is itself a managed type - so a struct that has `int[]` is a managed type because `int[]` is a managed type. + +Finally, while managed `INetworkSerializable` types are serialized in-place (and thus don't incur allocations for simple value updates), C# arrays and managed types serialized through custom serialization are **not** serialized in-place, and will incur an allocation on every update. + +### Using collections with `NetworkVariable`s + +You can use `NetworkVariable`s with both managed and unmanaged collections, but you need to call `NetworkVariable.CheckDirtyState()` after making changes to a collection (or items in a collection) for those changes to be detected. Then the [`OnValueChanged`](#onvaluechanged-example) event will trigger, if subscribed locally, and by the end of the frame the rest of the clients and server will be synchronized with the detected change(s). + +`NetworkVariable.CheckDirtyState()` checks every item in a collection, including recursively nested collections, which can have a significant impact on performance if collections are large. If you're making multiple changes to a collection, you only need to call `NetworkVariable.CheckDirtyState()` once after all changes are complete, rather than calling it after each change. + +## Synchronization and notification example + +The following client-server example shows how the initial `NetworkVariable` synchronization has already occurred by the time `OnNetworkSpawn` is invoked. It also shows how subscribing to `NetworkVariable.OnValueChanged` within `OnNetworkSpawn` provides notifications for any changes to `m_SomeValue.Value` that occur. + +1. The server initializes the `NetworkVariable` when the associated NetworkObject is spawned. +1. The client confirms that the `NetworkVariable` is synchronized to the initial value set by the server and assigns a callback method to `NetworkVariable.OnValueChanged`. +1. Once spawned, the client is notified of any changes made to the `NetworkVariable`. + +:::important +This example only tests the initial client synchronization of the value and when the value changes. If a second client joins, it will throw a warning about the `NetworkVariable.Value` not being the initial value. This example is intended for use with a single server or host and a single client. +::: + + ```csharp +public class TestNetworkVariableSynchronization : NetworkBehaviour +{ + private NetworkVariable m_SomeValue = new NetworkVariable(); + private const int k_InitialValue = 1111; + + public override void OnNetworkSpawn() + { + if (IsServer) + { + m_SomeValue.Value = k_InitialValue; + NetworkManager.OnClientConnectedCallback += NetworkManager_OnClientConnectedCallback; + } + else + { + if (m_SomeValue.Value != k_InitialValue) + { + Debug.LogWarning($"NetworkVariable was {m_SomeValue.Value} upon being spawned" + + $" when it should have been {k_InitialValue}"); + } + else + { + Debug.Log($"NetworkVariable is {m_SomeValue.Value} when spawned."); + } + m_SomeValue.OnValueChanged += OnSomeValueChanged; + } + } + + private void NetworkManager_OnClientConnectedCallback(ulong obj) + { + StartCoroutine(StartChangingNetworkVariable()); + } + + private void OnSomeValueChanged(int previous, int current) + { + Debug.Log($"Detected NetworkVariable Change: Previous: {previous} | Current: {current}"); + } + + private IEnumerator StartChangingNetworkVariable() + { + var count = 0; + var updateFrequency = new WaitForSeconds(0.5f); + while (count < 4) + { + m_SomeValue.Value += m_SomeValue.Value; + yield return updateFrequency; + } + NetworkManager.OnClientConnectedCallback -= NetworkManager_OnClientConnectedCallback; + } +} + ``` + +If you attach the above script to an in-scene placed NetworkObject, make a standalone build, run the standalone build as a host, and then connect to that host by entering Play Mode in the Editor, you can see (in the console output): +- The client side `NetworkVariable` value is the same as the server when `NetworkBehaviour.OnNetworkSpawn` is invoked. +- The client detects any changes made to the `NetworkVariable` after the in-scene placed NetworkObject is spawned. + +This works the same way with dynamically spawned NetworkObjects. + +## OnValueChanged example + +The [synchronization and notification example](#synchronization-and-notification-example) highlights the differences between synchronizing a `NetworkVariable` with newly-joining clients and notifying connected clients when a `NetworkVariable` changes, but it doesn't provide any concrete example usage. + +The `OnValueChanged` example shows a simple server-authoritative `NetworkVariable` being used to track the state of a door (that is, open or closed) using a non-ownership-based server RPC. With `RequireOwnership = false` any client can notify the server that it's performing an action on the door. Each time the door is used by a client, the `Door.ToggleServerRpc` is invoked and the server-side toggles the state of the door. When the `Door.State.Value` changes, all connected clients are synchronized to the (new) current `Value` and the `OnStateChanged` method is invoked locally on each client. + +```csharp +public class Door : NetworkBehaviour +{ + public NetworkVariable State = new NetworkVariable(); + + public override void OnNetworkSpawn() + { + State.OnValueChanged += OnStateChanged; + } + + public override void OnNetworkDespawn() + { + State.OnValueChanged -= OnStateChanged; + } + + public void OnStateChanged(bool previous, bool current) + { + // note: `State.Value` will be equal to `current` here + if (State.Value) + { + // door is open: + // - rotate door transform + // - play animations, sound etc. + } + else + { + // door is closed: + // - rotate door transform + // - play animations, sound etc. + } + } + + [Rpc(SendTo.Server)] + public void ToggleServerRpc() + { + // this will cause a replication over the network + // and ultimately invoke `OnValueChanged` on receivers + State.Value = !State.Value; + } +} +``` + +## Permissions + +The `NetworkVariable` constructor can take up to three parameters: + +```csharp +public NetworkVariable(T value = default, +NetworkVariableReadPermission readPerm = NetworkVariableReadPermission.Everyone, +NetworkVariableWritePermission writePerm = NetworkVariableWritePermission.Server); +``` + +By default, in a [client-server](../terms-concepts/client-server.md) context, the default permissions are: + +- **Server**: Has read and write permissions +- **Clients:** Have read-only permissions. + +The two types of permissions are defined within [NetworkVariablePermissions.cs](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/release/1.0.0/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariablePermission.cs): + +```csharp + /// + /// The permission types for reading a var + /// + public enum NetworkVariableReadPermission + { + /// + /// Everyone can read + /// + Everyone, + /// + /// Only the owner and the server can read + /// + Owner, + } + + /// + /// The permission types for writing a var + /// + public enum NetworkVariableWritePermission + { + /// + /// Only the server can write + /// + Server, + /// + /// Only the owner can write + /// + Owner + } +``` +:::important +Owner writer permissions are owner-only. +::: + +### Read permissions + +There are two options for reading a `NetworkVariable.Value`: + +- **Everyone (default)**: both owners and non-owners of the NetworkObject can read the value. + - This is useful for global states that all clients should be aware of, such as player scores, health, or any other state that all clients should know about. + - We provided an example of maintaining a door's open or closed state using the everyone permission. +- **Owner**: only the owner of the NetworkObject and the server can read the value. + - This is useful if your `NetworkVariable` represents something specific to the client's player that only the server and client should know about, such as a player's inventory or ammo count. + +### Write permissions + +There are two options for writing a `NetworkVariable.Value`: + +- **Server**: the server is the only one that can write the value. This is the default for [client-server contexts](../terms-concepts/client-server.md). + - This is useful for server-side specific states that all clients should be aware of but can't change, such as an NPC's status or some global world environment state (that is, is it night or day time). +- **Owner**: only the owner of the NetworkObject can write to the value. This is the default for [distributed authority contexts](../terms-concepts/distributed-authority.md). + - This is useful if your `NetworkVariable` represents something specific to the client's player that only the owning client should be able to set, such as a player's skin or other cosmetics. + +### Permissions example + +The following example has a few different permissions configurations that you might use in a game while keeping track of a player's state. It also provides details of each `NetworkVariable`'s purpose and the reasoning behind each `NetworkVariable`'s read and write permission settings. + +```csharp +public class PlayerState : NetworkBehaviour +{ + private const float k_DefaultHealth = 100.0f; + /// + /// Default Permissions: Everyone can read, server can only write + /// Player health is typically something determined (updated/written to) on the server + /// side, but a value everyone should be synchronized with (that is, read permissions). + /// + public NetworkVariable Health = new NetworkVariable(k_DefaultHealth); + + /// + /// Owner Read Permissions: Owner or server can read + /// Owner Write Permissions: Only the Owner can write + /// A player's ammo count is something that you might want, for convenience sake, the + /// client-side to update locally. This might be because you are trying to reduce + /// bandwidth consumption for the server and all non-owners/ players or you might be + /// trying to incorporate a more client-side "hack resistant" design where non-owners + /// are never synchronized with this value. + /// + public NetworkVariable AmmoCount = new NetworkVariable(default, + NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Owner); + + /// + /// Owner Write & Everyone Read Permissions: + /// A player's model's skin selection index. You might have the option to allow players + /// to select different skin materials as a way to further encourage a player's personal + /// association with their player character. It would make sense to make the permissions + /// setting of the NetworkVariable such that the client can change the value, but everyone + /// will be notified when it changes to visually reflect the new skin selection. + /// + public NetworkVariable SkinSelectionIndex = new NetworkVariable(default, + NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + + /// + /// Owner Read & Server Write Permissions: + /// You might incorporate some form of reconnection logic that stores a player's state on + /// the server side and can be used by the client to reconnect a player if disconnected + /// unexpectedly. In order for the client to let the server know it's the "same client" + /// you might have implemented a keyed array (that is, Hashtable, Dictionary, etc, ) to track + /// each connected client. The key value for each connected client would only be written to + /// the each client's PlayerState.ReconnectionKey. Under this scenario, you only want the + /// server to have write permissions and the owner (client) to be synchronized with this + /// value (via owner only read permissions). + /// + public NetworkVariable ReconnectionKey = new NetworkVariable(default, + NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Server); +} +``` + +:::important +You might be wondering about our earlier door example and why we chose to use a server RPC for clients to notify the server that the door's open/closed state has changed. In that scenario, the owner of the door will most likely be owned by the server, just like non-player characters will almost always be owned by the server. In a server-owned scenario, using an RPC to handle updating a `NetworkVariable` is the proper choice above permissions for most cases. +::: + +## Complex types + +Almost all of the examples on this page have been focused around numeric [value types](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-types). Netcode for GameObjects also supports complex types and can support both unmanaged types *and* managed types (although avoiding managed types where possible will improve your game's performance). + +### Synchronizing complex types example + +This example extends the previous `PlayerState` class to include some complex types to handle a weapon boosting game play mechanic. The example uses two complex values types: + +- `WeaponBooster`: A power-up weapon booster that can only be applied by the client. This is a simple example of a complex type. +- `AreaWeaponBooster`: A second kind of weapon booster power-up that players can deploy at a specific location, and any team members within the radius of the `AreaWeaponBooster` have the weapon booster applied. This is an example of a nested complex type. + +`WeaponBooster` only needs one `NetworkVariable` to handle synchronizing everyone with any currently active player-local `WeaponBooster`. However, the `AreaWeaponBooster` must consider what happens if there are eight team members that can, at any given moment, deploy an `AreaWeaponBooster`. It requires, at a minimum, a list of all deployed and currently active `AreaWeaponBooster`s. This task uses a `NetworkList` instead of a `NetworkVariable`. + +Below are the `PlayerState` additions along with the `WeaponBooster` structure (complex type): + +```csharp +public class PlayerState : NetworkBehaviour +{ + // ^^^^^^^ including all code from previous example ^^^^^^^ + + // The weapon booster currently applied to a player + private NetworkVariable PlayerWeaponBooster = new NetworkVariable(); + + /// + /// A list of team members active "area weapon boosters" that can be applied if the local player + /// is within their range. + /// + private NetworkList TeamAreaWeaponBoosters; + + void Awake() + { + //NetworkList can't be initialized at declaration time like NetworkVariable. It must be initialized in Awake instead. + //If you do initialize at declaration, you will run into memory leak errors. + TeamAreaWeaponBoosters = new NetworkList(); + } + + void Start() + { + /*At this point, the object hasn't been network spawned yet, so you're not allowed to edit network variables! */ + //list.Add(new AreaWeaponBooster()); + } + + void Update() + { + //This is just an example that shows how to add an element to the list after its initialization: + if (!IsServer) { return; } //remember: only the server can edit the list + if (Input.GetKeyUp(KeyCode.UpArrow)) + { + TeamAreaWeaponBoosters.Add(new AreaWeaponBooster())); + } + } + + public override void OnNetworkSpawn() + { + base.OnNetworkSpawn(); + if (IsClient) + { + TeamAreaWeaponBoosters.OnListChanged += OnClientListChanged; + } + if (IsServer) + { + TeamAreaWeaponBoosters.OnListChanged += OnServerListChanged; + TeamAreaWeaponBoosters.Add(new AreaWeaponBooster()); //if you want to initialize the list with some default values, this is a good time to do so. + } + } + + void OnServerListChanged(NetworkListEvent changeEvent) + { + Debug.Log($"[S] The list changed and now has {TeamAreaWeaponBoosters.Count} elements"); + } + + void OnClientListChanged(NetworkListEvent changeEvent) + { + Debug.Log($"[C] The list changed and now has {TeamAreaWeaponBoosters.Count} elements"); + } +} + +/// +/// Example: Complex Type +/// This is an example of how one might handle tracking any weapon booster currently applied +/// to a player. +/// +public struct WeaponBooster : INetworkSerializable, System.IEquatable +{ + public float PowerAmplifier; + public float Duration; + + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + if (serializer.IsReader) + { + var reader = serializer.GetFastBufferReader(); + reader.ReadValueSafe(out PowerAmplifier); + reader.ReadValueSafe(out Duration); + } + else + { + var writer = serializer.GetFastBufferWriter(); + writer.WriteValueSafe(PowerAmplifier); + writer.WriteValueSafe(Duration); + } + } + + public bool Equals(WeaponBooster other) + { + return PowerAmplifier == other.PowerAmplifier && Duration == other.Duration; + } +} +``` + +The example code above shows how a complex type implements `INetworkSerializable`. The second part of the example below shows that the `AreaWeaponBooster` includes a `WeaponBooster` property that would (for example) be applied to team members that are within the `AreaWeaponBoosters` radius: + +```csharp +/// +/// Example: Nesting Complex Types +/// This example uses the previous WeaponBooster complex type to be a "container" for +/// the "weapon booster" information of an AreaWeaponBooster. It then provides additional +/// information that would allow clients to easily determine, based on location and radius, +/// if it should add (for example) a special power up HUD symbol or special-FX to the local +/// player. +/// +public struct AreaWeaponBooster : INetworkSerializable, System.IEquatable +{ + public WeaponBooster ApplyWeaponBooster; // the nested complex type + public float Radius; + public Vector3 Location; + public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter + { + if (serializer.IsReader) + { + // The complex type handles its own de-serialization + serializer.SerializeValue(ref ApplyWeaponBooster); + // Now de-serialize the non-complex type properties + var reader = serializer.GetFastBufferReader(); + reader.ReadValueSafe(out Radius); + reader.ReadValueSafe(out Location); + } + else + { + // The complex type handles its own serialization + serializer.SerializeValue(ref ApplyWeaponBooster); + // Now serialize the non-complex type properties + var writer = serializer.GetFastBufferWriter(); + writer.WriteValueSafe(Radius); + writer.WriteValueSafe(Location); + } + } + + public bool Equals(AreaWeaponBooster other) + { + return other.ApplyWeaponBooster.Equals(ApplyWeaponBooster) && Radius == other.Radius && Location == other.Location; + } +} +``` + +Looking closely at the read and write segments of code within `AreaWeaponBooster.NetworkSerialize`, the nested complex type property `ApplyWeaponBooster` handles its own serialization and de-serialization. The `ApplyWeaponBooster`'s implemented `NetworkSerialize` method serializes and deserialized any `AreaWeaponBooster` type property. This design approach can help reduce code replication while providing a more modular foundation to build even more complex, nested types. + +## Custom NetworkVariable Implementations + +:::warning Disclaimer +The `NetworkVariable` and `NetworkList` classes were created as `NetworkVariableBase` class implementation examples. While the `NetworkVariable` class is considered production ready, you might run into scenarios where you have a more advanced implementation in mind. In this case, we encourage you to create your own custom implementation. +::: +In order to create your own `NetworkVariableBase` derived container, you should: + +- Create a class deriving from `NetworkVariableBase`. + +- Assure the the following methods are overridden: + - `void WriteField(FastBufferWriter writer)` + - `void ReadField(FastBufferReader reader)` + - `void WriteDelta(FastBufferWriter writer)` + - `void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)` +- Depdending upon your custom `NetworkVariableBase` container, you might look at `NetworkVariable` or `NetworkList` to see how those two examples were implemented. + + + +#### NetworkVariableSerialization<T> + +The way you read and write network variables changes depending on the type you use. + +* Known, non-generic types: Use `FastBufferReader.ReadValue` to read from and `FastBufferWriter.WriteValue` to write to the network variable value. +* Integer types: This type gives you the option to use `BytePacker` and `ByteUnpacker` to compress the network variable value. This process can save bandwidth but adds CPU processing time. +* Generic types: Use serializers that Unity generates based on types discovered during a compile-time code generation process. This means you need to tell Unity's code generation algorithm which types to generate serializers for. To tell Unity which types to serialize, use the following methods: + * Use `GenerateSerializationForTypeAttribute` to serialize hard-coded types. + * Use `GenerateSerializationForGenericParameterAttribute` to serialize generic types. + To learn how to use these methods, refer to [Network variable serialization](#network-variable-serialization). + +##### Tell Unity to serialize a hard-coded type +The following code example uses `GenerateSerializationForTypeAttribute` to generate serialization for a specific hard-coded type: +```csharp +[GenerateSerializationForType(typeof(Foo))] +public class MyNetworkVariableTypeUsingFoo : NetworkVariableBase {} +``` + +You can call a type that you know the name of with the `FastBufferReader` or `FastBufferWriter` methods. These methods don't work for Generic types because the name of the type is unknown. +##### Tell Unity to serialize a generic type +The following code example uses `GenerateSerializationForGenericParameterAttribute` to generate serialization for a specific Generic parameter in your `NetworkVariable` type: +```csharp +[GenerateSerializationForGenericParameter(0)] +public class MyNetworkVariableType : NetworkVariableBase {} +``` + +This attribute accepts an integer that indicates which parameter in the type to generate serialization for. This value is 0-indexed, which means that the first type is 0, the second type is 1, and so on. +The following code example places the attribute more than once on one class to generate serialization for multiple types, in this case,`TFirstType` and `TSecondType: + +```csharp +[GenerateSerializationForGenericParameter(0)] +[GenerateSerializationForGenericParameter(1)] +public class MyNetworkVariableType : NetworkVariableBase {} +``` + + +The `GenerateSerializationForGenericParameterAttribute` and `GenerateSerializationForTypeAttribute` attributes make Unity's code generation create the following methods: + +```csharp +NetworkVariableSerialization.Write(FastBufferWriter writer, ref T value); +NetworkVariableSerialization.Read(FastBufferWriter writer, ref T value); +NetworkVariableSerialization.Duplicate(in T value, ref T duplicatedValue); +NetworkVariableSerialization.AreEqual(in T a, in T b); +``` + +For dynamically allocated types with a value that isn't `null` (for example, managed types and collections like NativeArray and NativeList) call `Read` to read the value in the existing object and write data into it directy (in-place). This avoids more allocations. + +You can use `AreEqual` to determine if a value is different from the value that `Duplicate` cached. This avoids sending the same value multiple times. You can also use the previous value that `Duplicate` cached to calculate deltas to use in `ReadDelta` and `WriteDelta`. + +The type you use must be serializable according to the "Supported Types" list above. Each type needs its own serializer instantiated, so this step tells the codegen which types to create serializers for. + +:::note Unity's code generator assumes that all `NetworkVariable` types exist as fields inside `NetworkBehaviour` types. This means that Unity only inspects fields inside `NetworkBehaviour` types to identify the types to create serializers for. + + ### Custom NetworkVariable Example + +This example shows a custom `NetworkVariable` type to help you understand how you might implement such a type. In the current version of Netcode for GameObjects, this example is possible without using a custom `NetworkVariable` type; however, for more complex situations that aren't natively supported, this basic example should help inform you of how to approach the implementation: + +Looking at the read and write segments of code within `AreaWeaponBooster.NetworkSerialize`, the nested complex type property `ApplyWeaponBooster` handles its own serialization and de-serialization. The `ApplyWeaponBooster`'s implemented `NetworkSerialize` method serializes and deserializes any `AreaWeaponBooster` type property. This design approach can help reduce code replication while providing a more modular foundation to build even more complex, nested types. + +## Strings + +While `NetworkVariable` does support managed `INetworkSerializable` types, strings aren't in the list of supported types. This is because strings in C# are immutable types, preventing them from being deserialized in-place, so every update to a `NetworkVariable` would cause a Garbage Collected allocation to create the new string, which may lead to performance problems. + +While it's technically possible to support strings using custom serialization through `UserNetworkVariableSerialization`, it isn't recommended to do so due to the performance implications that come with it. Instead, we recommend using one of the `Unity.Collections.FixedString` value types. In the below example, we used a `FixedString128Bytes` as the `NetworkVariable` value type. On the server side, it changes the string value each time you press the space bar on the server or host instance. Joining clients will be synchronized with the current value applied on the server side, and each time you hit the space bar on the server side, the client synchronizes with the changed string. + +:::note +`NetworkVariable` won't serialize the entire 128 bytes each time the `Value` is changed. Only the number of bytes that are actually used to store the string value will be sent, no matter which size of `FixedString` you use. +::: + +```csharp +public class TestFixedString : NetworkBehaviour +{ + /// Create your 128 byte fixed string NetworkVariable + private NetworkVariable m_TextString = new NetworkVariable(); + + private string[] m_Messages ={ "This is the first message.", + "This is the second message (not like the first)", + "This is the third message (but not the last)", + "This is the fourth and last message (next will roll over to the first)" + }; + + private int m_MessageIndex = 0; + + public override void OnNetworkSpawn() + { + if (IsServer) + { + // Assin the current value based on the current message index value + m_TextString.Value = m_Messages[m_MessageIndex]; + } + else + { + // Subscribe to the OnValueChanged event + m_TextString.OnValueChanged += OnTextStringChanged; + // Log the current value of the text string when the client connected + Debug.Log($"Client-{NetworkManager.LocalClientId}'s TextString = {m_TextString.Value}"); + } + } + + public override void OnNetworkDespawn() + { + m_TextString.OnValueChanged -= OnTextStringChanged; + } + + private void OnTextStringChanged(FixedString128Bytes previous, FixedString128Bytes current) + { + // Just log a notification when m_TextString changes + Debug.Log($"Client-{NetworkManager.LocalClientId}'s TextString = {m_TextString.Value}"); + } + + private void LateUpdate() + { + if (!IsServer) + { + return; + } + + if (Input.GetKeyDown(KeyCode.Space)) + { + m_MessageIndex++; + m_MessageIndex %= m_Messages.Length; + m_TextString.Value = m_Messages[m_MessageIndex]; + Debug.Log($"Server-{NetworkManager.LocalClientId}'s TextString = {m_TextString.Value}"); + } + } +} +``` + + +:::note +The above example uses a pre-set list of strings to cycle through for example purposes only. If you have a predefined set of text strings as part of your actual design then you would not want to use a FixedString to handle synchronizing the changes to `m_TextString`. Instead, you would want to use a `uint` for the type `T` where the `uint` was the index of the string message to apply to `m_TextString`. +::: diff --git a/versioned_docs/version-2.0.0/basics/object-spawning.md b/versioned_docs/version-2.0.0/basics/object-spawning.md new file mode 100644 index 000000000..221fef2fc --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/object-spawning.md @@ -0,0 +1,347 @@ +--- +id: object-spawning +title: Object spawning +sidebar_label: Object spawning +--- + +In Unity, you typically create a new game object using the `Instantiate` function. Creating a game object with `Instantiate` will only create that object on the local machine. `Spawning` in Netcode for GameObjects (Netcode) means to instantiate and/or spawn the object that is synchronized between all clients by the server. + +## Network Prefabs + +A network Prefab is any unity Prefab asset that has one NetworkObject component attached to a `GameObject` within the prefab. More commonly, the NetworkObject component is attached to the root `GameObject` of the Prefab asset because this allows any child `GameObject` to have `NetworkBehaviour` components automatically assigned to the NetworkObject. The reason for this is that a NetworkObject component attached to a `GameObject` will be assigned (associated with) any `NetworkBehaviour` components on: + +- the same `GameObject` that the NetworkObject component is attached to +- any child or children of the `GameObject` that the NetworkObject is attached to. + +:::note +A caveat of the above two rules is when one of the children `GameObject`s also has a NetworkObject component assigned to it (a.k.a. "Nested NetworkObjects"). Because nested NetworkObject components aren't permited in network prefabs, Netcode for GameObjects will notify you in the editor if you are trying to add more than one NetworkObject to a Prefab and won't allow you to do this. +::: + +When a `NetworkBehaviour` is assigned to a NetworkObject, the `NetworkObject.NetworkObjectId` is used to help determine which `NetworkBehaviour` component instance will receive an update to a `NetworkVariable` or where to invoke an RPC. A NetworkObject component can have one or more `NetworkBehaviour` components assigned to it. + +### Registering a Network Prefab + +You must register a Network Prefab instance with a `NetworkManager` using a `NetworkedPrefabsList` scriptable object. +There are four steps to registering a network Prefab with a `NetworkManager`: + +1. Create a Network Prefab by creating a Prefab with a NetworkObject component attached to the root `GameObject`. +2. Create a scriptable object called `NetworkedPrefabsList` by right-clicking the project window, then: `Create/Netcode/NetworkedPrefabsList`. +3. Add your Network Prefab to the `NetworkPrefabsList`. +4. Add the `NetworkPrefabsList` to the Network Prefabs Lists that's associated with a `NetworkManager`. + +### Spawning a Network Prefab (Overview) + +Netcode uses a server authoritative networking model so spawning netcode objects can only be done on a server or host. To spawn a network prefab, you must first create an instance of the network Prefab and then invoke the spawn method on the NetworkObject component of the instance you created. +_In most cases, you will want to keep the NetworkObject component attached to the root `GameObject` of the network prefab._ + +By default a newly spawned network Prefab instance is owned by the server unless otherwise specified. + +See [Ownership](networkobject.md#ownership) for more information. + +The following is a basic example of how to spawn a network Prefab instance (with the default server ownership): + +```csharp +var instance = Instantiate(myPrefab); +var instanceNetworkObject = instance.GetComponent(); +instanceNetworkObject.Spawn(); +``` + +The `NetworkObject.Spawn` method takes 1 optional parameter that defaults to `true`: +```csharp +public void Spawn(bool destroyWithScene = true); +``` + +When you set the destroyWithScene property to `false` it will be treated the same as when you set [Object.DontDestroyOnLoad](https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html). Typically, you use this if you are loading a scene using [LoadSceneMode.Single](https://docs.unity3d.com/ScriptReference/SceneManagement.LoadSceneMode.html) parameter. + +[Learn more about Netcode Scene Management here](scenemanagement/scene-management-overview.md) + +:::caution You might find it useful to add a `GameObject` property in a `NetworkBehaviour`-derived component to use when assigning a network prefab instance for dynamically spawning. You need to make sure to instantiate a new instance **prior** to spawning. If you attempt to just spawn the actual network prefab instance it can result in unexpected results. +::: + +### Taking Prefab Overrides Into Consideration +Sometimes, you might want to make a simpler prefab instance to be spawned on server version the override for clients. You should take this into consideration when dynamically spawning a network prefab. If you're running as a host, you want the override to spawn since a host is both a server and a client. However, if you also want to have the ability to run as a dedicated server, you might want to spawn the source network prefab. + +There are two ways you can accomplish this, as explained below. + +#### Get The Network Prefab Override First +This option provides you with the overall view of getting the network prefab override, instantiating it, and then spawning it. + +```csharp +var instance = Instantiate(NetworkManager.GetNetworkPrefabOverride(myPrefab)); +var instanceNetworkObject = instance.GetComponent(); +instanceNetworkObject.Spawn(); +``` +In the above script, we get the prefab override using the `NetworkManager.GetNetworkPrefabOverride` method. Then we create an instance of the network prefab override, and finally we spawn the network prefab override instance's NetworkObject. + +#### Using InstantiateAndSpawn +The second option is to leverage the `NetworkSpawnManager.InstantiateAndSpawn` method that handles whether or not to spawn an override for you. The below script is written as if it's being invoked within a `NetworkBehaviour`. + +```csharp +var networkObject = NetworkManager.SpawnManager.InstantiateAndSpawn(myPrefab, ownerId); +``` +We pass in the overridden source network prefab we want to have instantiated and spawned, and then it returns the instantiated and spawned NetworkObject of the spawned object. The default behavior of `InstantiateAndSpawn` is to spawn the override if running as a host and the original source prefab if running as a server. + +`InstantiateAndSpawn` has several parameters to provide more control over this process: + +```csharp +InstantiateAndSpawn(NetworkObject networkPrefab, ulong ownerClientId = NetworkManager.ServerClientId, bool destroyWithScene = false, bool isPlayerObject = false, bool forceOverride = false, Vector3 position = default, Quaternion rotation = default) +``` + +Looking at the parameters, we can see it defaults to the server as the owner, ensures that the instantiated NetworkObject won't be destroyed if the scene is unloaded, is not spawned as a player, has a `forceOverride` parameter, and provides a way to set the position and rotation of the newly instantiated NetworkObject. + +The `forceOverride` parameter, when set to true, will use the override whether you're running as either server or host. + + +## Destroying / Despawning + +By default, a spawned network Prefab instance that is destroyed on the server/host will be automatically destroyed on all clients. + +When a client disconnects, all network Prefab instances created during the network session will be destroyed on the client-side by default. If you don't want that to happen, set the `DontDestroyWithOwner` field on NetworkObject to true before despawning. + +To do this at runtime: + +```csharp +m_SpawnedNetworkObject.DontDestroyWithOwner = true; +m_SpawnedNetworkObject.Despawn(); +``` + +To make this the default from the editor Inspector view: + +![image](/img/DontDestroyWithOwner.png) + +As an alternative way, you can make the `NetworkObject.DontDestroyWithOwner` property default to `true` by setting it on the NetworkObject itself like in the above screenshot. + +### Despawning + +Only a server can despawn a NetworkObject, and the default despawn behavior is to destroy the associated GameObject. to despawn but not destroy a NetworkObject, you should call `NetworkObject.Despawn` and pass false as the parameter. Clients will always be notified and will mirror the despawn behavior. If you despawn and destroy on the server then all clients will despawn and then destroy the `GameObject` that the `NetworkObjet` component is attached to. + +On the client side, you should never call `Object.Destroy` on any `GameObject` with a NetworkObject component attached to it (this isn't supported and will cause an exception to be thrown). If you want to use a more client authority model, have the client with ownership invoke an RPC to defer the despawning on server side. + +The only way to despawn NetworkObject for a specific client is to use `NetworkObject.NetworkHide`. +See: [Object Visibility](object-visibility.md) for more information on this. + +:::warning +If you have `GameObject` children, with `NetworkBehaviour` components attached, of a parent `GameObject`, with a NetworkObject component attached, you can't disable the `GameObject` children before spawning or despawning. Doing so, in v1.0.0, can cause unexpected results and it's recommended to make sure all children are enabled in the hierarchy before spawning or despawning. +::: + +## Dynamically Spawned Network Prefabs + +Netcode for GameObjects uses the term "dynamically spawned" to convey that the NetworkObject is being spawned via user specific code. Whereas a player or in-scene placed NetworkObject (with scene management enabled) is typically spawned by Netcode for GameObjects. There are several ways to spawn a network Prefab via code: + +### Dynamic Spawning (non-pooled): + +This type of dynamically spawned NetworkObject typically is a simple wrapper class that holds a reference to the Prefab asset. In the example below, the `NonPooledDynamicSpawner.PrefabToSpawn` property holds a reference to the network prefab: + +```csharp + public class NonPooledDynamicSpawner : NetworkBehaviour + { + public GameObject PrefabToSpawn; + public bool DestroyWithSpawner; + private GameObject m_PrefabInstance; + private NetworkObject m_SpawnedNetworkObject; + + public override void OnNetworkSpawn() + { + // Only the server spawns, clients will disable this component on their side + enabled = IsServer; + if (!enabled || PrefabToSpawn == null) + { + return; + } + // Instantiate the GameObject Instance + m_PrefabInstance = Instantiate(PrefabToSpawn); + + // Optional, this example applies the spawner's position and rotation to the new instance + m_PrefabInstance.transform.position = transform.position; + m_PrefabInstance.transform.rotation = transform.rotation; + + // Get the instance's NetworkObject and Spawn + m_SpawnedNetworkObject = m_PrefabInstance.GetComponent(); + m_SpawnedNetworkObject.Spawn(); + } + + public override void OnNetworkDespawn() + { + if (IsServer && DestroyWithSpawner && m_SpawnedNetworkObject != null && m_SpawnedNetworkObject.IsSpawned) + { + m_SpawnedNetworkObject.Despawn(); + } + base.OnNetworkDespawn(); + } + } +``` + +Consumable and/or items that can be picked up by a player or NPC(that is, a weapon, health, potion, etc.) would be some examples of when you might want to use non-pooled dynamically spawned `NetworkObjects`. + +:::caution +While the NonPooledDynamicSpawner example is one of the simplest ways to spawn a NetworkObject, there is a memory allocation cost associated with instantiating and destroying the GameObject and all attached components. This design pattern can sometimes be all you need for the netcode game asset you are working with, and other times you might want to respawn/re-use the object instance. When performance is a concern and you want to spawn more than just one NetworkObject during the lifetime of the spawner or want to repeatedly respawn a single NetworkObject, the less proccessor and memory allocation intensive technique is to use [pooled dynamic spawning](#pooled-dynamic-spawning). +::: + +:::note +Really, when we use the term "non-pooled" more often than not we are referring to the concept that a `GameObject` will be instantiated on both the server and the clients each time an instance is spawned. +::: + +### Pooled Dynamic Spawning + +Pooled dynamic spawning is when netcode objects (`GameObject` with one NetworkObject component) aren't destroyed on the server or the client when despawned. Instead, specific components are just disabled (or the `GameObject` itself) when a netcode object is despawned. A pooled dynamically spawned netcode object is typically instantiated during an already memory allocation heavy period of time (like when a scene is loaded or even at the start of your application before even establishing a network connection). Pooled dynamically spawned netcode objects are more commonly thought of as more than one netcode object that can be re-used without incurring the memory allocation and initialization costs. However, you might also run into scenarios where you need just one dynamically spawned netcode object to be treated like a pooled dynmically spawned netcode object. + +Fortunately, Netcode for GameObjects provides you with a way to be in control over the instatiation and destruction process for one or many netcode objects by via the `INetworkPrefabInstanceHandler` interface. Any `INetworkPrefabInstanceHandler`implementation should be registered with the `NetworkPrefabHandler`(for multiple netcode objects see [Object Pooling](../advanced-topics/object-pooling.md)) to accomplish this. + +The easiest way to not destroy a network Prefab instance is to have something, other than the instance itself, keeping a reference to the instance. This way you can simply set the root `GameObject` to be inactive when it's despawned while still being able to set it active when the same network Prefab type needs to be respawned. Below is one example of how you can accomplish this for a single netcode object instance: + +```csharp +public class SinglePooledDynamicSpawner : NetworkBehaviour, INetworkPrefabInstanceHandler +{ + public GameObject PrefabToSpawn; + public bool SpawnPrefabAutomatically; + + private GameObject m_PrefabInstance; + private NetworkObject m_SpawnedNetworkObject; + + + private void Start() + { + // Instantiate our instance when we start (for both clients and server) + m_PrefabInstance = Instantiate(PrefabToSpawn); + + // Get the NetworkObject component assigned to the Prefab instance + m_SpawnedNetworkObject = m_PrefabInstance.GetComponent(); + + // Set it to be inactive + m_PrefabInstance.SetActive(false); + } + + private IEnumerator DespawnTimer() + { + yield return new WaitForSeconds(2); + m_SpawnedNetworkObject.Despawn(); + StartCoroutine(SpawnTimer()); + yield break; + } + + private IEnumerator SpawnTimer() + { + yield return new WaitForSeconds(2); + SpawnInstance(); + yield break; + } + + /// + /// Invoked only on clients and not server or host + /// INetworkPrefabInstanceHandler.Instantiate implementation + /// Called when Netcode for GameObjects need an instance to be spawned + /// + public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) + { + m_PrefabInstance.SetActive(true); + m_PrefabInstance.transform.position = transform.position; + m_PrefabInstance.transform.rotation = transform.rotation; + return m_SpawnedNetworkObject; + } + + /// + /// Client and Server side + /// INetworkPrefabInstanceHandler.Destroy implementation + /// + public void Destroy(NetworkObject networkObject) + { + m_PrefabInstance.SetActive(false); + } + + public void SpawnInstance() + { + if (!IsServer) + { + return; + } + + if (m_PrefabInstance != null && m_SpawnedNetworkObject != null && !m_SpawnedNetworkObject.IsSpawned) + { + m_PrefabInstance.SetActive(true); + m_SpawnedNetworkObject.Spawn(); + StartCoroutine(DespawnTimer()); + } + } + + public override void OnNetworkSpawn() + { + // We register our network Prefab and this NetworkBehaviour that implements the + // INetworkPrefabInstanceHandler interface with the Prefab handler + NetworkManager.PrefabHandler.AddHandler(PrefabToSpawn, this); + + if (!IsServer || !SpawnPrefabAutomatically) + { + return; + } + + if (SpawnPrefabAutomatically) + { + SpawnInstance(); + } + } + + public override void OnNetworkDespawn() + { + if (m_SpawnedNetworkObject != null && m_SpawnedNetworkObject.IsSpawned) + { + m_SpawnedNetworkObject.Despawn(); + } + base.OnNetworkDespawn(); + } + + public override void OnDestroy() + { + // This example destroys the + if (m_PrefabInstance != null) + { + // Always deregister the prefab + NetworkManager.Singleton.PrefabHandler.RemoveHandler(PrefabToSpawn); + Destroy(m_PrefabInstance); + } + base.OnDestroy(); + } +} +``` + +You might run across a situation where you still want other components on the root `GameObject` of your network Prefab instance to remain active. Primarily, you want to be able to easily disable the components that would normally be active when the netcode object is considered spawned. + +Below is an example of what a non-pooled friendly Prefab might look like: + +![image](/img/non-pooled-friendly-prefab.png) + +The issues you might run into with the above Prefab hierarchy is that everything is on a single `GameObject`, and as such if you wanted to disable the `MeshRenderer` and the `NetworkObjectLabel`, [one of our classes in the Netcode for GameObjects test project](https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/blob/f0631414e5a5358a5ac7811d43273b1a82a60ca9/testproject/Assets/Scripts/NetworkObjectLabel.cs#L4), you would need to get those component types before disabling them (that is, during `Start` or `OnNetworkSpawn` or get them when `OnNetworkDespawn` is invoked). + +To reduce this level of complexity, a more "pooled dynamic spawning" friendly Prefab heirarchy might look like this: + +![image](/img/pooled-friendly-prefab.png) + +The NetworkObject sits at the root `GameObject` of the network prefab. The child `GameObject`, SpawnedComponents, then has everything that you might want to have disabled when the network Prefab instance isn't spawned: + +![image](/img/pooled-friendly-prefab-child.png) + +This reduces the complexity down to setting the SpawnedComponents `GameObject` to inactive, which will also disable all of the components attached to it. + +:::tip +Using this type of a hierarchical separation is useful in many ways (especially when you have a much more complex prefab). For more complex prefabs, you can further expand this pattern into specific categories (that is, visuals, physics, sound, etc) which will provide you with a more macrocosmic way to control enabling or disabling many different components without having to have references to all of them. +::: + +## In-Scene Placed NetworkObject + +Any objects in the scene with active and spawned NetworkObject components will get automatically replicated by Netcode. There is no need to manually spawn them when scene management is enabled in the `NetworkManager`. In-scene placed `NetworkObjects` should typically be used like a "static" netcode object, where the netcode object is typically spawned upon the scene being loaded on the server-side and synchronized with clients once they finish loading the same scene. + +[Learn more about In-Scene Placed `NetworkObjects`](scenemanagement/inscene-placed-networkobjects.md) + +Generally, there are **two** modes that define how an in-scene placed NetworkObject is synchronized. + +- Soft Synchronization (Scene Management enabled) +- Prefab Synchronization (Scene Management disabled) + +### Soft Synchronization + +`SoftSync` or "Soft Synchronization" is a term you might run across if you run into any issue with in-scene placed `NetworkObjects`. Soft synchronization only occurs if scene management is enabled in the `NetworkManager` properties. If you receive a "soft synchronization error", then this typically means that a client can't locate the same in-scene placed NetworkObject after loading a scene. + +### Prefab Synchronization + +`PrefabSync` or "Prefab Synchronization" is used if scene management is disabled in the `NetworkManager`. With Prefab synchronization, every in-scene placed NetworkObject has to be a network Prefab and must be registered with `NetworkPrefabs` list. When a client starts, Netcode will destroy all existing in-scene placed NetworkObjects and spawn its corresponding Prefab from the `NetworkPrefabs` list instead. This also means that you will have to implement your own scene manager and handle how you synchronize clients when they join a network session. + +**PrefabSync is ONLY recommended for advanced development and/or multi project setups**. diff --git a/versioned_docs/version-2.0.0/basics/object-visibility.md b/versioned_docs/version-2.0.0/basics/object-visibility.md new file mode 100644 index 000000000..9e3d637ae --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/object-visibility.md @@ -0,0 +1,120 @@ +--- +id: object-visibility +title: Object visibility +sidebar_label: Object visibility +--- + +## What Is NetworkObject Visibility? +Object (NetworkObject) visibility is a Netcode for GameObjects term used to describe whether a NetworkObject is visible to one or more clients as it pertains to a netcode/network perspective. When a NetworkObject is visible to a client, the server will assure the client has a spawned version (a clone) of the NetworkObject. This also means that all network traffic generated by the server for the visible NetworkObject will be sent to all clients that are aware (that is, it's "visible to the clients") of it. Likewise, when a NetworkObject is "hidden" (that is, not visible) from a client, then the client will despawn and destroy the NetworkObject if it was previously visible and no network traffic generated by the hidden NetworkObject will be received by the client(s) it's hidden from. + + +## Using Visibility + +One way to determine visibility is to assign a callback to `NetworkObject.CheckObjectVisibility`. This callback is invoked when new clients connect or just before the associated NetworkObject is spawned. Looking at the example below, we can see the callback includes a client identifier (clientId) value as a parameter which is used to determine whether the NetworkObject is visible to the client. If `NetworkObject.CheckObjectVisibility` isn't assigned, then Netcode for GameObjects assumes it's visible to all clients. + +### CheckObjectVisibility Callback Example +```csharp +public class VisibilityCheckExample : NetworkBehaviour +{ + public bool ContinuallyCheckVisibility = true; + public float VisibilityDistance = 5.0f; + + /// + /// This is automatically invoked when spawning the network prefab + /// relative to each client. + /// + /// client identifier to check + /// true/false whether it is visible to the client or not + private bool CheckVisibility(ulong clientId) + { + // If not spawned, then always return false + if (!IsSpawned) + { + return false; + } + + // We can do a simple distance check between the NetworkObject instance position and the client + return Vector3.Distance(NetworkManager.ConnectedClients[clientId].PlayerObject.transform.position, transform.position) <= VisibilityDistance; + } + + public override void OnNetworkSpawn() + { + if (IsServer) + { + // The server handles visibility checks and should subscribe when spawned locally on the server-side. + NetworkObject.CheckObjectVisibility += CheckVisibility; + // If we want to continually update, we don't need to check every frame but should check at least once per tick + if (ContinuallyCheckVisibility) + { + NetworkManager.NetworkTickSystem.Tick += OnNetworkTick; + } + } + base.OnNetworkSpawn(); + } + + private void OnNetworkTick() + { + // If CheckObjectVisibility is enabled, check the distance to clients + // once per network tick. + foreach (var clientId in NetworkManager.ConnectedClientsIds) + { + var shouldBeVisibile = CheckVisibility(clientId); + var isVisibile = NetworkObject.IsNetworkVisibleTo(clientId); + if (shouldBeVisibile && !isVisibile) + { + // Note: This will invoke the CheckVisibility check again + NetworkObject.NetworkShow(clientId); + } + else if (!shouldBeVisibile && isVisibile) + { + NetworkObject.NetworkHide(clientId); + } + } + } + + public override void OnNetworkDespawn() + { + if (IsServer) + { + NetworkObject.CheckObjectVisibility -= CheckVisibility; + NetworkManager.NetworkTickSystem.Tick -= OnNetworkTick; + } + base.OnNetworkDespawn(); + } +} +``` + +### Additional Visibility Methods and Properties: +The `CheckObjectVisibility` callback helps you determine if a NetworkObject is visible to a specific client when the NetworkObject is spawned. However, you might have the need to change a NetworkObject's visibility after it's spawned. To change the visibility of a NetworkObject that is already spawned, you can use the following methods: + +Make a NetworkObject visible to a single client: +```csharp +NetworkObject netObject = GetComponent(); +netObject.NetworkShow(clientIdToShowTo); +``` + +Make a NetworkObject invisible/hidden from a single client: +```csharp +NetworkObject netObject = GetComponent(); +netObject.NetworkHide(clientIdToHideFrom); +``` + +Make several NetworkObjects visible to a single client (static method): +```csharp +/// networkObjects is of type List +NetworkObject.NetworkShow(networkObjects, clientId); +``` + +Make several NetworkObjects invisible/hidden to a single client (static method): +```csharp +/// networkObjects is of type List +NetworkObject.NetworkHide(networkObjects, clientId); +``` + +Spawn a NetworkObject with no observers (_i.e. not visible to any clients initially_): +```csharp +NetworkObject.SpawnWithObservers = false; +NetworkObject.Spawn(); +``` + +See [Spawning With (or Without) Observers](networkobject.md#spawning-with-or-without-observers) for more information. diff --git a/versioned_docs/version-2.0.0/basics/ownership.md b/versioned_docs/version-2.0.0/basics/ownership.md new file mode 100644 index 000000000..349b55381 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/ownership.md @@ -0,0 +1,93 @@ +--- +id: ownership +title: Understanding ownership and authority +--- + +By default, Netcode for GameObjects assumes a [client-server topology](../terms-concepts/client-server.md), in which the server owns all NetworkObjects (with [some exceptions](networkobject.md#ownership)) and has ultimate authority over [spawning and despawning](object-spawning.md). + +Netcode for GameObjects also supports building games with a [distributed authority topology](../terms-concepts/distributed-authority.md), which provides more options for ownership and authority over NetworkObjects. + +## Ownership and distributed authority + +In a distributed authority setting, authority over NetworkObjects isn't bound to a single server, but distributed across clients depending on a NetworkObject's [ownership permission settings](#ownership-permission-settings). NetworkObjects with the distributable permission set are automatically distributed amongst clients as they connect and disconnect. + +When a client starts a distributed authority session it spawns its player, locks the local player's permissions so that no other client can take ownership, and then spawns some NetworkObjects. At this point, Client-A has full authority over the distributable spawned objects and its player object. + +![Distributed authority start](/img/distributed-authority-start.jpg) + +When another player joins, as in the diagram below, authority over distributable objects is split between both clients. Distributing the NetworkObjects in this way reduces the overall processing and bandwidth load for both clients. The same distribution happens when a player leaves, either gracefully or unexpectedly. The ownership and last known state of the subset of objects owned by the leaving player is transferred over to the remaining connected clients with no interruption in gameplay. + +![Distributed authority new client](/img/distributed-authority-new-client.jpg) + +### Ownership permission settings + +The following ownership permission settings, defined by `NetworkObject.OwnershipStatus`, are only available when running in distributed authority mode: + +* `None`: Ownership of this NetworkObject is considered static and can't be redistributed, requested, or transferred (a Player would have this, for example). +* `Distributable`: Ownership of this NetworkObject is automatically redistributed when a client joins or leaves, as long as ownership is not locked or a request is pending. +* `Transferable`: Ownership of this NetworkObject can be transferred immediately, as long as ownership is not locked and there are no pending requests. +* `RequestRequired`: Ownership of this NetworkObject must be requested before it can be transferred and will always be locked after transfer. + +You can also use `NetworkObject.SetOwnershipLock` to lock and unlock the permission settings of a NetworkObject for a period of time, preventing ownership changes on a temporary basis. + +## Checking for authority + +### `HasAuthority` + +The `HasAuthority` property, which is available on both NetworkObjects and NetworkBehaviours, is session-mode agnostic and works in both distributed authority and client-server contexts. It's recommended to use `HasAuthority` whenever you're working with individual objects, regardless of whether you're using a distributed authority or client-server topology. + +``` +public class MonsterAI : NetworkBehaviour +{ + public override void OnNetworkSpawn() + { + if (!HasAuthority) + { + return; + } + // Authority monster init script here + base.OnNetworkSpawn(); + } + + private void Update() + { + if (!IsSpawned || !HasAuthority) + { + return; + } + // Authority updates monster AI here + } +} +``` + +Using distributed authority with Netcode for GameObjects requires a shift in the understanding of authority: instead of authority belonging to the server in all cases, it belongs to whichever client instance currently has authority. This necessitates a shift away from using local, non-replicated properties to store pertinent states; instead, [NetworkVariables](networkvariable.md) should be used to keep states synchronized and saved when all clients disconnect from a session or ownership is transferred to another client. + +Distributed authority supports all built-in NetworkVariable data types. Since there's no concept of an authoritative server in a distributed authority session, all NetworkVariables are automatically configured with owner write and everyone read permissions. + +### `IsServer` + +`IsServer` or `!IsServer` is the traditional client-server method of checking whether the current context has authority, and is only available in client-server topologies (because distributed authority games have no single authoritative server). You can use a mix of `HasAuthority` and `IsServer` when building a client-server game: `HasAuthority` is recommended when performing object-specific operations, while `IsServer` can be useful to check for authority when performing global actions. + +``` +public class MonsterAI : NetworkBehaviour +{ + public override void OnNetworkSpawn() + { + if (!IsServer) + { + return; + } + // Server-side monster init script here + base.OnNetworkSpawn(); + } + + private void Update() + { + if (!IsSpawned || !IsServer) + { + return; + } + // Server-side updates monster AI here + } +} +``` diff --git a/versioned_docs/version-2.0.0/basics/playerobjects.md b/versioned_docs/version-2.0.0/basics/playerobjects.md new file mode 100644 index 000000000..fa0439b42 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/playerobjects.md @@ -0,0 +1,102 @@ +--- +id: playerobjects +title: PlayerObjects and player prefabs +--- + +PlayerObjects are an optional feature in Netcode for GameObjects that you can use to assign a [NetworkObject](networkobject.md) to a specific client. A client can only have one PlayerObject. + +PlayerObjects are instantiated by reference to a player prefab, which defines the characteristics of the PlayerObject. + +## Defining defaults for player prefabs + +If you're using `UnityEngine.InputSystem.PlayerInput` or `UnityEngine.PhysicsModule.CharacterController` components on your player prefab(s), you should disable them by default and only enable them for the local client's PlayerObject. Otherwise, you may get events from the most recently instantiated player prefab instance, even if it isn't the local client instance. + +You can disable these components in the **Inspector** view on the prefab itself, or disable them during `Awake` in one of your `NetworkBehaviour` components. Then you can enable the components only on the owner's instance using code like the example below: + +``` +PlayerInput m_PlayerInput; +private void Awake() +{ + m_PlayerInput = GetComponent(); + m_PlayerInput.enabled = false; +} + +public override void OnNetworkSpawn() +{ + m_PlayerInput.enabled = IsOwner; + base.OnNetworkSpawn(); +} + +public override void OnNetworkDespawn() +{ + m_PlayerInput.enabled = false; + base.OnNetworkDespawn(); +} +``` + +## Spawning PlayerObjects + +## Session-mode agnostic methods + +Netcode for GameObjects can spawn a default PlayerObject for you. If you enable **Create Player Prefab** in the [NetworkManager](../components/networkmanager.md) and assign a valid prefab, then Netcode for GameObjects spawns a unique instance of the designated player prefab for each connected and approved client, referred to as the PlayerObject. + +To manually spawn an object as PlayerObject, use the following method: + +```csharp +GetComponent().SpawnAsPlayerObject(clientId); +``` + +If the player already had a prefab instance assigned, then the client owns the NetworkObject of that prefab instance unless there's additional server-side specific user code that removes or changes the ownership. + +Alternatively, you can choose not to spawn anything immediately after a client connects and instead use a [NetworkBehaviour component](networkbehaviour.md) to handle avatar/initial player prefab selection. This NetworkBehaviour component could be configured by the server or initiating session owner, or be associated with an [in-scene](scenemanagement/inscene-placed-networkobjects.md) or [dynamically spawned](object-spawning.md#dynamically-spawned-network-prefabs) NetworkObject, as suits the needs of your project. + +### Client-server contexts only + +In addition to the [session-mode agnostic spawning methods](#session-mode-agnostic-methods) above, you can assign a unique player prefab on a per-client connection basis using a client [connection approval process](connection-approval.md) when in [client-server contexts](../terms-concepts/client-server.md). + +### Distributed authority contexts only + +In addition to the [session-mode agnostic spawning methods](#session-mode-agnostic-methods) above, you can use the [`OnFetchLocalPlayerPrefabToSpawn`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnFetchLocalPlayerPrefabToSpawn) method to assign a unique player prefab on a per-client basis when in [distributed authority contexts](../terms-concepts/distributed-authority.md). + +To use `OnFetchLocalPlayerPrefabToSpawn` in your project, assign a callback handler to `OnFetchLocalPlayerPrefabToSpawn` and whatever the client script returns is what will be spawned for that client. Ensure that the prefab being spawned is in a NetworkPrefabList [registered with the NetworkManager](object-spawning.md#registering-a-network-prefab). + +If you don't assign a callback handler to `OnFetchLocalPlayerPrefabToSpawn`, then the default behaviour is to return the `NetworkConfig.PlayerPrefab` (or null if neither are set). + +:::note `AutoSpawnPlayerPrefabClientSide` required +For `OnFetchLocalPlayerPrefabToSpawn` to work, [`AutoSpawnPlayerPrefabClientSide`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_AutoSpawnPlayerPrefabClientSide) must be enabled. +::: + +## PlayerObject spawning timeline + +When using automatic methods of PlayerObject spawning (such as enabling **Create Player Prefab** in the [NetworkManager](../components/networkmanager.md)), PlayerObjects are spawned at different times depending on whether you have [scene management](scenemanagement/scene-management-overview.md) enabled. + +- When scene management is disabled, PlayerObjects are spawned when a joining client's connection is approved. +- When scene management is enabled, PlayerObjects are spawned when a joining client finishes initial synchronization. + +If you choose not to automatically spawn a PlayerObject when a client joins, then the timing of when a PlayerObject is spawned is up to you based on your own implemented code. + +## Finding PlayerObjects + +To find a PlayerObject for a specific client ID, you can use the following methods: + +Within a NetworkBehaviour, you can check the local `NetworkManager.LocalClient` to get the local PlayerObjects: + +```csharp +NetworkManager.LocalClient.PlayerObject +``` + +Conversely, on the server-side, if you need to get the PlayerObject instance for a specific client, you can use the following: + +```csharp +NetworkManager.Singleton.ConnectedClients[clientId].PlayerObject; +``` + +To find your own player object just pass `NetworkManager.Singleton.LocalClientId` as the `clientId` in the sample above. + +## Additional resources + +- [NetworkObject](networkobject.md) +- [NetworkManager](../components/networkmanager.md) +- [Distributed authority topologies](../terms-concepts/distributed-authority.md) +- [Client-server topologies](../terms-concepts/client-server.md) +- [Object spawning](objectspawning.md) diff --git a/versioned_docs/version-2.0.0/basics/race-conditions.md b/versioned_docs/version-2.0.0/basics/race-conditions.md new file mode 100644 index 000000000..08f2bd793 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/race-conditions.md @@ -0,0 +1,40 @@ +--- +id: race-conditions +title: Ownership race conditions +--- + +:::info Distributed authority only + +The race conditions described on this page only affect games using a [distributed authority topology](../terms-concepts/distributed-authority.md). + +::: + +Ownership race conditions can occur when two players (clients) want to take ownership of the same NetworkObject at the same time. The following diagram illustrates a typical race condition where two clients (A and B) want to take ownership of an object (the star owned by Client-C) simultaneously. + +![Race condition](/img/race-condition.jpg) + +## Potential race conditions + +When two clients attempt to take ownership of the same NetworkObject at the same time, the outcome can vary depending on the `OwnershipStatus` of the NetworkObject. + +* `OwnershipStatus.None`: If ownership status isn't transferable, but Client-A and Client-B attempt to request ownership anyway, they’ll both receive a local ownership notification, via `NetworkObject.OnOwnershipPermissionsFailure`, with a status of `OwnershipPermissionsFailureStatus.NotTransferable`. This only occurs if the user script of both clients don't check the NetworkObject ownership permissions first. +* `OwnershipStatus.Distributable`: If this is the only ownership flag on the NetworkObject, then the end result for both clients is the same as `OwnershipStatus.None` above. +* `OwnershipStatus.RequestRequired`: If ownership transfer requires a request, then there are a few potential outcomes: + * If both clients attempt to locally change the ownership of the NetworkObject without first making a request, they'll both receive a local ownership notification, via `NetworkObject.OnOwnershipPermissionsFailure`, that they must request ownership (`OwnershipPermissionsFailureStatus.RequestRequired`). + * If both clients send a request for ownership to the authoritative client, then it can be treated as a 'first come first serve' request (this is the default response of an authoritative client) or, if the user script assigns a callback handler to the `NetworkObject.OnOwnershipRequested`, it will be up to the user script as to whether Client-A or Client-B is granted ownership. This is still handled in a 'first come first serve' fashion, but does provide an additional level of control over granting ownership upon request. +* `OwnershipStatus.Transferable`: If ownership is transferable and requests are not required, then ownership can be acquired by both clients simultaneously if the action or user script performed happens at the same time and occurs within a period of time less than the average RTT of both clients. This is where a true race condition can occur, and the end result can be ownership getting out of sync across clients. See the diagram below. + +![True race condition](/img/true-race-condition.jpg) + +## Preventing race conditions + +There are broadly three ways to help prevent these types of race conditions. + +* **Require ownership requests**: You can use the `RequestRequired` permission flag to ensure that any changes in ownership require a request first. This prevents race conditions, but requires two RTTs to complete the transfer of ownership, and can be too slow in situations where acquisition of ownership is time sensitive. +* **Use the distributed authority service**: The distributed authority service provides a certain level of governance over ownership transfer during a change in ownership of a NetworkObject with just the `Transferable` flag set. +* **Lock ownership**: When changing ownership, the user script can lock ownership for any period of time using `NetworkObject.SetOwnershipLock`. If this operation is performed immediately after changing ownership, then both the change in ownership and the updated ownership permissions are sent in the same message batch at the end of the frame. This helps the distributed authority service to govern and prevent race conditions more effectively, while also preventing immediate turnover of ownership to another client shortly after (within a few network ticks period of time) changing ownership locally. + +How you choose to prevent race conditions depends on how quickly you need to transfer ownership from one client to another, and how much contingency you intend to implement. + +* `OwnershipStatus.RequestRequired`: Is not prone to race conditions, but can take two RTTs (one RTT for the requestor and one RTT for the grantor/authority). If immediate ownership transfer is not required (that is, acquiring ownership in the same frame), then this is the recommended permissions setting to use. +* `OwnershipStatus.Transferable`: If not used in conjunction with ownership locking, can be prone to potential race-like conditions. Moreover, ownership could be transferred, locally, to a client for a full RTT period before receiving a change in ownership message that gives ownership to a client that had changed ownership slightly earlier. While this type of scenario can be managed, it’s worth noting that while immediate (i.e. `Transferable`) ownership transfer permissions provide a much faster transfer in ownership, it should be used with caution and a denial of ownership contingency script could be needed to handle such scenarios. diff --git a/versioned_docs/version-2.0.0/basics/scenemanagement/client-synchronization-mode.md b/versioned_docs/version-2.0.0/basics/scenemanagement/client-synchronization-mode.md new file mode 100644 index 000000000..5c6cd9f0e --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/scenemanagement/client-synchronization-mode.md @@ -0,0 +1,55 @@ +--- +id: client-synchronization-mode +title: Client synchronization mode +sidebar_label: Client synchronization mode +--- + +## Introduction +Client synchronization occurs when immediately after a client connects to a host or server and the connection is approved. During the client synchronization process, the server will determine everything that a client might need to know about in order to join an in-progress network session (networked game session). Netcode for GameObjects provides you with the ability to select the client synchronization mode that best suits your project's needs. During client synchronization a client will automatically set its local client synchronization mode to match that of the server or host setting. + +The client synchronization mode should be set when the server or host is first provided via the `NetworkSceneManager.SetClientSynchronizationMode` method that takes a [`LoadSceneMode`](https://docs.unity3d.com/ScriptReference/SceneManagement.LoadSceneMode.html) value as a parameter. Each client synchronization mode behaves in a similar way to loading a scene based on the chosen `LoadSceneMode`. + +:::note Distributed authority contexts +When using a [distributed authority network topology](../../terms-concepts/distributed-authority.md), any client can set the client synchronization mode when they're promoted to session owner. Late-joining clients are synchronized using whatever setting the current session owner has. +::: + +## Single Client Synchronization Mode +_(Existing Client-Side Scenes Unloaded During Synchronization)_ + +If you are already familiar with Netcode for GameObjects, then this is most likely the client synchronization mode you are familiar with. When client synchronization mode is set to `LoadSceneMode.Single`, a client treats the synchronization similar to that of loading a scene using `LoadSceneMode.Single`. When loading a scene in `LoadSceneMode.Single`, any currently loaded scene will be unloaded and then the new scene is loaded. From a client synchronization mode perspective, the host or server will always have a default active scene. As such, the default active scene is loaded first in `LoadSceneMode.Single` mode and then any other additional scenes that need to be synchronized are loaded as `LoadSceneMode.Additive`. + + +This means any scene that is loaded on the client side prior to being synchronized will be unloaded when the first/default active scene is loaded. + +![image](/img/ClientSynchronizationModeSingle.png) + +The above image shows a high level overview of single mode client synchronization. For this scenario, a client had just left a network session where the game session was on Level 1. The client still has two common scenes to all levels, Enemy Spawner and World Items scenes, and the Level scenes contain unique assets/settings based on the level number reached. The client then joins a new game session that is on Level 3. With single mode client synchronization, the currently active scene on the server-side is the first scene loaded in `LoadSceneMode.Single` on the client-side (thus why we refer to the client synchronization mode as "single"). By default, loading a new scene in `LoadSceneMode.Single` mode will unload all currently loaded scenes before loading the new scene. Then, for each additional scene that the server requires the client to have loaded in order to be fully synchronized, all remaining scenes are loaded additively (`LoadSceneMode.Additive`). + +For some games/projects this type of client synchronization is all that is needed. However, there are scenarios where you might want to have more control over which scenes are unloaded (if any) during the client synchronization process or you might want to reduce the client synchronization time by having certain commonly shared scenes pre-loaded (and possibly never unloaded) prior to connecting. If your project's needs falls into this realm, then the recommended solution is to use additive client synchronization mode. + +## Additive Client Synchronization Mode +_(Existing Client-Side Scenes Used During Synchronization)_ + +Additive client synchronization is mode similar to additive scene loading in that any scenes the client has currently loaded, prior to connecting to a server or host, will remain loaded and be taken into consideration as scenes to be used during the synchronization process. + +This can be particularly useful if you have common UI element scenes (i.e. menu interfaces) that you would like to persist throughout the duration of the application instance's life cycle (i.e. from the time the application starts to the time it is stopped/exited). It can also be useful to have certain scenes pre-loaded ahead of time to reduce load times when connecting to an existing network session. + +![image](/img/ClientSynchronizationModeAdditive.png) + +In the above image we can see a scenario where a server has a Menu UI Scene that it ignores using `NetworkSceneManager.VerifySceneBeforeLoading`, two scenes that the project is configured to always have pre-loaded _(Enemy Spawner and World Items scenes)_, and the server has reached "level 3" of the networked game session so it has the Level 3 scene loaded. The client side almost mirrors the server side (scenes loaded relative) when it began the synchronization process. The only difference between the server and the client was the Level 3 scene that the client has to load in order to be fully synchronized with the server. + +_If the server was running in client synchronization mode `LoadSceneMode.Single`, the client would have had to load (at a minimum) three scenes if the Level3 scene was the currently active scene on the server side (not to mention having to reload the Menu UI scene if the user on the client side wanted to adjust a global setting like the audio level of the game). By using `LoadSceneMode.additive` client synchronization, the synchronizing client only has to load one scene in order to be "scene synchronized" with the server._ + +While additive client synchronization mode might cover the majority of your project's needs, you could find scenarios where you need to be a bit more selective with the scenes a client should keep loaded and the scenes that should be unloaded. There are two additional settings that you can use (only when client synchronization mode set to `LoadSceneMode.Additive`) to gain further control over this process. + +### NetworkSceneManager.PostSynchronizationSceneUnloading +When this flag is set on the client side any scenes that were not synchronized with the server or host will be unloaded. This can be useful if your project primarily uses `LoadSceneMode.Additive` to load the majority of your scenes (i.e. sometimes referred to a "bootstrap scene loading approach"). A client could have additional scenes specific to a portion of your game/project from a previous game network session that are not pertinent in a newly established game network session. Any scenes that are not required to become fully synchronized with the current server or host will get unloaded upon the client completing the synchronization process. + +### NetworkSceneManager.VerifySceneBeforeUnloading +When `NetworkSceneManager.PostSynchronizationSceneUnloading` is set to `true` and the client synchronization mode is `LoadSceneMode.Additive`, if this callback is set then for each scene that is about to be unloaded the set callback will be invoked. If it returns `true` the scene will be unloaded and if it returns `false` the scene will remain loaded. This is useful if you have specific scenes you want to persist (menu interfaces etc.) but you also want some of the unused scenes (i.e. not used during synchronization) to be unloaded. + +![image](/img/ClientSynchronizationModeAdditive2.png) + +Let's take the previous client synchronization mode scenario into consideration with the client side having `NetworkSceneManager.PostSynchronizationSceneUnloading` set to `true` and registering a callback for `NetworkSceneManager.VerifySceneBeforeUnloading` where it returns false when client synchronization attempts to unload the Menu UI Scene. Let's also assume this game allows clients to join other network sessions while still in a current network session and that the client had just come from a previous game where the server was still on "level 1" (so the client has the level 1 scene still loaded). When the client begins to synchronize the two pre-loaded scenes are used for synchronization, the client still has to load the Level 3 scene, but at the end of the synchronization `NetworkSceneManager` begins to unload any remaining scenes not synchronized by the server and the client. When the Menu UI Scene is checked if it can be unloaded, the client side `NetworkSceneManager.VerifySceneBeforeUnloading` callback returns `false` so that scene remains loaded. Finally, the Level 1 scene is verified to be unloaded via `NetworkSceneManager.VerifySceneBeforeUnloading` which the user logic determines is "ok" to unload so it returns `true` and the Level 1 scene is unloaded. + +Additive client synchronization can help you to reduce client synchronization times while providing you with additional capabilities tailored for additively loaded scene usage patterns. diff --git a/versioned_docs/version-2.0.0/basics/scenemanagement/custom-management.md b/versioned_docs/version-2.0.0/basics/scenemanagement/custom-management.md new file mode 100644 index 000000000..d49285bb3 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/scenemanagement/custom-management.md @@ -0,0 +1,67 @@ +--- +id: custom-management +title: Custom scene management +sidebar_label: Custom management +--- + +:::caution +If you haven't already read the [Using NetworkSceneManager](using-networkscenemanager.md) section, it's highly recommended to do so before proceeding. +Custom Scene Management currently has some drawbacks that we hope to improve upon over time. +::: + +## Building Your Own Scene Management Solution +Netcode for GameObjects is designed to provide a scene management solution that should meet most projects' needs. However, some projects might still need to build their own scene management solution. The first step is to disable **Enable Scene Management** in your `NetworkManager`'s properties. All of your scene loading and unloading must be handled via the `UnityEngine.SceneManagement.SceneManager` class. + +### Integrated NetworkSceneManager Comparison (Enabled vs Disabled) +Network Scene Management | Enabled | Disabled +------------------------------|----------|---------- +Scene Event Synchronizing | Yes | No +In-Scene Network Prefab Synchronizing | Yes | Yes +In-Scene NetworkObject Synchronizing | Yes | No +Auto Scene Migration Synchronizing | Yes | No + +#### Scene Event Synchronizing +When integrated scene management is enabled, scene-related events are automatically synchronized for you. When integrated scene management is disabled, you need to handle the synchronization of scenes yourself. + +#### In-Scene Placed Network Prefabs and NetworkObjects +Clients are always synchronized with any in-scene placed Network Prefab instances when they join a session, regardless of whether integrated scene management is enabled. When scene management is disabled, only scenes loaded prior to a client joining are synchronized — any scenes loaded *after* a client is connected won't be synchronized (you would want to only dynamically spawn NetworkObjects at that point). + +:::warning Any in-scene defined NetworkObject (i.e. not a network prefab instance) will not get synchronized with a newly-joined client when scene management is disabled. +::: + +When the integrated `NetworkSceneManager` is enabled, both in-scene placed network prefab instances and in-scene defined NetworkObjects get synchronized, and when new scenes are loaded they get synchronized also. + +#### Auto (NetworkObject) Scene Migration Synchronization +When integrated scene management is enabled, you can have the server migrate an already spawned NetworkObject from one scene to another and it will automatically be synchronized with connected and late-joining clients. + +When integrated scene management is disabled, this is not handled and you need to use either NetworkVariables, Rpcs, or custom messages to handle synchronizing clients with any migration of `NetworkObjects`s from one scene to another. + +### Registering In-Scene Placed Network Prefab Instances +While integrated scene management handles the synchronization of in-scene placed NetworkObjects, custom scene management treats everything like a dynamically spawned NetworkObject. As such, you can only use network prefabs defined in your NetworkPrefabList assigned to your NetworkManager, and any in-scene defined NetworkObjects will not synchronize with clients. + +Once you've registered your in-scene placed Network Prefabs with your `NetworkPrefabList` and assigned that to your `NetworkManager`, you can then start a server or host and have a client connect and synchronize with the in-scene placed Network Prefab instances (as long as both client and server have pre-loaded the scene or scenes required). + +:::important +When a client first connects, it deletes any in-scene placed `NetworkObjects` in any of the scenes it has currently loaded. When using a custom scene management solution, in-scene placed NetworkObjects are actually dynamically spawned. This means any changes you make to your in-scene placed Network Prefabs will *not* be synchronized with clients automatically. +::: + +### Synchronizing In-Scene Placed Network Prefab Instances +If you want to change an in-scene placed network prefab instance, you need to handle the serialization of these settings yourself. You can do this by overriding `NetworkBehaviour.OnSynchronize` and serializing any property updates you want to have synchronized with clients when they join. [Read More About OnSynchronize Here](../../basics/networkbehaviour.md#pre-spawn-synchronization). + +## Starting a Netcode Enabled Game Session +The recommended way of starting session using your own scene management solution is to assure that when a client attempts to join a netcode game session it should already have (as best as possible) any scenes that the server might have loaded. While this does not assure that your newly connecting client will load any additional scenes that might have been loaded, using this approach initially will get you started so you can then come up with a strategy to handling: +- Scene Synchronization +- Scene Loading and Unloading + +### Scene synchronization + While you might have an initial set of scenes loaded, you are bound to want to load more scenes as your netcode enabled game session progresses. Once a client has fully connected (you can use `NetworkManager.OnClientConnected` for this), you will want to send the client a list of additional scenes to be loaded. + - You will want to come up with your own "client state" as it progresses through this synchronization process to determine when a client has loaded all scenes. + - For example: a client might be connected and has synchronized with the default scenes required to connect, but then you have one or more additional scenes you might have loaded (additively) that the client needs to load and synchronize (spawn) any in-scene placed Network Prefab instances. + - Remember, once a client has connected, if you load additional scenes then any in-scene placed network prefab instances will not be synchronized for you. It's recommended to dynamically spawn network prefab instances after the initial client synchronization if you plan on loading more scenes. + +### Scene Loading and Unloading +You can accomplish this using either RPCs or custom messages. You might even use your own `INetworkSerializable` implementation that has a list of scenes and whether they should be loaded or unloaded. You should always have some form of "complete" response factored into your design so you know when a client has finished loading/unloading a scene. You will also want to devise a strategy for loading a scene in `LoadSceneMode.Additive` and `LoadSceneMode.Single` modes. + +:::tip +Creating a global scene management script and attaching it to the same GameObject as the `NetworkManager` is one way to assure your custom netcode scene management solution persists while a game session is in progress. +::: diff --git a/versioned_docs/version-2.0.0/basics/scenemanagement/inscene-placed-networkobjects.md b/versioned_docs/version-2.0.0/basics/scenemanagement/inscene-placed-networkobjects.md new file mode 100644 index 000000000..3f0df24f8 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/scenemanagement/inscene-placed-networkobjects.md @@ -0,0 +1,281 @@ +--- +id: inscene-placed-networkobjects +title: In-scene placed NetworkObjects +sidebar_label: In-scene NetworkObjects +--- +:::caution +If you haven't already read the [Using NetworkSceneManager](using-networkscenemanager.md) section, it's highly recommended to do so before proceeding. +::: + +In-scene placed NetworkObjects are GameObjects with a NetworkObject component that was added to a scene from within the Editor. You can use in-scene placed NetworkObjects for: + +- Management systems + - For example, a NetworkObject pool management system that dynamically spawns network prefabs. +- Interactive world objects that are typically easier to place in-scene than spawn + - For example, a door that can only be unlocked by a key. If a player unlocks it then you want other players to know about it being unlocked, and making it an in-scene placed NetworkObject simplifies the positioning of the door relative to the surrounding world geometry. + - Static visual elements + - For example, a heads up display (HUD) that includes information about other items or players. + - Or some form of platform or teleporter that moves a player from one location to the next when a player enters a trigger or uses an object. + +Another benefit of in-scene placed NetworkObjects is that they don't require you to register them with the [`NetworkManager`](../../components/networkmanager.md). In-scene placed `NetworkObjects` are registered internally, when scene management is enabled, for tracking and identification purposes. + +:::tip +Items that can be picked up are typically better implemented using a [hybrid approach](#hybrid-approach) with both an in-scene placed and a dynamically spawned NetworkObject. The in-scene placed NetworkObject can be used to configure additional information about the item (what kind, does another one respawn after one is picked up, and if so how much time should it wait before spawning a new item), while the dynamically spawned object is the item itself. +::: + +## In-scene placed vs. dynamically spawned NetworkObjects (order of operations) + +Because in-scene placed NetworkObjects are instantiated when a scene loads, they have a different order of operations than dynamically spawned NetworkObjects when it comes to spawning. The table below illustrates that spawning occurs after `Awake` but before `Start` for dynamically spawned NetworkObjects, but for in-scene placed NetworkObjects it occurs after both `Awake` and `Start` are invoked. + +Dynamically spawned | In-scene placed +------------------- | --------------- +`Awake` | `Awake` +`OnNetworkSpawn` | `Start` +`Start` | `OnNetworkSpawn` + +In-scene placed NetworkObjects are instantiated when the scene is loaded, which means that both the `Awake` and `Start` methods are invoked before an in-scene placed NetworkObject is spawned. This distinct difference is important to keep in mind when doing any form of dependency-related initializations that might require an active network session. This is especially important to consider when you're using the same `NetworkBehaviour` component with both dynamically spawned and in-scene placed `NetworkObjects`. + +## In-scene placed network prefab instances + +For frequently used in-scene `NetworkObjects`, you can use a [network prefab](../object-spawning.md#network-prefabs) to simplify the creation process. + +### Creating in-scene placed network prefab instances + +To create a network prefab that can be used as an in-scene placed NetworkObject, do the following: + +1. Create a prefab asset (from a GameObject in a scene or creating it directly in a subfolder within Assets). +2. Add only one NetworkObject component to the root GameObject of the prefab. +3. Add any other `NetworkBehaviour`s (on the root or on any level of child GameObject under the root prefab GameObject). +4. If you created the prefab asset from an existing scene, then the original in-scene placed object will automatically become a network prefab instance. +5. If you created the prefab asset directly in a subfolder under the Assets directory, then drag and drop the newly created network prefab into the scene of choice. + +:::note +You may need to deactivate **Enter Play Mode Options** if your `NetworkBehaviour` components do not spawn. +::: + +## Using in-scene placed NetworkObjects + +There are some common use cases where in-scene placed NetworkObjects can prove useful. + +### Static objects + +You can use an in-scene placed NetworkObject to keep track of when a door is opened, a button pushed, a lever pulled, or any other type of state with a simple on/off toggle. These are referred to as static objects, and have the following in common: + + - Static objects are spawned while the originating scene is loaded and only despawned when the originating scene is unloaded. + - The originating scene is the scene in which the in-scene NetworkObject was placed. + - Static objects are typically associated with some world object that's visible to players (such as a door or switch). + - Static objects aren't migrated into other scenes or parented under another NetworkObject. + - For example, a drawbridge that comes down when a certain game state is reached. The drawbridge is most likely connected to some castle or perhaps a section of the castle and would never need to be migrated to another scene. + +### Netcode managers + +You can use an in-scene placed NetworkObject as a netcode manager, for tracking game states, or as a NetworkObject spawn manager. Typically, a manager stays instantiated and spawned as long as the scene it was placed in remains loaded. For scenarios where you want to keep a global game state, the recommended solution is to place the in-scene NetworkObject in an additively loaded scene that remains loaded for the duration of your network game session. + +If you're using scene switching (that is, loading a scene in `LoadSceneMode.Single`), then you can migrate the in-scene placed NetworkObject (used for management purposes) into the DDoL by sending its `GameObject` to the DDoL: + +```csharp +private void Start() +{ + // Don't use this for dynamically spawned NetworkObjects + DontDestroyOnLoad(gameObject); +} +``` + +:::warning +Once migrated into the DDoL, migrating the in-scene placed NetworkObject back into a different scene after it has already been spawned will cause soft synchronization errors with late-joining clients. Once in the DDoL it should stay in the DDoL. This is only for scene switching. If you aren't using scene switching, then it's recommended to use an additively loaded scene and keep that scene loaded for as long as you wish to persist the in-scene placed NetworkObject(s) being used for state management purposes. +::: + +## Complex in-scene NetworkObjects + +The most common mistake when using an in-scene placed NetworkObject is to try and use it like a dynamically spawned NetworkObject. When trying to decide if you should use an in-scene placed or dynamically spawned NetworkObject, you should ask yourself the following questions: + +- Do you plan on despawning and destroying the NetworkObject? + - If yes, then it's highly recommended that you use a dynmically spawned NetworkObject. +- Can it be parented, at runtime, under another NetworkObject? +- Excluding any special case DDoL scenarios, will it be moved into another scene other than the originating scene? +- Do you plan on having full scene-migrations (that is, `LoadSceneMode.Single` or scene switching) during a network session? + +If you answered yes to any of the above questions, then using only an in-scene placed NetworkObject to implement your feature might not be the right choice. However, you can use a hybrid approach to get the best of both methods. + +### Hybrid approach + +Because there are additional complexities involved with in-scene placed NetworkObjects, some use cases are more suited to dynamically spawned NetworkObjects, or require a combination of both types. Perhaps your project's design includes making some world items that can either be consumed (such as health) or picked up (such as weapons and items) by players. Initially, using a single in-scene placed NetworkObject might seem like the best approach for this world item feature. + +However, there's another way to accomplish the same thing while maintaining a clear distinction between dynamically spawned and in-scene placed NetworkObjects. Rather than combining everything into a single network prefab and handling the additional complexities involved with in-scene placed NetworkObjects, you can create two network prefabs: + +1. A world item network prefab: since this will be dynamically spawned, you can re-use this network prefab with any spawn manager (pooled or single). +2. A single-spawn manager (non-pooled): this will spawn the world item network prefab. The single-spawn manager can spawn the dynamically spawned NetworkObject at its exact location with an option to use the same rotation and scale. + +:::tip + Your single-spawn manager can also have a list of GameObjects used as spawn points if you want to spawn world items in various locations randomly and/or based on game state. +::: + +Using this approach allows you to: +1. Re-use the same single-spawn manager with any other network prefab registered with a `NetworkPrefabsList`. +2. Not worry about the complexities involved with treating an in-scene placed NetworkObject like a dynamically spawned one. + +[You can see a hybrid approach example here.](../object-spawning.md#dynamic-spawning-non-pooled) + +## Spawning and despawning in-scene placed NetworkObjects + +By default, an in-scene placed NetworkObject is spawned when the scene it's placed in is loaded and a network session is in progress. In-scene placed NetworkObjects differ from dynamically spawned NetworkObjects when it comes to spawning and despawning: when despawning a dynamically spawned NetworkObject, you can always spawn a new instance of the NetworkObject's associated network prefab. So, whether you decide to destroy a dynamically spawned NetworkObject or not, you can always make another clone of the same network prefab, unless you want to preserve the current state of the instance being despawned. + +With in-scene placed NetworkObjects, the scene it's placed in is similar to the network prefab used to dynamically spawn a NetworkObject, in that both are used to uniquely identify the spawned NetworkObject. The primary difference is that where you use a network prefab to create a new dynamically spawned instance, for in-scene placed objects you need to additively load the same scene to create another in-scene placed NetworkObject instance. + +### Identifying spawned NetworkObjects + +Dynamically spawned | In-scene placed +------------------- | --------------- +`NetworkPrefab` | Scene +`GlobalObjectIdHash` | Scene handle (when loaded) and `GlobalObjectIdHash` + +Once the NetworkObject is spawned, Netcode for GameObjects uses the `NetworkObjectId` to uniquely identify it for both types. An in-scene placed NetworkObject will continue to be uniquely identified by the scene handle that it originated from and the `GlobalObjectIdHash`, even if the in-scene placed NetworkObject is migrated to another additively loaded scene and the originating scene is unloaded. + +### Despawning and respawning the same in-scene placed NetworkObject + +When invoking the `Despawn` method of a NetworkObject with no parameters, it defaults to destroying the NetworkObject: + +```csharp +NetworkObject.Despawn(); // Will despawn and destroy (!!! not recommended !!!) +``` + +If you want an in-scene placed NetworkObject to persist after it's been despawned, it's recommended to always set the first parameter of the `Despawn` method to `false`: + +```csharp +NetworkObject.Despawn(false); // Will only despawn (recommended usage for in-scene placed NetworkObjects) +``` + +Now that you have a despawned NetworkObject, you might notice that the associated `GameObject` and all of its components are still active and possibly visible to all players (that is, like a `MeshRenderer` component). Unless you have a specific reason to keep the associated `GameObject` active in the hierarchy, you can override the virtual `OnNetworkDespawn` method in a `NetworkBehaviour`-derived component and set the `GameObject` to inactive: + +```csharp +using UnityEngine; +using Unity.Netcode; + +public class MyInSceneNetworkObjectBehaviour : NetworkBehaviour +{ + public override void OnNetworkDespawn() + { + gameObject.SetActive(false); + base.OnNetworkDespawn(); + } +} +``` + +This ensures that when your in-scene placed NetworkObject is despawned, it won't consume processing or rendering cycles and will become invisible to all players (either currently connected or that join the session later). Once the NetworkObject has been despawned and disabled, you might want to respawn it at some later time. To do this, set the server-side instance's `GameObject` back to being active and spawn it: + +```csharp +using UnityEngine; +using Unity.Netcode; + +public class MyInSceneNetworkObjectBehaviour : NetworkBehaviour +{ + public override void OnNetworkDespawn() + { + gameObject.SetActive(false); + base.OnNetworkDespawn(); + } + + public void Spawn(bool destroyWithScene) + { + if (IsServer && !IsSpawned) + { + gameObject.SetActive(true); + NetworkObject.Spawn(destroyWithScene); + } + } +} +``` + +:::info +You only need to enable the NetworkObject on the server-side to be able to respawn it. Netcode for GameObjects only enables a disabled in-scene placed NetworkObject on the client-side if the server-side spawns it. This **does not** apply to dynamically spawned `NetworkObjects`. Refer to [the object pooling page](../../advanced-topics/object-pooling.md) for an example of recycling dynamically spawned NetworkObjects. +::: + +### Setting an in-scene placed NetworkObject to a despawned state when instantiating + +Since in-scene placed NetworkObjects are automatically spawned when their respective scene has finished loading during a network session, you might run into the scenario where you want it to start in a despawned state until a certain condition has been met. To do this, you need to add some additional code in the `OnNetworkSpawn` part of your `NetworkBehaviour` component: + +```csharp +using UnityEngine; +using Unity.Netcode; + + public class MyInSceneNetworkObjectBehaviour : NetworkBehaviour + { + public bool StartDespawned; + + private bool m_HasStartedDespawned; + public override void OnNetworkSpawn() + { + if (IsServer && StartDespawned && !m_HasStartedDespawned) + { + m_HasStartedDespawned = true; + NetworkObject.Despawn(false); + } + base.OnNetworkSpawn(); + } + + public override void OnNetworkDespawn() + { + gameObject.SetActive(false); + base.OnNetworkDespawn(); + } + + public void Spawn(bool destroyWithScene) + { + if (IsServer && !IsSpawned) + { + gameObject.SetActive(true); + NetworkObject.Spawn(destroyWithScene); + } + } + } +``` + +The above example keeps track of whether the in-scene placed NetworkObject has started in a despawned state (to avoid despawning after its first time being spawned), and it only allows the server to execute that block of code in the overridden `OnNetworkSpawn` method. The above `MyInSceneNetworkObjectBehaviour` example also declares a public `bool` property `StartDespawned` to provide control over this through the inspector view in the Editor. + +### Synchronizing late-joining clients when an in-scene placed NetworkObject has been despawned and destroyed + +Referring back to the [section on complex in-scene NetworkObjects](#complex-in-scene-networkobjects), it's recommended to use dynamically spawned NetworkObjects if you intend to destroy the object when it's despawned. However, if either despawning but not destroying or using the [hybrid approach](#a-hybrid-approach-example) don't appear to be options for your project's needs, then there are two other possible (but not recommended) alternatives: + +- Have another in-scene placed NetworkObject track which in-scene placed NetworkObjects have been destroyed and upon a player late-joining (that is, `OnClientConnected`) you would need to send the newly-joined client the list of in-scene placed NetworkObjects that it should destroy. This adds an additional in-scene placed NetworkObject to your scene hierarchy and will consume memory keeping track of what was destroyed. +- Disable the visual and physics-related components (in Editor as a default) of the in-scene placed NetworkObject(s) in question and only enable them in `OnNetworkSpawn`. This doesn't delete/remove the in-scene placed NetworkObject(s) for the late-joining client and can be tricky to implement without running into edge case scenario bugs. + +These two alternatives aren't recommended, but are worth briefly exploring to better understand why it's recommend to use a [non-pooled hybrid approach](../object-spawning.md#dynamic-spawning-non-pooled), or just not destroying the in-scene placed NetworkObject when despawning it. + +## Parenting in-scene placed NetworkObjects + +In-scene placed NetworkObjects follow the same parenting rules as [dynamically spawned NetworkObjects](../../advanced-topics/networkobject-parenting.md) with only a few differences and recommendations: + +- You can create complex nested NetworkObject hierarchies when they're in-scene placed. +- If you plan on using full scene-migration (that is, `LoadSceneMode.Single` or scene switching) then parenting an in-scene placed NetworkObject that stays parented during the scene migration isn't recommended. + - In this scenario, you should use a hybrid approach where the in-scene placed NetworkObject dynamically spawns the item to be picked up. +- If you plan on using a bootstrap scene usage pattern with additive scene loading and unloading with no full scene-migration(s), then you can parent in-scene placed NetworkObjects. + +### Auto object parent sync option + + Already parented in-scene placed NetworkObjects auto object parent sync usage: + +- When disabled, the NetworkObject ignores its parent and considers all of its transform values as being world space synchronized (that is, no matter where you move or rotate its parent, it will keep its current position and rotation). + - Typically, when disabling this you need to handle synchronizing the client either through your own custom messages or RPCS, or add a `NetworkTransform` component to it. This is only useful if you want to have some global parent that might shift or have transform values that you don't want to impact the NetworkObject in question. +- When enabled, the NetworkObject is aware of its parent and will treat all of its transform values as being local space synchronized. + - This also applies to being pre-parented under a `GameObject` with no NetworkObject component. + +:::note +_**The caveat to the above is scale**:_ +Scale is treated always as local space relative for pre-parented in-scene placed `NetworkObjects`.
+ +*For dynamically spawned NetworkObjects:*
+It depends upon what WorldPositionStays value you use when parenting the NetworkObject in question.
+WorldPositionStays = true: Everything is world space relative. _(default)_
+WorldPositionStays = false: Everything is local space relative. _(children offset relative to the parent)_
+::: + +### Parenting and transform synchronization + +Without the use of a `NetworkTransform`, clients are only synchronized with the transform values when: + +- A client is being synchronized with the NetworkObject in question: + - During the client's first synchronization after a client has their connection approved. + - When a server spawns a new NetworkObject. +- A NetworkObject has been parented (or a parent removed). +- The server can override the `NetworkBehaviour.OnNetworkObjectParentChanged` method and adjust the transform values when that is invoked. + - These transform changes will be synchronized with clients via the `ParentSyncMessage`. diff --git a/versioned_docs/version-2.0.0/basics/scenemanagement/scene-events.md b/versioned_docs/version-2.0.0/basics/scenemanagement/scene-events.md new file mode 100644 index 000000000..3484d2c5a --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/scenemanagement/scene-events.md @@ -0,0 +1,281 @@ +--- +id: scene-events +title: Scene events +sidebar_label: Scene events +--- +:::caution +If you haven't already read the [Using NetworkSceneManager](using-networkscenemanager.md) section, it's highly recommended to do so before proceeding. +::: + +## Scene Event Associations +We learned that the term "Scene Event" refers to all (associated) subsequent scene events that transpire over time after a server has initiated a load or unload Scene Event. For most cases this is true, however `SceneEventType.Synchronize` is a unique type of Scene Event that handles much more than loading or unloading a single scene. to better understand the associations between scene event types, it's better to first see them grouped together: + +### Loading: +**Initiating Scene Event**: `SceneEventType.Load`
+**Associated Scene Events**: +- `SceneEventType.LoadComplete`:
+signifies that a scene has been loaded locally. Clients send this event to the server. +- `SceneEventType.LoadEventCompleted`:
+signifies that the server and all clients have finished loading the scene and signifies that the Scene Event has completed. + +### Unloading: +**Initiating Scene Event**: `SceneEventType.Unload`
+**Associated Scene Events**: +- `SceneEventType.UnloadComplete`:
+signifies that a scene has been unloaded locally. Clients send this event to the server. +- `SceneEventType.UnloadEventCompleted`:
+signifies that the server and all clients have finished unloading the scene and signifies that the Scene Event has completed. + +### Synchronization: +This is automatically happens after a client is connected and approved.
+**Initiating Scene Event**: `SceneEventType.Synchronize`
+**Associated Scene Events**: +- `SceneEventType.SynchronizeComplete`:
+signifies that the client has finished loading all scenes and locally spawned all Netcode objects. The client sends this scene event message back to the server. This message also includes a list of `NetworkObject.NetworkObjectId`s for all of the NetworkObjects the client spawned. +- `SceneEventType.ReSynchronize`:
+signifies that the server determines the client needs to be "re-synchronized" because one or more NetworkObjects were despawned while the client was synchronizing. This message is sent to the client with a `NetworkObject.NetworkObjectId` list for all NetworkObjects the client needs to despawn. + + +## Client Synchronization Details +While client synchronization does fall partially outside of the scene management realm, it ended up making more sense to handle the initial client synchronization via the `NetworkSceneManager` since a large part of the synchronization process involves loading scenes and synchronizing (in-scene placed and dynamically spawned) `NetworkObjects`. +- Scene synchronization is the first thing a client processes. + - The synchronization message includes a list of all scenes the server has loaded via the `NetworkSceneManager`. + - The client will load all of these scenes before proceeding to the NetworkObject synchronization. + - This approach was used to assure all `GameObject`, NetworkObject, and `NetworkBehaviour` dependencies are loaded and instantiated before a client attempts to locally spawn a NetworkObject. +- Synchronizing with all spawned `NetworkObjects`. + - Typically this involves both in-scene placed and dynamically spawned `NetworkObjects`. + - Learn more about [Object Spawning here](..\object-spawning.md). + - The NetworkObject list sent to the client is pre-ordered, by the server, to account for certain types of dependencies such as when using [Object Pooling](../../advanced-topics/object-pooling.md). + - Typically object pool managers are in-scene placed and need to be instantiated and spawned before spawning any of its pooled `NetworkObjects` on a client that is synchronizing. As such, `NetworkSceneManager` takes this into account to assure that all `NetworkObjects` spawned via the `NetworkPrefabHandler` will be instantiated and spawned after their object pool manager dependency has been instantiated and spawned locally on the client. + - You can have parented in-scene placed NetworkObjects (that is, items that are picked up or consumed by players) + - `NetworkSceneManager` uses a combination of the `NetworkObject.GlobalObjectIdHash` and the instantiating scene's handle to uniquely identify in-scene placed NetworkObjects. + +:::info +With additively loaded scenes, you can run into situations where your object pool manager, instantiated when the scene it's defined within is additively loaded by the server, is leaving its spawned NetworkObject instances within the [currently active scene](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.GetActiveScene.html). While assuring that newly connected clients being synchronized have loaded all of the scenes first helps to avoid scene dependency issues, this alone does not resolve issue with the NetworkObject spawning order. The integrated scene management, included in Netcode for GameObjects, takes scenarios such as this into consideration. +::: + +### The Client Synchronization Process +:::info +The following information isn't required information, but can be useful to better understand the integrated scene management synchronization process. +::: +
+Below is a diagram of the client connection approval and synchronization process: + +![image](/img/scenemanagement_synchronization_overview.png) + +Starting with the "Player" in the top right part of the above diagram, the client (Player) runs through the connection and approval process first which occurs within the `NetworkManager`. Once approved, the server-side `NetworkSceneManager` begins the client synchronization process by sending the `SceneEventType.Synchronize` Scene Event message to the approved client. The client then processes through the synchronization message. Once the client is finished processing the synchronize message, it responds to the server with a `SceneEventType.SynchronizeComplete` message. At this point the client is considered "synchronized". If the server determines any NetworkObject was despawned during the client-side synchronization message processing period, it will send a list of NetworkObject identifiers to the client via the `SceneEventType.ReSynchronize` message and the client will locally despawn the NetworkObjects. + +:::tip +When the server receives and processes the `SceneEventType.SynchronizeComplete` message, the client is considered connected (that is, `NetworkManager.IsConnectedClient` is set to `true`) and both the `NetworkManager.OnClientConnected` delegate handler and the scene event notification for `SceneEventType.SynchronizeComplete` are invoked locally. This can be useful to know if your server sends any additional messages to the already connected clients about the newly connected client's status (that is, a player's status needs to transition from joining to joined). +::: + +## Scene Event Notifications + +### When are Load or Unload SceneEvents Truly Complete? +There are two special scene event types that generate messages for the server and all connected clients: +`SceneEventType.LoadEventCompleted` and `SceneEventType.UnloadEventCompleted` + +:::note +Both of these server generated messages will create local notification events (on all clients and the server) that will contain the list of all client identifiers (ClientsThatCompleted) that have finished loading or unloading a scene. This can be useful to make sure all clients are synchronized with each other before allowing any netcode related game logic to begin. If a client disconnects or there is a time out, then any client that did not load or unload the scene will be included in a second list of client identifiers (ClientsThatTimedOut). +::: + +### Tracking Event Notifications (OnSceneEvent) +The following code provides an example of how to subscribe to and use `NetworkSceneManager.OnSceneEvent`. Since we want the server or host to receive all scene event notifications, we will want to subscribe immediately after we start the server or host. Each case has additional comments about each scene event type. Starting the client is fairly straight forward and follows the same pattern by subscribing to `NetworkSceneManager.OnSceneEvent` if the client successfully started. + +```csharp +public bool StartMyServer(bool isHost) +{ + var success = false; + if (isHost) + { + success = NetworkManager.Singleton.StartHost(); + } + else + { + success = NetworkManager.Singleton.StartServer(); + } + + if (success) + { + NetworkManager.Singleton.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + } + + return success; +} + +public bool StartMyClient() +{ + var success = NetworkManager.Singleton.StartClient(); + if (success) + { + NetworkManager.Singleton.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + } + return success; +} + +private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) +{ + // Both client and server receive these notifications + switch (sceneEvent.SceneEventType) + { + // Handle server to client Load Notifications + case SceneEventType.Load: + { + // This event provides you with the associated AsyncOperation + // AsyncOperation.progress can be used to determine scene loading progression + var asyncOperation = sceneEvent.AsyncOperation; + // Since the server "initiates" the event we can simply just check if we are the server here + if (IsServer) + { + // Handle server side load event related tasks here + } + else + { + // Handle client side load event related tasks here + } + break; + } + // Handle server to client unload notifications + case SceneEventType.Unload: + { + // You can use the same pattern above under SceneEventType.Load here + break; + } + // Handle client to server LoadComplete notifications + case SceneEventType.LoadComplete: + { + // This will let you know when a load is completed + // Server Side: receives thisn'tification for both itself and all clients + if (IsServer) + { + if (sceneEvent.ClientId == NetworkManager.LocalClientId) + { + // Handle server side LoadComplete related tasks here + } + else + { + // Handle client LoadComplete **server-side** notifications here + } + } + else // Clients generate thisn'tification locally + { + // Handle client side LoadComplete related tasks here + } + + // So you can use sceneEvent.ClientId to also track when clients are finished loading a scene + break; + } + // Handle Client to Server Unload Complete Notification(s) + case SceneEventType.UnloadComplete: + { + // This will let you know when an unload is completed + // You can follow the same pattern above as SceneEventType.LoadComplete here + + // Server Side: receives thisn'tification for both itself and all clients + // Client Side: receives thisn'tification for itself + + // So you can use sceneEvent.ClientId to also track when clients are finished unloading a scene + break; + } + // Handle Server to Client Load Complete (all clients finished loading notification) + case SceneEventType.LoadEventCompleted: + { + // This will let you know when all clients have finished loading a scene + // Received on both server and clients + foreach (var clientId in sceneEvent.ClientsThatCompleted) + { + // Example of parsing through the clients that completed list + if (IsServer) + { + // Handle any server-side tasks here + } + else + { + // Handle any client-side tasks here + } + } + break; + } + // Handle Server to Client unload Complete (all clients finished unloading notification) + case SceneEventType.UnloadEventCompleted: + { + // This will let you know when all clients have finished unloading a scene + // Received on both server and clients + foreach (var clientId in sceneEvent.ClientsThatCompleted) + { + // Example of parsing through the clients that completed list + if (IsServer) + { + // Handle any server-side tasks here + } + else + { + // Handle any client-side tasks here + } + } + break; + } + } +} +``` + +:::tip + This code can be applied to a component on your `GameObject` that has a `NetworkManager` component attached to it. Since the `GameObject`, with the `NetworkManager` component attached to it, is migrated into the DDOL (Dont Destroy on Load) scene, it will remain active for the duration of the network game session. + With that in mind, you can cache your scene events that occurred (for debug or reference purposes) and/or add your own events that other game objects can subscribe to. The general idea is that if you want to receive all notifications from the moment you start `NetworkManager` then you will want to subscribe to `NetworkSceneManager.OnSceneEvent` immediately after starting it. +::: + +Scene event notifications provide users with all NetworkSceneManager related scene events (and associated data) through a single event handler. The one exception would be scene loading or unloading progress which users can handle with a coroutine (upon receiving a Load or Unload event) and checking the `SceneEvent.AsyncOperation.progress` value over time. + +:::caution +You will want to assign the SceneEvent.AsyncOperation to a local property of the subscribing class and have a coroutine use that to determine the progress of the scene being loaded or unloaded. +::: + +You can stop the coroutine checking the progress upon receiving any of the following event notifications for the scene and event type in question: `LoadComplete`, `UnloadComplete` to handle local scene loading progress tracking. Otherwise, you should use the `LoadEventCompleted` or `UnloadEventCompleted` to assure that when your "scene loading progress" visual handler stops it will stop at ~roughly~ the same time as the rest of the connected clients. The server will always be slightly ahead of the clients since it notifies itself locally and then broadcasts this message to all connected clients. + +### SceneEvent Properties +The SceneEvent class has values that may or may not be set depending upon the `SceneEventType`. Below are two quick lookup tables to determine which property is set for each `SceneEventType`. + +**Part-1**
+![image](/img/SceneEventProperties-1.png)
+ +**Part-2**
+![image](/img/SceneEventProperties-2.png)
+ +So, the big "take-away" from the above table is that you need to understand the `SceneEventType` context of the `SceneEvent` you are processing to know which properties are valid and you can use. As an example, it wouldn't make sense to provide the AsyncOperation for the following `SceneEventType`s: +- LoadComplete or LoadEventCompleted +- UnloadComplete or UnloadEventCompleted +- Synchronize or Resynchronize + +### SceneEventType Specific Notifications +There might be a time where you aren't interested in all of the details for each scene event type that occurs. As it just so happens, `NetworkSceneManager` includes a single delegate handler for each `SceneEventType` that is only triggered for the associated `SceneEventType`. +You can explore the [NetworkSceneManager](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.SceneEventType.html) for a full listing of the corresponding single `SceneEventType` events. +Some examples: +- NetworkSceneManager.OnLoad: Triggered when for `OnLoad` scene events. +- NetworkSceneManager.OnUnload: Triggered when for `OnUnload` scene events. +- NetworkSceneManager.OnSynchronize: Triggered when a client begins synchronizing. + +:::info +The general idea was to provide several ways to get scene event notifications. You might have a component that needs to know when a client is finished synchronizing on the client side but you don't want that component to receive notifications for loading or unloading related events. Under this scenario you would subscribe to the `NetworkManager.OnSynchronizeComplete` event on the client-side. +::: + +### When is it "OK" to Subscribe? +Possibly the more important aspect of scene event notifications is knowing when/where to subscribe. The recommended time to subscribe is immediately upon starting your `NetworkManager` as a client, server, or host. This will avoid problematic scenarios like trying to subscribe to the `SceneEventType.Synchronize` event within an overridden `NetworkBehaviour.OnNetworkSpawn` method of your `NetworkBehaviour` derived child class. The reason that is "problematic" is that the NetworkObject has to be spawned before you can subscribe to and receive events of type `SceneEventType.Synchronize` because that will occur before anything is spawned. Additionally, you would only receive notifications of any scenes loaded after the scene that has the NetworkObject (or the object that spawns it) is loaded. + +An example of subscribing to `NetworkSceneManager.OnSynchronize` for a client: +```csharp + public bool ConnectPlayer() + { + var success = NetworkManager.Singleton.StartClient(); + if (success) + { + NetworkManager.Singleton.SceneManager.OnSynchronize += SceneManager_OnSynchronize; + } + return success; + } + + private void SceneManager_OnSynchronize(ulong clientId) + { + Debug.Log($"Client-Id ({clientId}) is synchronizing!"); + } +``` +_The general idea is that this would be something you would want to do immediately after you have started the server, host, or client._ diff --git a/versioned_docs/version-2.0.0/basics/scenemanagement/scene-management-overview.md b/versioned_docs/version-2.0.0/basics/scenemanagement/scene-management-overview.md new file mode 100644 index 000000000..fc1517044 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/scenemanagement/scene-management-overview.md @@ -0,0 +1,20 @@ +--- +id: scene-management-overview +title: Scene management overview +sidebar_label: Scene management overview +--- +## Netcode Scene Management +Generally speaking, netcode aware scene management complexity can vary depending upon your project's needs and goals. Netcode for GameObjects (Netcode) provides you with two potential paths: + +### [Integrated Scene Management](using-networkscenemanager.md): +The Netcode for GameObjects scene management solution is enabled by default and provides you with a fully functional netcode scene management solution. +:::info +We highly recommend advanced developers new to Netcode for GameObjects become familiar with the integrated scene management solution before creating their own netcode scene management solution. +::: + +### [Custom Scene Management](custom-management.md): +If your project has needs that go beyond the scope of the Netcode integrated scene management solution, you can disable scene management in the editor through `NetworkManager`'s properties. + +:::caution +Writing your own scene management can be time consuming and complex. We highly recommend that only advanced users already familiar with Netcode for GameObjects take this path. +::: diff --git a/versioned_docs/version-2.0.0/basics/scenemanagement/timing-considerations.md b/versioned_docs/version-2.0.0/basics/scenemanagement/timing-considerations.md new file mode 100644 index 000000000..5a54a771e --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/scenemanagement/timing-considerations.md @@ -0,0 +1,162 @@ +--- +id: timing-considerations +title: Timing considerations +sidebar_label: Timing considerations +--- +import ImageSwitcher from '@site/src/ImageSwitcher.js'; + +:::caution +If you haven't already read the [Using NetworkSceneManager](using-networkscenemanager.md) and/or [Scene Events](scene-events.md) sections, it's highly recommended to do so before proceeding. +::: + +## Introduction +Netcode for GameObjects handles many of the more complicated aspects of scene management. This section is tailored towards those who want to better understand the client-server communication sequence for scene events as they occur over time. +In each diagram, you will see two types of arrows: +- Horizontal arrows: Denotes a progression to the next state and/or event. +- Diagonal arrows: Denotes a message being sent (server to client or vice versa). + - These arrows will have the name of the message being sent or the type of the scene event message being sent. + + +## Client Synchronization: +The below diagram, Client Synchronization Updates, steps you through the entire client synchronization process from starting the client all the way to the client being fully synchronized and connected. +
+ +
+Above, we can see that the client will first send a connection request to the server that will process the request, handle the approval process via ConnectionApprovalCallback (if connection approval is enabled), and if approved the server will: +- Send a connection approved message back to the client + - This only means the connection is approved, but the client isn't yet synchronized. +- Start a `SceneEventType.Synchronize` scene event + - Build the synchronization message ("Synchronize NetworkObjects") that will contain all of the + information a client will need to properly synchronize with the current netcode game session. + - Send the scene event message to the client + +:::info +Throughout the integrated scene management sections, we have used the term "client synchronization period" to denote the time it takes a client to: +- Receive the synchronization message +- Process the synchronization message +- Send the `SceneEventType.SynchronizeComplete` message back to the server
+If you look at the server part of the timeline in the above diagram, the "client synchronization period" is everything that occurs within the green bracketed section between "Synchronize NetworkObjects" and "Synchronization" on the server-side. On the client-side, it includes the time it takes to receive the `SceneEventType.Synchronize` message, "Handle (the) SceneEvent", and the time it takes for the server to receive the `SceneEventType.SynchronizeComplete` that is sent by the client once it has completed synchronizing. +::: + +Upon receiving the synchronization event message, the client processes it, and when finished the client sends the `SceneEventType.SynchronizeComplete` scene event message. The client-side will (within the same frame) invoke the following local `NetworkSceneManager` callbacks (if subscribed to): +- `OnSceneEvent` with the scene event type being set to `SceneEventType.SynchronizeComplete` +- `OnSynchronizeComplete` + +:::important +Take note that after the client finishes processing the synchronization event, the server lags, in regards to when callbacks are triggered, behind the client. Typically this client-server latency is half RTT, and so you should be aware that just because a client hasn'tified locally that it has finished there is a small period of time (commonly in the 10's to 100's of milliseconds) where the server is still unaware of the client having finished synchronizing. +::: + +### Client-Side Synchronization Timeline +Now that we have covered the high-level synchronization process, we can dive a little deeper into what happens on the client side as it processes the synchronize message. The below sub-diagram, "Scene Event Synchronization Timeline", provides you with a more detailed view of how the client processes the synchronize message: +
+ +
+You can see that upon receiving the message, the client appears to be iterating over portions of the data included in the synchronize message. This is primarily the asynchronous scene loading phase of the client synchronization process. This means the more scenes loaded, the more a client will be required to load which means the Client Synchronization Period is directly proportional to the number of scene being loaded and the size of each scene being loaded. Once all scenes are loaded, the client will then locally spawn all NetworkObjects. As a final step, the client sends the list of all `NetworkObjectId`s it spawned as part of its `SceneEventType.SynchronizeComplete` scene event message so the server can determine if it needs to resynchronize the client with a list of any `NetworkObjects` that might have despawned during the Client Synchronization Period. + +## Loading Scenes + +### LoadSceneMode.Additive +Looking at the timeline diagram below, "Loading an Additive Scene", we can see that it includes a server, two clients, and that the server will always precede all clients when it comes to processing the scene loading event. The big-picture this diagram is conveying is that only when the server has finished loading the scene and spawning any in-scene placed NetworkObjects, instantiated by the newly loaded scene, will it send the scene loading event message to the clients. + +Another point of interest in the below diagram is how Client 1 receives the scene loading event, processes it, and then responds with a `SceneEventType.LoadComplete` scene event message before client 2. This delta between client 1 and client 2 represents the varying client-server latencies and further enforces the underlying concept behind the `SceneEventType.LoadEventCompleted` message. Once a server has received all `SceneEventType.LoadComplete` messages from the connected clients, it will then broadcast the `SceneEventType.LoadEventCompleted` message to all connected clients. At this point, we can consider the scene loading event (truly) complete and all connected clients are able to receive and process netcode messages. +
+ +
+:::caution +While a client can start sending the server messages (including NetworkVariable changes) upon local `SceneEventType.LoadComplete` event notifications, under more controlled testing environments where the network being used has little to no latency (that is, using loopback with multiple instances running on the same system or using your LAN), this approach won't expose latency related issues. Even though the timing might "work out" under controlled low latency conditions you can still run into edge case scenarios where if a client approaches or exceeds a 500ms RTT latency you can potentially run into issues. +::: + +:::tip +It is recommended that if your project's design requires that one or more `NetworkBehaviour`s immediately send any form of client to server message (that is, changing a `NetworkVariable`, sending an RPC, sending a custom message, etc.) upon a client being locally notified of a `SceneEventType.LoadComplete` then you should test with artificial/simulated network conditions. +[Learn More About Simulating NetworkConditions Here](../../tutorials/testing/testing_with_artificial_conditions.md) +::: + +### LoadSceneMode.Single (a.k.a. Scene Switching) +Loading a scene in `LoadSceneMode.Single` mode via `NetworkSceneManager` is almost exactly like loading a scene additively. The primary difference between additively loading and single mode loading is that when loading a scene in single mode: +- all currently loaded scenes are unloaded +- all NetworkObjects that have `DestroyWithScene` set to `true` will be despawned and destroyed. + +How you load scenes is up to your project/design requirements. + +- **Boot Strap Usage Pattern (Additive Loading Only)** + - Your only single mode loaded scene is the first scene loaded (that is, scene index of 0 in the scenes in build list within your project's build settings). + - Because your single mode loaded scene is automatically loaded, the server and all clients will already have this scene loaded + - To prevent clients from loading the bootstrap scene, you should use server-side [scene validation](using-networkscenemanager.md#scene-validation) + - All other scenes are loaded additively + - There is no real need to preserve any NetworkObject you want to persist when loading a scene additively. + - You need to keep track of the scenes loaded by `NetworkSceneManager` to be able to unload them. + +- **Scene Switch Usage Pattern (Single and Additive Loading)** + - Typically this usage pattern is desirable when your project's design separates "areas" by a primary scene that may have companion additively loaded scenes. + - Any scenes loaded by `NetworkSceneManager`, before scene switching, will be unloaded and any NetworkObject that has the `DestroyWithScene` set to `true` will be destroyed. + - If `DestroyWithScene` is set to `false` it will be "preserved" (_see the sub-diagram "Load New Scene Timeline" below_) + +
+ +
+ +## Load New Scene Timeline (Sub-Diagram) +Both scene loading diagrams (Additive and Single) refer to the below sub-diagram that provides additional details of the scene loading process. +:::important +When looking at the below sub-diagram, both single and additive scene loading modes use close to the exact same flow with the exception that additively loaded scenes only flow through the four middle steps that are grouped together by the light red filled region highlighted by red dashed lines. When loading a scene additively, no other scenes are unloaded nor are any NetworkObjects moved into the DDoL temporarily. This setting, by default, is true for dynamically spawned NetworkObjects unless otherwise specified when using `NetworkObject.Spawn`, `NetworkObject.SpawnWithOwnership`, or `NetworkObject.SpawnAsPlayerObject`. In-scene placed NetworkObject's, by default, will be destroyed with the scene that instantiated them. At any point, within a `NetworkBehaviour` you can change the `NetworkObject.DestroyWithScene` property. +::: + +
+ +
+ +**Load New Scene Additively** +1. Starts loading the scene +2. During the scene loading process, in-scene placed NetworkObjects are instantiated and their `Awake` and then `Start` methods are invoked (in that order). +3. Scene loading completes. +4. All in-scene placed NetworkObjects are spawned. +:::caution +Step #3 above signifies that the `UnityEngine.SceneManagement.SceneManager` has finished loading the scene. If you subscribe to the `UnityEngine.SceneManagement.SceneManager.sceneLoaded` event, then step #3 would happen on the same frame that your subscribed handler is invoked. **Don't use this event** as a way to determine that the current Load SceneEvent has completed.
_Doing so will result in unexpected results that most commonly are associated with "yet-to-be-spawned" (locally) NetworkObject's and/or their related `NetworkBehaviour` dependencies._
When using Netcode Integrated SceneManagement, it's recommended to use the `NetworkSceneManager` scene events to determine when the "netcode scene loading event" has completed locally or for all clients. +::: + +**Load New Scene Single (a.k.a "Scene Switching")** +1. All `NetworkObjects` that have their `DestroyWithScene` property set to `false` are migrated into the DDoL (temporarily). +2. All currently loaded scenes are unloaded. If you loaded any scenes additively, they will be automatically unloaded. +3. _(refer to the 4 steps outlined above)_ +4. After any newly instantiated NetworkObjects are spawned, the newly loaded scene is set as the currently active scene and then the NetworkObjects that were previously migrated into th DDoL (from step 1) are now migrated into the newly loaded scene. + +## Unloading a Scene +Primarily, this applies to unloading additively loaded scenes via th `NetworkSceneManager.UnloadScene` method. to unload a scene it must: +- have been additively loaded by `NetworkSceneManager` +- still be loaded + + +### Unloading an Additive Scene +If you look at the below diagram, "Unloading an Additive Scene", you will see a similar flow as that of loading a scene. The server still initiates the `SceneEventType.Unload` scene event and won't send this message to clients until it has completed the `Unload` scene event locally. + +
+ +
+ +### Unloading Scenes Timeline: +Review over the below diagram and take note of the following things: +- **Server Side:** + - When a server starts the `SceneEventType.Unload` event, Unity will naturally being to destroy all `GameObjects` in the scene being unloaded. + - If a `GameObject` has a NetworkObject component attached to it and it's still considered spawned at the time the `GameObject` is destroyed, then the NetworkObject will be despawned before the `GameObject` being destroyed. + - This will cause a series of server-to-client despawn messages to be sent to all clients. +- **Client Side:** + - While a server is unloading a scene, the client can begin to receive a bunch of despawn messages for the NetworkObjects being destroyed on the server-side while the scene is being unloaded. + - By the time a client receives the `SceneEventType.Unload` scene event message, it well can have no remaining NetworkObjects in the scene being unloaded. This won't impact the client-side scene unloading process, but it's useful to know that this will happen. + +
+ +
diff --git a/versioned_docs/version-2.0.0/basics/scenemanagement/using-networkscenemanager.md b/versioned_docs/version-2.0.0/basics/scenemanagement/using-networkscenemanager.md new file mode 100644 index 000000000..6ea7b4e74 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/scenemanagement/using-networkscenemanager.md @@ -0,0 +1,374 @@ +--- +id: using-networkscenemanager +title: Using NetworkSceneManager +sidebar_label: Using NetworkSceneManager +--- + +## Netcode for GameObjects Integrated Scene Management +### The `NetworkSceneManager` Provides You With: +- An existing framework that supports both the bootstrap and scene transitioning scene management usage patterns. +- Automated client synchronization that occurs when a client is connected and approved. + - All scenes loaded via `NetworkSceneManager` will be synchronized with clients during client synchronization. + - _Later in this document, you will learn more about using scene validation to control what scenes are synchronized on the client, server, or both side(s)_. + - All spawned Netcode objects are synchronized with clients during client synchronization. +- A comprehensive scene event notification system that provides you with a plethora of options. + - It provides scene loading, unloading, and client synchronization events ("Scene Events") that can be tracked on both the client and server. + - The server is notified of all scene events (including the clients') + - The server tracks each client's scene event progress (scene event progress tracking) + - Clients receive scene event messages from the server, trigger local notifications as it progresses through a Scene Event's, and send local notifications to the server. + - _clients **aren't** aware of other clients' scene events_ + - In-Scene placed NetworkObject synchronization + +:::info +In-Scene placed NetworkObjects can be used in many ways and are treated uniquely from that of dynamically spawned NetworkObjects. An in-scene placed NetworkObject is a GameObject with a NetworkObject and typically at least one `NetworkBehaviour` component attached to a child of or the same `GameObject`. it's recommended to read through all integrated scene management materials (this document, [Scene Events](scene-events.md), and [Timing Considerations](timing-considerations.md)) before learning about more advanced [In-Scene (placed) NetworkObjects](inscene-placed-networkobjects.md) topics. +::: + +All of these scene management features (and more) are handled by the [`NetworkSceneManager`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkSceneManager.html). + +### Accessing `NetworkSceneManager` +The `NetworkSceneManager` lives within the `NetworkManager` and is instantiated when the `NetworkManager` is started. +`NetworkSceneManager` can be accessed in the following ways: +- From within a `NetworkBehaviour` derived component, you can access it by using `NetworkManager.SceneManager` +- From anything else that does not already have a reference to `NetworkManager`, you can access it using `NetworkManager.Singleton.SceneManager`. + +**Here are some genral rules about accessing and using `NetworkSceneManager`:** +- Don't try to access the `NetworkSceneManager` when the `NetworkManager` is shutdown (it won't exist). + - The `NetworkSceneManager` is only instantiated when a `NetworkManager` is started. +- As a server: + - Any scene you want synchronized with currently connected or late joining clients **must** be loaded via the `NetworkSceneManager` + - If you use the `UnityEngine.SceneManagement.SceneManager` during a netcode enabled game session, then those scenes won't be synchronized with currently connected clients. + - A "late joining client" is a client that joins a game session that has already been started and there are already one or more clients connected. + - _All netcode aware objects spawned before a late joining client will be synchronized with a late joining client._ + - If you load a scene on the server-side using `UnityEngine.SceneManagement.SceneManager` then it will be synchronized with late joining clients unless you use server-side scene validation (see [Scene Validation](using-networkscenemanager.md#scene-validation)). + - _It is a best practice to use `NetworkSceneManager` when loading scenes _ + - The server does not require any clients to be currently connected before it starts loading scenes via the `NetworkSceneManager`. + - Any scene loaded via `NetworkSceneManager` will always default to being synchronized with clients. + - _Scene validation is explained later in this document._ + +:::warning +Don't try to access the `NetworkSceneManager` when the `NetworkManager` is shutdown. The `NetworkSceneManager` is only instantiated when a `NetworkManager` is started. _This same rule is true for all Netcode systems that reside within the `NetworkManager`_. +::: + +### Loading a Scene +In order to load a scene, there are four requirements: +1. The `NetworkManager` instance loading the scene must be a host or server. +2. The `NetworkSceneManager.Load` method is used to load the scene. +3. The scene being loaded must be registered with your project's [build settings scenes in build list](https://docs.unity3d.com/Manual/BuildSettings.html) +4. A Scene Event can't already be in progress. + +#### Basic Scene Loading Example +Imagine that you have an in-scene placed NetworkObject, let's call it "ProjectSceneManager", that handles your project's scene management using the `NetworkSceneManager` and you wanted your server to load a scene when the ProjectSceneManager is spawned. +In its simplest form, it can look something like: +```csharp +public class ProjectSceneManager : NetworkBehaviour +{ + /// INFO: You can remove the #if UNITY_EDITOR code segment and make SceneName public, + /// but this code assures if the scene name changes you won't have to remember to + /// manually update it. +#if UNITY_EDITOR + public UnityEditor.SceneAsset SceneAsset; + private void OnValidate() + { + if (SceneAsset != null) + { + m_SceneName = SceneAsset.name; + } + } +#endif + [SerializeField] + private string m_SceneName; + + public override void OnNetworkSpawn() + { + if (IsServer && !string.IsNullOrEmpty(m_SceneName)) + { + var status = NetworkManager.SceneManager.LoadScene(m_SceneName, LoadSceneMode.Additive); + if (status != SceneEventProgressStatus.Started) + { + Debug.LogWarning($"Failed to load {m_SceneName} " + + $"with a {nameof(SceneEventProgressStatus)}: {status}"); + } + } + } +} +``` +In the above code snippet, we have a `NetworkBehaviour` derived class, `ProjectSceneManager`, that has a public `SceneAsset` property (editor only property) that specifies the scene to load. In the `OnNetworkSpawn` method, we make sure that only the server loads the scene and we compare the `SceneEventProgressStatus` returned by the `NetworkSceneManager.Load` method to the `SceneEventProgressStatus.Started` status. If we received any other status, then typically the name of the status provides you with a reasonable clue as to what the issue might be. + +### Scene Events and Scene Event Progress +The term "Scene Event" refers to all subsequent scene events that transpire over time after a server has started a `SceneEventType.Load` ("load") or `SceneEventType.Unload` ("unload") Scene Event. For example, a server might load a scene additively while clients are connected. The following high-level overview provides you with a glimpse into the events that transpire during a load Scene Event(_it assumes both the server and clients have registered for scene event notifications_): +- Server starts a `SceneEventType.Load` event + - The server begins asynchronously loading the scene additively + - The server will receive a local notification that the scene load event has started + - Once the scene is loaded, the server spawns any in-scene placed `NetworkObjects` locally + - After spawning, the server will send the `SceneEventType.Load` message to all connected clients along with information about the newly instantiated and spawned NetworkObjects. + - The server will receive a local `SceneEventType.LoadComplete` event + - This only means the server is done loading and spawning `NetworkObjects` instantiated by the scene being loaded. +- The clients receive the scene event message for the `SceneEventType.Load` event and begin processing it. + - Upon starting the scene event, the clients will receive a local notification for the SceneEventType.Load event. + - As each client finishes loading the scene, they will generate a `SceneEventType.LoadComplete` event + - The client sends this event message to the server. + - The client is notified locally of this scene event. + - This notification only means that it has finished loading and synchronizing the scene, but does not mean all other clients have! +- The server receives the `SceneEventType.LoadComplete` events from the clients + - When all `SceneEventType.LoadComplete` events have been received, the server will generate a `SceneEventType.LoadEventCompleted` notification + - This notification is triggered locally on the server + - The server broadcasts this scene event to the clients +- Each client receives the `SceneEventType.LoadEventCompleted` event + - At this point all clients have completed the `SceneEventType.Load` event and are synchronized with all newly instantiated and spawned `NetworkObjects`. + +The purpose behind the above outline is to show that a Scene Event can lead to other scene event types being generated and that the entire sequence of events that transpire occur over a longer period of time than if you were loading a scene in a single player game. + +:::tip +When you receive the `SceneEventType.LoadEventCompleted` or the `SceneEventType.SynchronizeComplete` you know that the server or clients can start invoking netcode specific code (that is, sending Rpcs, updating `NetworkVariable`s, etc.). Alternately, `NetworkManager.OnClientConnectedCallback` is triggered when a client finishes synchronizing and can be used in a similar fashion. _However, that only works for the initial client synchronization and not for scene loading or unloading events._ +::: + +:::warning +Because the `NetworkSceneManager` still has additional tasks to complete once a scene is loaded, it isn't recommended to use `UnityEngine.SceneManagement.SceneManager.sceneLoaded` or `UnityEngine.SceneManagement.SceneManager.sceneUnloaded` as a way to know when a scene event has completed. These two callbacks will occur before `NetworkSceneManager` has finished the final processing after a scene is loaded or unloaded and you can run into timing related issues. If you are using the netcode integrated scene management, then it's highly recommended to subscribe to the `NetworkSceneManager`'s scene event notifications. +::: + +#### Scene Event Notifications +You can be notified of scene events by registering in one of two ways: +1. Receive all scene event notification types: `NetworkSceneManager.OnSceneEvent` +2. Receive only a specific scene event notification type: [`NetworkSceneManager`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkSceneManager.html#events) has one for each [`SceneEventType`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.SceneEventType.html)
+:::info +Receiving (via subscribing to the associated event callback) only specific scene event notification types does not change how a server or client receives and processes notifications. +::: + +**Receiving All Scene Event Notifications** +Typically, this is used on the server side to receive notifications for every scene event notification type for both the server and clients. You can receive all scene event type notifications by subscribing to the `NetworkSceneManager.OnSceneEvent` callback handler. + +**Receiving A Specific Scene Event Notification** +Typically, this is used with clients or components that might only need to be notified of a specific scene event type. There are 9 scene event types and each one has a corresponding "single event type" callback handler in `NetworkSceneManager`. + +**As an example:** +You might want to register for the `SceneEventType.LoadEventCompleted` scene event type to know, from a client perspective, that the server and all other clients have finished loading a scene. This notification lets you know when you can start performing other netcode related actions on the newly loaded and spawned NetworkObjects. + +#### Scene Event Progress Status +As we discussed in the earlier code example, it's important to check the status returned by `NetworkSceneManager.Load` to make sure your scene loading event has started. The following is a list of all [SceneEventProgressStatus](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.SceneEventProgressStatus.html) `enum` values with some additional helpful information: +- Started + - The scene event has started (success) +- SceneNotLoaded + - This is returned when you attempt to unload a scene that isn't loaded (or no longer is loaded) +- SceneEventInProgress + - This is returned if you attempt to start a new scene event (loading or unloading) while one is still in progress +- InvalidSceneName + - This can happen for one of the two reasons: + - The scene name isn't spelled correctly or the name of the scene changed + - The scene name isn't in your project's scenes in build list +- SceneFailedVerification + - This is returned if you fail scene verification on the server side (more about scene verification later) +- InternalNetcodeError + - Currently, this error will only happen if the scene you are trying to unload was never loaded by the `NetworkSceneManager`. + +### Unloading a Scene +Now that we understand the loading process, scene events, and can load a scene additively, the next step is understanding the integrated scene management scene unloading process. to unload a scene, here are the requirements: +1. The `NetworkManager` instance unloading the scene should have already been started in host or server mode and already loaded least one scene additively.
+ 1.1 Only additively loaded scenes can be unloaded. + 1.2 You can't unload the currently active scene (see info dialog below) +2. The `NetworkSceneManager.Unload` method is used to unload the scene +3. A Scene Event can't already be in progress + +:::info +Unloading the currently active scene, in Netcode, is commonly referred to as "scene switching" or loading another scene in `LoadSceneMode.Single` mode. This will unload all additively loaded scenes and upon the new scene being loaded in `LoadSceneMode.Single` mode it's set as the active scene and the previous active scene is unloaded. +::: + +#### Basic Scene Unloading Example +Below we are taking the previous scene loading example, the `ProjectSceneManager` class, and modifying it to handle unloading. This includes keeping a reference to the `SceneEvent.Scene` locally in our class because `NetworkSceneManager.Unload` requires the `Scene` to be unloaded. + +**Below is an example of how to:** +- Subscribe the server to `NetworkSceneManager.OnSceneEvent` notifications. +- Save a reference to the `Scene` that was loaded. +- Unload a scene loaded additively by `NetworkSceneManager`. +- Detect the scene event types associated with loading and unloading. + +```csharp +public class ProjectSceneManager : NetworkBehaviour +{ + /// INFO: You can remove the #if UNITY_EDITOR code segment and make SceneName public, + /// but this code assures if the scene name changes you won't have to remember to + /// manually update it. +#if UNITY_EDITOR + public UnityEditor.SceneAsset SceneAsset; + private void OnValidate() + { + if (SceneAsset != null) + { + m_SceneName = SceneAsset.name; + } + } +#endif + [SerializeField] + private string m_SceneName; + private Scene m_LoadedScene; + + public bool SceneIsLoaded + { + get + { + if (m_LoadedScene.IsValid() && m_LoadedScene.isLoaded) + { + return true; + } + return false; + } + } + + public override void OnNetworkSpawn() + { + if (IsServer && !string.IsNullOrEmpty(m_SceneName)) + { + NetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + var status = NetworkManager.SceneManager.LoadScene(m_SceneName, LoadSceneMode.Additive); + CheckStatus(status); + } + + base.OnNetworkSpawn(); + } + + private void CheckStatus(SceneEventProgressStatus status, bool isLoading = true) + { + var sceneEventAction = isLoading ? "load" : "unload"; + if (status != SceneEventProgressStatus.Started) + { + Debug.LogWarning($"Failed to {sceneEventAction} {m_SceneName} with" + + $" a {nameof(SceneEventProgressStatus)}: {status}"); + } + } + + /// + /// Handles processing notifications when subscribed to OnSceneEvent + /// + /// class that has information about the scene event + private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) + { + var clientOrServer = sceneEvent.ClientId == NetworkManager.ServerClientId ? "server" : "client"; + switch (sceneEvent.SceneEventType) + { + case SceneEventType.LoadComplete: + { + // We want to handle this for only the server-side + if (sceneEvent.ClientId == NetworkManager.ServerClientId) + { + // *** IMPORTANT *** + // Keep track of the loaded scene, you need this to unload it + m_LoadedScene = sceneEvent.Scene; + } + Debug.Log($"Loaded the {sceneEvent.SceneName} scene on " + + $"{clientOrServer}-({sceneEvent.ClientId})."); + break; + } + case SceneEventType.UnloadComplete: + { + Debug.Log($"Unloaded the {sceneEvent.SceneName} scene on " + + $"{clientOrServer}-({sceneEvent.ClientId})."); + break; + } + case SceneEventType.LoadEventCompleted: + case SceneEventType.UnloadEventCompleted: + { + var loadUnload = sceneEvent.SceneEventType == SceneEventType.LoadEventCompleted ? "Load" : "Unload"; + Debug.Log($"{loadUnload} event completed for the following client " + + $"identifiers:({sceneEvent.ClientsThatCompleted})"); + if (sceneEvent.ClientsThatTimedOut.Count > 0) + { + Debug.LogWarning($"{loadUnload} event timed out for the following client " + + $"identifiers:({sceneEvent.ClientsThatTimedOut})"); + } + break; + } + } + } + + public void UnloadScene() + { + // Assure only the server calls this when the NetworkObject is + // spawned and the scene is loaded. + if (!IsServer || !IsSpawned || !m_LoadedScene.IsValid() || !m_LoadedScene.isLoaded) + { + return; + } + + // Unload the scene + var status = NetworkManager.SceneManager.UnloadScene(m_LoadedScene); + CheckStatus(status, false); + } +} +``` +Really, if you take away the debug logging code the major differences are: +- We store the Scene loaded when the server receives its local `SceneEventType.SceneEventType.LoadComplete` notification +- When the `ProjectSceneManager.UnloadScene` method is invoked (_assuming this occurs outside of this class_) the `ProjectSceneManager.m_LoadedScene` is checked to make sure it's a valid scene and that it's indeed loaded before we invoke the `NetworkSceneManager.Unload` method. + +### Scene Validation +Sometimes you might need to prevent the server or client from loading a scene under certain conditions. Here are a few examples of when you might do this: +- One or more game states determine if a scene is loaded additively + - Typically, this is done on the server-side. +- The scene is already pre-loaded on the client + - Typically, this is done on the client-side. +- Security purposes + - As we mentioned earlier, `NetworkSceneManager` automatically considers all scenes in the build settings scenes in build list valid scenes to be loaded. You might use scene validation to prevent certain scenes from being loaded that can cause some form of security and/or game play issue. + - it's common to do this on either the server or client side depending upon your requirements/needs. + +**Usage Example** +The below example builds upon the previous example's code, and adds some psuedo-code for server-side scene validation: +```csharp + private bool ServerSideSceneValidation(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode) + { + // Comparing against the name or sceneIndex + if (sceneName == "SomeSceneName" || sceneIndex == 3) + { + return false; + } + + // Don't allow single mode scene loading (that is, bootstrap usage patterns might implement this) + if (loadSceneMode == LoadSceneMode.Single) + { + return false; + } + + return true; + } + + public override void OnNetworkSpawn() + { + if (IsServer && !string.IsNullOrEmpty(m_SceneName)) + { + NetworkManager.SceneManager.VerifySceneBeforeLoading = ServerSideSceneValidation; + NetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + var status = NetworkManager.SceneManager.LoadScene(m_SceneName, LoadSceneMode.Additive); + CheckStatus(status); + } + + base.OnNetworkSpawn(); + } +``` +The callback is the first thing invoked on the server-side when invoking the `NetworkSceneManager.Load` method. If the scene validation fails, `NetworkSceneManager.Load` will return `SceneEventProgressStatus.SceneFailedVerification` and the scene event is cancelled. + +:::caution +**Client-Side Scene Validation**
+This is where you need to be cautious with scene validation, because any scene that you don't validate on the client side should not contain Netcode objects that are considered required dependencies for a connecting client to properly synchronize with the current netcode (game) session state. +::: + +### Dynamically Generated Scenes +You might find yourself in a scenario where you just need to dynamically generate a scene. A common use for dynamically generated scenes is when you need to dynamically generate collision geometry that you wish to only create on the server-host side. For this scenario you most likely would only want the server to have this scene loaded, but you might run into issues when synchronizing clients. For single player games, you can just create a new scene at runtime, dynamically generate the collision geometry, add the collision geometry to the newly created scene, and everything works out. With Netcode for GameObjects there are two extra steps you need to take to assure you don't run into any issues: +- Create an empty scene for each scene you plan on dynamically generating and add them to the "Scenes in Build" list found within the "Build Settings". + - For this example we would only need one (that is, we might call the scene "WorldCollisionGeometry") +- Have the server-host register for `NetworkManager.SceneManager.VerifySceneBeforeLoading` handler and return false when one of the blank scene names is being validated as a valid scene for a client to load. + - For this example we would return false any time `VerifySceneBeforeLoading` was invoked with the scene name "WorldCollisionGeometry". + + :::caution + This only works under the scenario where the dynamically generated scene is a server-host side only scene unless you write server-side code that sends enough information to a newly connected client to replicate the dynamically generated scene via RPC, a custom message, a `NetworkVariable`, or an in-scene placed NetworkObject that is in a scene, other than the dynamically generated one, and has a `NetworkBehaviour` component with an overridden `OnSynchronize` method that includes additional serialization information about how to construct the dynamically generated scene. The limitation on this approach is that the scene should not contain already spawned NetworkObjects as those won't get automatically synchronized when a client first connects. + ::: + +### Active Scene Synchronization +Setting `NetworkSceneManager.ActiveSceneSynchronizationEnabled` to true on the host or server instance will automatically synchronize both connected and late joining clients with changes in the active scene by the server or host. When enabled, the server or host instance subscribes to the `SceneManager.activeSceneChanged` event and generates `SceneEventType.ActiveSceneChanged` messages when the active scene is changed. Disabling this property just means the server or host instance unsubscribes from the event and will no longer send `SceneEventType.ActiveSceneChanged` messages to keep client's synchronized with the server or host's currently active scene. + +This typically is most useful when the `NetworkSceneManager.ClientSynchronizationMode` is set to `LoadSceneMode.Additive` via the `NetworkSceneManager.SetClientSynchronizationMode` method on a server or host instance. + +See Also: [Client Synchronization Mode](client-synchronization-mode.md) + + +### What Next? +We have covered how to access the `NetworkSceneManager`, how to load and unload a scene, provided a basic overview on scene events and notifications, and even briefly discussed in-scene placed NetworkObjects. You now have the fundamental building-blocks one needs to learn more advanced integrated scene management topics. +_We recommend proceeding to the next integrated scene management topic, "Client Synchronization Mode", in the link below._ + + diff --git a/versioned_docs/version-2.0.0/basics/spawning-synchronization.md b/versioned_docs/version-2.0.0/basics/spawning-synchronization.md new file mode 100644 index 000000000..118c56487 --- /dev/null +++ b/versioned_docs/version-2.0.0/basics/spawning-synchronization.md @@ -0,0 +1,126 @@ +--- +id: spawning-synchronization +title: Spawning synchronization +--- + +Ensuring that objects spawn in a synchronized manner across clients can be difficult, although the challenges differ depending on which [network topology](../terms-concepts/network-topologies.md) you're using. + +## Spawning synchronization in client-server + +Due to the restrictive nature of only the server having the authority to spawn objects, visual anomalies between clients are common in [client-server](../terms-concepts/client-server.md) contexts. This typically occurs when using an owner-authoritative motion model, also known as a ClientNetworkTransform, and trying to visually synchronize the spawning of an object relative to a local player's current position (for example, a player shooting a projectile). + +![Client server spawn synchronization](/img/client-server-spawn-sync.jpg) + +In the diagram above, a client-owned object (a player) is in motion while firing a projectile. The code implementing this behavior involves sending a message (typically an [RPC](../advanced-topics/messaging-system.md)) from the client to the server to get an object to spawn based on the player's input. Due to the latency involved in sending this message to the server and then propagating the resulting spawn message across all clients, including back to the initiating client, the spawn projectile message reaches the initiating client after the client's local player has moved, resulting in a visual disconnect between the local player and the projectile. + +These types of issues are commonly resolved using a client prediction system or by separating the visual representation of the projectile from the NetworkObject and writing a script that moves the visual representation in the desired direction of the yet-to-be-spawned NetworkObject and then synchronizing the two over time once the NetworkObject is spawned. Both approaches are complex to implement in a client-server topology. + +## Spawning synchronization in distributed authority + +Managing spawning synchronization is easier in [distributed authority](../terms-concepts/distributed-authority.md) contexts, because the authority to spawn objects is shared between clients. Instead of a client having to send an RPC to the server, wait roughly the round trip time (RTT) between itself and the server-host, and then receive and process the spawn message, the client just spawns the projectile in the precise position desired relative to the player object, as explained in the diagram below. + +![Distributed authority spawn synchronization](/img/distributed-authority-spawn-sync.jpg) + +Handling this visual synchronization issue is similar to the traditional Unity development flow, with one additional step: + +1. Instantiate a (network) prefab instance. +2. Position/Configure the instantiated prefab instance. +3. (New step) Spawn the prefab instance. + +Transform changes on non-authoritative instances are network tick synchronized by the distributed authority service. This allows for remote-client cloned object instances to be visually synchronized with other spawned objects across all clients, since consumption and processing of transform state updates are processed relative to each clients' currently known network tick value. + +Distributed authority also provides a simpler way to visually synchronize the despawning of NetworkObjects (relative to a client-server session). Refer to the [deferred despawning page](deferred-despawning.md) for more details. + +## Examples + +Below are two example scripts, one that handles spawning using the client-server mode, and one that uses distributed authority mode. + +* The client-server script requires splitting the logic between client and server. The client has to send a message to the server in order to spawn the projectile, which introduces the latency previously described between a player's motion and the projectile being spawned. +* With distributed authority, spawning follows a more traditional single-player usage pattern. The notable difference from the client-server approach is that there's no need to send a message to a server in order to spawn the NetworkObject. + +### Client-server + +``` +/// +/// Client-Server Spawning +/// Pseudo player weapon that is assumed to be +/// attached to a fixed child node of the player. +/// (i.e. like a hand node or the like) +/// +public class PlayerWeapon : NetworkBehaviour +{ + // For example purposes, the projectile to spawn + public GameObject Projectile; + + + // For example purposes, an offset from the weapon + // to spawn the projectile. + public GameObject WeaponFiringOffset; + + + public void FireWeapon() + { + if (!IsOwner) + { + return; + } + + + if (IsServer) + { + OnFireWeapon(); + } + else + { + OnFireWeaponServerRpc(); + } + } + + + [ServerRpc] + private void OnFireWeaponServerRpc() + { + OnFireWeapon(); + } + + + private void OnFireWeapon() + { + var instance = Instantiate(Projectile); + var instanceNetworkObject = instance.GetComponent(); + instance.transform.position = WeaponFiringOffset.transform.position; + instance.transform.forward = transform.forward; + instanceNetworkObject.Spawn(); + } +} +``` + +### Distributed authority + +``` +/// +/// Pseudo player weapon that is assumed to be +/// attached to a fixed child node of the player. +/// (i.e. like a hand node or the like) +/// +public class PlayerWeapon : NetworkBehaviour +{ + // For example purposes, the projectile to spawn + public GameObject Projectile; + + + // For example purposes, an offset from the weapon + // to spawn the projectile. + public GameObject WeaponFiringOffset; + + + public void FireWeapon() + { + var instance = Instantiate(Projectile); + var instanceNetworkObject = instance.GetComponent(); + instance.transform.position = WeaponFiringOffset.transform.position; + instance.transform.forward = transform.forward; + instanceNetworkObject.Spawn(); + } +} +``` diff --git a/versioned_docs/version-2.0.0/community-contributions/codemonkey-videos.md b/versioned_docs/version-2.0.0/community-contributions/codemonkey-videos.md new file mode 100644 index 000000000..60d381b04 --- /dev/null +++ b/versioned_docs/version-2.0.0/community-contributions/codemonkey-videos.md @@ -0,0 +1,63 @@ +--- +id: codemonkey +title: Code Monkey tutorial videos +--- + +Code Monkey has a Multiplayer Unity Networking tutorial video that teachs various concepts of Netcode for GameObjects. Learn more about Code Monkey and his [Youtube channel](https://www.youtube.com/c/CodeMonkeyUnity), and don't forget to click the **Subscribe** button. + +This Unity Multiplayer video tutorial teaches you how to use Unity's Netcode For GameObjects to create a multiplayer game. + +