diff --git a/.yamato/_triggers.yml b/.yamato/_triggers.yml index a1c2779ac7..ac4356cd7a 100644 --- a/.yamato/_triggers.yml +++ b/.yamato/_triggers.yml @@ -90,8 +90,8 @@ pr_code_changes_checks: # Note that our daily tests will anyway run both test configurations in "minimal supported" and "trunk" configurations - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }} - .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }} - # Run code coverage test - - .yamato/code-coverage.yml#code_coverage_ubuntu_{{ validation_editors.default }} + # Run code coverage test (PRs use the pinned "safe" trunk) + - .yamato/code-coverage.yml#code_coverage_project_test_testproject_ubuntu_{{ pinnedTrunk }} triggers: expression: |- (pull_request.comment eq "ngo" OR @@ -161,8 +161,8 @@ develop_nightly: - .yamato/project-updated-dependencies-test.yml#updated-dependencies_testproject_NGO_win_{{ validation_editors.default }} # Run API validation to early-detect all new APIs that would force us to release new minor version of the package. Note that for this to work the package version in package.json must correspond to "actual package state" which means that it should be higher than last released version - .yamato/vetting-test.yml#vetting_test - # Run code coverage test - - .yamato/code-coverage.yml#code_coverage_ubuntu_{{ validation_editors.default }} + # Run code coverage test (nightly uses actual trunk) + - .yamato/code-coverage.yml#code_coverage_project_test_testproject_ubuntu_trunk # Run all tests on weekly bases diff --git a/.yamato/code-coverage.yml b/.yamato/code-coverage.yml index 64a2692225..18adbef416 100644 --- a/.yamato/code-coverage.yml +++ b/.yamato/code-coverage.yml @@ -2,7 +2,8 @@ --- # DESCRIPTION-------------------------------------------------------------------------- - # This job is responsible for executing package tests with code coverage analysis enabled. + # This job runs the in-repo testproject tests with code coverage analysis enabled. + # The project tests also exercise the NGO package tests, so a separate package-test coverage job is not needed. # Coverage analysis provides insights into: # Test coverage metrics for NGO assemblies # Line and branch coverage statistics @@ -10,25 +11,28 @@ # Additional metrics for coverage analysis # CONFIGURATION STRUCTURE-------------------------------------------------------------- - # Jobs are generated using nested loops through: - # 1. For default platform only (Ubuntu) since coverage would not vary between platforms (no need for checks on more platforms) - # 2. For default editor version (trunk) since coverage would not vary between editors (no need for checks on more editors) + # Jobs are generated for: + # 1. Default platform only (Ubuntu) since coverage would not vary between platforms (no need for checks on more platforms) + # 2. Two editors: actual "trunk" (used by nightly) and the "pinnedTrunk" safe version (used by PRs). + # Occasionally trunk breaks our tests, so PRs run against a pinned "safe" trunk while nightly runs actual trunk. #TECHNICAL CONSIDERATIONS--------------------------------------------------------------- - # In theory this job also runs package tests, but we don't want to use it as default since is heavier (because of added coverage analysis) and coverage is not changing that often # Requires Unity Editor installation # Burst compilation is disabled to ensure accurate coverage measurement # In order to properly use -coverage-results-path parameter we need to start it with $PWD (which means the absolute path). Otherwise coverage results will not be visible # QUALITY CONSIDERATIONS-------------------------------------------------------------------- - # To see where this job is included (in trigger job definitions) look into _triggers.yml file - + # To see where this job is included (in trigger job definitions) look into _triggers.yml file. Currently: + # [Code Coverage] Project Test runs on PR changes (pr_code_changes_checks, pinnedTrunk) and nightly (develop_nightly, trunk) +{% assign coverage_editors = "trunk," | append: pinnedTrunk | split: "," -%} {% for platform in test_platforms.default -%} -{% for editor in validation_editors.default -%} -code_coverage_{{ platform.name }}_{{ editor }}: - name: Code Coverage - NGO [{{ platform.name }}, {{ editor }}] +{% for project in projects.default -%} +{% for editor in coverage_editors -%} +{% if editor == "trunk" -%}{% assign editor_label = "trunk" -%}{% else -%}{% assign editor_label = "pinnedTrunk" -%}{% endif -%} +code_coverage_project_test_{{ project.name }}_{{ platform.name }}_{{ editor }}: + name: '[Code Coverage] Project Test - NGO {{ project.name }} [{{ platform.name }}, {{ editor }}]' agent: type: {{ platform.type }} image: {{ platform.image }} @@ -38,13 +42,14 @@ code_coverage_{{ platform.name }}_{{ editor }}: {% endif %} commands: - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models - - upm-pvp create-test-project test-project --packages "upm-ci~/packages/*.tgz" --unity .Editor - - UnifiedTestRunner --suite=editor --suite=playmode --editor-location=.Editor --testproject=test-project --enable-code-coverage --coverage-upload-options="reportsDir:$PWD/test-results/CoverageResults;name:NGOv2_{{ platform.name }}_{{ editor }};flags:NGOv2_{{ platform.name }}_{{ editor }};verbose" --coverage-results-path=$PWD/test-results/CoverageResults --coverage-options="generateHtmlReport;generateAdditionalMetrics;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime" --extra-editor-arg=--burst-disable-compilation --timeout={{ test_timeout }} --rerun-strategy=Test --retry={{ num_test_retries }} --clean-library-on-rerun --artifacts-path=test-results + - UnifiedTestRunner --testproject={{ project.path }} --suite=editor --suite=playmode --editor-location=.Editor --enable-code-coverage --coverage-upload-options="reportsDir:$PWD/test-results/CoverageResults;name:NGOv2_project_{{ project.name }}_{{ platform.name }}_{{ editor_label }};flags:NGOv2_project_{{ project.name }}_{{ platform.name }}_{{ editor_label }};verbose" --coverage-results-path=$PWD/test-results/CoverageResults --coverage-options="generateHtmlReport;generateAdditionalMetrics;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime" --extra-editor-arg=--burst-disable-compilation --timeout={{ test_timeout }} --rerun-strategy=Test --retry={{ num_test_retries }} --clean-library-on-rerun --artifacts-path=test-results artifacts: logs: paths: - "test-results/**/*" dependencies: + - .yamato/_run-all.yml#run_quick_checks # initial checks to perform fast validation of common errors - .yamato/package-pack.yml#package_pack_-_ngo_{{ platform.name }} {% endfor -%} {% endfor -%} +{% endfor -%} diff --git a/Tools/CI/service.cmb/README.md b/Tools/CI/service.cmb/README.md index e50f68b86b..97079d3393 100644 --- a/Tools/CI/service.cmb/README.md +++ b/Tools/CI/service.cmb/README.md @@ -1,6 +1,6 @@ # Testing against the CMB Service -The CMB Service is a tool that is external to our repository. The tool is inside the `runtime` folder in the [mps-common-multiplayer-backend](https://github.com/Unity-Technologies/mps-common-multiplayer-backend) repository. +The CMB Service is a tool that is external to our repository. The tool is inside the `runtime` folder of the [CMB service](https://github.com/Unity-Technologies/unity-player-services/tree/main/services/common-multiplayer-backend) in the Unity Player Services monorepo. Due to this, there is some more setup needed when running tests against the CMB Service. @@ -56,3 +56,7 @@ The following environment variables allow for further configuration of the setup `CMB_SERVICE_PORT` defines the port where the tests will try to connect to the service (defaults to `7789`). `NGO_HOST` defines the http address where the tests will try to connect to the service (defaults to `127.0.0.1`). + +## Running on CI (Yamato) + +The CMB tests can also be run from Yamato. The jobs are defined in [`.yamato/cmb-service-standalone-tests.yml`](../../../.yamato/cmb-service-standalone-tests.yml) and appear in Yamato as `CMB Service Test - NGO - [, , ]`. The job can be triggered manually from any branch meaning it can be easier to run the CMB tests from Yamato rather than set them up locally. The test uses [`run_cmb_service.sh`](./run_cmb_service.sh) to setup and run the CMB service. diff --git a/Tools/CI/service.cmb/run_cmb_service.sh b/Tools/CI/service.cmb/run_cmb_service.sh index 4bcf8c7468..8dd26df88a 100755 --- a/Tools/CI/service.cmb/run_cmb_service.sh +++ b/Tools/CI/service.cmb/run_cmb_service.sh @@ -17,7 +17,7 @@ # Example usage: # .//run_cmb_service.sh -e 7788 -s 7799 - # This script is currently used in the desktop-standalone-tests yamato job. + # This script is currently used in the cmb-service-standalone-tests yamato job (found at ../../../.yamato/cmb-service-standalone-tests.yml). # TECHNICAL CONSIDERATIONS--------------------------------------------------------------- # This is a bash script and so needs to be run on a Unix based system. @@ -136,11 +136,14 @@ else logMessage "Protocol Buffer Compiler Installed & ENV variables verified!\n PROTOC path is: $PROTOC" fi -# clone the cmb service repo -git clone https://github.com/Unity-Technologies/mps-common-multiplayer-backend.git +# Sparse-checkout only the CMB service directory from the unity-player-services monorepo. +# --filter=blob:none + --depth 1 avoids downloading file contents and history for the rest of the monorepo. +git clone --depth 1 --filter=blob:none --sparse https://github.com/Unity-Technologies/unity-player-services.git +cd ./unity-player-services +git sparse-checkout set services/common-multiplayer-backend/runtime # navigate to the cmb service directory -cd ./mps-common-multiplayer-backend/runtime +cd ./services/common-multiplayer-backend/runtime # Install rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index a9922a19b6..85157fe510 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -19,6 +19,8 @@ Additional documentation and release notes are available at [Multiplayer Documen - `Unity.Netcode.Editor.PackageChecker` → `Unity.Netcode.GameObjects.Editor.PackageChecker` - `Unity.Netcode.Editor.Tests` → `Unity.Netcode.GameObjects.Editor.Tests` + + ### Deprecated @@ -27,6 +29,10 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Issue with not being able to spawn initially disabled in-scene placed objects. (#4093) +- Issue with pre-instantiated network prefab instances being marked as in-scene placed. Now pre-instantiated network prefabs are dynamically spawned. (#4093) +- Issue where a user could spawn runtime created `NetworkObject` that has a GlobalObjectIdHash of zero. These are not valid instances and will no longer be allowed to spawn. (#4093) + ### Security diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md index b7b23e4afc..f1210fbc95 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md @@ -68,4 +68,4 @@ Reading and writing a value provides the minimal amount of `NetworkVariable` fun Here is a full implementation of a custom type with the methods needed for `UserNetworkVariableSerialization` -[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/NetworkVariable/NetworkVariableSerialization.cs#HealthExample)] +[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs#HealthExample)] diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md index 3f8327fc17..7b9d5d65c3 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md @@ -82,7 +82,7 @@ This allows the four bytes of the embedded struct to be rapidly serialized as a `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()` +- `FastBufferWriter` uses `WriteValue()` as the name of the method for all types *except* [`INetworkSerializable`](serialization/inetworkserializable.md) 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. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md index 28d0607ef3..7c37bd606d 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md @@ -75,7 +75,7 @@ void AbcdServerRpc(int somenumber) { /* ... */ } 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) +[ServerRpcParams Documentation](xref:Unity.Netcode.ServerRpcParams) ## ClientRpc Params @@ -94,7 +94,7 @@ void AbcdClientRpc(int framekey) { /* ... */ } 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) +[ClientRpcParams Documentation](xref:Unity.Netcode.ClientRpcParams) > [!NOTE] > `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/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md index b46a1b8b91..ac95d63bd7 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md @@ -328,4 +328,4 @@ void Update() ## Additional resources * [RPC parameters](rpc-params.md) -* [Customizing serialization](../custom-serialization.md#remote-procedure-call-rpc) +* [Customizing serialization](../custom-serialization.md#remote-procedure-call-rpcs) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md index 6608534375..8eab667a68 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md @@ -2,7 +2,7 @@ The network prefab handler system provides advanced control over how network prefabs are instantiated and destroyed during runtime. You can use it to override the default Netcode for GameObjects [object spawning](../basics/object-spawning.md) behavior by implementing custom prefab handlers. -The network prefab handler system is accessible from the [NetworkManager](../components/networkmanager.md) as `NetworkManager.PrefabHandler`. +The network prefab handler system is accessible from the [NetworkManager](../components/core/networkmanager.md) as `NetworkManager.PrefabHandler`. ## When to use a prefab handler @@ -13,14 +13,14 @@ For an overview of the default object spawning behavior, refer to the [object sp - **Custom initialization**: Setting up objects with game client specific data or configurations. - **Conditional spawning**: Initializing different prefab variants based on runtime conditions. -The prefab handler system addresses these needs through an interface-based architecture. The system relies on two key methods: `Instantiate` and `Destroy`. `Instantiate` is called on non-authority clients when an [authority](../terms-concepts/authority.md) spawns a new [NetworkObject](../basics/networkobject.md) that has a registered network prefab handler. `Destroy` is called on all game clients whenever a registered [NetworkObject](../basics/networkobject.md) is destroyed. +The prefab handler system addresses these needs through an interface-based architecture. The system relies on two key methods: `Instantiate` and `Destroy`. `Instantiate` is called on non-authority clients when an [authority](../terms-concepts/authority.md) spawns a new [NetworkObject](../components/core/networkobject.md) that has a registered network prefab handler. `Destroy` is called on all game clients whenever a registered [NetworkObject](../components/core/networkobject.md) is destroyed. ## Create a prefab handler Prefab handlers are classes that implement one of the Netcode for GameObjects prefab handler descriptions. There are currently two such descriptions: -- [**INetworkPrefabInstanceHandler**](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.INetworkPrefabInstanceHandler.html): This is the simplest interface for custom prefab handlers. -- [**NetworkPrefabInstanceHandlerWithData**](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkPrefabInstanceHandlerWithData.html): This specialized handler receives custom data from the authority during spawning, enabling dynamic prefab customization. +- [**INetworkPrefabInstanceHandler**](xref:Unity.Netcode.INetworkPrefabInstanceHandler): This is the simplest interface for custom prefab handlers. +- [**NetworkPrefabInstanceHandlerWithData**](xref:Unity.Netcode.NetworkPrefabInstanceHandlerWithData): This specialized handler receives custom data from the authority during spawning, enabling dynamic prefab customization. When using a prefab handler, Netcode for GameObjects uses the `Instantiate` and `Destroy` methods instead of default spawn handlers for the NetworkObject during spawning and despawning. The authority instance uses the traditional spawning approach where it will, via user script, instantiate and spawn a network prefab (even for those registered with a prefab handler). However, all non-authority clients will automatically use the instantiate method defined by the `INetworkPrefabInstanceHandler` implementation if the network prefab spawned has a registered `INetworkPrefabInstanceHandler` implementation with the `NetworkPrefabHandler` (`NetworkManager.PrefabHandler`). @@ -55,7 +55,7 @@ public abstract class NetworkPrefabInstanceHandlerWithData : INetworkPrefabIn ## Register a prefab handler -Once you've [created a prefab handler](#create-a-prefab-handler), whether by implementing or deriving, you need to register any new instance of that handler with the network prefab handler system using `NetworkManager.PrefabHandler.AddHandler`. Prefab handlers are registered against a NetworkObject's [GlobalObjectIdHash](../basics/networkobject.md#using-networkobjects). +Once you've [created a prefab handler](#create-a-prefab-handler), whether by implementing or deriving, you need to register any new instance of that handler with the network prefab handler system using `NetworkManager.PrefabHandler.AddHandler`. Prefab handlers are registered against a NetworkObject's [GlobalObjectIdHash](../components/core/networkobject.md#using-networkobjects). ```csharp public class GameManager : NetworkBehaviour @@ -70,7 +70,7 @@ public class GameManager : NetworkBehaviour } ``` -To un-register a prefab handler, you can [invoke the `NetworkManager.PrefabHandler.RemoveHandler` method](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkPrefabHandler.html#Unity_Netcode_NetworkPrefabHandler_RemoveHandler_System_UInt32_). There are several override versions of this method. +To un-register a prefab handler, you can [invoke the `NetworkManager.PrefabHandler.RemoveHandler` method](xref:Unity.Netcode.NetworkPrefabHandler.RemoveHandler*). There are several override versions of this method. ## Object spawning with prefab handlers diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/network-update-loop-reference.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/network-update-loop-reference.md index fd013937a4..72ec0db77c 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/network-update-loop-reference.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/network-update-loop-reference.md @@ -6,7 +6,7 @@ The following diagrams provide insight into the Network Update Loop process and
-![Injecting NetworkUpdateLoop Systems Into PlayerLoop](../images/injecting-networkupdatesloop.svg) +![Injecting NetworkUpdateLoop Systems Into PlayerLoop](../../images/injecting-networkupdatesloop.svg)
@@ -14,6 +14,6 @@ The following diagrams provide insight into the Network Update Loop process and
-![NetworkUpdateLoop Running INetworkUpdateSystem Updates](../images/runninginetworkupdatesystemupdates.svg) +![NetworkUpdateLoop Running INetworkUpdateSystem Updates](../../images/runninginetworkupdatesystemupdates.svg)
diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md index 435edf39a5..a2c6c755ea 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md @@ -26,7 +26,7 @@ If you aren't familiar with transform parenting in Unity, then it's recommended ### 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. +[`NetworkBehaviour.OnNetworkObjectParentChanged`](xref:Unity.Netcode.NetworkBehaviour.OnNetworkObjectParentChanged*) 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 /// @@ -49,7 +49,7 @@ The [owner](../terms-concepts/ownership.md) of a NetworkObject can always parent By default, only the [authority](../terms-concepts/authority.md) of a NetworkObject can parent a NetworkObject under a non-networked object. This means in a client-server game, only the server (or host) can control NetworkObject component parenting. In a distributed authority game the [owner](../terms-concepts/ownership.md) of the object can always parent the object. -To allow the [owner](../terms-concepts/ownership.md) to parent their owned NetworkObject in a client-server game, use the [`NetworkObject.AllowOwnerToParent`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_AllowOwnerToParent) property. +To allow the [owner](../terms-concepts/ownership.md) to parent their owned NetworkObject in a client-server game, use the [`NetworkObject.AllowOwnerToParent`](xref:Unity.Netcode.NetworkObject.AllowOwnerToParent) property. ![image](../images/networkobject/allowOwnerToParent.png) @@ -78,7 +78,7 @@ If you plan on parenting in-scene placed NetworkObject components with a player For more information, refer to: - [Real World In-scene NetworkObject Parenting of Players Solution](inscene_parenting_player.md) -- [Scene Event Notifications](../basics/scenemanagement/scene-events#scene-event-notifications) +- [Scene Event Notifications](../basics/scenemanagement/scene-events.md#scene-event-notifications) - [In-Scene NetworkObjects](../basics/scenemanagement/inscene-placed-networkobjects.md) ### WorldPositionStays usage diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md index 1742f87805..500b3d4b7c 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md @@ -183,6 +183,6 @@ For games with short play sessions casting the time to float is safe or `TimeAsF > [!NOTE] > 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. +The way network time gets calculated can be configured in the `NetworkTimeSystem` if needed. Refer to the [API docs](xref:Unity.Netcode.NetworkTimeSystem) 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/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md index 8a309cca24..134a78cb43 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md @@ -34,7 +34,7 @@ Since PhysX has no concept of local space, it can be difficult to synchronize tw ## Using AttachableBehaviour or Joint -The implementation of physics in a networked project differs from a single player project. This is especially true when you're using NetworkTransform and NetworkRigidbody components with [`NetworkRigidbody.UseRigidBodyForMotion`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkRigidbodyBase.html#Unity_Netcode_Components_NetworkRigidbodyBase_UseRigidBodyForMotion) enabled. Deciding whether to use a Joint or an AttachableBehaviour component depends on your project's requirements. +The implementation of physics in a networked project differs from a single player project. This is especially true when you're using NetworkTransform and NetworkRigidbody components with [`NetworkRigidbody.UseRigidBodyForMotion`](xref:Unity.Netcode.Components.NetworkRigidbodyBase.UseRigidBodyForMotion) enabled. Deciding whether to use a Joint or an AttachableBehaviour component depends on your project's requirements. For example, if you want to create world items that players can pick up, you may have the following requirements: diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md index 2733a0e373..ab60e161c6 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md @@ -1,7 +1,7 @@ # Customize serializable types with INetworkSerializable > [!NOTE] -> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before customizing serializable types with `INetworkSerializable`. +> Read the [Serialization overview](./serialization-overview.md) page to understand the basics of serialization before customizing serializable types with `INetworkSerializable`. You can use the `INetworkSerializable` interface to define custom serializable types. This interface has one function: `NetworkSerialize(BufferSerializer serializer)`, which ingests a bi-directional [`BufferSerializer`](../bufferserializer.md) that you can use to implement bi-directional custom serialization. @@ -23,7 +23,7 @@ struct SpawnPoint : INetworkSerializable } ``` -Types implementing `INetworkSerializable` are supported by [`FastBufferReader` and `FastBufferWriter`](./fastbufferwriter-fastbufferreader.md), [`RPC`s'](../message-system/rpc.md), and [`NetworkVariable`s](../../basics/networkvariable.md). +Types implementing `INetworkSerializable` are supported by [`FastBufferReader` and `FastBufferWriter`](../fastbufferwriter-fastbufferreader.md), [`RPC`s'](../message-system/rpc.md), and [`NetworkVariable`s](../../basics/networkvariable.md). ```csharp [Rpc(SendTo.Server)] diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md index 212bfbf339..b79df9ff27 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md @@ -1,7 +1,7 @@ # Serialize unmanaged structs with INetworkSerializeByMemcpy > [!NOTE] -> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before using `INetworkSerializeByMemcpy` to serialize unmanaged structs. +> Read the [Serialization overview](./serialization-overview.md) page to understand the basics of serialization before using `INetworkSerializeByMemcpy` to serialize unmanaged structs. 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. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md index 466d545b4b..d3800a7b09 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md @@ -1,7 +1,7 @@ # NetworkObject and NetworkBehaviour serialization > [!NOTE] -> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to serialize `NetworkObjects` and `NetworkBehaviours`. +> Read the [Serialization overview](./serialization-overview.md) page to understand the basics of serialization before learning how to serialize `NetworkObjects` and `NetworkBehaviours`. `GameObject`, [`NetworkObject`](../../components/core/networkobject.md) and [`NetworkBehaviour`](../../components/core/networkbehaviour.md) aren't serializable types so they can't be used in [`RPC`s](../message-system/rpc.md) or [`NetworkVariable`s](../../basics/networkvariable.md) by default. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md index 5681e5952c..3ecc26cb99 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md @@ -1,7 +1,7 @@ # Arrays and native containers > [!NOTE] -> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to serialize arrays and native containers. +> Read the [Serialization overview](./serialization-overview.md) page to understand the basics of serialization before learning how to serialize 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 custom types, need to be handled using a container class or structure that implements the [`INetworkSerializable`](inetworkserializable.md) interface. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md index fb52e42773..ad7fcd4d21 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md @@ -10,7 +10,7 @@ When Netcode for GameObjects first receives a type, it checks for any custom typ 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, for example), as well as any structs that contain only these basic types. -Serialization and deserialization is done via the structs [`FastBufferWriter` and `FastBufferReader`](fastbufferwriter-fastbufferreader.md). 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. +Serialization and deserialization is done via the structs [`FastBufferWriter` and `FastBufferReader`](../fastbufferwriter-fastbufferreader.md). 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. `FastBufferWriter` and `FastBufferReader` also contain the functions `FastBufferWriter.WriteNetworkSerializable()` and `FastBufferReader.ReadNetworkSerializable` for writing and reading values that use the `INetworkSerializable` interface. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md index 1f80a08d1a..614d2ce32b 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md @@ -19,7 +19,7 @@ You can also decide to clear all data when a session completes or add a timeout # Reconnection -The best way to reconnect players depends on your game. For example, if you use a [Player Object](../components/core/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. +The best way to reconnect players depends on your game. For example, if you use a [Player Object](../components/core/playerobjects.md), 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. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md index e4f45a5ead..b0d54e88be 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md @@ -1,6 +1,6 @@ # Single player sessions -Netcode for GameObjects provides a [SinglePlayerTransport](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.13/api/Unity.Netcode.Transports.SinglePlayer.SinglePlayerTransport.html) which derives from [NetworkTransport](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.13/api/Unity.Netcode.NetworkTransport.html). +Netcode for GameObjects provides a [SinglePlayerTransport](xref:Unity.Netcode.Transports.SinglePlayer.SinglePlayerTransport) which derives from [NetworkTransport](xref:Unity.Netcode.NetworkTransport). This provides the ability to run a hosted session using the single player transport without having to modify your primary netcode script. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/transports.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/transports.md index 1939d8a0ea..783f0f633c 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/transports.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/transports.md @@ -16,7 +16,7 @@ A transport layer can provide: ## 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). +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. Refer to the Transport [documentation](https://docs.unity3d.com/Packages/com.unity.transport@latest) for more information and how to [install](https://docs.unity3d.com/Packages/com.unity.transport@latest?subfolder=/manual/install.html). ## Unity's UNet Transport Layer API diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md b/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md index de160d26d2..7c6dd428a7 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md @@ -3,7 +3,7 @@ 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. > [!NOTE] -> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand how Netcode for GameObjects handles serialization. +> Read the [Serialization overview](../advanced-topics/serialization/serialization-overview.md) page to understand how Netcode for GameObjects handles serialization. To create your own `NetworkVariableBase`-derived container, you should: diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md b/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md index 7fdd95ee4d..b72213f32e 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md @@ -30,7 +30,7 @@ When using a [server authoritative networking model](../terms-concepts/authority 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. -Refer to [NetworkObject ownership](../advanced-topics/networkobject-ownership.md) for more information. +Refer to [NetworkObject ownership](../components/core/networkobject-ownership.md) for more information. The following is a basic example of how to spawn a network prefab instance: diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md index dff54d2da4..ffb1cde762 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md @@ -39,7 +39,7 @@ Once you've registered your in-scene placed Network Prefabs with your `NetworkPr > 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](../../components/core/networkbehaviour.md#prespawn-synchronization). +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](../../components/core/networkbehaviour-synchronize.md#prespawn-synchronization-with-onsynchronize). ## 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: diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md index 6bc209722b..617a8f5f36 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md @@ -78,6 +78,9 @@ private void Start() > [!NOTE] > 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. +> [!WARNING] +> Manually calling `DontDestroyOnLoad(gameObject)` is only supported for the scene-switching manager scenario described above (that is, a single in-scene placed NetworkObject used for state management that's migrated into the DDoL and stays there). Marking ordinary in-scene placed NetworkObjects as `DontDestroyOnLoad` isn't supported and can cause the object's `GlobalObjectIdHash` to no longer resolve after a scene change, resulting in errors such as "NetworkPrefab hash was not found!". To persist in-scene placed NetworkObjects across scene changes, prefer keeping them in an additively loaded scene that stays loaded for the duration of the session rather than using `DontDestroyOnLoad`. + ## 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: @@ -181,9 +184,13 @@ public class MyInSceneNetworkObjectBehaviour : NetworkBehaviour > [!NOTE] > 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. +### Pre-disabled in-scene placed NetworkObjects + +To initialize an in-scene placed NetworkObject in a disabled state and spawn it later, set its GameObject to inactive in the Editor while the respective scene is open. Once a networked session begins, you can re-enable and spawn it at any time. + ### 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: +To programmatically disable an in-scene placed NetworkObject, add some additional code in the `OnNetworkSpawn` part of a NetworkBehaviour component: ```csharp using UnityEngine; @@ -225,7 +232,7 @@ The above example keeps track of whether the in-scene placed NetworkObject has s ### 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: +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](#hybrid-approach) 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. diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md index 817affd33f..c01ffa1df5 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md @@ -240,7 +240,7 @@ So, the big "take-away" from the above table is that you need to understand the ### 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. +You can explore the [NetworkSceneManager](xref:Unity.Netcode.SceneEventType) 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. diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/using-networkscenemanager.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/using-networkscenemanager.md index 5837249132..ac54460414 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/using-networkscenemanager.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/using-networkscenemanager.md @@ -19,7 +19,7 @@ > [!NOTE] > 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). +All of these scene management features (and more) are handled by the [`NetworkSceneManager`](xref:Unity.Netcode.NetworkSceneManager). ### Accessing `NetworkSceneManager` The `NetworkSceneManager` lives within the NetworkManager and is instantiated when the NetworkManager is started. @@ -122,7 +122,7 @@ The purpose behind the above outline is to show that a Scene Event can lead to o #### 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)
+2. Receive only a specific scene event notification type: [`NetworkSceneManager`](xref:Unity.Netcode.NetworkSceneManager) has one for each [`SceneEventType`](xref:Unity.Netcode.SceneEventType)
> [!NOTE] > 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. @@ -137,7 +137,7 @@ Typically, this is used with clients or components that might only need to be no 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: +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](xref:Unity.Netcode.SceneEventProgressStatus) `enum` values with some additional helpful information: - Started - The scene event has started (success) - SceneNotLoaded diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md b/com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md index 8c4c49d2d8..b07a4113cf 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md @@ -20,7 +20,7 @@ Learn about the three core components of Netcode for GameObjects: NetworkObject, | **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[NetworkBehaviour](networkbehaviour.md)** | [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](networkbehaviour.md)** | [NetworkBehaviour](xref:Unity.Netcode.NetworkBehaviour) 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. | | **[Synchronizing](networkbehaviour-synchronize.md)** | Understand a NetworkBehaviour component's order of operations when it comes to spawning, despawning, and adding custom synchronization data. | diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-ownership.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-ownership.md index 3511fd44f8..fee658cd12 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-ownership.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-ownership.md @@ -1,25 +1,25 @@ # NetworkBehaviour ownership -Before reading these docs, ensure you understand the concepts of [ownership](../terms-concepts/ownership.md) and [NetworkObject ownership](./networkobject-ownership.md). It's also important to be familiar with the [NetworkBehaviour](./networkbehaviour.md) +Before reading these docs, ensure you understand the concepts of [ownership](../../terms-concepts/ownership.md) and [NetworkObject ownership](./networkobject-ownership.md). It's also important to be familiar with the [NetworkBehaviour](./networkbehaviour.md) -The owner of each NetworkBehaviour in your game is decided by the owner of that NetworkBehaviour's NetworkObject. The NetworkObject is found as a property on the NetworkBehaviour: [`NetworkBehaviour.NetworkObject`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_NetworkObject). +The owner of each NetworkBehaviour in your game is decided by the owner of that NetworkBehaviour's NetworkObject. The NetworkObject is found as a property on the NetworkBehaviour: [`NetworkBehaviour.NetworkObject`](xref:Unity.Netcode.NetworkBehaviour.NetworkObject). ## Helpful properties > [!NOTE] -> The following properties are only valid if the NetworkBehaviour has been spawned. Use [`NetworkBehaviour.IsSpawned`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_IsSpawned) to check the spawned status of the NetworkBehaviour +> The following properties are only valid if the NetworkBehaviour has been spawned. Use [`NetworkBehaviour.IsSpawned`](xref:Unity.Netcode.NetworkBehaviour.IsSpawned) to check the spawned status of the NetworkBehaviour -To identify whether the local client is the owner of a NetworkBehaviour, you can check the[`NetworkBehaviour.IsOwner`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_IsOwner) property. +To identify whether the local client is the owner of a NetworkBehaviour, you can check the[`NetworkBehaviour.IsOwner`](xref:Unity.Netcode.NetworkBehaviour.IsOwner) property. -To identify whether the server owns a NetworkBehaviour, you can check the [`NetworkBehaviour.IsOwnedByServer`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_IsOwnedByServer) property. +To identify whether the server owns a NetworkBehaviour, you can check the [`NetworkBehaviour.IsOwnedByServer`](xref:Unity.Netcode.NetworkBehaviour.IsOwnedByServer) property. -To identify whether the local client has authority of a NetworkBehaviour, you can check the[`NetworkBehaviour.HasAuthority`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_HasAuthority) property. +To identify whether the local client has authority of a NetworkBehaviour, you can check the[`NetworkBehaviour.HasAuthority`](xref:Unity.Netcode.NetworkBehaviour.HasAuthority) property. ## Detecting ownership changes There are three functions that can be implemented to detect ownership changes on a NetworkBehaviour. These functions are invoked in the order they are listed here. -### [OnLostOwnership](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_OnLostOwnership) +### [OnLostOwnership](xref:Unity.Netcode.NetworkBehaviour.OnLostOwnership) When using a [client-server network topology](../../terms-concepts/client-server.md) `OnLostOwnership` is invoked on both the server any time a connected client loses ownership of this NetworkBehaviour. It is also invoked on the game client who just lost ownership. @@ -34,7 +34,7 @@ void OnLostOwnership() } ``` -### [OnGainedOwnership](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_OnGainedOwnership) +### [OnGainedOwnership](xref:Unity.Netcode.NetworkBehaviour.OnGainedOwnership) When using a client-server network topology `OnGainedOwnership` is invoked on the server any time ownership is gained. It is also be invoked on the game client who just gained ownership. @@ -52,7 +52,7 @@ void OnGainedOwnership() } ``` -### [OnOwnershipChanged](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_OnOwnershipChanged_System_UInt64_System_UInt64_) +### [OnOwnershipChanged](xref:Unity.Netcode.NetworkBehaviour.OnOwnershipChanged*) Whenever you want notification on any and all ownership changes, implement the `OnOwnershipChanged` method. `OnOwnershipChanged` is invoked on all connected game clients whenever the ownership of the NetworkBehaviour it is implemented on changes. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-synchronize.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-synchronize.md index 166b69c4bc..e273296ea5 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-synchronize.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-synchronize.md @@ -1,6 +1,6 @@ # 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. +[NetworkBehaviour](xref:Unity.Netcode.NetworkBehaviour) 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 NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. @@ -238,4 +238,4 @@ However, there is an additional check to assure that the total expected bytes to ## Serializing NetworkBehaviours -NetworkBehaviours 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. +NetworkBehaviours require the use of specialized [`NetworkBehaviourReference`](xref:Unity.Netcode.NetworkBehaviourReference) structures to be serialized and used with RPCs and `NetworkVariable`s. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md index 0922a6d35a..4b1f7e0b02 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md @@ -1,6 +1,6 @@ # NetworkBehaviour -[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](xref:Unity.Netcode.NetworkBehaviour) 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. 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. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md index aa59d87d89..2a487b20a6 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md @@ -5,7 +5,7 @@ The NetworkManager is a required Netcode for GameObjects component that has all ## NetworkManager Inspector properties - **LogLevel**: Sets the network logging level -- **PlayerPrefab**: When a Prefab is assigned, the Prefab will be instantiated as the player object. For more information about player prefabs, refer to [Player NetworkObjects](networkobject.md#player-networkobjects). +- **PlayerPrefab**: When a Prefab is assigned, the Prefab will be instantiated as the player object. For more information about player prefabs, refer to [Player NetworkObjects](playerobjects.md). - **NetworkPrefabs**: Where you register your network prefabs. You can also create a single network Prefab override per registered network Prefab here. - **Protocol Version**: Set this value to help distinguish between builds when the most current build has new assets that can cause issues with older builds connecting. - **Network Transport**: Where your network specific settings and transport type is set. When using a [client-server topology](../../terms-concepts/client-server.md), This field accepts any INetworkTransport implementation. However, unless you have unique transport specific needs UnityTransport is the recommended transport to use with Netcode for GameObjects. For a [distributed authority topology](../../terms-concepts/distributed-authority.md), refer to the [distributed authority quickstart](../../learn/distributed-authority-quick-start.md). @@ -68,7 +68,7 @@ NetworkManager.Singleton.StartClient(); When starting a Server or joining an already started session as client, the NetworkManager can spawn a "Player Object" belonging to the client. For more information about player prefabs, refer to: - [NetworkObject Player Prefab Documentation](networkobject.md) - - [Connection Approval](../../basics/connection-approval) + - [Connection Approval](../../basics/connection-approval.md) ## Connecting @@ -135,7 +135,7 @@ The server-host attempts to wait for all client connections to close before it f At times you might need to disconnect a client for various reasons without shutting down the server. To do this, you can call the `NetworkManager.DisconnectClient` method while passing the identifier of the client you wish to disconnect as the only parameter. The client identifier can be found within: -- The `NetworkManager.ConnectedClients` dictionary that uses the client identifier as a key and the value as the [`NetworkClient`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkClient.html). +- The `NetworkManager.ConnectedClients` dictionary that uses the client identifier as a key and the value as the [`NetworkClient`](xref:Unity.Netcode.NetworkClient). - As a read only list of `NetworkClients` via the `NetworkManager.ConnectedClientsList`. - A full list of all connected client identifiers can be accessed via `NetworkManager.ConnectedClientsIds`. - The client identifier is passed as a parameter to all subscribers of the `NetworkManager.OnClientConnected` event. @@ -261,16 +261,16 @@ public class ConnectionNotificationManager : MonoBehaviour There are two static NetworkManager events you can use to be notified when a NetworkManager is instantiated or is about to be destroyed: -- [`NetworkManager.OnInstantiated`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnInstantiated): This is invoked when a NetworkManager is instantiated. -- [`NetworkManager.OnDestroying`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnDestroying): This is invoked when a NetworkManager is about to be destroyed. +- [`NetworkManager.OnInstantiated`](xref:Unity.Netcode.NetworkManager.OnInstantiated): This is invoked when a NetworkManager is instantiated. +- [`NetworkManager.OnDestroying`](xref:Unity.Netcode.NetworkManager.OnDestroying): This is invoked when a NetworkManager is about to be destroyed. ### When a NetworkManager is stopped Knowing when a NetworkManager has stopped is useful for establishing when it's safe to transition back to a main menu scene, or other similar tasks. There are two events you can use to be notified that the NetworkManager has finished shutting down: -- [`NetworkManager.OnClientStopped`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnClientStopped): This is invoked on a host or client when the NetworkManager has completely shut down and is ready to be restarted. -- [`NetworkManager.OnServerStopped`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnServerStopped): This is invoked on a host or server when the NetworkManager has completely shut down and is ready to be restarted. +- [`NetworkManager.OnClientStopped`](xref:Unity.Netcode.NetworkManager.OnClientStopped): This is invoked on a host or client when the NetworkManager has completely shut down and is ready to be restarted. +- [`NetworkManager.OnServerStopped`](xref:Unity.Netcode.NetworkManager.OnServerStopped): This is invoked on a host or server when the NetworkManager has completely shut down and is ready to be restarted. Since a host is both a client and a server, the event invocation order is: @@ -283,4 +283,4 @@ Since a host is both a client and a server, the event invocation order is: If you need to save the state of spawned objects before they're destroyed when the NetworkManager shuts down, you can use the following event notification: -- [`NetworkManager.OnPreShutdown`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnPreShutdown): This is invoked prior to finalizing the NetworkManager shut down process. Any remaining spawned objects will still be instantiated and spawned when this event is invoked. +- [`NetworkManager.OnPreShutdown`](xref:Unity.Netcode.NetworkManager.OnPreShutdown): This is invoked prior to finalizing the NetworkManager shut down process. Any remaining spawned objects will still be instantiated and spawned when this event is invoked. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md index 81bad2dfff..3ad5d5235b 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md @@ -9,11 +9,11 @@ Read more about how to detect when ownership has changed in [NetworkBehaviour ow ## Helpful properties > [!NOTE] -> All NetworkObject properties are only valid while the NetworkObject is spawned. Use [`NetworkObject.IsSpawned`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsSpawned) to check the spawned status of the NetworkObject. +> All NetworkObject properties are only valid while the NetworkObject is spawned. Use [`NetworkObject.IsSpawned`](xref:Unity.Netcode.NetworkObject.IsSpawned) to check the spawned status of the NetworkObject. -To identify whether the local client is the owner of a NetworkObject, you can check the[`NetworkObject.IsOwner`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.IsOwner.html) or the [`NetworkBehaviour.IsOwner`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.IsOwner.html) property. +To identify whether the local client is the owner of a NetworkObject, you can check the[`NetworkObject.IsOwner`](xref:Unity.Netcode.NetworkObject.IsOwner) or the [`NetworkBehaviour.IsOwner`](xref:Unity.Netcode.NetworkBehaviour.IsOwner) property. -To identify whether the server owns a NetworkObject, you can check the [`NetworkObject.IsOwnedByServer`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.IsOwnedByServer.html) or the [`NetworkBehaviour.IsOwnedByServer`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.IsOwnedByServer.html) property. +To identify whether the server owns a NetworkObject, you can check the [`NetworkObject.IsOwnedByServer`](xref:Unity.Netcode.NetworkObject.IsOwnedByServer) or the [`NetworkBehaviour.IsOwnedByServer`](xref:Unity.Netcode.NetworkBehaviour.IsOwnedByServer) property. > [!NOTE] > To assure a spawned NetworkObject persists after the owner leaves a session, set the `NetworkObject.DontDestroyWithOwner` property to true. This assures the client-owned NetworkObject doesn't get destroyed when the owning client leaves. @@ -39,7 +39,7 @@ NetworkObject.SpawnWithOwnership(clientId); > [!NOTE] > Using `SpawnWithOwnership` can result in unexpected behavior when the spawning game client makes any other changes on the object immediately after spawning. -Using `SpawnWithOwnership` and then editing the NetworkObject locally means that the client doing the spawning will behave as the spawn authority. The spawn authority has limited local [authority](../terms-concepts/authority.md) over the NetworkObject, but not [ownership](../terms-concepts/ownership.md) of the NetworkObject that's spawned. This means any owner-specific checks during the spawn sequence will not be invoked on the spawn authority side. +Using `SpawnWithOwnership` and then editing the NetworkObject locally means that the client doing the spawning will behave as the spawn authority. The spawn authority has limited local [authority](../../terms-concepts/authority.md) over the NetworkObject, but not [ownership](../../terms-concepts/ownership.md) of the NetworkObject that's spawned. This means any owner-specific checks during the spawn sequence will not be invoked on the spawn authority side. If you want to spawn a NetworkObject for another client and then immediately make adjustments to that NetworkObject, it's recommended to use the `Spawn` method. After adjusting, the spawn authority can immediately follow with a call to `ChangeOwnership`. @@ -111,15 +111,15 @@ The authority of any NetworkObject can always change ownership, as outlined in [ ### Ownership permission settings -The following ownership permission settings, defined by [`NetworkObject.OwnershipStatus`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.OwnershipStatus.html), control how ownership of NetworkObjects can be changed during a distributed authority session: +The following ownership permission settings, defined by [`NetworkObject.OwnershipStatus`](xref:Unity.Netcode.NetworkObject.OwnershipStatus), control how ownership of NetworkObjects can be changed during a distributed authority session: |**Ownership setting**|Description|Related Property|Multi-select| |-----|-----|-----|-----| |`None`|Ownership of this NetworkObject can't be redistributed, requested, or transferred (a Player might have this, for example).||No| -|`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.|[`IsOwnershipDistributable`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsOwnershipDistributable)|**Yes**| -|`Transferable`|Any client can change ownership of this NetworkObject at any time, as long as ownership is not locked or a request is pending.|[`IsOwnershipTransferable`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsOwnershipTransferable)|**Yes**| -|`RequestRequired`|Ownership of this NetworkObject must be requested before ownership can be changed.|[`IsOwnershipRequestRequired`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsOwnershipRequestRequired), [`IsRequestInProgress`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsRequestInProgress)|**Yes**| -|`SessionOwner`|This NetworkObject is always owned by the [session owner](distributed-authority.md#session-ownership) and can't be transferred or distributed. If the session owner changes, this NetworkObject is automatically transferred to the new session owner.|[`IsOwnershipSessionOwner`](Unity_Netcode_NetworkObject_IsOwnershipSessionOwner)|No| +|`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.|[`IsOwnershipDistributable`](xref:Unity.Netcode.NetworkObject.IsOwnershipDistributable)|**Yes**| +|`Transferable`|Any client can change ownership of this NetworkObject at any time, as long as ownership is not locked or a request is pending.|[`IsOwnershipTransferable`](xref:Unity.Netcode.NetworkObject.IsOwnershipTransferable)|**Yes**| +|`RequestRequired`|Ownership of this NetworkObject must be requested before ownership can be changed.|[`IsOwnershipRequestRequired`](xref:Unity.Netcode.NetworkObject.IsOwnershipRequestRequired), [`IsRequestInProgress`](xref:Unity.Netcode.NetworkObject.IsRequestInProgress)|**Yes**| +|`SessionOwner`|This NetworkObject is always owned by the [session owner](../../terms-concepts/distributed-authority.md#session-ownership) and can't be transferred or distributed. If the session owner changes, this NetworkObject is automatically transferred to the new session owner.|[`IsOwnershipSessionOwner`](xref:Unity.Netcode.NetworkObject.IsOwnershipSessionOwner)|No| Ownership permissions can be set in the editor using the **Ownership** dropdown. @@ -127,7 +127,7 @@ Ownership permissions can be set in the editor using the **Ownership** dropdown. They can also be set in script using -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. The [`IsOwnershipLocked`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsOwnershipLocked) property can be used to detect if an object has locked ownership. +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. The [`IsOwnershipLocked`](xref:Unity.Netcode.NetworkObject.IsOwnershipLocked) property can be used to detect if an object has locked ownership. ```csharp // To lock an object from any ownership changes @@ -148,7 +148,7 @@ NetworkObject.ChangeOwnership(clientId); NetworkObject.ChangeOwnership(NetworkManager.LocalClientId); ``` -When a non-authoritative game client calls `ChangeOwnership`, the ownership change can fail. On a failed attempt to change ownership, the `OnOwnershipPermissionsFailure` callback will be invoked with a [`NetworkObject.OwnershipPermissionsFailureStatus`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus.html) to give information on the failure. +When a non-authoritative game client calls `ChangeOwnership`, the ownership change can fail. On a failed attempt to change ownership, the `OnOwnershipPermissionsFailure` callback will be invoked with a [`NetworkObject.OwnershipPermissionsFailureStatus`](xref:Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus) to give information on the failure. ```csharp /* @@ -200,9 +200,9 @@ When a NetworkObject is set with `OwnershipPermissions.RequestRequired` any clie var requestStatus = NetworkObject.RequestOwnership(); ``` -`RequestOwnership` returns an [`OwnershipRequestStatus`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.OwnershipRequestStatus.html) to indicate the initial status of the request. To view the result of the request, the `OnOwnershipRequestResponse` callback will be invoked with a [`NetworkObject.OwnershipRequestResponseStatus`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus.html). +`RequestOwnership` returns an [`OwnershipRequestStatus`](xref:Unity.Netcode.NetworkObject.OwnershipRequestStatus) to indicate the initial status of the request. To view the result of the request, the `OnOwnershipRequestResponse` callback will be invoked with a [`NetworkObject.OwnershipRequestResponseStatus`](xref:Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus). -By default, any requests for ownership will automatically be approved. To control which client is approved for ownership, use the [`OnOwnershipRequested`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_OnOwnershipRequested) callback +By default, any requests for ownership will automatically be approved. To control which client is approved for ownership, use the [`OnOwnershipRequested`](xref:Unity.Netcode.NetworkObject.OnOwnershipRequested) callback ```csharp /* @@ -264,8 +264,8 @@ public class RequestableOwnershipBehaviour : NetworkBehaviour ## Additional resources -- [NetworkObject](../components/core/networkobject.md) +- [NetworkObject](./networkobject.md) - [NetworkBehaviour](./networkbehaviour.md) - [NetworkBehaviour ownership](./networkbehaviour-ownership.md) -- [Ownership](../terms-concepts/ownership.md) -- [Authority](../terms-concepts/authority.md) +- [Ownership](../../terms-concepts/ownership.md) +- [Authority](../../terms-concepts/authority.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md index 56730e4c71..495bed2691 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md @@ -7,7 +7,7 @@ Netcode for GameObjects' high level components, [the RPC system](../../advanced- 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 +NetworkObjects require the use of specialized [`NetworkObjectReference`](xref:Unity.Netcode.NetworkObjectReference) 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. @@ -19,6 +19,32 @@ When spawning a NetworkObject, the `NetworkObject.GlobalObjectIdHash` value init You can use [NetworkBehaviours](networkbehaviour.md) to add your own custom Netcode logic to the associated NetworkObject. +### What is a valid NetworkObject? + +There are two categories of NetworkObjects: +* Dynamically instantiated +* [In-scene placed](../../basics/scenemanagement/inscene-placed-networkobjects.md) + +The following provides the validity requirements for both types. + +#### Dynamically instantiated network prefabs + +Dynamically instantiated network prefabs must: + +* Be a valid [network prefab](./networkobject.md#network-prefabs) created within the Editor. +* Be registered in a network prefab list that's assigned to your NetworkManager. + +#### In-scene placed network prefabs + +In-scene placed network prefabs must: + +* Be a valid network prefab instance within a scene. +* Be a GameObject with a NetworkObject component created within the scene while in the Editor. + +### What is an invalid NetworkObject? + +GameObjects that have NetworkObject components added to them during runtime are **not supported** and will result in the NetworkObject's `GlobalObjectIdHash` being zero, which causes synchronization issues. In the event you make this mistake, a warning message will be logged and the NetworkObject won't be spawned. + ### 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. @@ -65,7 +91,7 @@ Refer to the [NetworkSceneManager active scene synchronization](../../basics/sce ![image](../../images/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. +Similar to [`NetworkObject.ActiveSceneSynchronization`](#active-scene-synchronization), [`NetworkObject.SceneMigrationSynchronization`](xref: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. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/playerobjects.md b/com.unity.netcode.gameobjects/Documentation~/components/core/playerobjects.md index e7ae88353f..a679806c44 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/playerobjects.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/playerobjects.md @@ -53,14 +53,14 @@ In addition to the [session-mode agnostic spawning methods](#session-mode-agnost ### 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). +In addition to the [session-mode agnostic spawning methods](#session-mode-agnostic-methods) above, you can use the [`OnFetchLocalPlayerPrefabToSpawn`](xref: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](../../basics/object-spawning.md#registering-a-network-prefab). If you don't assign a callback handler to `OnFetchLocalPlayerPrefabToSpawn`, then the default behavior 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. +For `OnFetchLocalPlayerPrefabToSpawn` to work, [`AutoSpawnPlayerPrefabClientSide`](xref:Unity.Netcode.NetworkManager.AutoSpawnPlayerPrefabClientSide) must be enabled. ::: ## PlayerObject spawning timeline diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md index 26465b9d6e..09136b095e 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md @@ -1,6 +1,6 @@ # ComponentController -Use a [ComponentController](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.ComponentController.html) component to enable or disable one or more components depending on the authority state of the ComponentController and have those changes synchronized with non-authority instances. +Use a [ComponentController](xref:Unity.Netcode.ComponentController) component to enable or disable one or more components depending on the authority state of the ComponentController and have those changes synchronized with non-authority instances. For example, you can use a ComponentController to enable or disable a MeshRenderer component on the owner of the ComponentController instance, while disabling it for all other clients. This is useful for controlling visibility of objects that should only be visible to the owner, such as a player's avatar or a weapon they are holding. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md index 64df3f347b..d9515ebb47 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md @@ -6,8 +6,8 @@ Understand the helper components available to use in your Netcode for GameObject | :------------------------------ | :------------------------------- | | **[AttachableBehaviour](attachablebehaviour.md)**| Use the AttachableBehaviour component to manage [ComponentController](componentcontroller.md) components and to attach a child GameObject to an [AttachableNode](attachablenode.md). The AttachableBehaviour component provides an alternative to NetworkObject parenting, allowing you to attach and detach child objects dynamically during runtime. | | **[AttachableNode](attachablenode.md)**| Use an AttachableNode component to provide an attachment point for an [AttachableBehaviour](attachablebehaviour.md) component. | -| **[ComponentController](componentcontroller.md)**| Use a [ComponentController](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.ComponentController.html) component to enable or disable one or more components depending on the authority state of the ComponentController and have those changes synchronized with non-authority instances. | +| **[ComponentController](componentcontroller.md)**| Use a [ComponentController](xref:Unity.Netcode.ComponentController) component to enable or disable one or more components depending on the authority state of the ComponentController and have those changes synchronized with non-authority instances. | | **[NetworkAnimator](networkanimator.md)**| The NetworkAnimator component provides you with a fundamental example of how to synchronize animations during a network session. Animation states are synchronized with players joining an existing network session and any client already connected before the animation state changing. | | **[NetworkRigidbody](networkrigidbody.md)**| NetworkRigidbody is a component that sets the Rigidbody of the GameObject into kinematic mode on all non-authoritative instances. | -| **[NetworkTransform](networktransform.md)**| [NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../core/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. | +| **[NetworkTransform](networktransform.md)**| [NetworkTransform](xref:Unity.Netcode.Components.NetworkTransform) is a concrete class that inherits from [NetworkBehaviour](../core/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. | | **[Physics](../../advanced-topics/physics.md)**| Netcode for GameObjects has a built in approach which allows for server-authoritative physics where the physics simulation only runs on the server. | diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/networkrigidbody.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/networkrigidbody.md index 8f412f7898..b931d59afb 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/helper/networkrigidbody.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/networkrigidbody.md @@ -12,7 +12,7 @@ When looking at a NetworkRigidbody in the Inspector view, there are three expose - When enabled and using a [NetworkTransform](networktransform.md), the NetworkTransform uses the PhysX position and rotation to synchronize changes during the `FixedUpdate` loop update stage. - __Auto Update Kinematic State__ - When enabled, NetworkRigidbody automatically determines whether the current instance should be kinematic or non-kinematic. - - For custom solutions, you can opt to disable this field or derive from [NetworkRigidbodyBase](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/api/Unity.Netcode.Components.NetworkRigidbodyBase.html) and design your own custom networked Rigidbody handler. + - For custom solutions, you can opt to disable this field or derive from [NetworkRigidbodyBase](xref:Unity.Netcode.Components.NetworkRigidbodyBase) and design your own custom networked Rigidbody handler. - __Auto Set Kinematic On Despawn__ - When enabled, this option makes the rigid body kinematic when despawned (which can be useful for [object pools](../../advanced-topics/object-pooling.md)). @@ -25,7 +25,7 @@ Some collision events aren't fired when using NetworkRigidBody: - You can adjust the __Contact Pairs Mode__ to use kinematic and non-kinematic by setting it to __Enable All Contact Pairs__.![image](../../images/networktransform/ProjectPhysicsSettings2.png) > [!NOTE] -> If there's a need for a gameplay event to happen on a collision, you can listen to the `OnCollisionEnter` function on the server and synchronize the event via `Rpc(SendTo.Everyone)` to all clients. If you plan on handling many collisions, then it's recommended to use the [RigidbodyContactEventManager component](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.RigidbodyContactEventManager.html) to handle collision checking during a job (`OnCollisionenter` can become expensive from a processing perspective if you have enough instances colliding). +> If there's a need for a gameplay event to happen on a collision, you can listen to the `OnCollisionEnter` function on the server and synchronize the event via `Rpc(SendTo.Everyone)` to all clients. If you plan on handling many collisions, then it's recommended to use the [RigidbodyContactEventManager component](xref:Unity.Netcode.Components.RigidbodyContactEventManager) to handle collision checking during a job (`OnCollisionenter` can become expensive from a processing perspective if you have enough instances colliding). ### NetworkRigidbody2D diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/networktransform.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/networktransform.md index b77c930647..8db6f66114 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/helper/networktransform.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/networktransform.md @@ -1,6 +1,6 @@ # NetworkTransform -[NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../core/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. +[NetworkTransform](xref:Unity.Netcode.Components.NetworkTransform) is a concrete class that inherits from [NetworkBehaviour](../core/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. The synchronization of a GameObject's Transform is a key netcode task, and usually proceeds in the following order: @@ -39,7 +39,7 @@ Theoretically, you can have a NetworkTransform on every child object of a 100 le When nesting NetworkTransforms, you should first determine if there are any alternative approaches to handling child object motion (such as synchronizing through animations or using an algorithm that uses the synchronized network time) to ensure you're not consuming unnecessary processing time and bandwidth. -For example, if you use a [NetworkAnimator](networkanimator.md) component to synchronize animations (instead of nested NetworkTransforms), then you don't need to manage synchronizing each child node of the model because the animation system and NetworkAnimator handle it for you. Similarly, if you want a child object to orbit its parent object, you can use the server time ([`NetworkManager.ServerTime`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?sufolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_ServerTime)) to feed into an algorithm that's used on all instances (local and remote). This removes the need to synchronize motion between child and parent because the algorithm is driven by a value that's already being synchronized on all clients. +For example, if you use a [NetworkAnimator](networkanimator.md) component to synchronize animations (instead of nested NetworkTransforms), then you don't need to manage synchronizing each child node of the model because the animation system and NetworkAnimator handle it for you. Similarly, if you want a child object to orbit its parent object, you can use the server time ([`NetworkManager.ServerTime`](xref:Unity.Netcode.NetworkManager.ServerTime)) to feed into an algorithm that's used on all instances (local and remote). This removes the need to synchronize motion between child and parent because the algorithm is driven by a value that's already being synchronized on all clients. > [!NOTE] > Generally, unless you have some unique motion that needs to be applied to a child object and it can't be synchronized through an animation or an algorithm based on a synchronized value, then you shouldn't nest NetworkTransforms. However, nesting NetworkTransforms is more optimal then creating multiple individual single-node network prefabs with NetworkTransforms and parenting those once they're spawned. @@ -58,13 +58,13 @@ Some NetworkTransform properties are automatically synchronized by the authorita The following are a list of NetworkTransform properties that will cause a full state update (effectively a teleport) when changed during runtime by the authority instance: -- [UseUnreliableDeltas](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_UseUnreliableDeltas) -- [InLocalSpace](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_InLocalSpace) -- [Interpolate](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_Interpolate) -- [SlerpPosition](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_SlerpPosition) -- [UseQuaternionSynchronization](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_UseQuaternionSynchronization) -- [UseQuaternionCompression](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_UseQuaternionCompression) -- [UseHalfFloatPrecision](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_UseHalfFloatPrecision) +- [UseUnreliableDeltas](xref:Unity.Netcode.Components.NetworkTransform.UseUnreliableDeltas) +- [InLocalSpace](xref:Unity.Netcode.Components.NetworkTransform.InLocalSpace) +- [Interpolate](xref:Unity.Netcode.Components.NetworkTransform.Interpolate) +- [SlerpPosition](xref:Unity.Netcode.Components.NetworkTransform.SlerpPosition) +- [UseQuaternionSynchronization](xref:Unity.Netcode.Components.NetworkTransform.UseQuaternionSynchronization) +- [UseQuaternionCompression](xref:Unity.Netcode.Components.NetworkTransform.UseQuaternionCompression) +- [UseHalfFloatPrecision](xref:Unity.Netcode.Components.NetworkTransform.UseHalfFloatPrecision) The following NetworkTransform properties can cause a full state update when changed during runtime by the authority instance: diff --git a/com.unity.netcode.gameobjects/Documentation~/install.md b/com.unity.netcode.gameobjects/Documentation~/install.md index 9fb1b54ef6..c3e5b597ec 100644 --- a/com.unity.netcode.gameobjects/Documentation~/install.md +++ b/com.unity.netcode.gameobjects/Documentation~/install.md @@ -7,7 +7,7 @@ Follow the instructions on this page to set up Netcode for GameObjects in your U Before you begin, you need the following: - An active Unity account with a valid license. -- A supported version of Unity. Check [Netcode for GameObjects' requirements](#netcode-installation-requirements) for the specific version details. +- A supported version of Unity. Check [Netcode for GameObjects' requirements](#compatibility) for the specific version details. - An existing Unity project. If you're new to Unity, you can refer to the [get started](./tutorials/get-started-with-ngo.md) section for guidance. ### Compatibility diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md b/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md index a14cc6d0da..e1c5b056ca 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md @@ -52,7 +52,7 @@ Note here an input from a client can be anything, from a user interacting with a #### Issue: Reactivity -An issue with server authority is you're waiting for your server to tell you to update your world. This means that if you send an input to the server and wait for the server to tell you your position change, you'll need to wait for a full **RTT** before you see the effect. There are [patterns](#patterns-to-solve-these-issues) you can use to solve this issue while still remaining server authoritative. +An issue with server authority is you're waiting for your server to tell you to update your world. This means that if you send an input to the server and wait for the server to tell you your position change, you'll need to wait for a full **RTT** before you see the effect. There are [patterns](#patterns-to-solve-latency-issues-in-a-server-authoritative-game) you can use to solve this issue while still remaining server authoritative. ### Client authority @@ -82,7 +82,7 @@ To avoid this, it's recommended to use client **owner** authority, which allows Client authority is a pretty dangerous door to leave open on your server because any malicious player can forge messages to say "kill player a, b, c, d, e, f, g" and win the game. It's pretty useful though for reactivity. Since the client is making all the important gameplay decisions, it can display the result of user inputs as soon as they happen instead of waiting a few hundred milliseconds. -When you don't think there's any reason for your players to cheat, client authority can be a great way to have reactivity without the complexity added with techniques like [input prediction](#prediction). +When you don't think there's any reason for your players to cheat, client authority can be a great way to have reactivity without the complexity added with techniques like [input prediction](#client-side-prediction). Another way of solving this issue in a client authoritative game is using soft validation server side. Instead of doing all simulation server side, the server only does basic validation. The server would, for example, do range checks to make sure a player isn't teleporting to places it shouldn't. Doing so is acceptable for most [PvE](https://en.wikipedia.org/wiki/Player_versus_environment) games. However, [PvP](https://en.wikipedia.org/wiki/Player_versus_player) games usually require server authority. @@ -157,7 +157,7 @@ To do continuous client driven actions, there's a few more considerations to tak - You then need to make sure you don't send RPCs to the server (containing your authoritative state) when no data has changed and do dirty checks. - You'd need to send it on tick or at worst on FixedUpdate. Sending on Update() would spam your connection. -A sample for a [ClientNetworkTransform](../components/helper/networktransform.md#clientnetworktransform) has been created, so you don't have to reimplement this yourself for transform updates. A [sample](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/tree/main/Basic/ClientDriven) has been created on how to use it. See [movement script](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/ClientDriven/Assets/Scripts/ClientPlayerMove.cs). +A sample for a [ClientNetworkTransform](../components/helper/networktransform.md#client-vs-server-authority) has been created, so you don't have to reimplement this yourself for transform updates. A [sample](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/tree/main/Basic/ClientDriven) has been created on how to use it. See [movement script](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/ClientDriven/Assets/Scripts/ClientPlayerMove.cs). > [!NOTE] > A rule of thumb here is to ask yourself: "Can the server correct me on this?". If it can, use server authority. diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/listenserverhostarchitecture.md b/com.unity.netcode.gameobjects/Documentation~/learn/listenserverhostarchitecture.md index e32e3858c8..d85764142b 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/listenserverhostarchitecture.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/listenserverhostarchitecture.md @@ -33,7 +33,7 @@ In contrast to dedicated servers, listen servers are cheaper without the need to ## Connecting to a listen server -Personal computers are hidden behind NATs (Network Address Translation devices) and routers to protect them from direct access. To connect to a listen server, you may choose an option such as [port forwarding](#port-forwarding), a [relay server](#relay-server), [NAT punch-through](#nat-punchthrough), or a [NAT punch with relay fallback](#nat-punch-and-relay-fallback). +Personal computers are hidden behind NATs (Network Address Translation devices) and routers to protect them from direct access. To connect to a listen server, you may choose an option such as [port forwarding](#port-forwarding), a [relay server](#relay-server), [NAT punch-through](#nat-punch-through), or a [NAT punch with relay fallback](#nat-punch-and-relay-fallback). ### Port Forwarding diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/rpcvnetvar.md b/com.unity.netcode.gameobjects/Documentation~/learn/rpcvnetvar.md index 0713cbab28..3d31914ddb 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/rpcvnetvar.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/rpcvnetvar.md @@ -1,7 +1,7 @@ # RPC vs NetworkVariable Choosing the wrong data syncing mechanism can create bugs, use too much bandwidth, and add too much complexity to your code. -Netcode for GameObjects (Netcode) has two main ways of syncing information between players: RPCs ([Remote Procedure Calls](../advanced-topics/messaging-system.md)) and replicated states [(`NetworkVariable`s)](../basics/networkvariable). They both send messages over the network. The logic and your design around how they send messages is what will make you choose one over the other. +Netcode for GameObjects (Netcode) has two main ways of syncing information between players: RPCs ([Remote Procedure Calls](../advanced-topics/messaging-system.md)) and replicated states [(`NetworkVariable`s)](../basics/networkvariable.md). They both send messages over the network. The logic and your design around how they send messages is what will make you choose one over the other. ## Choosing between `NetworkVariable`s or RPCs diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/ticks-and-update-rates.md b/com.unity.netcode.gameobjects/Documentation~/learn/ticks-and-update-rates.md index 2ab45e73c2..dac2bc523a 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/ticks-and-update-rates.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/ticks-and-update-rates.md @@ -16,7 +16,7 @@ When a server fails to process all incoming client data before the end of the cu Update rate is a measure of how frequently the client sends and receives data to and from the server. It's also measured in hertz and, like tick rate, a higher update rate results in a more responsive game at the cost of increased processing and network demands on the client and server. -![](../../images/update-rates-light.png) +![](../images/update-rates-light.png) In addition to adding to perceived latency, low update rates can cause their own issues, such as multiple updates being bundled together and arriving at the same time, causing undesirable behavior. @@ -24,7 +24,7 @@ In the example of 'super bullets': if an in-game gun is capable of shooting more The diagram below illustrates the difference between a 10 Hz update rate and a 60 Hz update rate. With a 10 Hz update rate, the game sends updates to the server ten times a second, so a gun that can fire 600 rounds per minute (RPM), or ten times a second, will only ever send a single bullet per update. A gun that fires at 750 RPM, more than ten times a second, will end up running into the super bullet problem. A higher update rate of 60 Hz resolves this issue, ensuring that there are enough updates for each bullet to arrive separately in its own update. -![](../../images/rpm_update_rates-light.png) +![](../images/rpm_update_rates-light.png) ### Discrepancy between tick rate and update rate diff --git a/com.unity.netcode.gameobjects/Documentation~/migratingfromUNet.md b/com.unity.netcode.gameobjects/Documentation~/migratingfromUNet.md index ee35801235..8d58a3366d 100644 --- a/com.unity.netcode.gameobjects/Documentation~/migratingfromUNet.md +++ b/com.unity.netcode.gameobjects/Documentation~/migratingfromUNet.md @@ -115,7 +115,7 @@ public class MyNetcodeExample : NetworkBehaviour } ``` -Refer to [NetworkBehaviour](basics/networkbehaviour.md) for more information. +Refer to [NetworkBehaviour](components/core/networkbehaviour.md) for more information. ## Replace SyncVar @@ -343,7 +343,7 @@ Refer to [Object Spawning](basics/object-spawning.md) for more information. ## Custom Spawn Handlers -Netcode has `Custom Spawn Handlers` to replace UNet's `Custom Spawn Functions`. See [Object Pooling](../advanced-topics/object-pooling) for more information. +Netcode has `Custom Spawn Handlers` to replace UNet's `Custom Spawn Functions`. See [Object Pooling](advanced-topics/object-pooling.md) for more information. ## Replace `NetworkContextProperties` diff --git a/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md b/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md index c690759906..8d1cbeac17 100644 --- a/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md +++ b/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md @@ -5,5 +5,5 @@ Understand how to use NetworkBehaviour components in your project. | **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[NetworkBehaviour](components/core/networkbehaviour.md)** | [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](components/core/networkobject.md) component and at least one NetworkBehaviour component. | +| **[NetworkBehaviour](components/core/networkbehaviour.md)** | [NetworkBehaviour](xref:Unity.Netcode.NetworkBehaviour) 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](components/core/networkobject.md) component and at least one NetworkBehaviour component. | | **[Synchronize](components/core/networkbehaviour-synchronize.md)** | You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/removed-tableofcontents.md b/com.unity.netcode.gameobjects/Documentation~/removed-tableofcontents.md index e9eadb4428..224e9f2f47 100644 --- a/com.unity.netcode.gameobjects/Documentation~/removed-tableofcontents.md +++ b/com.unity.netcode.gameobjects/Documentation~/removed-tableofcontents.md @@ -10,7 +10,7 @@ * [Bitesize introduction](samples/bitesize/bitesize-introduction.md) * [Bitesize space shooter](samples/bitesize/bitesize-spaceshooter.md) * [Bitesize client driven](samples/bitesize/bitesize-clientdriven.md) - * [Bitesize dynamic prefabs](samples/bitesize/bitesize-dynamicPrefabs.md) + * [Bitesize dynamic prefabs](samples/bitesize/bitesize-dynamicprefabs.md) * [Bitesize social hub](samples/bitesize/bitesize-socialhub.md) --> \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-landing.md b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-landing.md index 880fbb821e..1009a7343e 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-landing.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-landing.md @@ -8,5 +8,5 @@ The Bite Size samples are small, focused examples that demonstrate specific feat | **[Bitesize introduction](bitesize-introduction.md)** | The Bitesize Samples repository provides a series of sample code as modules to use in your games and better understand Netcode for GameObjects. | | **[Bitesize space shooter](bitesize-spaceshooter.md)** | Learn more about physics movement and status effects using Netcode for GameObjects (Netcode) NetworkVariables and ObjectPooling. | | **[Bitesize client driven](bitesize-clientdriven.md)** | Learn more about Client driven movements, networked physics, spawning vs statically placed objects, object reparenting. | -| **[Bitesize dynamic prefabs](bitesize-dynamicPrefabs.md)** | Learn more about the dynamic Prefab system, which allows you to add new spawnable Prefabs at runtime. | +| **[Bitesize dynamic prefabs](bitesize-dynamicprefabs.md)** | Learn more about the dynamic Prefab system, which allows you to add new spawnable Prefabs at runtime. | | **[Bitesize social hub](bitesize-socialhub.md)** | The Distributed Authority Social Hub Sample is a project that demonstrates Distributed Authority's features and helps you integrate Distributed Authority into your own game projects. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-usecases.md b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-usecases.md index ebdbfc5705..a41c3d1238 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-usecases.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-usecases.md @@ -16,11 +16,11 @@ The tutorials that open with each scene use the [Tutorial Framework package](htt ## The Anticipation scene The Anticipation scene demonstrates the Client Anticipation feature of Netcode for GameObjects in the following use cases: -- [`AnticipatedNetworkVariable`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.2/api/Unity.Netcode.AnticipatedNetworkVariable-1.html): +- [`AnticipatedNetworkVariable`](xref:Unity.Netcode.AnticipatedNetworkVariable%601): - Anticipate server actions based on player interaction to change NetworkVariables responsively. - Compensate for latency that the server causes when it changes a value. - Handle incorrect anticipation. -- [`AnticipatedNetworkTransform`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.2/api/Unity.Netcode.Components.AnticipatedNetworkTransform.html): +- [`AnticipatedNetworkTransform`](xref:Unity.Netcode.Components.AnticipatedNetworkTransform): - Responsive server-authoritative player movement. - Smooth player movement across clients. diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/getting-started-boss-room.md b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/getting-started-boss-room.md index 096d7f72db..214a8fdae5 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/getting-started-boss-room.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/getting-started-boss-room.md @@ -25,8 +25,8 @@ Join the multiplayer community on the Unity [Discord](https://discord.gg/mNgM2XR ### Contents and quick links - [Boss Room Overview](#boss-room-overview) -- [Install the Boss Room project](#getting-the-project) - - [Install Git LFS to clone locally](#installing-git-lfs-to-clone-locally) +- [Install the Boss Room project](#install-the-boss-room-project) + - [Install Git LFS to clone locally](#install-git-lfs-to-clone-locally) - [Direct download](#direct-download) - [Registering the project with Unity Gaming Services (UGS)](#registering-the-project-with-unity-gaming-services-ugs) - [Opening the project for the first time](#opening-the-project-for-the-first-time) diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md index 234830e4c0..ddde94a432 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md @@ -73,19 +73,19 @@ The Boss Room sample shows how to adapt some Unity Transport properties to fit i ### Disconnect Timeout {#disconnect-timeout} -The [Disconnect Timeout property](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Transports.UTP.UnityTransport.html#Unity_Netcode_Transports_UTP_UnityTransport_DisconnectTimeoutMS) controls how long the server (and the clients) wait before disconnecting. The Boss Room sample uses a Disconnect Timeout value of 10 seconds to prevent the server and the clients from hanging onto a connection for too long. +The [Disconnect Timeout property](xref:Unity.Netcode.Transports.UTP.UnityTransport.DisconnectTimeoutMS) controls how long the server (and the clients) wait before disconnecting. The Boss Room sample uses a Disconnect Timeout value of 10 seconds to prevent the server and the clients from hanging onto a connection for too long. ### Max Connect Attempts {#max-connect-attempts} -The [Max Connect Attempts property](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Transports.UTP.UnityTransport.html#Unity_Netcode_Transports_UTP_UnityTransport_MaxConnectAttempts) controls the times a client tries to connect before declaring a connection failure. The Boss Room sample uses a Max Connect Attempts value of 10 to prevent clients from waiting too long before declaring a connection failure. +The [Max Connect Attempts property](xref:Unity.Netcode.Transports.UTP.UnityTransport.MaxConnectAttempts) controls the times a client tries to connect before declaring a connection failure. The Boss Room sample uses a Max Connect Attempts value of 10 to prevent clients from waiting too long before declaring a connection failure. ### Connect Timeout {#connect-timeout} -The [Connect Timeout property](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Transports.UTP.UnityTransport.html#Unity_Netcode_Transports_UTP_UnityTransport_ConnectTimeoutMS) controls the number of times clients attempt to connect per second. The Boss Room sample uses a Connect Timeout value of 1 second, meaning that clients try to connect once per second. Having the Connect Timeout set to 1 second and the Max Connect Attempts set to 10 means that clients fail to connect after 10 seconds of waiting. +The [Connect Timeout property](xref:Unity.Netcode.Transports.UTP.UnityTransport.ConnectTimeoutMS) controls the number of times clients attempt to connect per second. The Boss Room sample uses a Connect Timeout value of 1 second, meaning that clients try to connect once per second. Having the Connect Timeout set to 1 second and the Max Connect Attempts set to 10 means that clients fail to connect after 10 seconds of waiting. ### Max Packet Queue Size {#max-packet-queue-size} -The [Max Packet Queue Size property](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Transports.UTP.UnityTransport.html#Unity_Netcode_Transports_UTP_UnityTransport_MaxPacketQueueSize) defines the maximum number of packets that can be sent and received during a single frame. +The [Max Packet Queue Size property](xref:Unity.Netcode.Transports.UTP.UnityTransport.MaxPacketQueueSize) defines the maximum number of packets that can be sent and received during a single frame. The impact of surpassing the Max Packet Queue Size threshold varies depending on the packet direction (sending or receiving). diff --git a/com.unity.netcode.gameobjects/Documentation~/serialization.md b/com.unity.netcode.gameobjects/Documentation~/serialization.md index 4dfd16f484..51aee596eb 100644 --- a/com.unity.netcode.gameobjects/Documentation~/serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/serialization.md @@ -14,4 +14,4 @@ Netcode for GameObjects has built-in serialization support for C# and Unity prim | **[Custom serialization](advanced-topics/custom-serialization.md)** | Create custom serialization types. | | **[NetworkObject serialization](advanced-topics/serialization/networkobject-serialization.md)** | `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`. | | **[FastBufferWriter and FastBufferReader](advanced-topics/fastbufferwriter-fastbufferreader.md)** | 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. | -| **[BufferSerializer](advanced-topics/bufferserializer.md)** | A bi-directional serializer primarily used for serializing within [`INetworkSerializable`](serialization/inetworkserializable.md) types. | +| **[BufferSerializer](advanced-topics/bufferserializer.md)** | A bi-directional serializer primarily used for serializing within [`INetworkSerializable`](advanced-topics/serialization/inetworkserializable.md) types. | diff --git a/com.unity.netcode.gameobjects/Documentation~/terms-concepts/distributed-authority.md b/com.unity.netcode.gameobjects/Documentation~/terms-concepts/distributed-authority.md index 74f49a39c8..17c1f8b77b 100644 --- a/com.unity.netcode.gameobjects/Documentation~/terms-concepts/distributed-authority.md +++ b/com.unity.netcode.gameobjects/Documentation~/terms-concepts/distributed-authority.md @@ -53,7 +53,7 @@ You can use this property to conditionally execute logic that should only run on ### Session owner NetworkObjects -For game systems that should always be [owned](./ownership.md) by the session owner, you can set the NetworkObject to have the `OwnershipStatus.SessionOwner` [ownership permission](../advanced-topics/networkobject-ownership.md). This ensures that the NetworkObject always belongs to the current session owner. If that session owner disconnects or leaves the game, the ownership of that NetworkObject will be automatically moved to the newly selected session owner. +For game systems that should always be [owned](./ownership.md) by the session owner, you can set the NetworkObject to have the `OwnershipStatus.SessionOwner` [ownership permission](../components/core/networkobject-ownership.md). This ensures that the NetworkObject always belongs to the current session owner. If that session owner disconnects or leaves the game, the ownership of that NetworkObject will be automatically moved to the newly selected session owner. ## NetworkObject distribution diff --git a/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md b/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md index 3d47f69024..56ff42526e 100644 --- a/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md +++ b/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md @@ -35,7 +35,7 @@ In [distributed authority](./distributed-authority.md) the owner of a NetworkObj ![diagram showing ownership precedes authority](../images/diagrams/distributedAuthorityOwnership.png) -When building your game you can use [ownership permissions](../advanced-topics/networkobject-ownership.md#ownership-permission-settings) to control how and when ownership of NetworkObjects can be transferred between clients. +When building your game you can use [ownership permissions](../components/core/networkobject-ownership.md#ownership-permission-settings) to control how and when ownership of NetworkObjects can be transferred between clients. NetworkObjects with the `OwnershipStatus.Distributable` permission will have their ownership automatically distributed between all connected game clients whenever a new client joins or an existing client leaves. This is the key mechanism in how the game simulation is distributed between clients in a distributed authority session. @@ -48,4 +48,4 @@ The `IsOwner` property, which is available on both NetworkObjects and NetworkBeh * [Authority](authority.md) * [Client-server](client-server.md) * [Distributed authority](distributed-authority.md) -* [NetworkObject ownership](../advanced-topics/networkobject-ownership.md) +* [NetworkObject ownership](../components/core/networkobject-ownership.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md index 4a80b3fd1a..bbd635c497 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md @@ -33,7 +33,7 @@ This section guides you through adding the essential components of a networked g - [A NetworkManager component](#create-the-networkmanager-component) - [A player object](#create-an-object-to-spawn-for-each-connected-player) -- [A scene](#add-your-scene-to-the-build) +- [A scene](#scene-management-and-the-scenes-in-build-list) ### Create the NetworkManager component @@ -68,7 +68,7 @@ First, create the NetworkManager component: ### Create an object to spawn for each connected player > [!NOTE] -> When you drop the prefab into the **PlayerPrefab** slot, you're telling the library that when a client connects to the game, it automatically spawns this prefab as the character for the connecting client. Netcode for GameObjects won't spawn a player object if you don't have any prefab set as the **PlayerPrefab**. Refer to [Player Objects](../components/core/networkobject.md#finding-playerobjects). +> When you drop the prefab into the **PlayerPrefab** slot, you're telling the library that when a client connects to the game, it automatically spawns this prefab as the character for the connecting client. Netcode for GameObjects won't spawn a player object if you don't have any prefab set as the **PlayerPrefab**. Refer to [Player Objects](../components/core/playerobjects.md#finding-playerobjects). This section guides you through creating an object that spawns for each connected player. @@ -111,7 +111,7 @@ Netcode for GameObjects comes with an integrated scene management solution that Now that you have a **NetworkManager**, assigned a **PlayerPrefab**, and added your current scene to the scenes in build test, you can quickly verify everything is functioning/configured correctly via entering play mode in the Unity Editor. By starting a host, you are starting NetworkManager as both a server and a client at the same time. -You can test your Hello World project using the Unity Editor or a command-line helper. If you choose the latter, refer to [Create a command line helper](../tutorials/command-line-helper/). Otherwise, refer to the following instructions to test using the Unity Editor. Only the Plane appears on the server until the first client connects. Then, Netcode for GameObjects spawns a new Player prefab for each connected client; however, they overlap in the Game view. +You can test your Hello World project using the Unity Editor or a command-line helper. If you choose the latter, refer to [Create a command line helper](command-line-helper.md). Otherwise, refer to the following instructions to test using the Unity Editor. Only the Plane appears on the server until the first client connects. Then, Netcode for GameObjects spawns a new Player prefab for each connected client; however, they overlap in the Game view. 1. Select **Play** from the top of the Unity Editor to start the scene. @@ -119,7 +119,7 @@ You can test your Hello World project using the Unity Editor or a command-line h 2. Select **NetworkManager** from the **Hierarchy** list. -![](\img\get-started-ngo\ngo-2.png) +![](../images/get-started-ngo/ngo-2.png) 3. With **NetworkManager** selected (in the Hierarchy tab), select **Start Host** from the **Inspector** tab. Alternatively, you can use the in-game GUI buttons. @@ -239,7 +239,7 @@ The `HelloWorldManager.cs` script accomplishes this menu within the `StartButton As seen in the earlier code snippet, the `HelloWorldManager.cs` script also uses the NetworkManager's instance via its singleton to grab properties like the `IsClient`, `IsServer`, and `IsLocalClient`. The `IsClient` and `IsServer` properties dictate the established connection state. -The `HelloWorldManager.cs` script also introduces a new method called `SubmitNewPosition()` that the `HelloWorldPlayer` script uses to [create a simple RPC call](#add-simple-rpc-use). +The `HelloWorldManager.cs` script also introduces a new method called `SubmitNewPosition()` that the `HelloWorldPlayer` script uses to [create a simple RPC call](#adding-rpcs-remote-procedure-calls). ## Adding RPCs (Remote Procedure Calls) diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/techniques_and_tricks_for_debugging_multiplayer_games.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/techniques_and_tricks_for_debugging_multiplayer_games.md index aadbec86b8..6f5bbcd88d 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/techniques_and_tricks_for_debugging_multiplayer_games.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/techniques_and_tricks_for_debugging_multiplayer_games.md @@ -10,7 +10,7 @@ Below is a list of practices and techniques that we use daily when working on th ### Use ParrelSync clone-based workflow during development. -[ParrelSync workflow](testing_locally.md#parrelsync) is faster than creating builds and it allows you to debug the separate editor instances via separate IDE debuggers. +[ParrelSync workflow](testing_with_artificial_conditions.md#clone-based-workflow-parrelsync) is faster than creating builds and it allows you to debug the separate editor instances via separate IDE debuggers. Use ParrelSync to run separate editor instances for your Host/Server and Client. @@ -22,7 +22,7 @@ Unity engine has two debug rendering APIs that are useful for the purposes of mu Both of these functions allow us to draw arbitrary debug lines that would be visible in the Scene view and in the Game view, provided Gizmo rendering is enabled (to enable Gizmo rendering in Game view you need to click on the `Gizmos` menu at the top of the Game view). -The key trick here is to use different colors for different kinds of information and to make the lines stay long enough for visual inspection by setting `duration` parameter. This technique shines when it's combined with [screen recordings](#7-recording-the-video-of-gameplay) of [multiple peers running side by side in separate editor instances via ParrelSync](#1-use-parrelsync-workflow-during-development). +The key trick here is to use different colors for different kinds of information and to make the lines stay long enough for visual inspection by setting `duration` parameter. This technique shines when it's combined with [screen recordings](#capturing-screen-recordings-of-the-game-instances) of [multiple peers running side by side in separate editor instances via ParrelSync](#use-parrelsync-clone-based-workflow-during-development). The code below would render a green debug line that's 2 units tall at the position of the transform, and this line would stay on screen for 4 seconds: `Debug.DrawLine(this.transform.position, this.transform.position + Vector3.UP * 2f, Color.green, duration: 4f);` diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md index f3c0646638..634f84288a 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md @@ -11,11 +11,11 @@ This is challenging when we are [iterating on our game locally](testing_locally. Thankfully there are a number of tools that can simulate adverse network conditions. -For testing locally within the Editor, you can use the [Network Simulator tool](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) along with [clone-based workflow via ParrelSync](testing_locally.md#parrelsync). +For testing locally within the Editor, you can use the [Network Simulator tool](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) along with [clone-based workflow via ParrelSync](#clone-based-workflow-parrelsync). For testing development builds with built-in artificial latency we suggest using [Network Simulator tools with some custom code to inject artificial conditions into the build](#debug-builds). -For testing release builds we suggest using [Clumsy](#clumsy-windows) if you're on Windows and Network Link Conditioner if you're on [macOS](#network-link-conditioner-mac-os) or [iOS](#network-link-conditioner-ios). A scriptable alternative to Network Link Conditioner on macOS is [dummynet](#dummynet-dnctl-and-pftcl-mac-os), which offers great control and comes packaged with the operating system. +For testing release builds we suggest using [Clumsy](#clumsy-windows) if you're on Windows and Network Link Conditioner if you're on [macOS](#network-link-conditioner-macos) or [iOS](#network-link-conditioner-ios). A scriptable alternative to Network Link Conditioner on macOS is [dummynet](#dummynet-dnctl-and-pftcl-macos), which offers great control and comes packaged with the operating system. > [!NOTE] > While artificial latency is great for simulating network conditions during development - it won't accurately emulate real world conditions. We recommend to test your game often on the targeted platforms and real live networking conditions. @@ -49,7 +49,7 @@ Adding packet loss, apart from introducing even more effective delay to our syst ### Different network conditions for different peers -[Clumsy](#clumsy-on-windows), [Network Link Conditioner](#network-link-conditioner-mac-os) and [dummynet](#dummynet-dnctl-and-pftcl-mac-os) are introducing changes on OS level, thus all the instances of the game that we open on our local machine would run under the same network conditions. +[Clumsy](#clumsy-windows), [Network Link Conditioner](#network-link-conditioner-macos) and [dummynet](#dummynet-dnctl-and-pftcl-macos) are introducing changes on OS level, thus all the instances of the game that we open on our local machine would run under the same network conditions. Don't forget to disable it once you're done debugging, else your network connection will feel slow! @@ -65,7 +65,7 @@ In this case we would want to have an ability to set artificial conditions on a > ParallelSync is **not** supported by Unity. More information on its usage is available [here](https://github.com/VeriorPies/ParrelSync). Troubleshooting information can be found [here](https://github.com/VeriorPies/ParrelSync/wiki/Troubleshooting-&-FAQs) -Simulator Tools effects only apply to editor instances and to [debug builds](#debug-builds), as such it matches well with [clone-based workflow via ParrelSync](testing_locally.md#parrelsync). +Simulator Tools effects only apply to editor instances and to [debug builds](#debug-builds), as such it matches well with [clone-based workflow via ParrelSync](#clone-based-workflow-parrelsync). Other tools should be used when testing release builds locally. diff --git a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs index f7529de12c..8f66c3aa4f 100644 --- a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs +++ b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs @@ -24,6 +24,12 @@ public void OnProcessScene(Scene scene, BuildReport report) log.AddInfo(scene.name, scene.handle); foreach (var networkObject in FindObjects.FromSceneByType(scene, true)) { + // Trap for users just creating things during runtime where this will be zero. + if (networkObject.GlobalObjectIdHash == 0) + { + log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s GlobalObjectIdHash value is zero! Runtime creating of {nameof(NetworkObject)}s is not supported. Skipping processing.").AddNetworkObject(networkObject)); + continue; + } if (networkObject.SceneOrigin.IsValid() && networkObject.SceneOrigin.handle != scene.handle) { log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s SceneOrigin doesn't match current scene being processed! Skipping processing.").AddInfo("SceneOrigin", networkObject.SceneOriginHandle).AddNetworkObject(networkObject)); @@ -36,7 +42,15 @@ public void OnProcessScene(Scene scene, BuildReport report) continue; } + // If already marked, the do nothing. + if (networkObject.InScenePlaced) + { + continue; + } + networkObject.InScenePlaced = true; + // Will not be true when making a build and the values are serialized. + networkObject.InScenePlacedPostProcessorMarkedDuringRuntime = Application.isPlaying; } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index c999c26e9f..bf275ab3c3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -768,7 +768,7 @@ protected virtual void Awake() if (!m_Animator) { #if !UNITY_EDITOR - Debug.LogError($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly."); + Debug.LogWarning($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly."); #endif return; } diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs index 9fdfffbd38..0603ba44a7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs @@ -47,22 +47,110 @@ public class NetworkPrefabs [NonSerialized] private List m_Prefabs = new List(); + /// + /// Returns the last registered prefab. + /// + internal NetworkPrefab GetLastRegisteredPrefab() + { + if (m_Prefabs.Count == 0) + { + return null; + } + return m_Prefabs[m_Prefabs.Count - 1]; + } + + /// + /// Applies a network prefab at a specific index + /// + /// index to apply + /// network prefab to be applied + /// + internal bool AssignPrefabAtIndex(int index, NetworkPrefab networkPrefab) + { + if (index >= m_Prefabs.Count) + { + NetworkManager.Singleton.Log.Error(new Logging.Context(LogLevel.Normal, $"[{nameof(NetworkPrefabs)}][{nameof(AssignPrefabAtIndex)}] Cannot apply prefab to index {index} when the {nameof(m_Prefabs)} count is only {m_Prefabs.Count}!")); + return false; + } + m_Prefabs[index] = networkPrefab; + return true; + } + + [NonSerialized] + private Dictionary m_PrefabHashIds = new Dictionary(); + [NonSerialized] private List m_RuntimeAddedPrefabs = new List(); - private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + private bool InternalAddPrefab(NetworkPrefab networkPrefab) { if (AddPrefabRegistration(networkPrefab)) { // Don't add this to m_RuntimeAddedPrefabs // This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there. m_Prefabs.Add(networkPrefab); + + // We are not getting all potential overrides but just determining if the prefab has been registered. + if (!m_PrefabHashIds.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash)) + { + m_PrefabHashIds.Add(networkPrefab.SourcePrefabGlobalObjectIdHash, networkPrefab); + } + if (!m_PrefabHashIds.ContainsKey(networkPrefab.TargetPrefabGlobalObjectIdHash)) + { + m_PrefabHashIds.Add(networkPrefab.TargetPrefabGlobalObjectIdHash, networkPrefab); + } + return true; } + return false; } - private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + private void InternalRemovePrefab(NetworkPrefab networkPrefab) { m_Prefabs.Remove(networkPrefab); + m_PrefabHashIds.Remove(networkPrefab.SourcePrefabGlobalObjectIdHash); + } + + internal bool IsBasedOnRegisteredPrefab(NetworkObject networkObject) + { + + + return m_PrefabHashIds.ContainsKey(networkObject.GlobalObjectIdHash); + } + + internal bool IsActualPrefabAsset(NetworkObject networkObject) + { + var isActualPrefabAsset = false; + if (m_PrefabHashIds.TryGetValue(networkObject.GlobalObjectIdHash, out NetworkPrefab networkPrefab)) + { + switch (networkPrefab.Override) + { + case NetworkPrefabOverride.Prefab: + case NetworkPrefabOverride.None: + { + isActualPrefabAsset = networkPrefab.Prefab != null && networkObject.gameObject == networkPrefab.Prefab; + break; + } + case NetworkPrefabOverride.Hash: + { + isActualPrefabAsset = networkPrefab.SourceHashToOverride == networkObject.GlobalObjectIdHash; + break; + } + } + } + return isActualPrefabAsset; + } + + private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + { + // Don't add this to m_RuntimeAddedPrefabs + // This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there. + InternalAddPrefab(networkPrefab); + // Log warning if this returns false? + } + + private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + { + InternalRemovePrefab(networkPrefab); } /// @@ -93,6 +181,7 @@ internal void Shutdown() /// When true, logs warnings about invalid prefabs that are removed during initialization public void Initialize(bool warnInvalid = true) { + m_PrefabHashIds.Clear(); m_Prefabs.Clear(); NetworkPrefabsLists.RemoveAll(x => x == null); foreach (var list in NetworkPrefabsLists) @@ -113,7 +202,7 @@ public void Initialize(bool warnInvalid = true) prefabs.AddRange(list.PrefabList); } } - + m_PrefabHashIds = new Dictionary(); m_Prefabs = new List(); List removeList = null; @@ -124,11 +213,7 @@ public void Initialize(bool warnInvalid = true) foreach (var networkPrefab in prefabs) { - if (AddPrefabRegistration(networkPrefab)) - { - m_Prefabs.Add(networkPrefab); - } - else + if (!InternalAddPrefab(networkPrefab)) { removeList?.Add(networkPrefab); } @@ -136,11 +221,7 @@ public void Initialize(bool warnInvalid = true) foreach (var networkPrefab in m_RuntimeAddedPrefabs) { - if (AddPrefabRegistration(networkPrefab)) - { - m_Prefabs.Add(networkPrefab); - } - else + if (!InternalAddPrefab(networkPrefab)) { removeList?.Add(networkPrefab); } @@ -171,14 +252,12 @@ public void Initialize(bool warnInvalid = true) /// public bool Add(NetworkPrefab networkPrefab) { - if (AddPrefabRegistration(networkPrefab)) + var added = InternalAddPrefab(networkPrefab); + if (added) { - m_Prefabs.Add(networkPrefab); m_RuntimeAddedPrefabs.Add(networkPrefab); - return true; } - - return false; + return added; } /// @@ -197,8 +276,7 @@ public void Remove(NetworkPrefab prefab) { throw new ArgumentNullException(nameof(prefab)); } - - m_Prefabs.Remove(prefab); + InternalRemovePrefab(prefab); m_RuntimeAddedPrefabs.Remove(prefab); OverrideToNetworkPrefab.Remove(prefab.TargetPrefabGlobalObjectIdHash); NetworkPrefabOverrideLinks.Remove(prefab.SourcePrefabGlobalObjectIdHash); @@ -310,10 +388,9 @@ private bool AddPrefabRegistration(NetworkPrefab networkPrefab) // Make sure the prefab isn't already registered. if (NetworkPrefabOverrideLinks.ContainsKey(source)) { - var networkObject = networkPrefab.Prefab.GetComponent(); - + var nameOrHashOverride = networkPrefab.Override == NetworkPrefabOverride.Hash ? $"Hash: {networkPrefab.SourcePrefabGlobalObjectIdHash}" : networkPrefab.Prefab?.name; // This should never happen, but in the case it somehow does log an error and remove the duplicate entry - Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!"); + Debug.LogError($"{nameof(NetworkPrefab)} ({nameOrHashOverride}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!"); return false; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs b/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs index 59329f8a81..109d40f4d3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs @@ -69,8 +69,8 @@ private struct ObjectsInSceneEnumerator : IEnumerable, IEnumerator wher internal ObjectsInSceneEnumerator(Scene scene, bool includeInactive) { m_IncludeInactive = includeInactive; - - m_RootObjects = scene.GetRootGameObjects(); + // If the scene is invalid, use an empty array. + m_RootObjects = scene.IsValid() ? scene.GetRootGameObjects() : new UnityEngine.GameObject[0]; m_RootIndex = 0; m_CurrentChildObjects = null; m_CurrentChildIndex = 0; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index d493b65d00..306cbf4572 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -358,6 +358,9 @@ private void CheckForInScenePlaced() // Default scene migration synchronization to false for in-scene placed NetworkObjects SceneMigrationSynchronization = false; + + // Set our disabled in-scene placed flag for spawning initially disabled in-scene placed objects. + m_InScenePlacedDisabledByDefault = !gameObject.activeInHierarchy; } } #endif // UNITY_EDITOR @@ -424,6 +427,24 @@ public void ApplyScale(Vector3 scale) } } #endif + /// + /// This is intentionally private since this is a sealed class. + /// + private void Awake() + { + SetCachedParent(transform.parent); + SceneOrigin = gameObject.scene; + } + + /// + /// Used to provide support for initially disabled in-scene placed objects. + /// This is only ever set on in-scene placed objects that are already disabled + /// in the scene asset itself. + /// + [HideInInspector] + [SerializeField] + private bool m_InScenePlacedDisabledByDefault; + /// /// Gets the NetworkManager that owns this NetworkObject instance /// @@ -1328,6 +1349,13 @@ internal set } } + /// + /// This provides a means to determine if the post processing had applied + /// the in-scene placed status or if it was already serialized. This is used + /// when determining if the thing being spawned is a valid thing to spawn. + /// + internal bool InScenePlacedPostProcessorMarkedDuringRuntime; + /// /// Sets whether this NetworkObject was instantiated as part of a scene /// @@ -1954,6 +1982,13 @@ private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool play } } + // Trap for runtime generated instances as this is not valid + if (GlobalObjectIdHash == 0) + { + NetworkManager.Log.ErrorServer(new Context(LogLevel.Error, $"Detected {nameof(NetworkObject)} {nameof(GlobalObjectIdHash)} value of 0!" + + $"This is typically a sign of runtime generated network prefab assets which are not supported.").AddNetworkObject(this)); + return; + } // Calculate the legacy IsSceneObject value as the public field is obsolete with warning // We can't break the public behavior of the field. @@ -1961,13 +1996,15 @@ private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool play var legacyIsSceneObject = IsSceneObject.HasValue && IsSceneObject.Value; #pragma warning restore CS0618 // Type or member is obsolete - // If SpawnInternal is being called on an object that is marked as InScenePlaced, - // The scene object was never automatically spawned when the scene was loaded. - // Count this object as a dynamically spawned object. - // TODO-[MTT-15388]: Actually support disabled/not spawned InScenePlaced NetworkObjects - if (InScenePlaced && !HasBeenSpawned) + // If the initial state of the GameObject was disabled and InScenePlaced is marked, + // then spawn it as in-scene placed. + // Otherwise: + // If we are marked as in-scene place, have never been spawned, and the root GameObject + // was not disabled upon being instantiated, then treat this as a dynamically spawned + // instance. + if (InScenePlaced && !m_InScenePlacedDisabledByDefault && !HasBeenSpawned) { - if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement && NetworkManagerOwner.LogLevel <= LogLevel.Developer) + if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement && NetworkManagerOwner.LogLevel <= LogLevel.Normal) { Debug.LogWarning($"[{name}][SceneOrigin={SceneOriginHandle}] Dynamically spawning InScenePlaced network object. This can cause issues!", this); } @@ -3125,17 +3162,31 @@ internal void SynchronizeOwnerNetworkVariables(ulong originalOwnerId, ulong orig childBehaviour.MarkOwnerReadDirtyAndCheckOwnerWriteIsDirty(); } + // If the spawn authority of a distributed authority network topology has invoked a change in ownership, + // then we want to invoke the NetworkBehaviourUpdate prior to changing the owner back. + if (NetworkManager.DistributedAuthorityMode) + { + // Force send a state update for all owner read NetworkVariables and any currently dirty + // owner write NetworkVariables. + NetworkManagerOwner.BehaviourUpdater.NetworkBehaviourUpdate(true); + } + // Now set the new owner and previous owner identifiers back to their original new values - // before we run the NetworkBehaviourUpdate. For owner read only permissions this order of - // operations is **particularly important** as we need to first (above) mark things as dirty - // from the context of the original owner and then second (below) we need to send the messages - // which requires the new owner to be set for owner read permission NetworkVariables. + // after we run the NetworkBehaviourUpdate. OwnerClientId = currentOwnerId; PreviousOwnerId = originalOwnerId; - // Force send a state update for all owner read NetworkVariables and any currently dirty - // owner write NetworkVariables. - NetworkManagerOwner.BehaviourUpdater.NetworkBehaviourUpdate(true); + // For owner read only permissions this order of operations is **particularly important** as + // we need to first (above) mark things as dirty from the context of the original owner and + // then second (below) we need to send the messages which requires the new owner to be set. + // Note: Owner read only NetworkVariables do not make sense in a distributed authority topology + // since the only instance that can write is the owner. + if (!NetworkManager.DistributedAuthorityMode) + { + // Force send a state update for all owner read NetworkVariables and any currently dirty + // owner write NetworkVariables. + NetworkManagerOwner.BehaviourUpdater.NetworkBehaviourUpdate(true); + } } // NGO currently guarantees that the client will receive spawn data for all objects in one network tick. @@ -3876,13 +3927,6 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) } } - private void Awake() - { - SetCachedParent(transform.parent); - SceneOrigin = gameObject.scene; - - } - #if UNIFIED_NETCODE #if DEBUG_ENABLE_DISABLE diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index 02fbe958e9..dd10eddc68 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -51,7 +51,7 @@ public enum SendTo /// NotMe, /// - /// Send this RPC to everone, filtered to the current observer list. + /// Send this RPC to everyone, filtered to the current observer list. /// Will execute locally. /// Everyone, @@ -220,7 +220,7 @@ public void Dispose() public BaseRpcTarget NotMe; /// - /// Send this RPC to everone, filtered to the current observer list. + /// Send this RPC to everyone, filtered to the current observer list. /// Will execute locally. /// public BaseRpcTarget Everyone; diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 2cf076e1db..b0a3c6b6b0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1135,7 +1135,6 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n NetworkLog.LogError(new Context(LogLevel.Developer, "Player prefab is marked as belonging to a scene. This may cause issues.").AddNetworkObject(networkObject).AddInfo("SceneName", networkObject.SceneOrigin.name)); networkObject.InScenePlaced = false; } - NetworkLog.InternalAssert(sceneObject == networkObject.InScenePlaced, "Legacy sceneObject value should match calculated InScenePlaced value."); if (!networkObject.InScenePlaced && NetworkManager.LogLevel <= LogLevel.Error) { @@ -1613,10 +1612,48 @@ internal void ServerSpawnSceneObjectsOnStartSweep() continue; } - // This used to be two loops. - // The first added all NetworkObjects to a list and the second spawned all NetworkObjects in the list. - // Now, a parent will set its children's IsSceneObject value when spawned, so we check for null or for true. - if (networkObject.InScenePlaced) + // Do not attempt to spawn if it is the actual prefab asset itself: + // - This is not supported by NGO. + // - This will lead to other issues if it gets destroyed, when de-spawned, but the prefab is still registered. + // - This also prevents from spawning integration test prefabs. + if (NetworkManager.NetworkConfig.Prefabs.IsActualPrefabAsset(networkObject)) + { + NetworkManager.Log.Warning(new Context(LogLevel.Developer, $"Skipping {networkObject.name} as it is the actual prefab asset itself!")); + continue; + } + + // Determine if this is even a valid thing to spawn: + // - If it is not based on a registered prefab, it is invalid. + // - If the GlobalObjectIdHash is zero, it is invalid. + var isValidInstanceToSpawn = NetworkManager.NetworkConfig.Prefabs.IsBasedOnRegisteredPrefab(networkObject) && networkObject.GlobalObjectIdHash != 0; + + // If we are a valid prefab asset, marked as in-scene placed, but this was marked during runtime by the post processor. + if (isValidInstanceToSpawn && networkObject.InScenePlaced && networkObject.InScenePlacedPostProcessorMarkedDuringRuntime) + { + // Then it is not in-scene placed and was pre-instantiated. Spawn dynamically. + networkObject.InScenePlaced = false; + } + else if (networkObject.InScenePlaced && !networkObject.InScenePlacedPostProcessorMarkedDuringRuntime) + { + // If this was marked as in-scene placed within the editor, then it is valid. + isValidInstanceToSpawn = true; + } + + var wasPreInstantiated = !networkObject.IsSpawned && !networkObject.InScenePlaced; + + // Dynamically created NetworkObjects instances are not supported and will not be spawned during the sweep. + if (wasPreInstantiated && !isValidInstanceToSpawn) + { + NetworkManager.Log.Error(new Context(LogLevel.Error, $"Detected a pre-instantiated {nameof(GameObject)} " + + $"with a {nameof(NetworkObject)} component instance that is not a registered prefab nor an in-scene placed {nameof(NetworkObject)}." + + $" Dynamically creating unregistered {nameof(NetworkObject)}s is not supported! This {nameof(NetworkObject)} will not be spawned.").AddNetworkObject(networkObject)); + continue; + } + + // The only valid things to spawn during the sweep are: + // - In-scene placed NetworkObjects. + // - Pre-instantiated NetworkObjects that are registered with the NetworkManager's network prefab list(s). + if (networkObject.InScenePlaced || wasPreInstantiated) { var ownerId = networkObject.OwnerClientId; if (NetworkManager.DistributedAuthorityMode) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs index 2715f33d93..ffe180d52e 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs @@ -1,3 +1,4 @@ +#if !NGO_EXCLUDE_HEAVY_TESTS using System.IO; using System.Reflection; using NUnit.Framework; @@ -40,3 +41,4 @@ public void BasicBuildTest() } } } +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs index 3e46ec4b9e..d427251afe 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs @@ -38,8 +38,7 @@ internal class BufferDataValidationComponent : NetworkBehaviour private List m_SendBuffer; private List m_PreCalculatedBufferValues; - // Start is called before the first frame update - private void Start() + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) { m_WaitForValidation = false; m_CurrentBufferSize = BufferSizeStart; @@ -49,6 +48,7 @@ private void Start() { m_PreCalculatedBufferValues.Add((byte)Random.Range(0, 255)); } + base.OnNetworkPreSpawn(ref networkManager); } /// @@ -67,7 +67,12 @@ public bool IsTestComplete() // Update is called once per frame private void Update() { - if (NetworkManager.Singleton.IsListening && EnableTesting && !IsTestComplete() && !m_WaitForValidation) + if (!EnableTesting || !IsSpawned) + { + return; + } + + if (!m_WaitForValidation && !IsTestComplete()) { m_SendBuffer.Clear(); //Keep the current contents of the bufffer and fill the buffer with the delta difference of the buffer's current size and new size from the m_PreCalculatedBufferValues @@ -77,16 +82,16 @@ private void Update() m_WaitForValidation = true; //Send the buffer - SendBufferServerRpc(m_SendBuffer.ToArray()); + SendBufferRpc(m_SendBuffer.ToArray()); } + } /// - /// Server side RPC for testing + /// Sends to self for buffer queue testing /// - /// server rpc parameters - [ServerRpc] - private void SendBufferServerRpc(byte[] buffer) + [Rpc(SendTo.Me)] + private void SendBufferRpc(byte[] buffer) { TestFailed = !NetworkManagerHelper.BuffersMatch(0, buffer.Length, buffer, m_SendBuffer.ToArray()); if (!TestFailed) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs index 3e77d9762b..616c9f03d4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs @@ -242,7 +242,7 @@ internal class NetworkVariableTestComponent : NetworkBehaviour private float m_WaitForChangesTimeout; // Start is called before the first frame update - private void InitializeTest() + public void InitializeTest() { // Generic Constructor Test Coverage m_NetworkVariableBool = new NetworkVariable(); @@ -356,11 +356,6 @@ public bool IsTestComplete() return m_FinishedTests; } - public void Awake() - { - InitializeTest(); - } - public void AssertAllValuesAreCorrect() { Assert.AreEqual(false, m_NetworkVariableBool.Value); @@ -423,56 +418,52 @@ public void AssertAllValuesAreCorrect() // Update is called once per frame private void Update() { - if (EnableTesting) + if (!EnableTesting || !IsSpawned) { - //Added timeout functionality for near future changes to NetworkVariables - if (!m_FinishedTests && m_ChangesAppliedToNetworkVariables) - { - //We finish testing if all NetworkVariables changed their value or we timed out waiting for - //all NetworkVariables to change their value - m_FinishedTests = DidAllValuesChange() || (m_WaitForChangesTimeout < Time.realtimeSinceStartup); - } - else + return; + } + + if (!m_ChangesAppliedToNetworkVariables) + { + //Now change all of the values to make sure we are at least testing the local callback + m_NetworkVariableBool.Value = false; + m_NetworkVariableByte.Value = 255; + m_NetworkVariableColor.Value = new Color(100, 100, 100); + m_NetworkVariableColor32.Value = new Color32(100, 100, 100, 100); + m_NetworkVariableDouble.Value = 1000; + m_NetworkVariableFloat.Value = 1000.0f; + m_NetworkVariableInt.Value = 1000; + m_NetworkVariableLong.Value = 100000; + m_NetworkVariableSByte.Value = -127; + m_NetworkVariableQuaternion.Value = new Quaternion(100, 100, 100, 100); + m_NetworkVariablePose.Value = new Pose(new Vector3(100, 100, 100), new Quaternion(100, 100, 100, 100)); + m_NetworkVariableShort.Value = short.MaxValue; + m_NetworkVariableVector4.Value = new Vector4(1000, 1000, 1000, 1000); + m_NetworkVariableVector3.Value = new Vector3(1000, 1000, 1000); + m_NetworkVariableVector2.Value = new Vector2(1000, 1000); + m_NetworkVariableRay.Value = new Ray(Vector3.one, Vector3.right); + m_NetworkVariableULong.Value = ulong.MaxValue; + m_NetworkVariableUInt.Value = uint.MaxValue; + m_NetworkVariableUShort.Value = ushort.MaxValue; + m_NetworkVariableFixedString32.Value = new FixedString32Bytes("FixedString32Bytes"); + m_NetworkVariableFixedString64.Value = new FixedString64Bytes("FixedString64Bytes"); + m_NetworkVariableFixedString128.Value = new FixedString128Bytes("FixedString128Bytes"); + m_NetworkVariableFixedString512.Value = new FixedString512Bytes("FixedString512Bytes"); + m_NetworkVariableFixedString4096.Value = new FixedString4096Bytes("FixedString4096Bytes"); + m_NetworkVariableManaged.Value = new ManagedNetworkSerializableType { - if (NetworkManager != null && NetworkManager.IsListening) - { - //Now change all of the values to make sure we are at least testing the local callback - m_NetworkVariableBool.Value = false; - m_NetworkVariableByte.Value = 255; - m_NetworkVariableColor.Value = new Color(100, 100, 100); - m_NetworkVariableColor32.Value = new Color32(100, 100, 100, 100); - m_NetworkVariableDouble.Value = 1000; - m_NetworkVariableFloat.Value = 1000.0f; - m_NetworkVariableInt.Value = 1000; - m_NetworkVariableLong.Value = 100000; - m_NetworkVariableSByte.Value = -127; - m_NetworkVariableQuaternion.Value = new Quaternion(100, 100, 100, 100); - m_NetworkVariablePose.Value = new Pose(new Vector3(100, 100, 100), new Quaternion(100, 100, 100, 100)); - m_NetworkVariableShort.Value = short.MaxValue; - m_NetworkVariableVector4.Value = new Vector4(1000, 1000, 1000, 1000); - m_NetworkVariableVector3.Value = new Vector3(1000, 1000, 1000); - m_NetworkVariableVector2.Value = new Vector2(1000, 1000); - m_NetworkVariableRay.Value = new Ray(Vector3.one, Vector3.right); - m_NetworkVariableULong.Value = ulong.MaxValue; - m_NetworkVariableUInt.Value = uint.MaxValue; - m_NetworkVariableUShort.Value = ushort.MaxValue; - m_NetworkVariableFixedString32.Value = new FixedString32Bytes("FixedString32Bytes"); - m_NetworkVariableFixedString64.Value = new FixedString64Bytes("FixedString64Bytes"); - m_NetworkVariableFixedString128.Value = new FixedString128Bytes("FixedString128Bytes"); - m_NetworkVariableFixedString512.Value = new FixedString512Bytes("FixedString512Bytes"); - m_NetworkVariableFixedString4096.Value = new FixedString4096Bytes("FixedString4096Bytes"); - m_NetworkVariableManaged.Value = new ManagedNetworkSerializableType - { - Str = "ManagedNetworkSerializableType", - Ints = new[] { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 }, - Embedded = new EmbeddedManagedNetworkSerializableType { Int = 20000 } - }; - - //Set the timeout (i.e. how long we will wait for all NetworkVariables to have registered their changes) - m_WaitForChangesTimeout = Time.realtimeSinceStartup + 0.50f; - m_ChangesAppliedToNetworkVariables = true; - } - } + Str = "ManagedNetworkSerializableType", + Ints = new[] { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 }, + Embedded = new EmbeddedManagedNetworkSerializableType { Int = 20000 } + }; + + //Set the timeout (i.e. how long we will wait for all NetworkVariables to have registered their changes) + m_WaitForChangesTimeout = Time.realtimeSinceStartup + 0.50f; + m_ChangesAppliedToNetworkVariables = true; + } + else if (!m_FinishedTests) + { + m_FinishedTests = DidAllValuesChange() || (m_WaitForChangesTimeout < Time.realtimeSinceStartup); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs index 2e0c81b690..56231899d4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs @@ -40,6 +40,11 @@ public ConnectionApprovalTests(PlayerCreation playerCreation) private string m_ValidationToken; + internal override bool ShouldCreatePlayerPrefab() + { + return m_PlayerCreation != PlayerCreation.NoPlayer && m_PlayerCreation != PlayerCreation.FailValidation; + } + protected override bool ShouldCheckForSpawnedPlayers() { return m_PlayerCreation != PlayerCreation.NoPlayer; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs index 5569278aa0..aa5b2c819c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs @@ -39,11 +39,9 @@ public override void OnNetworkDespawn() protected override void OnServerAndClientsCreated() { m_PrefabToSpawn = CreateNetworkObjectPrefab("TestPrefab"); + m_PrefabToSpawn.AddComponent(); - var childObject = new GameObject - { - name = "ChildObject" - }; + var childObject = new GameObject("ChildObject"); childObject.transform.parent = m_PrefabToSpawn.transform; childObject.AddComponent(); base.OnServerAndClientsCreated(); @@ -137,8 +135,6 @@ public IEnumerator ValidatedDisableddNetworkBehaviourWarning([Values] bool disab public IEnumerator ValidateNoSpam() { m_AllowServerToStart = true; - var objectToTest = new GameObject(); - var simpleNetworkBehaviour = objectToTest.AddComponent(); // Now just start the Host yield return StartServerAndClients(); @@ -146,23 +142,36 @@ public IEnumerator ValidateNoSpam() // set the log level to developer m_ServerNetworkManager.LogLevel = LogLevel.Developer; - // The only valid condition for this would be if the NetworkBehaviour is spawned. + // We make an instance of the m_PrefabToSpawn + var validateInstance = Object.Instantiate(m_PrefabToSpawn); + + // Then destroy the NetworkObject componwent of that instance. + Object.DestroyImmediate(validateInstance.GetComponent()); + + // Now get the Networkbehaviour and verify when you attempt to get a reference + // to the NetworkObject a warning is logged. + var simpleNetworkBehaviour = validateInstance.GetComponent(); simpleNetworkBehaviour.IsSpawned = true; // Verify the warning gets logged under normal conditions var isNull = simpleNetworkBehaviour.NetworkObject == null; LogAssert.Expect(LogType.Warning, $"[Netcode] Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?"); - var networkObjectToTest = objectToTest.AddComponent(); - networkObjectToTest.NetworkManagerOwner = m_ServerNetworkManager; - networkObjectToTest.Spawn(); + simpleNetworkBehaviour.IsSpawned = false; + simpleNetworkBehaviour = null; + + // Destroy this test instance + Object.DestroyImmediate(validateInstance); + + // Now create a spawned instance (NetworkObject will exist) + var instance = SpawnObject(m_PrefabToSpawn, GetAuthorityNetworkManager()).GetComponent(); // Assure no log messages are logged when they should not be logged - isNull = simpleNetworkBehaviour.NetworkObject != null; + isNull = instance.GetComponent().NetworkObject != null; LogAssert.NoUnexpectedReceived(); - networkObjectToTest.Despawn(); - Object.Destroy(networkObjectToTest); + instance.Despawn(); + Object.Destroy(instance.gameObject); } /// @@ -182,13 +191,11 @@ public IEnumerator ValidateDeleteChildNetworkBehaviour() // Now just start the Host yield return StartServerAndClients(); - var parentObject = new GameObject(); - var childObject = new GameObject(); - childObject.transform.parent = parentObject.transform; - var parentNetworkObject = parentObject.AddComponent(); - childObject.AddComponent(); - parentNetworkObject.Spawn(); + var serverInstance = SpawnObject(m_PrefabToSpawn, GetAuthorityNetworkManager()); + var parentNetworkObject = serverInstance.GetComponent(); + var childObject = parentNetworkObject.transform.GetChild(0).gameObject; + yield return s_DefaultWaitForTick; // Destroy the child object with child NetworkBehaviour @@ -201,7 +208,7 @@ public IEnumerator ValidateDeleteChildNetworkBehaviour() // Destroy the parent object which should not cause any exceptions // (validating the fix) - Object.Destroy(parentObject); + Object.Destroy(serverInstance); } protected override void OnPlayerPrefabGameObjectCreated() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs index 2eeb1dac6f..0e3fffe6ab 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs @@ -20,22 +20,9 @@ public NetworkManagerPlayerPrefab(HostOrServer hostOrServer) : base(hostOrServer { } - /// - /// Assure no player prefab is assigned. - /// - protected override void OnServerAndClientsCreated() - { - foreach (var networkManager in m_NetworkManagers) - { - networkManager.NetworkConfig.PlayerPrefab = null; - } - base.OnServerAndClientsCreated(); - } - - protected override void OnNewClientCreated(NetworkManager networkManager) + internal override bool ShouldCreatePlayerPrefab() { - networkManager.NetworkConfig.PlayerPrefab = null; - base.OnNewClientCreated(networkManager); + return false; } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index 44367d5120..85a72d9f19 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -251,9 +251,17 @@ public NetworkVariableTests(Serialization serialization) m_EnsureLengthSafety = serialization == Serialization.EnsureLengthSafety; } + private bool m_CanStart = false; + protected override bool CanStartServerAndClients() { - return false; + return m_CanStart; + } + + protected override void OnInlineSetup() + { + m_CanStart = false; + base.OnInlineSetup(); } protected override void OnOneTimeSetup() @@ -343,21 +351,30 @@ private void InitializeServerAndClients(HostOrServer useHost) [Test] public void AllNetworkVariableTypes([Values] HostOrServer useHost) { - // Create, instantiate, and host - // This would normally go in Setup, but since every other test but this one - // uses NetworkManagerHelper, and it does its own NetworkManager setup / teardown, - // for now we put this within this one test until we migrate it to MIH - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out NetworkManager server, useHost == HostOrServer.Host ? NetworkManagerHelper.NetworkManagerOperatingMode.Host : NetworkManagerHelper.NetworkManagerOperatingMode.Server)); + var prefabToSpawn = CreateNetworkObjectPrefab("NetVarTest"); + prefabToSpawn.AddComponent(); - Assert.IsTrue(server.IsHost == (useHost == HostOrServer.Host), $"{nameof(useHost)} does not match the server.IsHost value!"); + m_CanStart = true; + StartServerAndClientsWithTimeTravel(); + var authority = GetAuthorityNetworkManager(); - Guid gameObjectId = NetworkManagerHelper.AddGameNetworkObject("NetworkVariableTestComponent"); + // Shutdown the other clients + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager == authority) + { + continue; + } + StopOneClientWithTimeTravel(networkManager); + } - var networkVariableTestComponent = NetworkManagerHelper.AddComponentToObject(gameObjectId); + var instance = SpawnObject(prefabToSpawn, authority); + var networkVariableTestComponent = instance.GetComponent(); - NetworkManagerHelper.SpawnNetworkObject(gameObjectId); + Assert.IsTrue(networkVariableTestComponent.IsSpawned, $"Failed to spawn {instance.name}!"); // Start Testing + networkVariableTestComponent.InitializeTest(); networkVariableTestComponent.EnableTesting = true; var success = WaitForConditionOrTimeOutWithTimeTravel(() => true == networkVariableTestComponent.IsTestComplete()); @@ -369,13 +386,8 @@ public void AllNetworkVariableTypes([Values] HostOrServer useHost) Assert.IsTrue(networkVariableTestComponent.DidAllValuesChange()); networkVariableTestComponent.AssertAllValuesAreCorrect(); - // Disable this once we are done. - networkVariableTestComponent.gameObject.SetActive(false); - - // This would normally go in Teardown, but since every other test but this one - // uses NetworkManagerHelper, and it does its own NetworkManager setup / teardown, - // for now we put this within this one test until we migrate it to MIH - NetworkManagerHelper.ShutdownNetworkManager(); + // Stop the authority NetworkManager instance + StopOneClientWithTimeTravel(authority); } [Test] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs index baab984b82..ac14f5bfc0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs @@ -25,14 +25,12 @@ protected override IEnumerator OnSetup() yield return null; } - protected override void OnServerAndClientsCreated() + private GameObject GenerateAndRegisterPrefab() { - m_Prefab = new GameObject("Object"); - var networkObject = m_Prefab.AddComponent(); - m_Prefab.AddComponent(); - + var originalPrefabInstance = NetcodeIntegrationTestHelpers.CreateNetworkObject("PrefabTest"); // Make it a prefab - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(originalPrefabInstance.GetComponent()); + m_ServerNetworkManager.NetworkConfig.SpawnTimeout = 0; m_ServerNetworkManager.NetworkConfig.ForceSamePrefabs = false; @@ -41,6 +39,12 @@ protected override void OnServerAndClientsCreated() client.NetworkConfig.SpawnTimeout = 0; client.NetworkConfig.ForceSamePrefabs = false; } + return originalPrefabInstance; + } + + protected override void OnServerAndClientsCreated() + { + RegisterPrefab(); } private EmptyComponent GetObjectForClient(ulong clientId) @@ -50,15 +54,25 @@ private EmptyComponent GetObjectForClient(ulong clientId) { if (component.IsSpawned && component.NetworkManager.LocalClientId == clientId) { - return component; + var prefabGlobalObjectIdHash = m_Prefab.GetComponent().GlobalObjectIdHash; + var componentGlobalObjectIdHash = m_Prefab.GetComponent().GlobalObjectIdHash; + if (prefabGlobalObjectIdHash == componentGlobalObjectIdHash) + { + return component; + } } } return null; } - private void RegisterPrefab() + private void RegisterPrefab(bool includeClients = true) { + m_Prefab = GenerateAndRegisterPrefab(); m_ServerNetworkManager.AddNetworkPrefab(m_Prefab); + if (!includeClients) + { + return; + } foreach (var client in m_ClientNetworkManagers) { client.AddNetworkPrefab(m_Prefab); @@ -89,7 +103,7 @@ public IEnumerator WhenSpawningBeforeAddingPrefab_SpawnFails() [UnityTest] public IEnumerator WhenSpawningAfterAddingServerPrefabButBeforeAddingClientPrefab_SpawnFails() { - m_ServerNetworkManager.AddNetworkPrefab(m_Prefab); + RegisterPrefab(false); var serverObject = Object.Instantiate(m_Prefab); serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; @@ -104,10 +118,12 @@ public IEnumerator WhenSpawningAfterAddingPrefabOnServerAndClient_SpawnSucceeds( RegisterPrefab(); var serverObject = Object.Instantiate(m_Prefab); - serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; - serverObject.GetComponent().Spawn(); - yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeHandled(m_ClientNetworkManagers[0]); - Assert.IsNotNull(GetObjectForClient(m_ClientNetworkManagers[0].LocalClientId)); + var serverNetworkObject = serverObject.GetComponent(); + serverNetworkObject.NetworkManagerOwner = m_ServerNetworkManager; + serverNetworkObject.Spawn(); + yield return WaitForSpawnedOnAllOrTimeOut(serverObject); + AssertOnTimeout($"{serverObject.name} did not spawn on all clients!"); + Assert.IsTrue(m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(serverNetworkObject.NetworkObjectId), $"Client did not spawn object!"); } [UnityTest] @@ -116,10 +132,13 @@ public IEnumerator WhenSpawningAfterRemovingPrefabOnClient_SpawnFails() RegisterPrefab(); var serverObject = Object.Instantiate(m_Prefab); + var serverNetworkObject = serverObject.GetComponent(); + serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; serverObject.GetComponent().Spawn(); - yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(m_ClientNetworkManagers[0]); - Assert.IsNotNull(GetObjectForClient(m_ClientNetworkManagers[0].LocalClientId)); + yield return WaitForSpawnedOnAllOrTimeOut(serverObject); + AssertOnTimeout($"{serverObject.name} did not spawn on all clients!"); + Assert.IsTrue(m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(serverNetworkObject.NetworkObjectId), $"Client did not spawn object!"); serverObject.GetComponent().Despawn(); yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(m_ClientNetworkManagers[0]); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs index 0c5895b612..d15522665d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs @@ -19,6 +19,28 @@ public NetworkPrefabHandlerSynchronizationTests(HostOrServer hostOrServer) : bas private GameObject m_ClientSideValidPrefab; private GameObject m_ClientSideExceptionPrefab; + public class VerifyLastClientSentRpcToServer : NetworkBehaviour + { + public bool RpcReceived { get; private set; } + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + RpcReceived = false; + base.OnNetworkPreSpawn(ref networkManager); + } + + public void DelayUntilOneMessageReceivedRpc(RpcParams rpcParams = default) + { + RpcReceived = true; + } + } + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } + protected override void OnServerAndClientsCreated() { m_ValidPrefab = CreateNetworkObjectPrefab("ValidPrefab"); @@ -28,14 +50,22 @@ protected override void OnServerAndClientsCreated() } [UnityTest] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer, RuntimePlatform.OSXPlayer, RuntimePlatform.OSXEditor })] // Ignored test tracked in MTT-15473 public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() { var nonAuthority = GetNonAuthorityNetworkManager(); var networkObjectToSpawnOnClient = m_ClientSideValidPrefab.GetComponent(); - nonAuthority.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); - nonAuthority.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); + + var clientSideHandler = new GameObject(); + var clientPrefabHandler = clientSideHandler.AddComponent(); + clientPrefabHandler.Initialize(nonAuthority, m_ClientSideValidPrefab.GetComponent()); + + nonAuthority.PrefabHandler.AddHandler(m_ValidPrefab, clientPrefabHandler); + + var clientSideExceptionHandler = new GameObject(); + var clientSideExceptionPrefabHandler = clientSideExceptionHandler.AddComponent(); + + nonAuthority.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, clientSideExceptionPrefabHandler); var authority = GetAuthorityNetworkManager(); @@ -60,8 +90,16 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() // Create a new client and register the same PrefabHandlers on the client var newClient = CreateNewClient(); - newClient.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); - newClient.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); + + var lateJoinClientSideHandler = new GameObject(); + var lateJoinClientPrefabHandler = clientSideHandler.AddComponent(); + var lateJoinExceptionHandler = new GameObject(); + var lateJoinClientExceptionHandler = lateJoinExceptionHandler.AddComponent(); + + lateJoinClientPrefabHandler.Initialize(newClient, m_ClientSideValidPrefab.GetComponent()); + + newClient.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, lateJoinClientExceptionHandler); + newClient.PrefabHandler.AddHandler(m_ValidPrefab, lateJoinClientPrefabHandler); // Expect assertions from the new client LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); @@ -73,6 +111,8 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() // Start and synchronize the new client yield return StartClient(newClient); + AssertOnTimeout($"Timed out waiting for the late joining client, {newClient.name}, to connect!"); + // Validate the valid prefab spawned on all clients without issue var expectedAuthorityHash = m_ValidPrefab.GetComponent().GlobalObjectIdHash; @@ -92,6 +132,21 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() Assert.That(networkManager.SpawnManager.SpawnedObjects.ContainsKey(exceptionObject.NetworkObjectId), Is.False, "Non authority should not have spawned exception object!"); } } + + // Assure this test continues to run until we verify the late joining client has sent 1 message to the server + // This should be the fix for MTT-15473 where the test finishes/exits before the message from the client has been received and processed by the server. + Assert.IsTrue(authority.SpawnManager.SpawnedObjects.ContainsKey(newClient.LocalClient.PlayerObject.NetworkObjectId), $"Server does not have a player for Client-{newClient.LocalClientId}!"); + + // Get server and late joining client's VerifyLastClientSentRpcToServer NetworkBehaviour + var serverLateClientInstance = authority.SpawnManager.SpawnedObjects[newClient.LocalClient.PlayerObject.NetworkObjectId].GetComponent(); + var sendRpc = newClient.LocalClient.PlayerObject.GetComponent(); + + // Send a message from the late joining client to the server + sendRpc.DelayUntilOneMessageReceivedRpc(); + + // Wait for the server to have received this message before exiting the test. + // If the log message has not been received by the server at this point, then there is some other type of bug specific to iOS and Mac. + yield return WaitForConditionOrTimeOut(() => serverLateClientInstance.RpcReceived); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs index f964271a28..56b6eb710d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs @@ -1,6 +1,6 @@ using System; +using System.Collections; using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -15,111 +15,120 @@ namespace Unity.Netcode.RuntimeTests /// Destroying a newly spawned NetworkObject instance works /// Removing a INetworkPrefabInstanceHandler is removed and can be verified (very last check) /// - internal class NetworkPrefabHandlerTests + internal class NetworkPrefabHandlerTests : NetcodeIntegrationTest { - [OneTimeSetUp] - public void OneTimeSetup() + protected override int NumberOfClients => 0; + + protected override void OnOneTimeSetup() { // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); + base.OnOneTimeSetup(); } private const string k_TestPrefabObjectName = "NetworkPrefabTestObject"; - private uint m_ObjectId = 1; + private uint m_ObjectId = 0; + + private bool m_CanStart; + + protected override bool CanStartServerAndClients() + { + return m_CanStart; + } + private GameObject MakeValidNetworkPrefab() { - Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(k_TestPrefabObjectName + m_ObjectId.ToString()); - NetworkObject validPrefab = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID]; - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(validPrefab); m_ObjectId++; - return validPrefab.gameObject; + return CreateNetworkObjectPrefab(k_TestPrefabObjectName + m_ObjectId.ToString()); } - - /// /// Tests the NetwokConfig NetworkPrefabsList initialization during NetworkManager's Init method to make sure that /// it will still initialize but remove the invalid prefabs /// - [Test] - public void NetworkConfigInvalidNetworkPrefabTest() + [UnityTest] + public IEnumerator NetworkConfigInvalidNetworkPrefabTest() { + var authority = GetAuthorityNetworkManager(); // Add null entry - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(null); + authority.NetworkConfig.Prefabs.Add(null); // Add a NetworkPrefab with no prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab()); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab()); // Add a NetworkPrefab override with an invalid hash - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 0 }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 0 }); // Add a NetworkPrefab override with a valid hash but an invalid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); // Add a NetworkPrefab override with a valid hash to override but an invalid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); // Add a NetworkPrefab override with an invalid source prefab to override - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = null }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = null }); - // Add a NetworkPrefab override with a valid source prefab to override but an invalid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = MakeValidNetworkPrefab(), OverridingTargetPrefab = null }); + // Create a valid network prefab "asset". + var validPrefabAsset = MakeValidNetworkPrefab().GetComponent(); - // Add a valid prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = MakeValidNetworkPrefab() }); + // Add a NetworkPrefab override with a valid source prefab to override but an invalid target prefab. + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = validPrefabAsset.gameObject, OverridingTargetPrefab = null }); - // Add a NetworkPrefab override with a valid hash and valid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 11111111, OverridingTargetPrefab = MakeValidNetworkPrefab() }); + var validPrefabForSourceHash = MakeValidNetworkPrefab().GetComponent(); + // This would be the scenario that a hash would be used (typically when scene management is disabled) + validPrefabForSourceHash.InScenePlaced = true; - // Add a NetworkPrefab override with a valid prefab and valid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = MakeValidNetworkPrefab(), OverridingTargetPrefab = MakeValidNetworkPrefab() }); + var networkPrefab = authority.NetworkConfig.Prefabs.GetLastRegisteredPrefab(); + networkPrefab.SourceHashToOverride = validPrefabForSourceHash.GlobalObjectIdHash; + networkPrefab.OverridingTargetPrefab = validPrefabAsset.gameObject; + networkPrefab.Override = NetworkPrefabOverride.Hash; + Assert.True(authority.NetworkConfig.Prefabs.AssignPrefabAtIndex(authority.NetworkConfig.Prefabs.Prefabs.Count - 1, networkPrefab), $"Failed to assign network prefab!"); - var exceptionOccurred = false; - try - { - NetworkManagerHelper.NetworkManagerObject.StartHost(); - } - catch - { - exceptionOccurred = true; - } + var sourcePrefab = MakeValidNetworkPrefab(); + networkPrefab = authority.NetworkConfig.Prefabs.GetLastRegisteredPrefab(); + var index = authority.NetworkConfig.Prefabs.Prefabs.Count - 1; + var targetPrefab = MakeValidNetworkPrefab(); + networkPrefab.Prefab = sourcePrefab; + networkPrefab.SourcePrefabToOverride = sourcePrefab; + networkPrefab.OverridingTargetPrefab = targetPrefab; + Assert.True(authority.NetworkConfig.Prefabs.AssignPrefabAtIndex(index, networkPrefab), $"Failed to assign network prefab!"); - Assert.False(exceptionOccurred); + m_CanStart = true; + yield return StartServerAndClients(); // In the end we should only have 3 valid registered network prefabs - Assert.True(NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Count == 3); + Assert.AreEqual(5, authority.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Count); } - private const string k_PrefabObjectName = "NetworkPrefabHandlerTestObject"; - [Test] - public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType) + [UnityTest] + public IEnumerator NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType) { - var networkConfig = new NetworkConfig() - { - NetworkTopology = topologyType, - }; + var authority = GetAuthorityNetworkManager(); + authority.NetworkConfig.NetworkTopology = topologyType; + var baseObject = MakeValidNetworkPrefab().GetComponent(); - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _, networkConfig: networkConfig)); - var testPrefabObjectName = k_PrefabObjectName; + m_CanStart = true; + yield return StartServerAndClients(); - Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(testPrefabObjectName); - NetworkObject baseObject = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID]; + var testPrefabObjectName = k_TestPrefabObjectName; - var networkPrefabHandler = new NetworkPrefabHandler(); - var networkPrefabInstanceHandler = new NetworkPrefabInstanceHandler(baseObject); + var networkPrefabHandler = authority.PrefabHandler; + var prefabHandlerObject = new GameObject(); + var networkPrefabInstanceHandler = prefabHandlerObject.AddComponent(); + networkPrefabInstanceHandler.Initialize(authority, baseObject); var prefabPosition = new Vector3(1.0f, 5.0f, 3.0f); var prefabRotation = new Quaternion(1.0f, 0.5f, 0.4f, 0.1f); //Register via GameObject - var gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.gameObject, networkPrefabInstanceHandler); + var gameObjectRegistered = authority.PrefabHandler.ContainsHandler(baseObject); //Test result of registering via GameObject reference Assert.True(gameObjectRegistered); - var spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + var spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -131,11 +140,11 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType Assert.True(prefabPosition == spawnedObject.transform.position); Assert.True(prefabRotation == spawnedObject.transform.rotation); - networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler //Register via NetworkObject - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefabInstanceHandler); + gameObjectRegistered = authority.PrefabHandler.AddHandler(baseObject, networkPrefabInstanceHandler); //Test result of registering via NetworkObject reference Assert.True(gameObjectRegistered); @@ -144,7 +153,7 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType prefabPosition = new Vector3(2.0f, 1.0f, 5.0f); prefabRotation = new Quaternion(4.0f, 1.5f, 5.4f, 5.1f); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -156,11 +165,11 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType Assert.True(prefabPosition == spawnedObject.transform.position); Assert.True(prefabRotation == spawnedObject.transform.rotation); - networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler //Register via GlobalObjectIdHash - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.GlobalObjectIdHash, networkPrefabInstanceHandler); + gameObjectRegistered = authority.PrefabHandler.AddHandler(baseObject.GlobalObjectIdHash, networkPrefabInstanceHandler); //Test result of registering via GlobalObjectIdHash reference Assert.True(gameObjectRegistered); @@ -169,7 +178,7 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType prefabPosition = new Vector3(6.0f, 4.0f, 1.0f); prefabRotation = new Quaternion(3f, 2f, 4f, 1f); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -181,59 +190,49 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType Assert.True(prefabPosition == spawnedObject.transform.position); Assert.True(prefabRotation == spawnedObject.transform.rotation); - networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler // Register a handler that throws an exception var networkPrefabExceptionThrower = new NetworkPrefabExceptionThrower(); - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefabExceptionThrower); + gameObjectRegistered = authority.PrefabHandler.AddHandler(baseObject, networkPrefabExceptionThrower); //Test result of registering exception handler Assert.True(gameObjectRegistered); LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); // No object should have been spawned, but test should have continued Assert.Null(spawnedObject); - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler Assert.False(networkPrefabInstanceHandler.StillHasInstances()); - } - [SetUp] - public void Setup() - { - //Create, instantiate, and host - NetworkManagerHelper.StartNetworkManager(out _, NetworkManagerHelper.NetworkManagerOperatingMode.None); + UnityEngine.Object.Destroy(prefabHandlerObject); } - [TearDown] - public void TearDown() + protected override IEnumerator OnTearDown() { - //Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); - var networkObjects = FindObjects.ByType(); - var networkObjectsList = networkObjects.Where(c => c.name.Contains(k_PrefabObjectName)); - foreach (var networkObject in networkObjectsList) - { - UnityEngine.Object.DestroyImmediate(networkObject); - } + m_CanStart = false; + return base.OnTearDown(); } } /// /// The Prefab instance handler to use for this test /// - internal class NetworkPrefabInstanceHandler : INetworkPrefabInstanceHandler + internal class NetworkPrefabInstanceHandler : MonoBehaviour, INetworkPrefabInstanceHandler { private NetworkObject m_NetworkObject; private List m_Instances; + private NetworkManager m_NetworkManager; + public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) { - var networkObjectInstance = UnityEngine.Object.Instantiate(m_NetworkObject.gameObject).GetComponent(); + var networkObjectInstance = Instantiate(m_NetworkObject.gameObject).GetComponent(); networkObjectInstance.transform.SetPositionAndRotation(position, rotation); m_Instances.Add(networkObjectInstance); return networkObjectInstance; @@ -241,28 +240,40 @@ public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaterni public void Destroy(NetworkObject networkObject) { - var instancesContainsNetworkObject = m_Instances.Contains(networkObject); - Assert.True(instancesContainsNetworkObject); - m_Instances.Remove(networkObject); - UnityEngine.Object.Destroy(networkObject.gameObject); + if (m_Instances == null || m_Instances.Count > 0) + { + var instancesContainsNetworkObject = m_Instances.Contains(networkObject); + Assert.True(instancesContainsNetworkObject); + m_Instances.Remove(networkObject); + Destroy(networkObject.gameObject); + } } public bool StillHasInstances() { - return (m_Instances.Count > 0); + return m_Instances.Count > 0; + } + + private void OnDestroy() + { + m_NetworkManager?.PrefabHandler.RemoveHandler(m_NetworkObject); + m_Instances.Clear(); + m_Instances = null; } - public NetworkPrefabInstanceHandler(NetworkObject networkObject) + public void Initialize(NetworkManager networkManager, NetworkObject networkObject) { + m_NetworkManager = networkManager; m_NetworkObject = networkObject; m_Instances = new List(); + networkManager.PrefabHandler.AddHandler(networkObject, this); } } /// /// Causes an exception during client connection /// - internal class NetworkPrefabExceptionThrower : INetworkPrefabInstanceHandler + internal class NetworkPrefabExceptionThrower : MonoBehaviour, INetworkPrefabInstanceHandler { public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) { @@ -271,7 +282,7 @@ public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaterni public void Destroy(NetworkObject networkObject) { - UnityEngine.Object.Destroy(networkObject.gameObject); + Destroy(networkObject.gameObject); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs index 7b06e49576..c4bcd3f124 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs @@ -5,6 +5,7 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; +using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests { @@ -24,7 +25,7 @@ public NetworkPrefabHandlerWithDataTests(NetworkTopologyTypes topology) : base(t protected override void OnServerAndClientsCreated() { // Creates a network object prefab and registers it to all clients. - m_Prefab = CreateNetworkObjectPrefab(k_TestPrefabObjectName).gameObject; + m_Prefab = CreateNetworkObjectPrefab(k_TestPrefabObjectName); var authority = GetAuthorityNetworkManager(); m_ClientHandlers = new PrefabInstanceHandlerWithData[NumberOfClients]; @@ -67,8 +68,11 @@ public IEnumerator InstantiationPayload_LateJoinersReceiveData() var data = new NetworkSerializableTest { Value = 42, Value2 = 2.71f }; var spawned = SpawnPrefabWithData(data); + Debug.Log("Spawn"); yield return WaitForConditionOrTimeOut(() => AllHandlersSynchronized(data)); AssertOnTimeout("Not all handlers synchronized"); + yield return WaitForSpawnedOnAllOrTimeOut(spawned); + AssertOnTimeout($"Not all clients spawned {spawned.name}!"); // When running with Distributed Authority, test a late-joiner after an ownership change // The object owner will synchronize the late joining client, showing that the instantiationData will survive host migration. @@ -88,7 +92,7 @@ public IEnumerator InstantiationPayload_LateJoinersReceiveData() }); AssertOnTimeout($"Timed out while waiting for Client-{newOwner.LocalClientId} to own object"); } - + Debug.Log("Late client..."); // Late join a client yield return CreateAndStartNewClient(); @@ -105,9 +109,12 @@ private void RegisterPrefabHandler(NetworkManager manager, out PrefabInstanceHan private NetworkObject SpawnPrefabWithData(NetworkSerializableTest data) { - var instance = UnityEngine.Object.Instantiate(m_Prefab).GetComponent(); + var authority = GetAuthorityNetworkManager(); + var instance = Object.Instantiate(m_Prefab).GetComponent(); + GetAuthorityNetworkManager().PrefabHandler.SetInstantiationData(instance, data); - instance.Spawn(); + + SpawnObjectInstance(instance, authority); return instance; } @@ -129,12 +136,12 @@ public PrefabInstanceHandlerWithData(GameObject prefab) public override NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation, NetworkSerializableTest data) { InstantiationData = data; - return UnityEngine.Object.Instantiate(m_Prefab, position, rotation).GetComponent(); + return Object.Instantiate(m_Prefab, position, rotation).GetComponent(); } public override void Destroy(NetworkObject networkObject) { - UnityEngine.Object.DestroyImmediate(networkObject.gameObject); + Object.DestroyImmediate(networkObject.gameObject); } } @@ -150,7 +157,9 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } public bool IsSynchronizedWith(NetworkSerializableTest other) - => Value == other.Value && Math.Abs(Value2 - other.Value2) < 0.0001f; + { + return Value == other.Value && Math.Abs(Value2 - other.Value2) < 0.0001f; + } } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs index c0b870193a..56de84deb9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs @@ -1,41 +1,41 @@ -using System; +using System.Collections; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { - internal sealed class NetworkVariableNameTests + internal class NetworkVariableNameTests : NetcodeIntegrationTest { + protected override int NumberOfClients => 1; private NetworkVariableNameComponent m_NetworkVariableNameComponent; - [OneTimeSetUp] - public void OneTimeSetup() - { - // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. - NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); - } + private GameObject m_PrefabToTest; - [SetUp] - public void SetUp() + protected override void OnServerAndClientsCreated() { - NetworkManagerHelper.StartNetworkManager(out _); - - var gameObjectId = NetworkManagerHelper.AddGameNetworkObject(Guid.NewGuid().ToString()); - m_NetworkVariableNameComponent = NetworkManagerHelper.AddComponentToObject(gameObjectId); - NetworkManagerHelper.SpawnNetworkObject(gameObjectId); + m_PrefabToTest = CreateNetworkObjectPrefab("NetVarNameTest"); + m_PrefabToTest.AddComponent(); + base.OnServerAndClientsCreated(); } - [TearDown] - public void TearDown() + [UnityTest] + public IEnumerator VerifyNetworkVariableNameInitialization() { - NetworkManagerHelper.ShutdownNetworkManager(); - } + var authority = GetAuthorityNetworkManager(); + var authorityInstance = SpawnObject(m_PrefabToTest, authority); + var authorityNetworkObject = authorityInstance.GetComponent(); - [Test] - public void VerifyNetworkVariableNameInitialization() - { - // Fields have regular naming - Assert.AreEqual(nameof(NetworkVariableNameComponent.NetworkVarList), m_NetworkVariableNameComponent.NetworkVarList.Name); + yield return WaitForSpawnedOnAllOrTimeOut(authorityInstance); + AssertOnTimeout($"Not all clients spawned {authorityInstance.name}!"); + + foreach (var networkManager in m_NetworkManagers) + { + var componentInstance = networkManager.SpawnManager.SpawnedObjects[authorityNetworkObject.NetworkObjectId].GetComponent(); + // Verify fields have regular naming + Assert.AreEqual(nameof(NetworkVariableNameComponent.NetworkVarList), componentInstance.NetworkVarList.Name); + } } private class NetworkVariableNameComponent : NetworkBehaviour diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs index 1489fb61b9..94741ad3e5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs @@ -1,6 +1,4 @@ -using System; using System.Collections; -using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; @@ -13,62 +11,47 @@ namespace Unity.Netcode.RuntimeTests /// - That all RPCs invoke at the appropriate `NetworkUpdateStage` (Client and Server) /// - A lower level `MessageQueueContainer` test that validates `MessageQueueFrameItems` after they have been put into the queue /// - internal class RpcQueueTests + internal class RpcQueueTests : NetcodeIntegrationTest { - [OneTimeSetUp] - public void OneTimeSetup() + protected override int NumberOfClients => 0; + + private GameObject m_TestPrefab; + + protected override void OnOneTimeSetup() { // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); + base.OnOneTimeSetup(); } - [SetUp] - public void Setup() + protected override void OnServerAndClientsCreated() { - // Create, instantiate, and host - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _)); + m_TestPrefab = CreateNetworkObjectPrefab("RpcQueueTest"); + m_TestPrefab.AddComponent(); + base.OnServerAndClientsCreated(); } /// /// This tests the RPC Queue outbound and inbound buffer capabilities. /// /// IEnumerator - [UnityTest, Order(2)] + [UnityTest] public IEnumerator BufferDataValidation() { - Guid gameObjectId = NetworkManagerHelper.AddGameNetworkObject("GrowingBufferObject"); + var authority = GetAuthorityNetworkManager(); + var instance = SpawnObject(m_TestPrefab, authority); - var growingRpcBufferSizeComponent = NetworkManagerHelper.AddComponentToObject(gameObjectId); + yield return WaitForSpawnedOnAllOrTimeOut(instance); + AssertOnTimeout($"Not all clients spawned {instance.name}!"); - NetworkManagerHelper.SpawnNetworkObject(gameObjectId); + var bufferDataValidationComponent = instance.GetComponent(); // Start Testing - growingRpcBufferSizeComponent.EnableTesting = true; - - var testsAreComplete = growingRpcBufferSizeComponent.IsTestComplete(); - - // Wait for the RPC pipeline test to complete or if we exceeded the maximum iterations bail - while (!testsAreComplete) - { - yield return new WaitForSeconds(0.003f); - - testsAreComplete = growingRpcBufferSizeComponent.IsTestComplete(); - } + bufferDataValidationComponent.EnableTesting = true; - // Stop Testing - growingRpcBufferSizeComponent.EnableTesting = false; + yield return WaitForConditionOrTimeOut(() => bufferDataValidationComponent.IsTestComplete()); + AssertOnTimeout($"Timed out waiting for the {nameof(BufferDataValidationComponent)} tests to complete!"); - // Just disable this once we are done. - growingRpcBufferSizeComponent.gameObject.SetActive(false); - - Assert.IsTrue(testsAreComplete); - } - - [TearDown] - public void TearDown() - { - // Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs new file mode 100644 index 0000000000..dabc67996b --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs @@ -0,0 +1,373 @@ +using System.Collections; +using System.Runtime.CompilerServices; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; + + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.DAHost)] + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class BaseReferenceTests : NetcodeIntegrationTest + { + protected struct GroupedComponents + { + public GameObject GameObject; + public NetworkObject NetworkObject; + public TestNetworkBehaviour TestNetworkBehaviour; + } + + protected override int NumberOfClients => 1; + + protected GameObject m_TestPrefab; + + protected GroupedComponents m_ValidatingInstance + { + get; + private set; + } + protected GroupedComponents m_ReferenceToUse + { + get; + private set; + } + + public BaseReferenceTests(HostOrServer hostOrServer) : base(hostOrServer) + { + } + + protected override void OnServerAndClientsCreated() + { + m_TestPrefab = CreateNetworkObjectPrefab("ReferenceTest"); + m_TestPrefab.AddComponent(); + base.OnServerAndClientsCreated(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected GroupedComponents GetGroup(GameObject gameObject) + { + return new GroupedComponents() + { + GameObject = gameObject, + NetworkObject = gameObject.GetComponent(), + TestNetworkBehaviour = gameObject.GetComponent() + }; + } + + protected IEnumerator SpawnTestPrefabInstance(bool spawnSingle = false) + { + var authority = GetAuthorityNetworkManager(); + m_ValidatingInstance = GetGroup(SpawnObject(m_TestPrefab, authority)); + + yield return WaitForSpawnedOnAllOrTimeOut(m_ValidatingInstance.GameObject); + AssertOnTimeout($"[{GetType().Name}][Validating Instance] Faild to spawn {m_ValidatingInstance.GameObject.name} on all clients!"); + + if (!spawnSingle) + { + m_ReferenceToUse = GetGroup(SpawnObject(m_TestPrefab, authority)); + + yield return WaitForSpawnedOnAllOrTimeOut(m_ReferenceToUse.GameObject); + AssertOnTimeout($"[{GetType().Name}][Reference to use] Faild to spawn {m_ReferenceToUse.GameObject.name} on all clients!"); + } + } + + #region NetworkBehaviour specific conditional methods + protected bool RpcWasReceivedAndBehaviourValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var authorityReferenceId = m_ReferenceToUse.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + var referenceNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject) + && networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + var referenceBehaviour = referenceNetworkObject.GetComponent(); + + if (!validatingBehaviour.ReceivedRPC) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); + continue; + } + if (!validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} failed to acquire the reference!"); + continue; + } + if (referenceBehaviour != validatingBehaviour.RpcReceivedBehaviour) + { + var currentReferenceBehaviour = validatingBehaviour.RpcReceivedBehaviour != null ? validatingBehaviour.RpcReceivedBehaviour.name : "null"; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference {referenceBehaviour.name} but was {currentReferenceBehaviour}!"); + } + } + else + { + var wasNotSpawned = validatingNetworkObject == null ? m_ValidatingInstance.GameObject.name : m_ReferenceToUse.GameObject.name; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); + } + validatingNetworkObject = null; + referenceNetworkObject = null; + } + + return stringBuilder.Length == 0; + } + + protected bool NetworkVariableChangedAndBehaviourValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var authorityReferenceId = m_ReferenceToUse.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + var referenceNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject) + && networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + var referenceBehaviour = referenceNetworkObject.GetComponent(); + + if (!validatingBehaviour.TestVariableChanged) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the NetworkVariable update!"); + continue; + } + if (!validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} failed to acquire a reference!"); + continue; + } + if (referenceBehaviour != validatingBehaviour.TestVariableBehaviour) + { + var currentReferenceBehaviour = validatingBehaviour.TestVariableBehaviour != null ? validatingBehaviour.RpcReceivedBehaviour.name : "null"; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference {referenceBehaviour.name} but was {currentReferenceBehaviour}!"); + } + } + else + { + var wasNotSpawned = validatingNetworkObject == null ? m_ValidatingInstance.GameObject.name : m_ReferenceToUse.GameObject.name; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); + } + validatingNetworkObject = null; + referenceNetworkObject = null; + } + return stringBuilder.Length == 0; + } + + protected bool RpcSerializingNullValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + + if (!validatingBehaviour.ReceivedRPC) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); + continue; + } + if (validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); + continue; + } + if (validatingBehaviour.RpcReceivedBehaviour != null) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference NULL but was {validatingBehaviour.RpcReceivedBehaviour.name}!"); + } + } + else + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_ValidatingInstance.GameObject.name}!"); + } + validatingNetworkObject = null; + } + return stringBuilder.Length == 0; + } + + protected bool NetworkVariableSerializingNullValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + + if (!validatingBehaviour.TestVariableChanged) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not detected a change in the NetworkVariable!"); + continue; + } + if (validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); + continue; + } + if (validatingBehaviour.TestVariableBehaviour != null) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference NULL but was {validatingBehaviour.TestVariableBehaviour.name}!"); + } + } + else + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_ValidatingInstance.GameObject.name}!"); + } + validatingNetworkObject = null; + } + return stringBuilder.Length == 0; + } + #endregion + + protected bool NetworkObjectSerializedValidation(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var authorityReferenceId = m_ReferenceToUse.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + var referenceNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject) && + networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + var referenceBehaviour = referenceNetworkObject.GetComponent(); + + if (!validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingNetworkObject.name} failed to acquire the reference!"); + continue; + } + if (referenceNetworkObject != validatingBehaviour.RpcReceivedNetworkObject) + { + var currentReferencedObject = validatingBehaviour.RpcReceivedNetworkObject != null ? validatingBehaviour.RpcReceivedNetworkObject.name : "null"; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected {nameof(NetworkObject)} reference " + + $"{referenceNetworkObject.name} but was {currentReferencedObject}!"); + } + } + else + { + var wasNotSpawned = validatingNetworkObject == null ? m_ValidatingInstance.GameObject.name : m_ReferenceToUse.GameObject.name; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); + } + validatingNetworkObject = null; + referenceNetworkObject = null; + } + + return stringBuilder.Length == 0; + } + + protected bool SerializingNullNetworkObjectValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + + if (!validatingBehaviour.ReceivedRPC) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); + continue; + } + if (validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); + continue; + } + if (validatingBehaviour.RpcReceivedNetworkObject != null) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected {nameof(NetworkObjectReference)} to be NULL but was {validatingBehaviour.RpcReceivedNetworkObject.name}!"); + } + } + else + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_ValidatingInstance.GameObject.name}!"); + } + validatingNetworkObject = null; + } + return stringBuilder.Length == 0; + } + + + protected class TestNetworkBehaviour : NetworkBehaviour + { + public bool ReceivedRPC; + public bool TestVariableChanged; + public bool AcquiredReference; + + public NetworkVariable NetworkBehaviourVariable = new NetworkVariable(); + + public TestNetworkBehaviour TestVariableBehaviour; + + public TestNetworkBehaviour RpcReceivedBehaviour; + public TestNetworkBehaviour RpcReceived; + + public NetworkVariable NetworkObjectVariable = new NetworkVariable(); + + public NetworkObject TestVariableNetworkObject; + public GameObject TestVariableGameObject; + + public NetworkObject RpcReceivedNetworkObject; + public GameObject RpcReceivedGameObject; + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + // Set it to ourself so we can validate serializing null too. + RpcReceivedBehaviour = this; + TestVariableBehaviour = null; + base.OnNetworkPreSpawn(ref networkManager); + } + + public override void OnNetworkSpawn() + { + NetworkBehaviourVariable.OnValueChanged += OnTestVariableChanged; + NetworkObjectVariable.OnValueChanged += OnNetworkObjectVariableChanged; + base.OnNetworkSpawn(); + } + + public override void OnNetworkPreDespawn() + { + NetworkBehaviourVariable.OnValueChanged -= OnTestVariableChanged; + NetworkObjectVariable.OnValueChanged -= OnNetworkObjectVariableChanged; + base.OnNetworkPreDespawn(); + } + private void OnTestVariableChanged(NetworkBehaviourReference previous, NetworkBehaviourReference current) + { + TestVariableChanged = true; + AcquiredReference = current.TryGet(out TestVariableBehaviour, NetworkManager); + } + + private void OnNetworkObjectVariableChanged(NetworkObjectReference previous, NetworkObjectReference current) + { + TestVariableChanged = true; + AcquiredReference = current.TryGet(out TestVariableNetworkObject, NetworkManager); + } + + [Rpc(SendTo.Everyone)] + public void SendNetworkBehaviourReferenceRpc(NetworkBehaviourReference value) + { + AcquiredReference = value.TryGet(out RpcReceivedBehaviour, NetworkManager); + ReceivedRPC = true; + } + + [Rpc(SendTo.Everyone)] + public void SendNetworkObjectReferenceRpc(NetworkObjectReference value) + { + ReceivedRPC = true; + AcquiredReference = value.TryGet(out RpcReceivedNetworkObject, NetworkManager); + RpcReceivedGameObject = value; + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs.meta new file mode 100644 index 0000000000..5d79890713 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 87bdaa820aaed704f8309de710e9153f \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs index 51d2ae8e71..20e27d6492 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs @@ -2,191 +2,160 @@ using System.Collections; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; -using UnityEngine; using UnityEngine.TestTools; +using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests { /// /// Unit tests to test: - /// - Serializing NetworkObject to NetworkObjectReference - /// - Deserializing NetworkObjectReference to NetworkObject - /// - Implicit operators of NetworkObjectReference + /// - Serializing NetworkBehaviour to NetworkBehaviourReference + /// - Deserializing NetworkBehaviourReference to NetworkBehaviour + /// - Implicit operators of NetworkBehaviourReference /// - internal class NetworkBehaviourReferenceTests : IDisposable + internal class NetworkBehaviourReferenceTests : BaseReferenceTests { - [OneTimeSetUp] - public void OneTimeSetup() + public NetworkBehaviourReferenceTests(HostOrServer hostOrServer) : base(hostOrServer) { - // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. - NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); } - private class TestNetworkBehaviour : NetworkBehaviour - { - public static bool ReceivedRPC; - - public NetworkVariable TestVariable = new NetworkVariable(); - - public TestNetworkBehaviour RpcReceivedBehaviour; - - [ServerRpc] - public void SendReferenceServerRpc(NetworkBehaviourReference value) - { - RpcReceivedBehaviour = (TestNetworkBehaviour)value; - ReceivedRPC = true; - } - } + #region Tests using non-null NetworkBehaviours and RPCs [UnityTest] public IEnumerator TestRpc() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Explicitly send the NetworkBehaviour as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(new NetworkBehaviourReference(m_ReferenceToUse.TestNetworkBehaviour)); - testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference(testNetworkBehaviour)); + // Validated the reference + yield return WaitForConditionOrTimeOut(RpcWasReceivedAndBehaviourValidated); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); + } - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedBehaviour == null) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } - yield return null; - } + [UnityTest] + public IEnumerator TestRpcImplicitNetworkBehaviour() + { + yield return SpawnTestPrefabInstance(); - // validate - Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour); + // Implicitly send the NetworkBehaviour as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(m_ReferenceToUse.TestNetworkBehaviour); + + // Validated the reference + yield return WaitForConditionOrTimeOut(RpcWasReceivedAndBehaviourValidated); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); } + #endregion + #region Tests using non-null NetworkBehaviours and NetworkVariable [UnityTest] - public IEnumerator TestSerializeNull([Values] bool initializeWithNull) + public IEnumerator TestNetworkVariable() { - TestNetworkBehaviour.ReceivedRPC = false; - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); - - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); - - // If not initializing with null, then use the default constructor with no assigned NetworkBehaviour - if (!initializeWithNull) - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference()); - } - else // Otherwise, initialize and pass in null as the reference - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference(null)); - } + yield return SpawnTestPrefabInstance(); - // wait for rpc completion - float t = 0; - while (!TestNetworkBehaviour.ReceivedRPC) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + // Assure the authority instance's value is the default (null) value + Assert.IsNull((NetworkBehaviour)m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value); - yield return null; - } + // Implicitly assign the NetworkBehaviourReference by assigning the NetworkBehaviour to the NetworkVariable. + m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value = m_ReferenceToUse.TestNetworkBehaviour; - // validate - Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedBehaviour); + // Validated the NetworkVariable reference propogates to clients + yield return WaitForConditionOrTimeOut(NetworkVariableChangedAndBehaviourValidated); + AssertOnTimeout($"[{nameof(TestNetworkVariable)}] Failed to validate reference!"); } + #endregion + #region Validating using NULL as a NetworkBehaviourReference [UnityTest] - public IEnumerator TestRpcImplicitNetworkBehaviour() + public IEnumerator TestSerializeNull() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(true); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Initialize with NULL parameter + var initializeWithNull = new NetworkBehaviourReference(null); + // Initialize with no parameter + var initializeWithNothing = new NetworkBehaviourReference(); - testNetworkBehaviour.SendReferenceServerRpc(testNetworkBehaviour); + // Initialized with NULL parameter + // Explicitly send the NetworkBehaviour as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(initializeWithNull); - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedBehaviour == null) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + // Validated the reference + yield return WaitForConditionOrTimeOut(RpcSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); - yield return null; + // Reset the RPC NetworkBehaviourReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) + { + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); + testBehaviour.RpcReceivedBehaviour = testBehaviour; } - // validate - Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour); - } + // Initialized with no parameter + // Explicitly send the NetworkBehaviour as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(initializeWithNothing); - [Test] - public void TestNetworkVariable() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + // Validated the reference + yield return WaitForConditionOrTimeOut(RpcSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); - // check default value is null - Assert.IsNull((NetworkBehaviour)testNetworkBehaviour.TestVariable.Value); + // Initialize NetworkBehaviourVariable with NULL parameter + m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value = initializeWithNull; + yield return WaitForConditionOrTimeOut(NetworkVariableSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with null parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); - testNetworkBehaviour.TestVariable.Value = testNetworkBehaviour; + // Reset the NetworkVaraible NetworkBehaviourReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) + { + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); + testBehaviour.TestVariableBehaviour = testBehaviour; + testBehaviour.TestVariableChanged = false; + } - Assert.AreEqual((NetworkBehaviour)testNetworkBehaviour.TestVariable.Value, testNetworkBehaviour); + // Initialize NetworkBehaviourVariable with no parameter + m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value = initializeWithNothing; + yield return WaitForConditionOrTimeOut(NetworkVariableSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with no parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); } + #endregion + + #region Serialization Failure validation tests - [Test] - public void FailSerializeNonSpawnedNetworkObject() + /// + /// This test is ok to create but not spawn. + /// + [UnityTest] + public IEnumerator FailSerializeNonSpawnedNetworkObject() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var component = networkObjectContext.Object.gameObject.AddComponent(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); Assert.Throws(() => { - NetworkBehaviourReference outReference = component; + NetworkBehaviourReference outReference = instance.GetComponent(); }); + + Object.Destroy(instance); } - [Test] - public void FailSerializeGameObjectWithoutNetworkObject() + [UnityTest] + public IEnumerator FailSerializeGameObjectWithoutNetworkObject() { - using var gameObjectContext = UnityObjectContext.CreateGameObject(); - var component = gameObjectContext.Object.gameObject.AddComponent(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); + Object.Destroy(instance.GetComponent()); Assert.Throws(() => { - NetworkBehaviourReference outReference = component; + NetworkBehaviourReference outReference = instance.GetComponent(); }); - } - public void Dispose() - { - //Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); - } - - public NetworkBehaviourReferenceTests() - { - //Create, instantiate, and host - NetworkManagerHelper.StartNetworkManager(out _); + Object.Destroy(instance); } + #endregion } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs index 1fbce803fc..699f4a21f9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs @@ -1,7 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; using NUnit.Framework; -using Unity.Collections; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; @@ -15,415 +15,154 @@ namespace Unity.Netcode.RuntimeTests /// - Deserializing NetworkObjectReference to NetworkObject /// - Implicit operators of NetworkObjectReference /// - internal class NetworkObjectReferenceTests : IDisposable + internal class NetworkObjectReferenceTests : BaseReferenceTests { - [OneTimeSetUp] - public void OneTimeSetup() - { - // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. - NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); - } - - private class TestNetworkBehaviour : NetworkBehaviour - { - public static bool ReceivedRPC; - - public NetworkVariable TestVariable = new NetworkVariable(); - - public NetworkObject RpcReceivedNetworkObject; - - public GameObject RpcReceivedGameObject; - - [ServerRpc] - public void SendReferenceServerRpc(NetworkObjectReference value) - { - ReceivedRPC = true; - RpcReceivedGameObject = value; - RpcReceivedNetworkObject = value; - } - } - - [Test] - public void TestSerializeNetworkObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - var outWriter = new FastBufferWriter(1300, Allocator.Temp); - try - { - // serialize - var outSerializer = new BufferSerializer(new BufferSerializerWriter(outWriter)); - NetworkObjectReference outReference = networkObjectContext.Object; - outReference.NetworkSerialize(outSerializer); - - // deserialize - NetworkObjectReference inReference = default; - var inReader = new FastBufferReader(outWriter, Allocator.Temp); - try - { - var inSerializer = - new BufferSerializer(new BufferSerializerReader(inReader)); - inReference.NetworkSerialize(inSerializer); - } - finally - { - inReader.Dispose(); - } - - // validate - Assert.NotNull((NetworkObject)inReference); - Assert.AreEqual(inReference.NetworkObjectId, networkObjectContext.Object.NetworkObjectId); - Assert.AreEqual(outReference, inReference); - Assert.AreEqual(networkObjectContext.Object, (NetworkObject)inReference); - } - finally - { - outWriter.Dispose(); - } - } - - [Test] - public void TestSerializeGameObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - var outWriter = new FastBufferWriter(1300, Allocator.Temp); - try - { - // serialize - var outSerializer = new BufferSerializer(new BufferSerializerWriter(outWriter)); - NetworkObjectReference outReference = networkObjectContext.Object.gameObject; - outReference.NetworkSerialize(outSerializer); - - // deserialize - NetworkObjectReference inReference = default; - var inReader = new FastBufferReader(outWriter, Allocator.Temp); - try - { - var inSerializer = - new BufferSerializer(new BufferSerializerReader(inReader)); - inReference.NetworkSerialize(inSerializer); - } - finally - { - inReader.Dispose(); - } - GameObject gameObject = inReference; - - // validate - Assert.AreEqual(outReference, inReference); - Assert.AreEqual(networkObjectContext.Object.gameObject, gameObject); - } - finally - { - outWriter.Dispose(); - } - } - - [Test] - public void TestImplicitConversionToGameObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); + protected override int NumberOfClients => 1; - NetworkObjectReference outReference = networkObjectContext.Object.gameObject; - GameObject go = outReference; - Assert.AreEqual(networkObjectContext.Object.gameObject, go); - } - - [Test] - public void TestImplicitToGameObjectIsNullWhenNotFound() + public NetworkObjectReferenceTests(HostOrServer hostOrServer) : base(hostOrServer) { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - - NetworkObjectReference outReference = networkObjectContext.Object.gameObject; - - networkObjectContext.Object.Despawn(); - Object.DestroyImmediate(networkObjectContext.Object.gameObject); - - GameObject go = outReference; - Assert.IsNull(go); - } - [Test] - public void TestTryGet() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - - NetworkObjectReference networkObjectReference = networkObjectContext.Object; - - Assert.True(networkObjectReference.TryGet(out NetworkObject networkObject)); - Assert.NotNull(networkObject); - networkObjectReference.TryGet(out NetworkObject result); - Assert.AreEqual(networkObject, result); } - public enum NetworkObjectConstructorTypes + protected override void OnOneTimeSetup() { - None, - NullNetworkObject, - NullGameObject + // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. + NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); + base.OnOneTimeSetup(); } [UnityTest] - public IEnumerator TestSerializeNull([Values] NetworkObjectConstructorTypes networkObjectConstructorTypes) + public IEnumerator TestSerializeNetworkObject() { - TestNetworkBehaviour.ReceivedRPC = false; - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(); - switch (networkObjectConstructorTypes) - { - case NetworkObjectConstructorTypes.None: - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference()); - break; - } - case NetworkObjectConstructorTypes.NullNetworkObject: - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((NetworkObject)null)); - break; - } - case NetworkObjectConstructorTypes.NullGameObject: - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((GameObject)null)); - break; - } - } + // Explicitly send the NetworkObject as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(new NetworkObjectReference(m_ReferenceToUse.NetworkObject)); + // Validated the reference + yield return WaitForConditionOrTimeOut(NetworkObjectSerializedValidation); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][Explicit reference] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - // wait for rpc completion - float t = 0; - while (!TestNetworkBehaviour.ReceivedRPC) - { + // Implicitly send the NetworkObject as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(m_ReferenceToUse.NetworkObject); - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + // Validated the reference + yield return WaitForConditionOrTimeOut(NetworkObjectSerializedValidation); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][Implicit reference] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - yield return null; - } + // Use the GameObject to set the NetworkObjet reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(new NetworkObjectReference(m_ReferenceToUse.GameObject)); - // validate - Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedGameObject); + // Validated the reference + yield return WaitForConditionOrTimeOut(NetworkObjectSerializedValidation); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][GameObject reference] Failed to validate {nameof(NetworkObjectReference)} serialization!"); } [UnityTest] - public IEnumerator TestRpc() + public IEnumerator TestSerializeNull() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(true); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Initialize with NULL parameter + var initializeWithNullGameObject = new NetworkObjectReference((GameObject)null); + var initializeWithNullNetworkObject = new NetworkObjectReference((NetworkObject)null); + // Initialize with no parameter + var initializeWithNothing = new NetworkObjectReference(); - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference(otherObjectContext.Object)); + // Initialize with NULL GameObject parameter + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(initializeWithNullGameObject); - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedGameObject == null) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + // Validated the reference + yield return WaitForConditionOrTimeOut(SerializingNullNetworkObjectValidated); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][GameObject as NULL] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - yield return null; + // Reset the RPC NetworkObjectReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) + { + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); + testBehaviour.RpcReceivedNetworkObject = testBehaviour.NetworkObject; } - // validate - Assert.AreEqual(otherObjectContext.Object, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(otherObjectContext.Object.gameObject, testNetworkBehaviour.RpcReceivedGameObject); - } - - [UnityTest] - public IEnumerator TestRpcImplicitNetworkObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); - - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Initialize with NULL NetworkObject parameter + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(initializeWithNullNetworkObject); - testNetworkBehaviour.SendReferenceServerRpc(otherObjectContext.Object); + // Validated the reference + yield return WaitForConditionOrTimeOut(SerializingNullNetworkObjectValidated); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][NetworkObject as NULL] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedGameObject == null) + // Reset the RPC NetworkObjectReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } - - yield return null; + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); + testBehaviour.RpcReceivedNetworkObject = testBehaviour.NetworkObject; } - // validate - Assert.AreEqual(otherObjectContext.Object, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(otherObjectContext.Object.gameObject, testNetworkBehaviour.RpcReceivedGameObject); + // Initialize with no parameter + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(initializeWithNothing); + + // Validated the reference + yield return WaitForConditionOrTimeOut(SerializingNullNetworkObjectValidated); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][No Parameter] Failed to validate {nameof(NetworkObjectReference)} serialization!"); } [UnityTest] - public IEnumerator TestRpcImplicitGameObject() + public IEnumerator TestGetReferenceAndConversion() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + var referenceToUse = new NetworkObjectReference(m_ReferenceToUse.NetworkObject); - testNetworkBehaviour.SendReferenceServerRpc(otherObjectContext.Object.gameObject); - - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedGameObject == null) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + Assert.True(referenceToUse.TryGet(out NetworkObject networkObject)); + Assert.NotNull(networkObject, $"TryGet succeeded but value returned is null!"); - yield return null; - } - - // validate - Assert.AreEqual(otherObjectContext.Object, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(otherObjectContext.Object.gameObject, testNetworkBehaviour.RpcReceivedGameObject); - } - - [Test] - public void TestNetworkVariable() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + // TODO: Revisit this to determine if this portion of the test is actually needed + referenceToUse.TryGet(out NetworkObject result); + Assert.AreEqual(networkObject, result); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Now implicitly convert from a NetworkObjectReference to a GameObject + GameObject fromReference = referenceToUse; + Assert.IsTrue(fromReference == m_ReferenceToUse.GameObject, $"Implicitly converting {nameof(NetworkObjectReference)} to {nameof(GameObject)} failed!"); - // check default value is null - Assert.IsNull((NetworkObject)testNetworkBehaviour.TestVariable.Value); + // Despawn + m_ReferenceToUse.NetworkObject.Despawn(); + var referenceName = m_ReferenceToUse.GameObject.name; + yield return WaitForDespawnedOnAllOrTimeOut(new List() { m_ReferenceToUse.NetworkObject }); + AssertOnTimeout($"Timed out waiting for {referenceName} to de-spawn!"); - testNetworkBehaviour.TestVariable.Value = networkObjectContext.Object; + // Destroy + Object.DestroyImmediate(m_ReferenceToUse.GameObject); - Assert.AreEqual((GameObject)testNetworkBehaviour.TestVariable.Value, networkObjectContext.Object.gameObject); - Assert.AreEqual((NetworkObject)testNetworkBehaviour.TestVariable.Value, networkObjectContext.Object); + // Validate trying to implicitly convert returns a null value when the GameObject + // no longer exists + fromReference = referenceToUse; + Assert.IsTrue(fromReference == null, $"Implicitly converting {nameof(NetworkObjectReference)} to {nameof(GameObject)} failed when destroyed!"); } - [Test] - public void TestDespawn() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - var originalId = networkObjectContext.Object.NetworkObjectId; - - NetworkObjectReference networkObjectReference = networkObjectContext.Object; - Assert.AreEqual(networkObjectContext.Object, (NetworkObject)networkObjectReference); - - networkObjectContext.Object.Despawn(); - Assert.IsFalse(networkObjectReference.TryGet(out NetworkObject _)); - networkObjectContext.Object.Spawn(); - - // After spawning again the reference will still no longer work as it still points to the old object - Assert.AreNotEqual(originalId, networkObjectContext.Object.NetworkObjectId); - Assert.IsFalse(networkObjectReference.TryGet(out NetworkObject _)); - - // creating a new reference will make it work again - networkObjectReference = networkObjectContext.Object; - Assert.AreEqual(networkObjectContext.Object, (NetworkObject)networkObjectReference); - } - - [Test] - public void FailSerializeNonSpawnedNetworkObject() + [UnityTest] + public IEnumerator FailSerializeNonSpawnedNetworkObject() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); + Assert.Throws(() => { - NetworkObjectReference outReference = networkObjectContext.Object; + NetworkObjectReference outReference = instance.GetComponent(); }); } - [Test] - public void FailSerializeGameObjectWithoutNetworkObject() + [UnityTest] + public IEnumerator FailSerializeGameObjectWithoutNetworkObject() { - using var gameObjectContext = UnityObjectContext.CreateGameObject(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); + Assert.Throws(() => { - NetworkObjectReference outReference = gameObjectContext.Object; + NetworkObjectReference outReference = instance; }); } - - public void Dispose() - { - //Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); - } - - public NetworkObjectReferenceTests() - { - //Create, instantiate, and host - NetworkManagerHelper.StartNetworkManager(out _); - } - } - - /// - /// Helper method for tests to create and destroy Unity Objects. - /// - /// The type of Object this context incorporates. - internal class UnityObjectContext : UnityObjectContext where T : Object - { - private T m_Object; - - internal UnityObjectContext(T unityObject, Object root) - : base(root) - { - m_Object = unityObject; - } - - public T Object => m_Object; - } - - internal class UnityObjectContext : IDisposable - { - private Object m_Root; - - protected UnityObjectContext(Object root) - { - m_Root = root; - } - - public static UnityObjectContext CreateGameObject(string name = "") - { - var gameObject = new GameObject(name); - return new UnityObjectContext(gameObject, gameObject); - } - - public static UnityObjectContext CreateNetworkObject(string name = "") - { - var gameObject = new GameObject(name); - var networkObject = gameObject.AddComponent(); - return new UnityObjectContext(networkObject, gameObject); - } - - public void Dispose() - { - Object.DestroyImmediate(m_Root); - } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 445055a964..51d91eea3d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -820,6 +820,11 @@ protected void CreateServerAndClients() CreateServerAndClients(NumberOfClients); } + internal virtual bool ShouldCreatePlayerPrefab() + { + return true; + } + /// /// Creates the server and clients /// @@ -828,7 +833,11 @@ protected void CreateServerAndClients(int numberOfClients) { VerboseDebug($"Entering {nameof(CreateServerAndClients)}"); - CreatePlayerPrefab(); + if (ShouldCreatePlayerPrefab()) + { + CreatePlayerPrefab(); + } + if (m_EnableTimeTravel) { diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index d685fb68ca..46fd493b5c 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -356,6 +356,7 @@ public void ParameterBoundsCheck() { var gameObject = new GameObject(); gameObject.AddComponent(); + gameObject.AddComponent(); var networkAnimator = gameObject.AddComponent(); var writer = new FastBufferWriter(40, Unity.Collections.Allocator.TempJob); @@ -371,6 +372,8 @@ public void ParameterBoundsCheck() LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex($"parameters. Ignoring the remainger of this {nameof(ParametersUpdateMessage)}!")); // Pass in the invalid ParametersUpdateMessage networkAnimator.UpdateParameters(ref invalidParameters); + + Object.DestroyImmediate(gameObject); } private bool AllTriggersDetected(OwnerShipMode ownerShipMode) @@ -1078,27 +1081,18 @@ public void ShutdownWhileSpawnedAndStartBackUpTest() TimeTravelToNextTick(); - WaitForConditionOrTimeOutWithTimeTravel(() => !m_ServerNetworkManager.ShutdownInProgress); + WaitForConditionOrTimeOutWithTimeTravel(() => !m_ServerNetworkManager.ShutdownInProgress && m_ServerNetworkManager.IsConnectedClient); Assert.IsTrue(m_ServerTestHelperDespawned, $"Server-Side {nameof(AnimatorTestHelper)} did not have a valid IsServer setting!"); AssertOnTimeout($"Timed out waiting for the server to shutdown!"); VerboseDebug($" ++++++++++++++++++ Disconnect-Reconnect Restarting Server and Client ++++++++++++++++++ "); - // Since the dynamically generated PlayerPrefab is destroyed when the server shuts down, - // we need to create a new one and assign it to NetworkPrefab index 0 - m_PlayerPrefab = new GameObject("Player"); - NetworkObject networkObject = m_PlayerPrefab.AddComponent(); - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); - m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs[playerPrefabIndex].Prefab = m_PlayerPrefab; - m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; // Now, restart the server and the client m_ServerNetworkManager.StartHost(); foreach (var clientNetworkManager in m_ClientNetworkManagers) { - clientNetworkManager.NetworkConfig.Prefabs.Prefabs[playerPrefabIndex].Prefab = m_PlayerPrefab; - clientNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; clientNetworkManager.StartClient(); }