diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c20a80df87..a812acbafe 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue with distributing parented children that have the distributable and/or transferrable permissions set and have the same owner as the root parent, that has the distributable permission set, were not being distributed to the same client upon the owning client disconnecting when using a distributed authority network topology. (#3203) - Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200) ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 854bc8fba2..efaff3a2c3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1753,16 +1753,24 @@ internal void GetObjectDistribution(ref Dictionary(); + if (parentNetworkObject != null && parentNetworkObject.OwnerClientId == networkObject.OwnerClientId + && (networkObject.IsOwnershipDistributable || networkObject.IsOwnershipTransferable)) { - var parentNetworkObject = networkObject.transform.parent.GetComponent(); - if (parentNetworkObject != null && parentNetworkObject.OwnerClientId == networkObject.OwnerClientId) - { - continue; - } + continue; } + } + + // At this point we only allow things marked with the distributable permission and is not locked to be distributed + if (networkObject.IsOwnershipDistributable && !networkObject.IsOwnershipLocked) + { + // We have to check if it is an in-scene placed NetworkObject and if it is get the source prefab asset GlobalObjectIdHash value of the in-scene placed instance // since all in-scene placed instances use unique GlobalObjectIdHash values. var globalOjectIdHash = networkObject.IsSceneObject.HasValue && networkObject.IsSceneObject.Value ? networkObject.InScenePlacedSourceGlobalObjectIdHash : networkObject.GlobalObjectIdHash; @@ -1877,8 +1885,29 @@ internal void DistributeNetworkObjects(ulong clientId) { if ((i % offsetCount) == 0) { + var children = ownerList.Value[i].GetComponentsInChildren(); + // Since the ownerList.Value[i] has to be distributable, then transfer all child NetworkObjects + // with the same owner clientId and are marked as distributable also to the same client to keep + // the owned distributable parent with the owned distributable children + foreach (var child in children) + { + // Ignore the parent and any child that does not have the same owner or that is already owned by the currently targeted client + if (child == ownerList.Value[i] || child.OwnerClientId != ownerList.Value[i].OwnerClientId || child.OwnerClientId == clientId) + { + continue; + } + if ((!child.IsOwnershipDistributable || !child.IsOwnershipTransferable) && NetworkManager.LogLevel == LogLevel.Developer) + { + NetworkLog.LogWarning($"Sibling {child.name} of root parent {ownerList.Value[i].name} is neither transferrable or distributable! Object distribution skipped and could lead to a potentially un-owned or owner-mismatched {nameof(NetworkObject)}!"); + continue; + } + // Transfer ownership of all distributable =or= transferrable children with the same owner to the same client to preserve the sibling ownership tree. + ChangeOwnership(child, clientId, true); + // Note: We don't increment the distributed count for these children as they are skipped when getting the object distribution + } + // Finally, transfer ownership of the root parent ChangeOwnership(ownerList.Value[i], clientId, true); - //if (EnableDistributeLogging) + if (EnableDistributeLogging) { Debug.Log($"[Client-{ownerList.Key}][NetworkObjectId-{ownerList.Value[i].NetworkObjectId} Distributed to Client-{clientId}"); }