From a1fac913513adf79197ffd2075c3c20b577e3531 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Wed, 24 Dec 2025 11:11:29 +0100 Subject: [PATCH 01/21] chore: optimization --- .../Runtime/Core/NetworkObject.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 311a3df650..70631a49c3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -359,7 +359,8 @@ internal bool HasParentNetworkObject(Transform transform) /// /// Gets the NetworkManager that owns this NetworkObject instance /// - public NetworkManager NetworkManager => NetworkManagerOwner ? NetworkManagerOwner : NetworkManager.Singleton; + // TODO use the correct one, here are we sure it is spawned? if it is, use NetworkManager + public NetworkManager NetworkManager = NetworkManager.Singleton; /// /// Useful to know if we should or should not send a message @@ -1114,8 +1115,11 @@ public bool HasOwnershipStatus(OwnershipStatus status) [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool InternalHasAuthority() { - var networkManager = NetworkManager; - return networkManager.DistributedAuthorityMode ? OwnerClientId == networkManager.LocalClientId : networkManager.IsServer; + if (!IsSpawned) + { + return false; + } + return NetworkManagerOwner.DistributedAuthorityMode ? OwnerClientId == NetworkManagerOwner.LocalClientId : NetworkManagerOwner.IsServer; } /// @@ -1166,17 +1170,17 @@ private bool InternalHasAuthority() /// /// Gets if the object is the personal clients player object /// - public bool IsLocalPlayer => NetworkManager != null && IsPlayerObject && OwnerClientId == NetworkManager.LocalClientId; + public bool IsLocalPlayer => IsPlayerObject && OwnerClientId == NetworkManager.LocalClientId; /// /// Gets if the object is owned by the local player or if the object is the local player object /// - public bool IsOwner => NetworkManager != null && OwnerClientId == NetworkManager.LocalClientId; + public bool IsOwner => OwnerClientId == NetworkManager.LocalClientId; /// /// Gets Whether or not the object is owned by anyone /// - public bool IsOwnedByServer => NetworkManager != null && OwnerClientId == NetworkManager.ServerClientId; + public bool IsOwnedByServer => OwnerClientId == NetworkManager.ServerClientId; /// /// Gets if the object has yet been spawned across the network From b1b85ac62ed2cc264deceeb7814f0a9a3c2fde3d Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 17:16:38 +0100 Subject: [PATCH 02/21] chore: optimize NetworkManager accessors --- .../Runtime/Core/NetworkObject.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 70631a49c3..a75acd4460 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -357,9 +357,8 @@ internal bool HasParentNetworkObject(Transform transform) } /// - /// Gets the NetworkManager that owns this NetworkObject instance + /// Gets the NetworkManager that owns this NetworkObject instance. /// - // TODO use the correct one, here are we sure it is spawned? if it is, use NetworkManager public NetworkManager NetworkManager = NetworkManager.Singleton; /// @@ -1125,7 +1124,7 @@ private bool InternalHasAuthority() /// /// The NetworkManager that owns this NetworkObject. /// This property controls where this NetworkObject belongs. - /// This property is null by default currently, which means that the above NetworkManager getter will return the Singleton. + /// This property is null by default currently. /// In the future this is the path where alternative NetworkManagers should be injected for running multi NetworkManagers /// internal NetworkManager NetworkManagerOwner; @@ -2233,7 +2232,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) return false; } - if (NetworkManager == null || !NetworkManager.IsListening) + if (!NetworkManager.IsListening) { return false; } @@ -2254,7 +2253,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (parent != null && (IsSpawned ^ parent.IsSpawned) && NetworkManager != null && !NetworkManager.ShutdownInProgress) + if (parent != null && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) { if (NetworkManager.LogLevel <= LogLevel.Developer) { @@ -2292,7 +2291,7 @@ private void OnTransformParentChanged() return; } - if (NetworkManager == null || !NetworkManager.IsListening) + if (!NetworkManager.IsListening) { // DANGO-TODO: Review as to whether we want to provide a better way to handle changing parenting of objects when the // object is not spawned. Really, we shouldn't care about these types of changes. @@ -3422,9 +3421,10 @@ internal void SubscribeToActiveSceneForSynch() /// private void CurrentlyActiveSceneChanged(Scene current, Scene next) { + // TODO should we remove NetworkManager null check here? // Early exit if there is no NetworkManager assigned, the NetworkManager is shutting down, the NetworkObject // is not spawned, or an in-scene placed NetworkObject - if (NetworkManager == null || NetworkManager.ShutdownInProgress || !IsSpawned || IsSceneObject != false) + if (NetworkManager.ShutdownInProgress || !IsSpawned || IsSceneObject != false) { return; } @@ -3523,10 +3523,11 @@ private void Awake() /// internal bool UpdateForSceneChanges() { + // TODO should we remove NetworkManager null check here? // Early exit if SceneMigrationSynchronization is disabled, there is no NetworkManager assigned, // the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed // NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle - if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager == null || NetworkManager.ShutdownInProgress || + if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager.ShutdownInProgress || !NetworkManager.NetworkConfig.EnableSceneManagement || IsSceneObject != false || !gameObject) { // Stop checking for a scene migration @@ -3598,7 +3599,7 @@ internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour) { if (networkBehaviour.IsSpawned && IsSpawned) { - if (NetworkManager?.LogLevel == LogLevel.Developer) + if (NetworkManager.LogLevel == LogLevel.Developer) { NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)"); } From 606c9f73ed877c892d19f58842fcf8bd32f7f4b4 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:17:31 +0100 Subject: [PATCH 03/21] Using m_CachedNetworkObject instead of NetworkObject --- .../Runtime/Components/NetworkTransform.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 7c44ba13cb..8d751c72ec 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3625,7 +3625,7 @@ private void CleanUpOnDestroyOrDespawn() #else var forUpdate = true; #endif - if (m_CachedNetworkObject != null) + if (m_CachedNetworkObject) { NetworkManager?.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); } @@ -3695,7 +3695,7 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState() } private NetworkObject m_CachedNetworkObject; /// - /// The internal initialzation method to allow for internal API adjustments + /// The internal initialization method to allow for internal API adjustments /// /// private void InternalInitialization(bool isOwnershipChange = false) @@ -3707,7 +3707,7 @@ private void InternalInitialization(bool isOwnershipChange = false) m_CachedNetworkObject = NetworkObject; // Determine if this is the first NetworkTransform in the associated NetworkObject's list - m_IsFirstNetworkTransform = NetworkObject.NetworkTransforms[0] == this; + m_IsFirstNetworkTransform = m_CachedNetworkObject.NetworkTransforms[0] == this; if (m_CachedNetworkManager && m_CachedNetworkManager.DistributedAuthorityMode) { @@ -3719,7 +3719,7 @@ private void InternalInitialization(bool isOwnershipChange = false) { if (CanCommitToTransform) { - if (NetworkObject.HasParentNetworkObject(transform)) + if (m_CachedNetworkObject.HasParentNetworkObject(transform)) { InLocalSpace = true; } @@ -3763,7 +3763,7 @@ private void InternalInitialization(bool isOwnershipChange = false) if (CanCommitToTransform) { // Make sure authority doesn't get added to updates (no need to do this on the authority side) - m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, false); + m_CachedNetworkManager.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); if (UseHalfFloatPrecision) { m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); @@ -3792,7 +3792,7 @@ private void InternalInitialization(bool isOwnershipChange = false) m_PreviousScaleLerpSmoothing = ScaleLerpSmoothing; // Non-authority needs to be added to updates for interpolation and applying state purposes - m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, true); + m_CachedNetworkManager.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, true); // Remove this instance from the tick update DeregisterForTickUpdate(this); ResetInterpolatedStateToCurrentAuthoritativeState(); From 18a180590a2877cd7acb399ac86a478a2f79887a Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:18:30 +0100 Subject: [PATCH 04/21] Using m_CachedNetworkManager instead of NetworkManager --- .../Runtime/Components/NetworkTransform.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 8d751c72ec..3693a0afd4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3554,7 +3554,7 @@ protected internal override void InternalOnNetworkPostSpawn() } // Standard non-authority synchronization is handled here - if (!CanCommitToTransform && NetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing) + if (!CanCommitToTransform && m_CachedNetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing) { NonAuthorityFinalizeSynchronization(); } @@ -3627,7 +3627,7 @@ private void CleanUpOnDestroyOrDespawn() #endif if (m_CachedNetworkObject) { - NetworkManager?.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); + m_CachedNetworkManager?.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); } DeregisterForTickUpdate(this); @@ -3673,7 +3673,7 @@ protected virtual void OnInitialize(ref NetworkVariable r /// private void ResetInterpolatedStateToCurrentAuthoritativeState() { - var serverTime = NetworkManager.ServerTime.Time; + var serverTime = m_CachedNetworkManager.ServerTime.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation(); @@ -3890,7 +3890,7 @@ private void DefaultParentChanged() m_RotationInterpolator.Clear(); // Always use NetworkManager here as this can be invoked prior to spawning - var tempTime = new NetworkTime(NetworkManager.NetworkConfig.TickRate, NetworkManager.ServerTime.Tick).Time; + var tempTime = new NetworkTime(NetworkManager.NetworkConfig.TickRate, m_CachedNetworkManager.ServerTime.Tick).Time; UpdatePositionInterpolator(m_InternalCurrentPosition, tempTime, true); m_ScaleInterpolator.ResetTo(m_InternalCurrentScale, tempTime); m_RotationInterpolator.ResetTo(m_InternalCurrentRotation, tempTime); @@ -3922,7 +3922,7 @@ internal override void InternalOnNetworkObjectParentChanged(NetworkObject parent if (LastTickSync == m_LocalAuthoritativeNetworkState.GetNetworkTick()) { m_InternalCurrentPosition = m_LastStateTargetPosition = GetSpaceRelativePosition(); - m_PositionInterpolator.ResetTo(m_PositionInterpolator.Parent, m_InternalCurrentPosition, NetworkManager.ServerTime.Time); + m_PositionInterpolator.ResetTo(m_PositionInterpolator.Parent, m_InternalCurrentPosition, m_CachedNetworkManager.ServerTime.Time); if (InLocalSpace) { transform.localPosition = m_InternalCurrentPosition; @@ -3954,7 +3954,7 @@ internal override void InternalOnNetworkObjectParentChanged(NetworkObject parent { m_InternalCurrentRotation = GetSpaceRelativeRotation(); m_TargetRotation = m_InternalCurrentRotation.eulerAngles; - m_RotationInterpolator.ResetTo(m_RotationInterpolator.Parent, m_InternalCurrentRotation, NetworkManager.ServerTime.Time); + m_RotationInterpolator.ResetTo(m_RotationInterpolator.Parent, m_InternalCurrentRotation, m_CachedNetworkManager.ServerTime.Time); if (InLocalSpace) { transform.localRotation = m_InternalCurrentRotation; @@ -4638,13 +4638,13 @@ private void UpdateTransformState() { continue; } - NetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, clientId); + m_CachedNetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, clientId); } } else { // Clients (owner authoritative) send messages to the server-host - NetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, NetworkManager.ServerClientId); + m_CachedNetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, NetworkManager.ServerClientId); } m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OutboundMessage.BytesWritten; } @@ -4783,7 +4783,7 @@ public NetworkTransformTickRegistration(NetworkManager networkManager) internal void RegisterForTickSynchronization() { s_TickSynchPosition++; - m_NextTickSync = NetworkManager.ServerTime.Tick + (s_TickSynchPosition % (int)NetworkManager.NetworkConfig.TickRate); + m_NextTickSync = m_CachedNetworkManager.ServerTime.Tick + (s_TickSynchPosition % (int)NetworkManager.NetworkConfig.TickRate); } private static void RegisterNetworkManagerForTickUpdate(NetworkManager networkManager) From 7a49a9a3ab96823fe953d2f3043910486a542939 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:26:36 +0100 Subject: [PATCH 05/21] Convert "==" to explicit Unity engine object lifetime check --- .../Runtime/Core/NetworkBehaviour.cs | 4 ++-- .../Runtime/Core/NetworkObject.cs | 22 +++++++------------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 9910668f59..4368c1e972 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -574,7 +574,7 @@ public NetworkObject NetworkObject { get { - if (m_NetworkObject != null) + if (m_NetworkObject) { return m_NetworkObject; } @@ -594,7 +594,7 @@ public NetworkObject NetworkObject // or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages // per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages. // We must check IsSpawned, otherwise a warning will be logged under certain valid conditions (see OnDestroy) - if (IsSpawned && m_NetworkObject == null && (m_NetworkManager == null || !m_NetworkManager.ShutdownInProgress)) + if (IsSpawned && !m_NetworkObject && (!m_NetworkManager || !m_NetworkManager.ShutdownInProgress)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index a75acd4460..adeffe9884 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -52,13 +52,7 @@ public sealed class NetworkObject : MonoBehaviour /// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0 /// [HideInInspector] - public uint PrefabIdHash - { - get - { - return GlobalObjectIdHash; - } - } + public uint PrefabIdHash => GlobalObjectIdHash; /// /// InstantiationData sent during the instantiation process. @@ -2167,7 +2161,7 @@ internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays) public bool TrySetParent(Transform parent, bool worldPositionStays = true) { // If we are removing ourself from a parent - if (parent == null) + if (!parent) { return TrySetParent((NetworkObject)null, worldPositionStays); } @@ -2253,7 +2247,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (parent != null && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) + if (!parent && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) { if (NetworkManager.LogLevel <= LogLevel.Developer) { @@ -2265,7 +2259,7 @@ internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays m_CachedWorldPositionStays = worldPositionStays; - if (parent == null) + if (!parent) { CurrentParent = null; transform.SetParent(null, worldPositionStays); @@ -2381,7 +2375,7 @@ private void OnTransformParentChanged() var message = new ParentSyncMessage { NetworkObjectId = NetworkObjectId, - IsLatestParentSet = m_LatestParent != null && m_LatestParent.HasValue, + IsLatestParentSet = m_LatestParent is not null, LatestParent = m_LatestParent, RemoveParent = removeParent, AuthorityApplied = authorityApplied, @@ -2458,7 +2452,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // has been set, this will not be entered into again (i.e. the later code will be invoked and // users will get notifications when the parent changes). var isInScenePlaced = IsSceneObject.HasValue && IsSceneObject.Value; - if (transform.parent != null && !removeParent && !m_LatestParent.HasValue && isInScenePlaced) + if (!transform.parent && !removeParent && !m_LatestParent.HasValue && isInScenePlaced) { var parentNetworkObject = transform.parent.GetComponent(); @@ -2466,7 +2460,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // attached. Under this case, we preserve the hierarchy but we don't keep track of the parenting. // Note: We only start tracking parenting if the user removes the child from the standard GameObject // parent and then re-parents the child under a GameObject with a NetworkObject component attached. - if (parentNetworkObject == null) + if (!parentNetworkObject) { // If we are parented under a GameObject, go ahead and mark the world position stays as false // so clients synchronize their transform in local space. (only for in-scene placed NetworkObjects) @@ -3176,7 +3170,7 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager { var obj = new SceneObject { - HasParent = transform.parent != null, + HasParent = m_CachedParent is not null, WorldPositionStays = m_CachedWorldPositionStays, NetworkObjectId = NetworkObjectId, OwnerClientId = OwnerClientId, From 7d9ffd5c786d07dedac1e8ac50f50470da4fb9b8 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:27:22 +0100 Subject: [PATCH 06/21] Fix wording --- .../Runtime/Core/NetworkObject.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index adeffe9884..03a03b4b2e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2157,7 +2157,7 @@ internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays) /// /// The new parent for this NetworkObject transform will be the child of. /// If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before. - /// Whether or not reparenting was successful. + /// Whether or not re-parenting was successful. public bool TrySetParent(Transform parent, bool worldPositionStays = true) { // If we are removing ourself from a parent @@ -2177,7 +2177,7 @@ public bool TrySetParent(Transform parent, bool worldPositionStays = true) /// /// The new parent for this NetworkObject transform will be the child of. /// If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before. - /// Whether or not reparenting was successful. + /// Whether or not re-parenting was successful. public bool TrySetParent(GameObject parent, bool worldPositionStays = true) { // If we are removing ourself from a parent @@ -2218,7 +2218,7 @@ public bool TryRemoveParent(bool worldPositionStays = true) /// /// The new parent for this NetworkObject transform will be the child of. /// If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before. - /// Whether or not reparenting was successful. + /// Whether or not re-parenting was successful. public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) { if (!AutoObjectParentSync) @@ -2235,7 +2235,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) // It wouldn't make sense to not allow parenting, but keeping this note here as a reminder. var isAuthority = HasAuthority || (AllowOwnerToParent && IsOwner); - // If we don't have authority and we are not shutting down, then don't allow any parenting. + // If we don't have authority, and we are not shutting down, then don't allow any parenting. // If we are shutting down and don't have authority then allow it. if (!isAuthority && !NetworkManager.ShutdownInProgress) { @@ -2295,7 +2295,7 @@ private void OnTransformParentChanged() return; } transform.parent = m_CachedParent; - Debug.LogException(new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before reparenting")); + Debug.LogException(new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before re-parenting")); return; } var isAuthority = false; @@ -2307,18 +2307,17 @@ private void OnTransformParentChanged() // If we do not have authority and we are spawned if (!isAuthority && IsSpawned) { - - // If the cached parent has not already been set and we are in distributed authority mode, then log an exception and exit early as a non-authority instance + // If the cached parent has not already been set, and we are in distributed authority mode, then log an exception and exit early as a non-authority instance // is trying to set the parent. if (distributedAuthority) { transform.parent = m_CachedParent; - NetworkLog.LogError($"[Not Owner] Only the owner-authority of child {gameObject.name}'s {nameof(NetworkObject)} component can reparent it!"); + NetworkLog.LogError($"[Not Owner] Only the owner-authority of child {gameObject.name}'s {nameof(NetworkObject)} component can re-parent it!"); } else { transform.parent = m_CachedParent; - Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s")); + Debug.LogException(new NotServerException($"Only the server can re-parent {nameof(NetworkObject)}s")); } return; } @@ -2336,7 +2335,7 @@ private void OnTransformParentChanged() else { transform.parent = m_CachedParent; - Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented after being spawned")); + Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be re-parented after being spawned")); } return; } @@ -2356,7 +2355,7 @@ private void OnTransformParentChanged() { transform.parent = m_CachedParent; AuthorityAppliedParenting = false; - Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented under another spawned {nameof(NetworkObject)}")); + Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be re-parented under another spawned {nameof(NetworkObject)}")); return; } @@ -2457,7 +2456,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa var parentNetworkObject = transform.parent.GetComponent(); // If parentNetworkObject is null then the parent is a GameObject without a NetworkObject component - // attached. Under this case, we preserve the hierarchy but we don't keep track of the parenting. + // attached. Under this case, we preserve the hierarchy, but we don't keep track of the parenting. // Note: We only start tracking parenting if the user removes the child from the standard GameObject // parent and then re-parents the child under a GameObject with a NetworkObject component attached. if (!parentNetworkObject) @@ -2476,7 +2475,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa else { // If we made it this far, go ahead and set the network parenting values - // with the WorldPoisitonSays value set to false + // with the WorldPositionSays value set to false. // Note: Since in-scene placed NetworkObjects are parented in the scene // the default "assumption" is that children are parenting local space // relative. @@ -2489,7 +2488,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa } } - // If we are removing the parent or our latest parent is not set, then remove the parent + // If we are removing the parent or our latest parent is not set, then remove the parent. // removeParent is only set when: // - The server-side NetworkObject.OnTransformParentChanged is invoked and the parent is being removed // - The client-side when handling a ParentSyncMessage @@ -3192,7 +3191,7 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager // Handle Parenting if (!AlwaysReplicateAsRoot && obj.HasParent) { - var parentNetworkObject = transform.parent.GetComponent(); + var parentNetworkObject = m_CachedParent.GetComponent(); if (parentNetworkObject) { From b255749d0823ac0d2256dfc4c48bf5dcd589d287 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:28:17 +0100 Subject: [PATCH 07/21] Use m_CachedParent and Scene --- .../Runtime/Core/NetworkObject.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 03a03b4b2e..7defc1d350 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -334,17 +334,17 @@ private void CheckForInScenePlaced() internal bool HasParentNetworkObject(Transform transform) { - if (transform.parent != null) + if (m_CachedParent != null) { - var networkObject = transform.parent.GetComponent(); + var networkObject = m_CachedParent.GetComponent(); if (networkObject != null && networkObject != this) { return true; } - if (transform.parent.parent != null) + if (m_CachedParent.parent != null) { - return HasParentNetworkObject(transform.parent); + return HasParentNetworkObject(m_CachedParent); } } return false; @@ -3427,7 +3427,7 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) { // Only dynamically spawned NetworkObjects that are not already in the newly assigned active scene will migrate // and update their scene handles - if (IsSceneObject.HasValue && !IsSceneObject.Value && gameObject.scene != next && gameObject.transform.parent == null) + if (IsSceneObject.HasValue && !IsSceneObject.Value && m_SceneOrigin != next && m_CachedParent == null) { SceneManager.MoveGameObjectToScene(gameObject, next); SceneChangedUpdate(next); @@ -3486,7 +3486,7 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) OnMigratedToNewScene?.Invoke(); // Only the authority side will notify clients of non-parented NetworkObject scene changes - if (isAuthority && notify && transform.parent == null) + if (isAuthority && notify && !m_CachedParent) { NetworkManager.SceneManager.NotifyNetworkObjectSceneChanged(this); } From 0ebab51c3d0ad6eedc19e21466cf66e0f07aa1ba Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:29:09 +0100 Subject: [PATCH 08/21] Changed parentObject scope --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 7defc1d350..512d992dd3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2341,10 +2341,9 @@ private void OnTransformParentChanged() } var removeParent = false; var parentTransform = transform.parent; - var parentObject = (NetworkObject)null; if (parentTransform != null) { - if (!transform.parent.TryGetComponent(out parentObject)) + if (!transform.parent.TryGetComponent(out NetworkObject parentObject)) { transform.parent = m_CachedParent; AuthorityAppliedParenting = false; From a1664825d6e74e3b8dd35e101b1a776ab5d3f9cd Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 20:36:36 +0100 Subject: [PATCH 09/21] Removing comments (added in PR) --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 512d992dd3..8be6d70fed 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -3413,8 +3413,7 @@ internal void SubscribeToActiveSceneForSynch() /// private void CurrentlyActiveSceneChanged(Scene current, Scene next) { - // TODO should we remove NetworkManager null check here? - // Early exit if there is no NetworkManager assigned, the NetworkManager is shutting down, the NetworkObject + // Early exit if the NetworkManager is shutting down, the NetworkObject // is not spawned, or an in-scene placed NetworkObject if (NetworkManager.ShutdownInProgress || !IsSpawned || IsSceneObject != false) { @@ -3515,8 +3514,7 @@ private void Awake() /// internal bool UpdateForSceneChanges() { - // TODO should we remove NetworkManager null check here? - // Early exit if SceneMigrationSynchronization is disabled, there is no NetworkManager assigned, + // Early exit if SceneMigrationSynchronization is disabled, // the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed // NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager.ShutdownInProgress || From f817796cd8d01375ab8887a51a9d01cd0bb12796 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 20:37:56 +0100 Subject: [PATCH 10/21] Revert null check removal for Log --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 8be6d70fed..2a7844096e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -3589,7 +3589,7 @@ internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour) { if (networkBehaviour.IsSpawned && IsSpawned) { - if (NetworkManager.LogLevel == LogLevel.Developer) + if (NetworkManager?.LogLevel == LogLevel.Developer) { NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)"); } From e8d60537be7673361184d44446fc98e7e968e9f1 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 5 Jan 2026 16:14:33 +0100 Subject: [PATCH 11/21] Avoiding breaking change --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 2a7844096e..6390789b37 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -353,7 +353,7 @@ internal bool HasParentNetworkObject(Transform transform) /// /// Gets the NetworkManager that owns this NetworkObject instance. /// - public NetworkManager NetworkManager = NetworkManager.Singleton; + public NetworkManager NetworkManager { get; private set; } = NetworkManager.Singleton; /// /// Useful to know if we should or should not send a message From 43cc1cd0622b6c5627200f1995d9d53ea5b5400b Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 5 Jan 2026 16:57:23 +0100 Subject: [PATCH 12/21] Reverting NetworkManager changes --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 6390789b37..a363f69625 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -353,7 +353,7 @@ internal bool HasParentNetworkObject(Transform transform) /// /// Gets the NetworkManager that owns this NetworkObject instance. /// - public NetworkManager NetworkManager { get; private set; } = NetworkManager.Singleton; + public NetworkManager NetworkManager => NetworkManagerOwner ? NetworkManagerOwner : NetworkManager.Singleton; /// /// Useful to know if we should or should not send a message From 25e95773a6346791c5dffd5ffda3c014266fea96 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Tue, 6 Jan 2026 16:09:05 +0100 Subject: [PATCH 13/21] Address PR feedback --- .../Runtime/Core/NetworkObject.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index a363f69625..3b6c78ae5f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1118,7 +1118,7 @@ private bool InternalHasAuthority() /// /// The NetworkManager that owns this NetworkObject. /// This property controls where this NetworkObject belongs. - /// This property is null by default currently. + /// This property will be null when the NetworkObject is not spawned. /// In the future this is the path where alternative NetworkManagers should be injected for running multi NetworkManagers /// internal NetworkManager NetworkManagerOwner; @@ -1163,17 +1163,17 @@ private bool InternalHasAuthority() /// /// Gets if the object is the personal clients player object /// - public bool IsLocalPlayer => IsPlayerObject && OwnerClientId == NetworkManager.LocalClientId; + public bool IsLocalPlayer => IsSpawned && IsPlayerObject && OwnerClientId == NetworkManagerOwner.LocalClientId; /// /// Gets if the object is owned by the local player or if the object is the local player object /// - public bool IsOwner => OwnerClientId == NetworkManager.LocalClientId; + public bool IsOwner => IsSpawned && OwnerClientId == NetworkManager.LocalClientId; /// /// Gets Whether or not the object is owned by anyone /// - public bool IsOwnedByServer => OwnerClientId == NetworkManager.ServerClientId; + public bool IsOwnedByServer => IsSpawned && OwnerClientId == NetworkManager.ServerClientId; /// /// Gets if the object has yet been spawned across the network @@ -2275,7 +2275,7 @@ internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays private void OnTransformParentChanged() { - if (!AutoObjectParentSync || NetworkManager.ShutdownInProgress) + if (!AutoObjectParentSync) { return; } @@ -2285,8 +2285,12 @@ private void OnTransformParentChanged() return; } - if (!NetworkManager.IsListening) + if (!IsSpawned || !NetworkManager.IsListening) { + if (NetworkManager.ShutdownInProgress) + { + return; + } // DANGO-TODO: Review as to whether we want to provide a better way to handle changing parenting of objects when the // object is not spawned. Really, we shouldn't care about these types of changes. if (NetworkManager.DistributedAuthorityMode && m_CachedParent != null && transform.parent == null) @@ -2302,7 +2306,7 @@ private void OnTransformParentChanged() // With distributed authority, we need to track "valid authoritative" parenting changes. // So, either the authority or AuthorityAppliedParenting is considered a "valid parenting change". isAuthority = HasAuthority || AuthorityAppliedParenting || (AllowOwnerToParent && IsOwner); - var distributedAuthority = NetworkManager.DistributedAuthorityMode; + var distributedAuthority = NetworkManagerOwner.DistributedAuthorityMode; // If we do not have authority and we are spawned if (!isAuthority && IsSpawned) @@ -2373,7 +2377,7 @@ private void OnTransformParentChanged() var message = new ParentSyncMessage { NetworkObjectId = NetworkObjectId, - IsLatestParentSet = m_LatestParent is not null, + IsLatestParentSet = m_LatestParent is not null && m_LatestParent.HasValue, LatestParent = m_LatestParent, RemoveParent = removeParent, AuthorityApplied = authorityApplied, @@ -3415,7 +3419,7 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) { // Early exit if the NetworkManager is shutting down, the NetworkObject // is not spawned, or an in-scene placed NetworkObject - if (NetworkManager.ShutdownInProgress || !IsSpawned || IsSceneObject != false) + if (!IsSpawned || IsSceneObject != false || NetworkManager.ShutdownInProgress) { return; } @@ -3425,7 +3429,7 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) { // Only dynamically spawned NetworkObjects that are not already in the newly assigned active scene will migrate // and update their scene handles - if (IsSceneObject.HasValue && !IsSceneObject.Value && m_SceneOrigin != next && m_CachedParent == null) + if (IsSceneObject.HasValue && !IsSceneObject.Value && gameObject.scene != next && gameObject.transform.parent == null) { SceneManager.MoveGameObjectToScene(gameObject, next); SceneChangedUpdate(next); @@ -3517,8 +3521,8 @@ internal bool UpdateForSceneChanges() // Early exit if SceneMigrationSynchronization is disabled, // the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed // NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle - if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager.ShutdownInProgress || - !NetworkManager.NetworkConfig.EnableSceneManagement || IsSceneObject != false || !gameObject) + if (!SceneMigrationSynchronization || !IsSpawned || NetworkManagerOwner.ShutdownInProgress || + !NetworkManagerOwner.NetworkConfig.EnableSceneManagement || IsSceneObject != false || !gameObject) { // Stop checking for a scene migration return false; From 73a0bf87d3dd930ef6df476bb8cad8207f3379bf Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Wed, 7 Jan 2026 19:26:48 +0100 Subject: [PATCH 14/21] Fix opposite null check --- .../Runtime/Components/NetworkTransform.cs | 4 ++-- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 3693a0afd4..834923a33c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3763,7 +3763,7 @@ private void InternalInitialization(bool isOwnershipChange = false) if (CanCommitToTransform) { // Make sure authority doesn't get added to updates (no need to do this on the authority side) - m_CachedNetworkManager.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); + m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, false); if (UseHalfFloatPrecision) { m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); @@ -3792,7 +3792,7 @@ private void InternalInitialization(bool isOwnershipChange = false) m_PreviousScaleLerpSmoothing = ScaleLerpSmoothing; // Non-authority needs to be added to updates for interpolation and applying state purposes - m_CachedNetworkManager.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, true); + m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, true); // Remove this instance from the tick update DeregisterForTickUpdate(this); ResetInterpolatedStateToCurrentAuthoritativeState(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 3b6c78ae5f..c8eff6a5fa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1168,7 +1168,7 @@ private bool InternalHasAuthority() /// /// Gets if the object is owned by the local player or if the object is the local player object /// - public bool IsOwner => IsSpawned && OwnerClientId == NetworkManager.LocalClientId; + public bool IsOwner => IsSpawned && OwnerClientId == NetworkManagerOwner.LocalClientId; /// /// Gets Whether or not the object is owned by anyone @@ -2247,7 +2247,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (!parent && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) + if (parent && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) { if (NetworkManager.LogLevel <= LogLevel.Developer) { @@ -2454,7 +2454,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // has been set, this will not be entered into again (i.e. the later code will be invoked and // users will get notifications when the parent changes). var isInScenePlaced = IsSceneObject.HasValue && IsSceneObject.Value; - if (!transform.parent && !removeParent && !m_LatestParent.HasValue && isInScenePlaced) + if (transform.parent && !removeParent && !m_LatestParent.HasValue && isInScenePlaced) { var parentNetworkObject = transform.parent.GetComponent(); From 3f17e8354db1722a7512b981c4369d1653faacb1 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Thu, 8 Jan 2026 10:24:40 +0100 Subject: [PATCH 15/21] Address PR feedback --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index c8eff6a5fa..97338530cf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2226,7 +2226,8 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) return false; } - if (!NetworkManager.IsListening) + var networkManager = NetworkManager; + if (!networkManager || !networkManager.IsListening) { return false; } @@ -2237,7 +2238,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) // If we don't have authority, and we are not shutting down, then don't allow any parenting. // If we are shutting down and don't have authority then allow it. - if (!isAuthority && !NetworkManager.ShutdownInProgress) + if (!isAuthority && !networkManager.ShutdownInProgress) { return false; } From e02e462924485444e30a2a00029da5af07d3f172 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Thu, 8 Jan 2026 17:21:39 +0100 Subject: [PATCH 16/21] Cleanup NetworkObject: use cached NetworkManager WIP --- .../Runtime/Core/NetworkObject.cs | 140 ++++++++++-------- 1 file changed, 80 insertions(+), 60 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 97338530cf..7f89ca3cdb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -358,7 +358,7 @@ internal bool HasParentNetworkObject(Transform transform) /// /// Useful to know if we should or should not send a message /// - internal bool HasRemoteObservers => !(Observers.Count == 0 || (Observers.Contains(NetworkManager.LocalClientId) && Observers.Count == 1)); + internal bool HasRemoteObservers => !(Observers.Count == 0 || (Observers.Contains(NetworkManagerOwner.LocalClientId) && Observers.Count == 1)); /// /// Distributed Authority Mode Only @@ -392,29 +392,29 @@ internal bool HasParentNetworkObject(Transform transform) /// Defaults to true, determines whether the will be destroyed. public void DeferDespawn(int tickOffset, bool destroy = true) { - if (!NetworkManager.DistributedAuthorityMode) + if (!IsSpawned) { - NetworkLog.LogError($"This method is only available in distributed authority mode."); + NetworkLog.LogError($"Cannot defer despawning {name} because it is not spawned!"); return; } - if (!IsSpawned) + if (!NetworkManagerOwner.DistributedAuthorityMode) { - NetworkLog.LogError($"Cannot defer despawning {name} because it is not spawned!"); + NetworkLog.LogError($"This method is only available in distributed authority mode."); return; } if (!HasAuthority) { - NetworkLog.LogError($"Only the authority can invoke {nameof(DeferDespawn)} and local Client-{NetworkManager.LocalClientId} is not the authority of {name}!"); + NetworkLog.LogError($"Only the authority can invoke {nameof(DeferDespawn)} and local Client-{NetworkManagerOwner.LocalClientId} is not the authority of {name}!"); return; } // Apply the relative tick offset for when this NetworkObject should be despawned on // non-authoritative instances. - DeferredDespawnTick = NetworkManager.ServerTime.Tick + tickOffset; + DeferredDespawnTick = NetworkManagerOwner.ServerTime.Tick + tickOffset; - var connectionManager = NetworkManager.ConnectionManager; + var connectionManager = NetworkManagerOwner.ConnectionManager; for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { @@ -426,7 +426,7 @@ public void DeferDespawn(int tickOffset, bool destroy = true) } // DAHost handles sending updates to all clients - if (NetworkManager.DAHost) + if (NetworkManagerOwner.DAHost) { for (int i = 0; i < connectionManager.ConnectedClientsList.Count; i++) { @@ -595,17 +595,17 @@ internal void RemoveOwnershipExtended(OwnershipStatusExtended extended) /// true or false depending upon lock operation's success public bool SetOwnershipLock(bool lockOwnership = true) { - // If we are not in distributed autority mode, then exit early - if (!NetworkManager.DistributedAuthorityMode) + // If we don't have authority exit early + if (!HasAuthority) { - Debug.LogError($"[Feature Not Allowed In Client-Server Mode] Ownership flags are a distributed authority feature only!"); + NetworkLog.LogWarningServer($"[Attempted Lock Without Authority] Client-{NetworkManager.LocalClientId} is trying to lock ownership but does not have authority!"); return false; } - // If we don't have authority exit early - if (!HasAuthority) + // If we are not in distributed autority mode, then exit early + if (!NetworkManager.DistributedAuthorityMode) { - NetworkLog.LogWarningServer($"[Attempted Lock Without Authority] Client-{NetworkManager.LocalClientId} is trying to lock ownership but does not have authority!"); + Debug.LogError($"[Feature Not Allowed In Client-Server Mode] Ownership flags are a distributed authority feature only!"); return false; } @@ -730,6 +730,11 @@ public enum OwnershipRequestStatus /// This object is marked as SessionOwnerOnly and therefore cannot be requested /// SessionOwnerOnly, + + /// + /// The request is invalid (if not spawned for instance) + /// + Invalid, } /// @@ -748,8 +753,20 @@ public enum OwnershipRequestStatus /// public OwnershipRequestStatus RequestOwnership() { + // An ownership change request can only be made if spawned. + if (!IsSpawned) + { + + if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"[{name}] is trying to perform an ownership request but the object is not spawned!"); + } + + return OwnershipRequestStatus.Invalid; + } + // Exit early the local client is already the owner - if (OwnerClientId == NetworkManager.LocalClientId) + if (OwnerClientId == NetworkManagerOwner.LocalClientId) { return OwnershipRequestStatus.AlreadyOwner; } @@ -785,14 +802,14 @@ public OwnershipRequestStatus RequestOwnership() NetworkObjectId = NetworkObjectId, OwnerClientId = OwnerClientId, ClientIdCount = 1, - RequestClientId = NetworkManager.LocalClientId, + RequestClientId = NetworkManagerOwner.LocalClientId, ClientIds = new ulong[1] { OwnerClientId }, DistributedAuthorityMode = true, OwnershipFlags = (ushort)Ownership, }; - var sendTarget = NetworkManager.DAHost ? OwnerClientId : NetworkManager.ServerClientId; - NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, sendTarget); + var sendTarget = NetworkManagerOwner.DAHost ? OwnerClientId : NetworkManager.ServerClientId; + NetworkManagerOwner.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, sendTarget); return OwnershipRequestStatus.RequestSent; } @@ -855,9 +872,10 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) // respond to any additional ownership request with OwnershipRequestResponseStatus.RequestInProgress. AddOwnershipExtended(OwnershipStatusExtended.Requested); + // if OwnershipRequestStatus cannot be called before spawn // This action is always authorized as long as the client still has authority. // We need to pass in that this is a request approval ownership change. - NetworkManager.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, HasAuthority, true); + NetworkManagerOwner.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, HasAuthority, true); } else { @@ -871,15 +889,15 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) { ChangeMessageType = ChangeOwnershipMessage.ChangeType.RequestDenied, NetworkObjectId = NetworkObjectId, - OwnerClientId = NetworkManager.LocalClientId, // Always use the local clientId (see above notes) + OwnerClientId = NetworkManagerOwner.LocalClientId, // Always use the local clientId (see above notes) RequestClientId = clientRequestingOwnership, DistributedAuthorityMode = true, OwnershipRequestResponseStatus = (byte)response, OwnershipFlags = (ushort)Ownership, }; - var sendTarget = NetworkManager.DAHost ? clientRequestingOwnership : NetworkManager.ServerClientId; - NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, sendTarget); + var sendTarget = NetworkManagerOwner.DAHost ? clientRequestingOwnership : NetworkManager.ServerClientId; + NetworkManagerOwner.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, sendTarget); } } @@ -976,7 +994,7 @@ public enum OwnershipLockActions /// public bool SetOwnershipStatus(OwnershipStatus status, bool clearAndSet = false, OwnershipLockActions lockAction = OwnershipLockActions.None) { - if (status.HasFlag(OwnershipStatus.SessionOwner) && !NetworkManager.LocalClient.IsSessionOwner) + if (status.HasFlag(OwnershipStatus.SessionOwner) && !NetworkManagerOwner.LocalClient.IsSessionOwner) { NetworkLog.LogWarning("Only the session owner is allowed to set the ownership status to session owner only."); return false; @@ -1066,22 +1084,22 @@ internal void SendOwnershipStatusUpdate() OwnershipFlags = (ushort)Ownership, }; - if (NetworkManager.DAHost) + if (NetworkManagerOwner.DAHost) { foreach (var clientId in Observers) { - if (clientId == NetworkManager.LocalClientId) + if (clientId == NetworkManagerOwner.LocalClientId) { continue; } - NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, clientId); + NetworkManagerOwner.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, clientId); } } else { changeOwnership.ClientIdCount = Observers.Count; changeOwnership.ClientIds = Observers.ToArray(); - NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); + NetworkManagerOwner.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); } } @@ -1171,7 +1189,7 @@ private bool InternalHasAuthority() public bool IsOwner => IsSpawned && OwnerClientId == NetworkManagerOwner.LocalClientId; /// - /// Gets Whether or not the object is owned by anyone + /// Gets whether or not the object is owned by anyone /// public bool IsOwnedByServer => IsSpawned && OwnerClientId == NetworkManager.ServerClientId; @@ -1434,7 +1452,7 @@ public void NetworkShow(ulong clientId) if (!HasAuthority) { - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { throw new NotServerException($"Only the owner-authority can change visibility when distributed authority mode is enabled!"); } @@ -1446,7 +1464,7 @@ public void NetworkShow(ulong clientId) if (Observers.Contains(clientId)) { - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { Debug.LogError($"The object {name} is already visible to Client-{clientId}!"); return; @@ -1459,13 +1477,13 @@ public void NetworkShow(ulong clientId) if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId)) { - if (NetworkManager.LogLevel <= LogLevel.Normal) + if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"[NetworkShow] Trying to make {nameof(NetworkObject)} {gameObject.name} visible to client ({clientId}) but {nameof(CheckObjectVisibility)} returned false!"); } return; } - NetworkManager.SpawnManager.MarkObjectForShowingTo(this, clientId); + NetworkManagerOwner.SpawnManager.MarkObjectForShowingTo(this, clientId); Observers.Add(clientId); } @@ -1496,8 +1514,13 @@ public static void NetworkShow(List networkObjects, ulong clientI // Do the safety loop first to prevent putting the netcode in an invalid state. for (int i = 0; i < networkObjects.Count; i++) { + if (!networkObjects[i].IsSpawned) + { + throw new SpawnStateException("Object is not spawned"); + } + var networkObject = networkObjects[i]; - var networkManager = networkObject.NetworkManager; + var networkManager = networkObject.NetworkManagerOwner; if (networkManager.DistributedAuthorityMode && clientId == networkObject.OwnerClientId) { @@ -1513,7 +1536,7 @@ public static void NetworkShow(List networkObjects, ulong clientI // that the local instance does not own if (!networkObjects[i].HasAuthority) { - if (networkObjects[i].NetworkManager.DistributedAuthorityMode) + if (networkObjects[i].NetworkManagerOwner.DistributedAuthorityMode) { // It will log locally and to the "master-host". NetworkLog.LogErrorServer("Only the owner-authority can change visibility when distributed authority mode is enabled!"); @@ -1525,19 +1548,15 @@ public static void NetworkShow(List networkObjects, ulong clientI } } - if (!networkObjects[i].IsSpawned) - { - throw new SpawnStateException("Object is not spawned"); - } if (networkObjects[i].Observers.Contains(clientId)) { throw new VisibilityChangeException($"{nameof(NetworkObject)} with NetworkId: {networkObjects[i].NetworkObjectId} is already visible"); } - if (networkObjects[i].NetworkManager != networkManager) + if (networkObjects[i].NetworkManagerOwner != networkManager) { - throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManager)); + throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManagerOwner)); } } @@ -1568,9 +1587,9 @@ public void NetworkHide(ulong clientId) throw new SpawnStateException("Object is not spawned"); } - if (!HasAuthority && !NetworkManager.DAHost) + if (!HasAuthority && !NetworkManagerOwner.DAHost) { - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { throw new NotServerException($"Only the owner-authority can change visibility when distributed authority mode is enabled!"); } @@ -1580,11 +1599,11 @@ public void NetworkHide(ulong clientId) } } - if (!NetworkManager.SpawnManager.RemoveObjectFromShowingTo(this, clientId)) + if (!NetworkManagerOwner.SpawnManager.RemoveObjectFromShowingTo(this, clientId)) { if (!Observers.Contains(clientId)) { - if (NetworkManager.LogLevel <= LogLevel.Developer) + if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) { Debug.LogWarning($"{name} is already hidden from Client-{clientId}! (ignoring)"); return; @@ -1596,41 +1615,41 @@ public void NetworkHide(ulong clientId) { NetworkObjectId = NetworkObjectId, DestroyGameObject = !IsSceneObject.Value, - IsDistributedAuthority = NetworkManager.DistributedAuthorityMode, - IsTargetedDestroy = NetworkManager.DistributedAuthorityMode, + IsDistributedAuthority = NetworkManagerOwner.DistributedAuthorityMode, + IsTargetedDestroy = NetworkManagerOwner.DistributedAuthorityMode, TargetClientId = clientId, // Just always populate this value whether we write it or not DeferredDespawnTick = DeferredDespawnTick, }; var size = 0; - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { - if (!NetworkManager.DAHost) + if (!NetworkManagerOwner.DAHost) { // Send destroy call to service or DAHost - size = NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); + size = NetworkManagerOwner.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); } else // DAHost mocking service { // Send destroy call - size = NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); + size = NetworkManagerOwner.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); // Broadcast the destroy to all clients so they can update their observers list - foreach (var client in NetworkManager.ConnectionManager.ConnectedClientIds) + foreach (var client in NetworkManagerOwner.ConnectionManager.ConnectedClientIds) { - if (client == clientId || client == NetworkManager.LocalClientId) + if (client == clientId || client == NetworkManagerOwner.LocalClientId) { continue; } - size += NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, client); + size += NetworkManagerOwner.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, client); } } } else { // Send destroy call - size = NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); + size = NetworkManagerOwner.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); } - NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size); + NetworkManagerOwner.NetworkMetrics.TrackObjectDestroySent(clientId, this, size); } } @@ -1661,7 +1680,7 @@ public static void NetworkHide(List networkObjects, ulong clientI for (int i = 0; i < networkObjects.Count; i++) { var networkObject = networkObjects[i]; - var networkManager = networkObject.NetworkManager; + var networkManager = networkObject.NetworkManagerOwner; if (networkManager.DistributedAuthorityMode && clientId == networkObject.OwnerClientId) { @@ -1677,7 +1696,7 @@ public static void NetworkHide(List networkObjects, ulong clientI // that the local instance does not own if (!networkObjects[i].HasAuthority) { - if (networkObjects[i].NetworkManager.DistributedAuthorityMode) + if (networkObjects[i].NetworkManagerOwner.DistributedAuthorityMode) { // It will log locally and to the "master-host". NetworkLog.LogErrorServer($"Only the owner-authority can change hide a {nameof(NetworkObject)} when distributed authority mode is enabled!"); @@ -1700,9 +1719,9 @@ public static void NetworkHide(List networkObjects, ulong clientI throw new VisibilityChangeException($"{nameof(NetworkObject)} with {nameof(NetworkObjectId)}: {networkObjects[i].NetworkObjectId} is already hidden"); } - if (networkObjects[i].NetworkManager != networkManager) + if (networkObjects[i].NetworkManagerOwner != networkManager) { - throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManager)); + throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManagerOwner)); } } @@ -1714,6 +1733,7 @@ public static void NetworkHide(List networkObjects, ulong clientI private void OnDestroy() { + // TODO Can we destroy an object before it is spawned? // If no NetworkManager is assigned, then just exit early if (!NetworkManager) { From 07b43ecaf36d66990b79de5ea0bfb9f0ef83aacd Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Fri, 9 Jan 2026 11:22:09 +0100 Subject: [PATCH 17/21] Reset NetworkTransform and NetworkBehavior --- .../Runtime/Components/NetworkTransform.cs | 26 +++++++++---------- .../Runtime/Core/NetworkBehaviour.cs | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 834923a33c..7c44ba13cb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3554,7 +3554,7 @@ protected internal override void InternalOnNetworkPostSpawn() } // Standard non-authority synchronization is handled here - if (!CanCommitToTransform && m_CachedNetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing) + if (!CanCommitToTransform && NetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing) { NonAuthorityFinalizeSynchronization(); } @@ -3625,9 +3625,9 @@ private void CleanUpOnDestroyOrDespawn() #else var forUpdate = true; #endif - if (m_CachedNetworkObject) + if (m_CachedNetworkObject != null) { - m_CachedNetworkManager?.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); + NetworkManager?.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); } DeregisterForTickUpdate(this); @@ -3673,7 +3673,7 @@ protected virtual void OnInitialize(ref NetworkVariable r /// private void ResetInterpolatedStateToCurrentAuthoritativeState() { - var serverTime = m_CachedNetworkManager.ServerTime.Time; + var serverTime = NetworkManager.ServerTime.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation(); @@ -3695,7 +3695,7 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState() } private NetworkObject m_CachedNetworkObject; /// - /// The internal initialization method to allow for internal API adjustments + /// The internal initialzation method to allow for internal API adjustments /// /// private void InternalInitialization(bool isOwnershipChange = false) @@ -3707,7 +3707,7 @@ private void InternalInitialization(bool isOwnershipChange = false) m_CachedNetworkObject = NetworkObject; // Determine if this is the first NetworkTransform in the associated NetworkObject's list - m_IsFirstNetworkTransform = m_CachedNetworkObject.NetworkTransforms[0] == this; + m_IsFirstNetworkTransform = NetworkObject.NetworkTransforms[0] == this; if (m_CachedNetworkManager && m_CachedNetworkManager.DistributedAuthorityMode) { @@ -3719,7 +3719,7 @@ private void InternalInitialization(bool isOwnershipChange = false) { if (CanCommitToTransform) { - if (m_CachedNetworkObject.HasParentNetworkObject(transform)) + if (NetworkObject.HasParentNetworkObject(transform)) { InLocalSpace = true; } @@ -3890,7 +3890,7 @@ private void DefaultParentChanged() m_RotationInterpolator.Clear(); // Always use NetworkManager here as this can be invoked prior to spawning - var tempTime = new NetworkTime(NetworkManager.NetworkConfig.TickRate, m_CachedNetworkManager.ServerTime.Tick).Time; + var tempTime = new NetworkTime(NetworkManager.NetworkConfig.TickRate, NetworkManager.ServerTime.Tick).Time; UpdatePositionInterpolator(m_InternalCurrentPosition, tempTime, true); m_ScaleInterpolator.ResetTo(m_InternalCurrentScale, tempTime); m_RotationInterpolator.ResetTo(m_InternalCurrentRotation, tempTime); @@ -3922,7 +3922,7 @@ internal override void InternalOnNetworkObjectParentChanged(NetworkObject parent if (LastTickSync == m_LocalAuthoritativeNetworkState.GetNetworkTick()) { m_InternalCurrentPosition = m_LastStateTargetPosition = GetSpaceRelativePosition(); - m_PositionInterpolator.ResetTo(m_PositionInterpolator.Parent, m_InternalCurrentPosition, m_CachedNetworkManager.ServerTime.Time); + m_PositionInterpolator.ResetTo(m_PositionInterpolator.Parent, m_InternalCurrentPosition, NetworkManager.ServerTime.Time); if (InLocalSpace) { transform.localPosition = m_InternalCurrentPosition; @@ -3954,7 +3954,7 @@ internal override void InternalOnNetworkObjectParentChanged(NetworkObject parent { m_InternalCurrentRotation = GetSpaceRelativeRotation(); m_TargetRotation = m_InternalCurrentRotation.eulerAngles; - m_RotationInterpolator.ResetTo(m_RotationInterpolator.Parent, m_InternalCurrentRotation, m_CachedNetworkManager.ServerTime.Time); + m_RotationInterpolator.ResetTo(m_RotationInterpolator.Parent, m_InternalCurrentRotation, NetworkManager.ServerTime.Time); if (InLocalSpace) { transform.localRotation = m_InternalCurrentRotation; @@ -4638,13 +4638,13 @@ private void UpdateTransformState() { continue; } - m_CachedNetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, clientId); + NetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, clientId); } } else { // Clients (owner authoritative) send messages to the server-host - m_CachedNetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, NetworkManager.ServerClientId); + NetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, NetworkManager.ServerClientId); } m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OutboundMessage.BytesWritten; } @@ -4783,7 +4783,7 @@ public NetworkTransformTickRegistration(NetworkManager networkManager) internal void RegisterForTickSynchronization() { s_TickSynchPosition++; - m_NextTickSync = m_CachedNetworkManager.ServerTime.Tick + (s_TickSynchPosition % (int)NetworkManager.NetworkConfig.TickRate); + m_NextTickSync = NetworkManager.ServerTime.Tick + (s_TickSynchPosition % (int)NetworkManager.NetworkConfig.TickRate); } private static void RegisterNetworkManagerForTickUpdate(NetworkManager networkManager) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 4368c1e972..9910668f59 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -574,7 +574,7 @@ public NetworkObject NetworkObject { get { - if (m_NetworkObject) + if (m_NetworkObject != null) { return m_NetworkObject; } @@ -594,7 +594,7 @@ public NetworkObject NetworkObject // or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages // per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages. // We must check IsSpawned, otherwise a warning will be logged under certain valid conditions (see OnDestroy) - if (IsSpawned && !m_NetworkObject && (!m_NetworkManager || !m_NetworkManager.ShutdownInProgress)) + if (IsSpawned && m_NetworkObject == null && (m_NetworkManager == null || !m_NetworkManager.ShutdownInProgress)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { From 4b53df01fd1318c74b6c2c2917b47e23e41b2e99 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Fri, 9 Jan 2026 15:21:07 +0100 Subject: [PATCH 18/21] Revert not wanted changes + continue cleanup --- .../Runtime/Core/NetworkObject.cs | 238 +++++++++++------- 1 file changed, 142 insertions(+), 96 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 7f89ca3cdb..f2f24ce2a3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -52,7 +52,13 @@ public sealed class NetworkObject : MonoBehaviour /// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0 /// [HideInInspector] - public uint PrefabIdHash => GlobalObjectIdHash; + public uint PrefabIdHash + { + get + { + return GlobalObjectIdHash; + } + } /// /// InstantiationData sent during the instantiation process. @@ -351,7 +357,7 @@ internal bool HasParentNetworkObject(Transform transform) } /// - /// Gets the NetworkManager that owns this NetworkObject instance. + /// Gets the NetworkManager that owns this NetworkObject instance /// public NetworkManager NetworkManager => NetworkManagerOwner ? NetworkManagerOwner : NetworkManager.Singleton; @@ -595,15 +601,21 @@ internal void RemoveOwnershipExtended(OwnershipStatusExtended extended) /// true or false depending upon lock operation's success public bool SetOwnershipLock(bool lockOwnership = true) { + if (!IsSpawned) + { + Debug.LogError($"Trying to add or remove ownership lock on [{name}] which is not spawned!"); + return false; + } + // If we don't have authority exit early if (!HasAuthority) { - NetworkLog.LogWarningServer($"[Attempted Lock Without Authority] Client-{NetworkManager.LocalClientId} is trying to lock ownership but does not have authority!"); + NetworkLog.LogWarningServer($"[Attempted Lock Without Authority] Client-{NetworkManagerOwner.LocalClientId} is trying to lock ownership but does not have authority!"); return false; } // If we are not in distributed autority mode, then exit early - if (!NetworkManager.DistributedAuthorityMode) + if (!NetworkManagerOwner.DistributedAuthorityMode) { Debug.LogError($"[Feature Not Allowed In Client-Server Mode] Ownership flags are a distributed authority feature only!"); return false; @@ -756,15 +768,13 @@ public OwnershipRequestStatus RequestOwnership() // An ownership change request can only be made if spawned. if (!IsSpawned) { - if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"[{name}] is trying to perform an ownership request but the object is not spawned!"); + NetworkLog.LogWarning($"Trying to perform an ownership request but {name} is not spawned!"); } return OwnershipRequestStatus.Invalid; } - // Exit early the local client is already the owner if (OwnerClientId == NetworkManagerOwner.LocalClientId) { @@ -872,7 +882,6 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) // respond to any additional ownership request with OwnershipRequestResponseStatus.RequestInProgress. AddOwnershipExtended(OwnershipStatusExtended.Requested); - // if OwnershipRequestStatus cannot be called before spawn // This action is always authorized as long as the client still has authority. // We need to pass in that this is a request approval ownership change. NetworkManagerOwner.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, HasAuthority, true); @@ -1734,34 +1743,35 @@ public static void NetworkHide(List networkObjects, ulong clientI private void OnDestroy() { // TODO Can we destroy an object before it is spawned? + var networkManager = NetworkManager; // If no NetworkManager is assigned, then just exit early - if (!NetworkManager) + if (!networkManager) { return; } // An authorized destroy is when done by the authority instance or done due to a scene event and the NetworkObject // was marked as destroy pending scene event (which means the destroy with scene property was set). - var isAuthorityDestroy = HasAuthority || NetworkManager.DAHost || DestroyPendingSceneEvent; + var isAuthorityDestroy = HasAuthority || networkManager.DAHost || DestroyPendingSceneEvent; - if (NetworkManager.IsListening && !isAuthorityDestroy && IsSpawned && - (IsSceneObject == null || (IsSceneObject.Value != true))) + if (IsSpawned && !isAuthorityDestroy && networkManager.IsListening && + (IsSceneObject == null || IsSceneObject.Value != true)) { // If we destroyed a GameObject with a NetworkObject component on the non-authority side, handle cleaning up the SceneMigrationSynchronization. - NetworkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this); + networkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this); // Clients should not despawn NetworkObjects while connected to a session, but we don't want to destroy the current call stack // if this happens. Instead, we should just generate a network log error and exit early (as long as we are not shutting down). - if (!NetworkManager.ShutdownInProgress) + if (!networkManager.ShutdownInProgress) { // Since we still have a session connection, log locally and on the server to inform user of this issue. // If the NetworkObject's GameObject is not valid or the scene is no longer valid or loaded, then this was due to the // unloading of a scene which is done by the authority... if (gameObject != null && gameObject.scene.IsValid() && gameObject.scene.isLoaded) { - if (NetworkManager.LogLevel <= LogLevel.Error) + if (networkManager.LogLevel <= LogLevel.Error) { - if (NetworkManager.DistributedAuthorityMode) + if (networkManager.DistributedAuthorityMode) { NetworkLog.LogError($"[Invalid Destroy][{gameObject.name}][NetworkObjectId:{NetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-owner client is not valid during a distributed authority session. Call {nameof(Destroy)} or {nameof(Despawn)} on the client-owner instead."); } @@ -1782,13 +1792,13 @@ private void OnDestroy() } // Always attempt to remove from scene changed updates - NetworkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this); + networkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this); - if (NetworkManager.SpawnManager != null && NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) + if (networkManager.SpawnManager != null && networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) { if (this == networkObject) { - NetworkManager.SpawnManager.OnDespawnObject(networkObject, false); + networkManager.SpawnManager.OnDespawnObject(networkObject, false); } } } @@ -1800,16 +1810,17 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla { NetworkManagerOwner = NetworkManager.Singleton; } - if (!NetworkManager.IsListening) + + if (!NetworkManagerOwner.IsListening) { - throw new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before spawning objects"); + throw new NotListeningException($"{nameof(NetworkManagerOwner)} is not listening, start a server or host before spawning objects"); } - if ((!NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode) || (NetworkManager.DistributedAuthorityMode && !NetworkManager.LocalClient.IsSessionOwner && NetworkManager.LocalClientId != ownerClientId)) + if ((!NetworkManagerOwner.IsServer && !NetworkManagerOwner.DistributedAuthorityMode) || (NetworkManagerOwner.DistributedAuthorityMode && !NetworkManagerOwner.LocalClient.IsSessionOwner && NetworkManagerOwner.LocalClientId != ownerClientId)) { - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { - throw new NotServerException($"When distributed authority mode is enabled, you can only spawn NetworkObjects that belong to the local instance! Local instance id {NetworkManager.LocalClientId} is not the same as the assigned owner id: {ownerClientId}!"); + throw new NotServerException($"When distributed authority mode is enabled, you can only spawn NetworkObjects that belong to the local instance! Local instance id {NetworkManagerOwner.LocalClientId} is not the same as the assigned owner id: {ownerClientId}!"); } else { @@ -1817,19 +1828,19 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla } } - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { - if (NetworkManager.LocalClient == null || !NetworkManager.IsConnectedClient || !NetworkManager.ConnectionManager.LocalClient.IsApproved) + if (NetworkManagerOwner.LocalClient == null || !NetworkManagerOwner.IsConnectedClient || !NetworkManagerOwner.ConnectionManager.LocalClient.IsApproved) { Debug.LogError($"Cannot spawn {name} until the client is fully connected to the session!"); return; } - if (NetworkManager.NetworkConfig.EnableSceneManagement) + if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement) { - if (!NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(gameObject.scene.handle)) + if (!NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(gameObject.scene.handle)) { // Most likely this issue is due to an integration test - if (NetworkManager.LogLevel <= LogLevel.Developer) + if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) { NetworkLog.LogWarning($"Failed to find scene handle {gameObject.scene.handle} for {gameObject.name}!"); } @@ -1838,43 +1849,43 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla } else { - NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle]; + NetworkSceneHandle = NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle]; } } if (DontDestroyWithOwner && !IsOwnershipDistributable) { //Ownership |= OwnershipStatus.Distributable; // DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set - if (NetworkManager.LogLevel == LogLevel.Developer) + if (NetworkManagerOwner.LogLevel == LogLevel.Developer) { NetworkLog.LogWarning("DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set. For now, if the NetworkObject does not destroy with the owner it will set ownership to SessionOwner."); } } } - NetworkManager.SpawnManager.AuthorityLocalSpawn(this, NetworkManager.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene); + NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene); - if ((NetworkManager.DistributedAuthorityMode && NetworkManager.DAHost) || (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer)) + if ((NetworkManagerOwner.DistributedAuthorityMode && NetworkManagerOwner.DAHost) || (!NetworkManagerOwner.DistributedAuthorityMode && NetworkManagerOwner.IsServer)) { - for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++) + for (int i = 0; i < NetworkManagerOwner.ConnectedClientsList.Count; i++) { - if (NetworkManager.ConnectedClientsList[i].ClientId == NetworkManager.ServerClientId) + if (NetworkManagerOwner.ConnectedClientsList[i].ClientId == NetworkManager.ServerClientId) { continue; } - if (Observers.Contains(NetworkManager.ConnectedClientsList[i].ClientId)) + if (Observers.Contains(NetworkManagerOwner.ConnectedClientsList[i].ClientId)) { - NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ConnectedClientsList[i].ClientId, this); + NetworkManagerOwner.SpawnManager.SendSpawnCallForObject(NetworkManagerOwner.ConnectedClientsList[i].ClientId, this); } } } - else if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) + else if (NetworkManagerOwner.DistributedAuthorityMode && !NetworkManagerOwner.DAHost) { // If spawning with observers or if not spawning with observers but the observer count is greater than 1 (i.e. owner/authority creating), // then we want to send a spawn notification. if (SpawnWithObservers || !SpawnWithObservers && Observers.Count > 1) { - NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ServerClientId, this); + NetworkManagerOwner.SpawnManager.SendSpawnCallForObject(NetworkManagerOwner.ServerClientId, this); } } else @@ -1963,7 +1974,8 @@ public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ow /// Should the object be destroyed when the scene is changed public void Spawn(bool destroyWithScene = false) { - var clientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : NetworkManager.ServerClientId; + var networkManager = NetworkManager; + var clientId = networkManager.DistributedAuthorityMode ? networkManager.LocalClientId : NetworkManager.ServerClientId; SpawnInternal(destroyWithScene, clientId, false); } @@ -2009,7 +2021,7 @@ public void Despawn(bool destroy = true) { behavior.MarkVariablesDirty(false); } - NetworkManager.SpawnManager.DespawnObject(this, destroy); + NetworkManagerOwner.SpawnManager.DespawnObject(this, destroy); } internal void ResetOnDespawn() @@ -2027,7 +2039,12 @@ internal void ResetOnDespawn() /// public void RemoveOwnership() { - NetworkManager.SpawnManager.RemoveOwnership(this); + if (!IsSpawned) + { + NetworkLog.LogErrorServer("Trying to remove ownership on an object that is not spawned!"); + return; + } + NetworkManagerOwner.SpawnManager.RemoveOwnership(this); } /// @@ -2036,7 +2053,13 @@ public void RemoveOwnership() /// The new owner clientId public void ChangeOwnership(ulong newOwnerClientId) { - NetworkManager.SpawnManager.ChangeOwnership(this, newOwnerClientId, HasAuthority); + if (!IsSpawned) + { + NetworkLog.LogWarning("Trying to change ownership on an not spawned object!"); + return; + } + + NetworkManagerOwner.SpawnManager.ChangeOwnership(this, newOwnerClientId, HasAuthority); } /// @@ -2045,14 +2068,20 @@ public void ChangeOwnership(ulong newOwnerClientId) /// internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulong newOwnerClientId) { - var distributedAuthorityMode = NetworkManager.DistributedAuthorityMode; - var isServer = NetworkManager.IsServer; - var isPreviousOwner = originalOwnerClientId == NetworkManager.LocalClientId; - var isNewOwner = newOwnerClientId == NetworkManager.LocalClientId; + if (!IsSpawned) + { + NetworkLog.LogWarning("Trying to change ownership on an not spawned object!"); + return; + } + + var distributedAuthorityMode = NetworkManagerOwner.DistributedAuthorityMode; + var isServer = NetworkManagerOwner.IsServer; + var isPreviousOwner = originalOwnerClientId == NetworkManagerOwner.LocalClientId; + var isNewOwner = newOwnerClientId == NetworkManagerOwner.LocalClientId; if (distributedAuthorityMode || isPreviousOwner) { - NetworkManager.SpawnManager.UpdateOwnershipTable(this, originalOwnerClientId, true); + NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, originalOwnerClientId, true); } foreach (var childBehaviour in ChildNetworkBehaviours) @@ -2064,7 +2093,7 @@ internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulo } } - NetworkManager.SpawnManager.UpdateOwnershipTable(this, newOwnerClientId); + NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, newOwnerClientId); if (distributedAuthorityMode || isServer || isNewOwner) { @@ -2177,11 +2206,11 @@ internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays) /// /// The new parent for this NetworkObject transform will be the child of. /// If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before. - /// Whether or not re-parenting was successful. + /// Whether or not reparenting was successful. public bool TrySetParent(Transform parent, bool worldPositionStays = true) { // If we are removing ourself from a parent - if (!parent) + if (parent == null) { return TrySetParent((NetworkObject)null, worldPositionStays); } @@ -2241,13 +2270,7 @@ public bool TryRemoveParent(bool worldPositionStays = true) /// Whether or not re-parenting was successful. public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (!AutoObjectParentSync) - { - return false; - } - - var networkManager = NetworkManager; - if (!networkManager || !networkManager.IsListening) + if (!AutoObjectParentSync || !IsSpawned || !NetworkManagerOwner.IsListening) { return false; } @@ -2258,7 +2281,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) // If we don't have authority, and we are not shutting down, then don't allow any parenting. // If we are shutting down and don't have authority then allow it. - if (!isAuthority && !networkManager.ShutdownInProgress) + if (!isAuthority && !NetworkManagerOwner.ShutdownInProgress) { return false; } @@ -2268,9 +2291,9 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (parent && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) + if (parent && (IsSpawned ^ parent.IsSpawned) && !NetworkManagerOwner.ShutdownInProgress) { - if (NetworkManager.LogLevel <= LogLevel.Developer) + if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) { var nameOfNotSpawnedObject = IsSpawned ? $" the parent ({parent.name})" : $"the child ({name})"; NetworkLog.LogWarning($"Parenting failed because {nameOfNotSpawnedObject} is not spawned!"); @@ -2296,6 +2319,7 @@ internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays private void OnTransformParentChanged() { + // Shouldn't we do our if not spawned check here? if (!AutoObjectParentSync) { return; @@ -2306,9 +2330,10 @@ private void OnTransformParentChanged() return; } - if (!IsSpawned || !NetworkManager.IsListening) + var networkManager = NetworkManager; + if (!IsSpawned || !networkManager.IsListening) { - if (NetworkManager.ShutdownInProgress) + if (networkManager.ShutdownInProgress) { return; } @@ -2320,7 +2345,7 @@ private void OnTransformParentChanged() return; } transform.parent = m_CachedParent; - Debug.LogException(new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before re-parenting")); + Debug.LogException(new NotListeningException($"{nameof(networkManager)} is not listening, start a server or host before re-parenting")); return; } var isAuthority = false; @@ -2329,8 +2354,8 @@ private void OnTransformParentChanged() isAuthority = HasAuthority || AuthorityAppliedParenting || (AllowOwnerToParent && IsOwner); var distributedAuthority = NetworkManagerOwner.DistributedAuthorityMode; - // If we do not have authority and we are spawned - if (!isAuthority && IsSpawned) + // If we do not have authority + if (!isAuthority) { // If the cached parent has not already been set, and we are in distributed authority mode, then log an exception and exit early as a non-authority instance // is trying to set the parent. @@ -2347,6 +2372,8 @@ private void OnTransformParentChanged() return; } + + //Shouldn't this be done first? if the object is not, spawned we enter the first if (!IsSpawned || !NetworkManager.IsListening) if (!IsSpawned) { AuthorityAppliedParenting = false; @@ -2368,7 +2395,7 @@ private void OnTransformParentChanged() var parentTransform = transform.parent; if (parentTransform != null) { - if (!transform.parent.TryGetComponent(out NetworkObject parentObject)) + if (!parentTransform.TryGetComponent(out NetworkObject parentObject)) { transform.parent = m_CachedParent; AuthorityAppliedParenting = false; @@ -2398,7 +2425,7 @@ private void OnTransformParentChanged() var message = new ParentSyncMessage { NetworkObjectId = NetworkObjectId, - IsLatestParentSet = m_LatestParent is not null && m_LatestParent.HasValue, + IsLatestParentSet = m_LatestParent != null && m_LatestParent.HasValue, LatestParent = m_LatestParent, RemoveParent = removeParent, AuthorityApplied = authorityApplied, @@ -2417,7 +2444,7 @@ private void OnTransformParentChanged() } // If we're not the server, we should tell the server about this parent change - if (!NetworkManager.IsServer) + if (!NetworkManagerOwner.IsServer) { // Don't send a message in DA mode if we're the only observers of this object (we're the only authority). if (distributedAuthority && Observers.Count <= 1) @@ -2425,12 +2452,12 @@ private void OnTransformParentChanged() return; } - NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); + NetworkManagerOwner.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); return; } // Otherwise we are a Server (client-server or DAHost). Send to all observers - foreach (var clientId in NetworkManager.ConnectionManager.ConnectedClientIds) + foreach (var clientId in NetworkManagerOwner.ConnectionManager.ConnectedClientIds) { if (clientId == NetworkManager.ServerClientId) { @@ -2438,7 +2465,7 @@ private void OnTransformParentChanged() } if (Observers.Contains(clientId)) { - NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); + NetworkManagerOwner.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); } } } @@ -2475,7 +2502,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // has been set, this will not be entered into again (i.e. the later code will be invoked and // users will get notifications when the parent changes). var isInScenePlaced = IsSceneObject.HasValue && IsSceneObject.Value; - if (transform.parent && !removeParent && !m_LatestParent.HasValue && isInScenePlaced) + if (transform.parent != null && !removeParent && !m_LatestParent.HasValue && isInScenePlaced) { var parentNetworkObject = transform.parent.GetComponent(); @@ -2483,7 +2510,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // attached. Under this case, we preserve the hierarchy, but we don't keep track of the parenting. // Note: We only start tracking parenting if the user removes the child from the standard GameObject // parent and then re-parents the child under a GameObject with a NetworkObject component attached. - if (!parentNetworkObject) + if (parentNetworkObject == null) { // If we are parented under a GameObject, go ahead and mark the world position stays as false // so clients synchronize their transform in local space. (only for in-scene placed NetworkObjects) @@ -2499,7 +2526,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa else { // If we made it this far, go ahead and set the network parenting values - // with the WorldPositionSays value set to false. + // with the WorldPoisitonSays value set to false // Note: Since in-scene placed NetworkObjects are parented in the scene // the default "assumption" is that children are parenting local space // relative. @@ -2512,7 +2539,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa } } - // If we are removing the parent or our latest parent is not set, then remove the parent. + // If we are removing the parent or our latest parent is not set, then remove the parent // removeParent is only set when: // - The server-side NetworkObject.OnTransformParentChanged is invoked and the parent is being removed // - The client-side when handling a ParentSyncMessage @@ -2533,16 +2560,17 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa return true; } + var networkManager = NetworkManager; // If we have a latest parent id but it hasn't been spawned yet, then add this instance to the orphanChildren // HashSet and return false (i.e. parenting not applied yet) - if (m_LatestParent.HasValue && !NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value)) + if (m_LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value)) { OrphanChildren.Add(this); return false; } // If we made it here, then parent this instance under the parentObject - var parentObject = NetworkManager.SpawnManager.SpawnedObjects[m_LatestParent.Value]; + var parentObject = networkManager.SpawnManager.SpawnedObjects[m_LatestParent.Value]; // If we are handling an orphaned child and its parent is orphaned too, then don't parent yet. if (orphanedChildPass) @@ -2588,7 +2616,12 @@ internal void InvokeBehaviourNetworkPreSpawn() internal void InvokeBehaviourNetworkSpawn() { - NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); + if (!IsSpawned) + { + Debug.LogError("Not spawned!"); + return; + } + NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); // Always invoke all InternalOnNetworkSpawn methods on each child NetworkBehaviour // ** before ** invoking OnNetworkSpawn. @@ -2653,14 +2686,19 @@ internal void InternalInSceneNetworkObjectsSpawned() internal void InvokeBehaviourNetworkDespawn() { + if (!IsSpawned) + { + Debug.LogError("Cannot despawn if is not spawned!"); + return; + } // Invoke OnNetworkPreDespawn on all child behaviours for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { ChildNetworkBehaviours[i].InternalOnNetworkPreDespawn(); } - NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); - NetworkManager.SpawnManager.RemoveNetworkObjectFromSceneChangedUpdates(this); + NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); + NetworkManagerOwner.SpawnManager.RemoveNetworkObjectFromSceneChangedUpdates(this); for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { @@ -2742,6 +2780,11 @@ internal List ChildNetworkBehaviours /// the previous owner prior to beginning the change in ownership change. internal void SynchronizeOwnerNetworkVariables(ulong originalOwnerId, ulong originalPreviousOwnerId) { + if (!IsSpawned) + { + Debug.LogError("Cannot synchronize network variables if not spawned!"); + return; + } var currentOwnerId = OwnerClientId; OwnerClientId = originalOwnerId; PreviousOwnerId = originalPreviousOwnerId; @@ -2760,7 +2803,7 @@ internal void SynchronizeOwnerNetworkVariables(ulong originalOwnerId, ulong orig // Force send a state update for all owner read NetworkVariables and any currently dirty // owner write NetworkVariables. - NetworkManager.BehaviourUpdater.NetworkBehaviourUpdate(true); + NetworkManagerOwner.BehaviourUpdater.NetworkBehaviourUpdate(true); } // NGO currently guarantees that the client will receive spawn data for all objects in one network tick. @@ -2976,6 +3019,7 @@ public struct TransformData : INetworkSerializeByMemcpy public void Serialize(FastBufferWriter writer) { + // CHECK should we check if OwnerObject.IsSpawned here? if (OwnerObject.NetworkManager.DistributedAuthorityMode) { HasOwnershipFlags = true; @@ -3191,9 +3235,10 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager.ServerClientId, bool syncObservers = false) { + // CHECK can we use NetworkManagerOwner here? if we do, also replace here:CheckForGlobalObjectIdHashOverride var obj = new SceneObject { - HasParent = m_CachedParent is not null, + HasParent = transform.parent != null, WorldPositionStays = m_CachedWorldPositionStays, NetworkObjectId = NetworkObjectId, OwnerClientId = OwnerClientId, @@ -3438,9 +3483,9 @@ internal void SubscribeToActiveSceneForSynch() /// private void CurrentlyActiveSceneChanged(Scene current, Scene next) { - // Early exit if the NetworkManager is shutting down, the NetworkObject - // is not spawned, or an in-scene placed NetworkObject - if (!IsSpawned || IsSceneObject != false || NetworkManager.ShutdownInProgress) + // Early exit if the NetworkObject is not spawned, is an in-scene placed NetworkObject, + // or the NetworkManager is shutting down. + if (!IsSpawned || IsSceneObject != false || NetworkManagerOwner.ShutdownInProgress) { return; } @@ -3463,13 +3508,14 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) /// internal void SceneChangedUpdate(Scene scene, bool notify = false) { + //CHECK // Avoiding edge case scenarios, if no NetworkSceneManager exit early - if (NetworkManager.SceneManager == null || !IsSpawned) + if (NetworkManagerOwner.SceneManager == null || !IsSpawned) { return; } - if (NetworkManager.SceneManager.IsSceneEventInProgress()) + if (NetworkManagerOwner.SceneManager.IsSceneEventInProgress()) { return; } @@ -3478,17 +3524,17 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) SceneOriginHandle = scene.handle; // non-authority needs to update the NetworkSceneHandle - if (!isAuthority && NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) + if (!isAuthority && NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) { - NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; + NetworkSceneHandle = NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; } else if (isAuthority) { // Since the authority is the source of truth for the NetworkSceneHandle, // the NetworkSceneHandle is the same as the SceneOriginHandle. - if (NetworkManager.DistributedAuthorityMode && NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) + if (NetworkManagerOwner.DistributedAuthorityMode && NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) { - NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; + NetworkSceneHandle = NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; } else { @@ -3496,13 +3542,13 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) } } else // Otherwise, the client did not find the client to server scene handle - if (NetworkManager.LogLevel == LogLevel.Developer) + if (NetworkManagerOwner.LogLevel == LogLevel.Developer) { // There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject // into, but that scenario seemed very edge case and under most instances a user should be notified that this // server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to // the server-side too. - NetworkLog.LogWarningServer($"[Client-{NetworkManager.LocalClientId}][{gameObject.name}] Server - " + + NetworkLog.LogWarningServer($"[Client-{NetworkManagerOwner.LocalClientId}][{gameObject.name}] Server - " + $"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" + $"has no associated server side (network) scene handle!"); } @@ -3511,7 +3557,7 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) // Only the authority side will notify clients of non-parented NetworkObject scene changes if (isAuthority && notify && !m_CachedParent) { - NetworkManager.SceneManager.NotifyNetworkObjectSceneChanged(this); + NetworkManagerOwner.SceneManager.NotifyNetworkObjectSceneChanged(this); } } @@ -3614,7 +3660,7 @@ internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour) { if (networkBehaviour.IsSpawned && IsSpawned) { - if (NetworkManager?.LogLevel == LogLevel.Developer) + if (NetworkManagerOwner?.LogLevel == LogLevel.Developer) { NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)"); } From e903ba8062081b2f0e2a3dc7e466cd7bdba2c982 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Fri, 9 Jan 2026 17:05:21 +0100 Subject: [PATCH 19/21] Small fix --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index f2f24ce2a3..6e1fe09a36 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1885,7 +1885,7 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla // then we want to send a spawn notification. if (SpawnWithObservers || !SpawnWithObservers && Observers.Count > 1) { - NetworkManagerOwner.SpawnManager.SendSpawnCallForObject(NetworkManagerOwner.ServerClientId, this); + NetworkManagerOwner.SpawnManager.SendSpawnCallForObject(NetworkManager.ServerClientId, this); } } else From aab3f4dbce97f0fd61dc5158a507bec71bdb974d Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Fri, 9 Jan 2026 19:42:44 +0100 Subject: [PATCH 20/21] Check cached usage and cleanup --- .../Runtime/Core/NetworkObject.cs | 89 +++++++++---------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 6e1fe09a36..408bd24eaa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -340,9 +340,10 @@ private void CheckForInScenePlaced() internal bool HasParentNetworkObject(Transform transform) { - if (m_CachedParent != null) + var parent = transform.parent; + if (parent != null) { - var networkObject = m_CachedParent.GetComponent(); + var networkObject = parent.GetComponent(); if (networkObject != null && networkObject != this) { return true; @@ -350,7 +351,7 @@ internal bool HasParentNetworkObject(Transform transform) if (m_CachedParent.parent != null) { - return HasParentNetworkObject(m_CachedParent); + return HasParentNetworkObject(parent); } } return false; @@ -400,7 +401,7 @@ public void DeferDespawn(int tickOffset, bool destroy = true) { if (!IsSpawned) { - NetworkLog.LogError($"Cannot defer despawning {name} because it is not spawned!"); + NetworkLog.LogError($"Cannot defer despawn because [{name}] is not spawned!"); return; } @@ -770,7 +771,7 @@ public OwnershipRequestStatus RequestOwnership() { if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"Trying to perform an ownership request but {name} is not spawned!"); + NetworkLog.LogWarning($"Trying to perform an ownership request but [{name}] is not spawned!"); } return OwnershipRequestStatus.Invalid; @@ -1137,6 +1138,10 @@ private bool InternalHasAuthority() { if (!IsSpawned) { + if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"Trying to check authority but {name} is not spawned yet!"); + } return false; } return NetworkManagerOwner.DistributedAuthorityMode ? OwnerClientId == NetworkManagerOwner.LocalClientId : NetworkManagerOwner.IsServer; @@ -1200,7 +1205,7 @@ private bool InternalHasAuthority() /// /// Gets whether or not the object is owned by anyone /// - public bool IsOwnedByServer => IsSpawned && OwnerClientId == NetworkManager.ServerClientId; + public bool IsOwnedByServer => OwnerClientId == NetworkManager.ServerClientId; /// /// Gets if the object has yet been spawned across the network @@ -1543,9 +1548,9 @@ public static void NetworkShow(List networkObjects, ulong clientI // Distributed authority mode adjustments to log a network error and continue when trying to show a NetworkObject // that the local instance does not own - if (!networkObjects[i].HasAuthority) + if (!networkObject.HasAuthority) { - if (networkObjects[i].NetworkManagerOwner.DistributedAuthorityMode) + if (networkManager.DistributedAuthorityMode) { // It will log locally and to the "master-host". NetworkLog.LogErrorServer("Only the owner-authority can change visibility when distributed authority mode is enabled!"); @@ -1558,14 +1563,14 @@ public static void NetworkShow(List networkObjects, ulong clientI } - if (networkObjects[i].Observers.Contains(clientId)) + if (networkObject.Observers.Contains(clientId)) { - throw new VisibilityChangeException($"{nameof(NetworkObject)} with NetworkId: {networkObjects[i].NetworkObjectId} is already visible"); + throw new VisibilityChangeException($"{nameof(NetworkObject)} with NetworkId: {networkObject.NetworkObjectId} is already visible"); } - if (networkObjects[i].NetworkManagerOwner != networkManager) + if (networkObject.NetworkManagerOwner != networkManager) { - throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManagerOwner)); + throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(networkManager)); } } @@ -1703,9 +1708,9 @@ public static void NetworkHide(List networkObjects, ulong clientI // Distributed authority mode adjustments to log a network error and continue when trying to show a NetworkObject // that the local instance does not own - if (!networkObjects[i].HasAuthority) + if (!networkObject.HasAuthority) { - if (networkObjects[i].NetworkManagerOwner.DistributedAuthorityMode) + if (networkObject.NetworkManagerOwner.DistributedAuthorityMode) { // It will log locally and to the "master-host". NetworkLog.LogErrorServer($"Only the owner-authority can change hide a {nameof(NetworkObject)} when distributed authority mode is enabled!"); @@ -1718,19 +1723,19 @@ public static void NetworkHide(List networkObjects, ulong clientI } // CLIENT SPAWNING TODO: Log error and continue as opposed to throwing an exception - if (!networkObjects[i].IsSpawned) + if (!networkObject.IsSpawned) { throw new SpawnStateException("Object is not spawned"); } - if (!networkObjects[i].Observers.Contains(clientId)) + if (!networkObject.Observers.Contains(clientId)) { - throw new VisibilityChangeException($"{nameof(NetworkObject)} with {nameof(NetworkObjectId)}: {networkObjects[i].NetworkObjectId} is already hidden"); + throw new VisibilityChangeException($"{nameof(NetworkObject)} with {nameof(NetworkObjectId)}: {networkObject.NetworkObjectId} is already hidden"); } - if (networkObjects[i].NetworkManagerOwner != networkManager) + if (networkObject.NetworkManagerOwner != networkManager) { - throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManagerOwner)); + throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(networkManager)); } } @@ -1742,7 +1747,6 @@ public static void NetworkHide(List networkObjects, ulong clientI private void OnDestroy() { - // TODO Can we destroy an object before it is spawned? var networkManager = NetworkManager; // If no NetworkManager is assigned, then just exit early if (!networkManager) @@ -2013,7 +2017,7 @@ public void Despawn(bool destroy = true) { if (!IsSpawned) { - NetworkLog.LogErrorServer("Object is not spawned!"); + NetworkLog.LogErrorServer($"Cannot despawn [{name}] as it is not spawned!"); return; } @@ -2041,7 +2045,7 @@ public void RemoveOwnership() { if (!IsSpawned) { - NetworkLog.LogErrorServer("Trying to remove ownership on an object that is not spawned!"); + NetworkLog.LogErrorServer($"Trying to remove the ownership of [{name}] but it is not spawned!"); return; } NetworkManagerOwner.SpawnManager.RemoveOwnership(this); @@ -2055,7 +2059,7 @@ public void ChangeOwnership(ulong newOwnerClientId) { if (!IsSpawned) { - NetworkLog.LogWarning("Trying to change ownership on an not spawned object!"); + NetworkLog.LogWarning($"Trying to change ownership on [{name}] but it is not spawned!"); return; } @@ -2070,7 +2074,7 @@ internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulo { if (!IsSpawned) { - NetworkLog.LogWarning("Trying to change ownership on an not spawned object!"); + NetworkLog.LogWarning($"Trying to change ownership on [{name}] but it is not spawned!"); return; } @@ -2267,7 +2271,7 @@ public bool TryRemoveParent(bool worldPositionStays = true) /// /// The new parent for this NetworkObject transform will be the child of. /// If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before. - /// Whether or not re-parenting was successful. + /// Whether or not reparenting was successful. public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) { if (!AutoObjectParentSync || !IsSpawned || !NetworkManagerOwner.IsListening) @@ -2279,7 +2283,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) // It wouldn't make sense to not allow parenting, but keeping this note here as a reminder. var isAuthority = HasAuthority || (AllowOwnerToParent && IsOwner); - // If we don't have authority, and we are not shutting down, then don't allow any parenting. + // If we don't have authority and we are not shutting down, then don't allow any parenting. // If we are shutting down and don't have authority then allow it. if (!isAuthority && !NetworkManagerOwner.ShutdownInProgress) { @@ -2291,9 +2295,9 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (parent && (IsSpawned ^ parent.IsSpawned) && !NetworkManagerOwner.ShutdownInProgress) + if (parent && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) { - if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) + if (NetworkManager.LogLevel <= LogLevel.Developer) { var nameOfNotSpawnedObject = IsSpawned ? $" the parent ({parent.name})" : $"the child ({name})"; NetworkLog.LogWarning($"Parenting failed because {nameOfNotSpawnedObject} is not spawned!"); @@ -2303,7 +2307,7 @@ internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays m_CachedWorldPositionStays = worldPositionStays; - if (!parent) + if (parent == null) { CurrentParent = null; transform.SetParent(null, worldPositionStays); @@ -2319,7 +2323,6 @@ internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays private void OnTransformParentChanged() { - // Shouldn't we do our if not spawned check here? if (!AutoObjectParentSync) { return; @@ -2345,7 +2348,7 @@ private void OnTransformParentChanged() return; } transform.parent = m_CachedParent; - Debug.LogException(new NotListeningException($"{nameof(networkManager)} is not listening, start a server or host before re-parenting")); + Debug.LogException(new NotListeningException($"{nameof(networkManager)} is not listening, start a server or host before reparenting")); return; } var isAuthority = false; @@ -2362,18 +2365,17 @@ private void OnTransformParentChanged() if (distributedAuthority) { transform.parent = m_CachedParent; - NetworkLog.LogError($"[Not Owner] Only the owner-authority of child {gameObject.name}'s {nameof(NetworkObject)} component can re-parent it!"); + NetworkLog.LogError($"[Not Owner] Only the owner-authority of child {gameObject.name}'s {nameof(NetworkObject)} component can reparent it!"); } else { transform.parent = m_CachedParent; - Debug.LogException(new NotServerException($"Only the server can re-parent {nameof(NetworkObject)}s")); + Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s")); } return; } - //Shouldn't this be done first? if the object is not, spawned we enter the first if (!IsSpawned || !NetworkManager.IsListening) if (!IsSpawned) { AuthorityAppliedParenting = false; @@ -2387,7 +2389,7 @@ private void OnTransformParentChanged() else { transform.parent = m_CachedParent; - Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be re-parented after being spawned")); + Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented after being spawned")); } return; } @@ -2406,7 +2408,7 @@ private void OnTransformParentChanged() { transform.parent = m_CachedParent; AuthorityAppliedParenting = false; - Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be re-parented under another spawned {nameof(NetworkObject)}")); + Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented under another spawned {nameof(NetworkObject)}")); return; } @@ -2507,7 +2509,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa var parentNetworkObject = transform.parent.GetComponent(); // If parentNetworkObject is null then the parent is a GameObject without a NetworkObject component - // attached. Under this case, we preserve the hierarchy, but we don't keep track of the parenting. + // attached. Under this case, we preserve the hierarchy but we don't keep track of the parenting. // Note: We only start tracking parenting if the user removes the child from the standard GameObject // parent and then re-parents the child under a GameObject with a NetworkObject component attached. if (parentNetworkObject == null) @@ -2618,7 +2620,7 @@ internal void InvokeBehaviourNetworkSpawn() { if (!IsSpawned) { - Debug.LogError("Not spawned!"); + NetworkLog.LogWarning($"Trying to invoke network spawn behavior on {name} but is not spawned!"); return; } NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); @@ -2688,7 +2690,7 @@ internal void InvokeBehaviourNetworkDespawn() { if (!IsSpawned) { - Debug.LogError("Cannot despawn if is not spawned!"); + NetworkLog.LogWarning($"Trying to invoke network despawn behavior on {name} but is not spawned!"); return; } // Invoke OnNetworkPreDespawn on all child behaviours @@ -2782,7 +2784,7 @@ internal void SynchronizeOwnerNetworkVariables(ulong originalOwnerId, ulong orig { if (!IsSpawned) { - Debug.LogError("Cannot synchronize network variables if not spawned!"); + NetworkLog.LogWarning($"Trying to synchronize network variables on {name} but is not spawned!"); return; } var currentOwnerId = OwnerClientId; @@ -3019,7 +3021,6 @@ public struct TransformData : INetworkSerializeByMemcpy public void Serialize(FastBufferWriter writer) { - // CHECK should we check if OwnerObject.IsSpawned here? if (OwnerObject.NetworkManager.DistributedAuthorityMode) { HasOwnershipFlags = true; @@ -3235,7 +3236,6 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager.ServerClientId, bool syncObservers = false) { - // CHECK can we use NetworkManagerOwner here? if we do, also replace here:CheckForGlobalObjectIdHashOverride var obj = new SceneObject { HasParent = transform.parent != null, @@ -3508,9 +3508,8 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) /// internal void SceneChangedUpdate(Scene scene, bool notify = false) { - //CHECK - // Avoiding edge case scenarios, if no NetworkSceneManager exit early - if (NetworkManagerOwner.SceneManager == null || !IsSpawned) + // Avoiding edge case scenarios, if not spawned or no NetworkSceneManager exit early + if (!IsSpawned || NetworkManagerOwner.SceneManager == null) { return; } From cf103901a3855973bd666fb4688ecab7c0f09bb0 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Fri, 9 Jan 2026 22:46:22 +0100 Subject: [PATCH 21/21] Address feedback --- .../Runtime/Core/NetworkObject.cs | 98 +++++++++++-------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 408bd24eaa..2779d4cf73 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -401,7 +401,11 @@ public void DeferDespawn(int tickOffset, bool destroy = true) { if (!IsSpawned) { - NetworkLog.LogError($"Cannot defer despawn because [{name}] is not spawned!"); + if (NetworkManager.LogLevel == LogLevel.Error) + { + NetworkLog.LogErrorServer($"[Attempted deferred despawn while un-spawned]"); + } + return; } @@ -604,7 +608,11 @@ public bool SetOwnershipLock(bool lockOwnership = true) { if (!IsSpawned) { - Debug.LogError($"Trying to add or remove ownership lock on [{name}] which is not spawned!"); + if (NetworkManager.LogLevel == LogLevel.Error) + { + NetworkLog.LogErrorServer("[Attempted Lock While un-spawned]"); + } + return false; } @@ -743,11 +751,6 @@ public enum OwnershipRequestStatus /// This object is marked as SessionOwnerOnly and therefore cannot be requested /// SessionOwnerOnly, - - /// - /// The request is invalid (if not spawned for instance) - /// - Invalid, } /// @@ -771,10 +774,10 @@ public OwnershipRequestStatus RequestOwnership() { if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"Trying to perform an ownership request but [{name}] is not spawned!"); + NetworkLog.LogErrorServer("[Attempted ownership request while un-spawned]"); } - return OwnershipRequestStatus.Invalid; + return OwnershipRequestStatus.Locked; } // Exit early the local client is already the owner if (OwnerClientId == NetworkManagerOwner.LocalClientId) @@ -1138,9 +1141,9 @@ private bool InternalHasAuthority() { if (!IsSpawned) { - if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) + if (NetworkManager.LogLevel == LogLevel.Error) { - NetworkLog.LogWarning($"Trying to check authority but {name} is not spawned yet!"); + NetworkLog.LogErrorServer("[Attempted authority check while un-spawned]"); } return false; } @@ -1528,14 +1531,15 @@ public static void NetworkShow(List networkObjects, ulong clientI // Do the safety loop first to prevent putting the netcode in an invalid state. for (int i = 0; i < networkObjects.Count; i++) { - if (!networkObjects[i].IsSpawned) + var networkObject = networkObjects[i]; + var networkManager = networkObject.NetworkManagerOwner; + + if (!networkObject.IsSpawned) { + // CHECK replace this by NetworkLog.LogWarning($"Trying to show object but is not spawned!"); continue; (change all in another PR) throw new SpawnStateException("Object is not spawned"); } - var networkObject = networkObjects[i]; - var networkManager = networkObject.NetworkManagerOwner; - if (networkManager.DistributedAuthorityMode && clientId == networkObject.OwnerClientId) { NetworkLog.LogErrorServer($"Cannot hide an object from the owner when distributed authority mode is enabled! (Skipping {networkObject.gameObject.name})"); @@ -1722,6 +1726,7 @@ public static void NetworkHide(List networkObjects, ulong clientI } } + // CHECK, should I address this comment and replace all exceptions with log warnings/errors? (yes, in another PR, same as the comment below) // CLIENT SPAWNING TODO: Log error and continue as opposed to throwing an exception if (!networkObject.IsSpawned) { @@ -2017,7 +2022,11 @@ public void Despawn(bool destroy = true) { if (!IsSpawned) { - NetworkLog.LogErrorServer($"Cannot despawn [{name}] as it is not spawned!"); + if (NetworkManager.LogLevel == LogLevel.Error) + { + NetworkLog.LogErrorServer($"[Attempted despawn while un-spawned]"); + } + return; } @@ -2045,7 +2054,10 @@ public void RemoveOwnership() { if (!IsSpawned) { - NetworkLog.LogErrorServer($"Trying to remove the ownership of [{name}] but it is not spawned!"); + if (NetworkManager.LogLevel == LogLevel.Error) + { + NetworkLog.LogErrorServer("[Attempted ownership removal while un-spawned]"); + } return; } NetworkManagerOwner.SpawnManager.RemoveOwnership(this); @@ -2059,7 +2071,11 @@ public void ChangeOwnership(ulong newOwnerClientId) { if (!IsSpawned) { - NetworkLog.LogWarning($"Trying to change ownership on [{name}] but it is not spawned!"); + if (NetworkManager.LogLevel == LogLevel.Error) + { + NetworkLog.LogErrorServer("[Attempted ownership change while un-spawned]"); + } + return; } @@ -2074,7 +2090,11 @@ internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulo { if (!IsSpawned) { - NetworkLog.LogWarning($"Trying to change ownership on [{name}] but it is not spawned!"); + if (NetworkManager.LogLevel == LogLevel.Error) + { + NetworkLog.LogErrorServer($"[Attempted behavior invoke on ownership changed while un-spawned]"); + } + return; } @@ -2210,7 +2230,7 @@ internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays) /// /// The new parent for this NetworkObject transform will be the child of. /// If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before. - /// Whether or not reparenting was successful. + /// Whether or not re-parenting was successful. public bool TrySetParent(Transform parent, bool worldPositionStays = true) { // If we are removing ourself from a parent @@ -2375,7 +2395,6 @@ private void OnTransformParentChanged() return; } - if (!IsSpawned) { AuthorityAppliedParenting = false; @@ -2618,11 +2637,6 @@ internal void InvokeBehaviourNetworkPreSpawn() internal void InvokeBehaviourNetworkSpawn() { - if (!IsSpawned) - { - NetworkLog.LogWarning($"Trying to invoke network spawn behavior on {name} but is not spawned!"); - return; - } NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); // Always invoke all InternalOnNetworkSpawn methods on each child NetworkBehaviour @@ -2690,7 +2704,11 @@ internal void InvokeBehaviourNetworkDespawn() { if (!IsSpawned) { - NetworkLog.LogWarning($"Trying to invoke network despawn behavior on {name} but is not spawned!"); + if (NetworkManager.LogLevel == LogLevel.Error) + { + NetworkLog.LogErrorServer("[Attempted network despawn behavior invoke while un-spawned]"); + } + return; } // Invoke OnNetworkPreDespawn on all child behaviours @@ -2782,11 +2800,6 @@ internal List ChildNetworkBehaviours /// the previous owner prior to beginning the change in ownership change. internal void SynchronizeOwnerNetworkVariables(ulong originalOwnerId, ulong originalPreviousOwnerId) { - if (!IsSpawned) - { - NetworkLog.LogWarning($"Trying to synchronize network variables on {name} but is not spawned!"); - return; - } var currentOwnerId = OwnerClientId; OwnerClientId = originalOwnerId; PreviousOwnerId = originalPreviousOwnerId; @@ -3021,7 +3034,7 @@ public struct TransformData : INetworkSerializeByMemcpy public void Serialize(FastBufferWriter writer) { - if (OwnerObject.NetworkManager.DistributedAuthorityMode) + if (OwnerObject.NetworkManagerOwner.DistributedAuthorityMode) { HasOwnershipFlags = true; SpawnWithObservers = OwnerObject.SpawnWithObservers; @@ -3070,7 +3083,7 @@ public void Serialize(FastBufferWriter writer) // The NetworkSceneHandle is the server-side relative // scene handle that the NetworkObject resides in. - if (OwnerObject.NetworkManager.DistributedAuthorityMode) + if (OwnerObject.NetworkManagerOwner.DistributedAuthorityMode) { writer.WriteValue(OwnerObject.NetworkSceneHandle); } @@ -3246,7 +3259,7 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager IsSceneObject = IsSceneObject ?? true, DestroyWithScene = DestroyWithScene, DontDestroyWithOwner = DontDestroyWithOwner, - HasOwnershipFlags = NetworkManager.DistributedAuthorityMode, + HasOwnershipFlags = NetworkManagerOwner.DistributedAuthorityMode, OwnershipFlags = (ushort)Ownership, SyncObservers = syncObservers, Observers = syncObservers ? Observers.ToArray() : null, @@ -3554,7 +3567,7 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) OnMigratedToNewScene?.Invoke(); // Only the authority side will notify clients of non-parented NetworkObject scene changes - if (isAuthority && notify && !m_CachedParent) + if (isAuthority && notify && !transform.parent) { NetworkManagerOwner.SceneManager.NotifyNetworkObjectSceneChanged(this); } @@ -3612,17 +3625,18 @@ internal bool UpdateForSceneChanges() /// appropriate hash value internal uint CheckForGlobalObjectIdHashOverride() { - if (NetworkManager.IsServer || NetworkManager.DistributedAuthorityMode) + var networkManager = NetworkManager; + if (networkManager.IsServer || networkManager.DistributedAuthorityMode) { - if (NetworkManager.PrefabHandler.ContainsHandler(this)) + if (networkManager.PrefabHandler.ContainsHandler(this)) { - var globalObjectIdHash = NetworkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash); + var globalObjectIdHash = networkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash); return globalObjectIdHash == 0 ? GlobalObjectIdHash : globalObjectIdHash; } // If scene management is disabled and this is an in-scene placed NetworkObject then go ahead // and send the InScenePlacedSourcePrefab's GlobalObjectIdHash value (i.e. what to dynamically spawn) - if (!NetworkManager.NetworkConfig.EnableSceneManagement && IsSceneObject.Value && InScenePlacedSourceGlobalObjectIdHash != 0) + if (!networkManager.NetworkConfig.EnableSceneManagement && IsSceneObject.Value && InScenePlacedSourceGlobalObjectIdHash != 0) { return InScenePlacedSourceGlobalObjectIdHash; } @@ -3640,9 +3654,9 @@ internal uint CheckForGlobalObjectIdHashOverride() else { // For legacy manual instantiation and spawning, check the OverrideToNetworkPrefab for a possible match - if (NetworkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab.ContainsKey(GlobalObjectIdHash)) + if (networkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab.ContainsKey(GlobalObjectIdHash)) { - return NetworkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab[GlobalObjectIdHash]; + return networkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab[GlobalObjectIdHash]; } } }