Skip to content

fix: NetworkObject.NetworkHide destroys client-side in-scene placed NetworkObjects [MTT-4211] #2086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081)

### Fixed
- Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086)
- Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076)
- Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074)
- Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074)
Expand Down
69 changes: 55 additions & 14 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,19 @@ private void Awake()
}

/// <summary>
/// Shows a previously hidden <see cref="NetworkObject"/> to a client
/// Makes the previously hidden <see cref="NetworkObject"/> "netcode visible" to the targeted client.
/// </summary>
/// <param name="clientId">The client to show the <see cref="NetworkObject"/> to</param>
/// <remarks>
/// Usage: Use to start sending updates for a previously hidden <see cref="NetworkObject"/> to the targeted client.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be instantiated and spawned on the targeted client side.<br />
/// In-Scene Placed: The instantiated but despawned <see cref="NetworkObject"/>s will be spawned on the targeted client side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkShow(ulong)"/><br />
/// <see cref="NetworkHide(ulong)"/> or <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="clientId">The targeted client</param>
public void NetworkShow(ulong clientId)
{
if (!IsSpawned)
Expand All @@ -260,11 +270,22 @@ public void NetworkShow(ulong clientId)
NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this);
}


/// <summary>
/// Shows a list of previously hidden <see cref="NetworkObject"/>s to a client
/// Makes a list of previously hidden <see cref="NetworkObject"/>s "netcode visible" for the client specified.
/// </summary>
/// <param name="networkObjects">The <see cref="NetworkObject"/>s to show</param>
/// <param name="clientId">The client to show the objects to</param>
/// <remarks>
/// Usage: Use to start sending updates for previously hidden <see cref="NetworkObject"/>s to the targeted client.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be instantiated and spawned on the targeted client's side.<br />
/// In-Scene Placed: Already instantiated but despawned <see cref="NetworkObject"/>s will be spawned on the targeted client's side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkShow(ulong)"/><br />
/// <see cref="NetworkHide(ulong)"/> or <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="networkObjects">The objects to become "netcode visible" to the targeted client</param>
/// <param name="clientId">The targeted client</param>
public static void NetworkShow(List<NetworkObject> networkObjects, ulong clientId)
{
if (networkObjects == null || networkObjects.Count == 0)
Expand Down Expand Up @@ -305,9 +326,19 @@ public static void NetworkShow(List<NetworkObject> networkObjects, ulong clientI
}

/// <summary>
/// Hides a object from a specific client
/// Hides the <see cref="NetworkObject"/> from the targeted client.
/// </summary>
/// <param name="clientId">The client to hide the object for</param>
/// <remarks>
/// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for a currently visible <see cref="NetworkObject"/>.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
/// <see cref="NetworkShow(ulong)"/> or <see cref="NetworkShow(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="clientId">The targeted client</param>
public void NetworkHide(ulong clientId)
{
if (!IsSpawned)
Expand Down Expand Up @@ -335,18 +366,28 @@ public void NetworkHide(ulong clientId)
var message = new DestroyObjectMessage
{
NetworkObjectId = NetworkObjectId,
DestroyGameObject = true
DestroyGameObject = !IsSceneObject.Value
};
// Send destroy call
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
}

/// <summary>
/// Hides a list of objects from a client
/// Hides a list of <see cref="NetworkObject"/>s from the targeted client.
/// </summary>
/// <param name="networkObjects">The objects to hide</param>
/// <param name="clientId">The client to hide the objects from</param>
/// <remarks>
/// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for the currently visible <see cref="NetworkObject"/>s.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkHide(ulong)"/><br />
/// <see cref="NetworkShow(ulong)"/> or <see cref="NetworkShow(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="networkObjects">The <see cref="NetworkObject"/>s that will become "netcode invisible" to the targeted client</param>
/// <param name="clientId">The targeted client</param>
public static void NetworkHide(List<NetworkObject> networkObjects, ulong clientId)
{
if (networkObjects == null || networkObjects.Count == 0)
Expand Down Expand Up @@ -455,8 +496,8 @@ public void SpawnWithOwnership(ulong clientId, bool destroyWithScene = false)
/// <summary>
/// Spawns a <see cref="NetworkObject"/> across the network and makes it the player object for the given client
/// </summary>
/// <param name="clientId">The clientId whos player object this is</param>
/// <param name="destroyWithScene">Should the object be destroyd when the scene is changed</param>
/// <param name="clientId">The clientId who's player object this is</param>
/// <param name="destroyWithScene">Should the object be destroyed when the scene is changed</param>
public void SpawnAsPlayerObject(ulong clientId, bool destroyWithScene = false)
{
SpawnInternal(destroyWithScene, clientId, true);
Expand Down Expand Up @@ -697,7 +738,7 @@ private void OnTransformParentChanged()
// For instance, if we're spawning NetworkObject 5 and its parent is 10, what should happen if we do not have 10 yet?
// let's say 10 is on the way to be replicated in a few frames and we could fix that parent-child relationship later.
//
// If you couldn't find your parent, we put you into OrphanChildren set and everytime we spawn another NetworkObject locally due to replication,
// If you couldn't find your parent, we put you into OrphanChildren set and every time we spawn another NetworkObject locally due to replication,
// we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one.
internal static HashSet<NetworkObject> OrphanChildren = new HashSet<NetworkObject>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ public IEnumerator InSceneNetworkObjectSynchAndSpawn()

yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients);
AssertOnTimeout($"Timed out waiting for all in-scene instances to be spawned! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients}");

// Test NetworkHide on the first client
var firstClientId = m_ClientNetworkManagers[0].LocalClientId;

serverObject.NetworkHide(firstClientId);

yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients - 1);
AssertOnTimeout($"[NetworkHide] Timed out waiting for Client-{firstClientId} to despawn the in-scene placed NetworkObject! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients - 1}");

// Validate that the first client can spawn the "netcode hidden" in-scene placed NetworkObject
serverObject.NetworkShow(firstClientId);
yield return WaitForConditionOrTimeOut(() => NetworkObjectTestComponent.SpawnedInstances.Count == TotalClients);
AssertOnTimeout($"[NetworkShow] Timed out waiting for Client-{firstClientId} to spawn the in-scene placed NetworkObject! Current spawned count: {NetworkObjectTestComponent.SpawnedInstances.Count()} | Expected spawn count: {TotalClients}");

CleanUpLoadedScene();
}

Expand Down