Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ Additional documentation and release notes are available at [Multiplayer Documen


### Removed

- Removed un-needed exceptions on `NetworkObject.cs`. (#3867)

### Fixed

- Fixed `NetworkShow` behavior when it is called twice. (#3867)

### Security


### Obsolete

- `NotListeningException` is now marked as obsolete as it is not used internally anymore. (#3867)

## [2.10.0] - 2026-03-01

Expand Down
112 changes: 83 additions & 29 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1458,7 +1458,10 @@ internal NetworkSceneHandle GetSceneOriginHandle()
{
if (SceneOriginHandle.IsEmpty() && IsSpawned && IsSceneObject != false)
{
throw new Exception($"{nameof(GetSceneOriginHandle)} called when {nameof(SceneOriginHandle)} is still zero but the {nameof(NetworkObject)} is already spawned!");
if (NetworkManager.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"{nameof(GetSceneOriginHandle)} called when {nameof(SceneOriginHandle)} is still zero but the {nameof(NetworkObject)} is already spawned!");
}
}
return !SceneOriginHandle.IsEmpty() ? SceneOriginHandle : gameObject.scene.handle;
}
Expand All @@ -1481,32 +1484,40 @@ public void NetworkShow(ulong clientId)
{
if (!IsSpawned)
{
throw new SpawnStateException("Object is not spawned");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{name}] Attempted NetworkShow while {nameof(NetworkObject)} not spawned.");
}
return;
}

if (!HasAuthority)
if (!HasAuthority && !NetworkManagerOwner.DAHost)
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
throw new NotServerException($"Only the owner-authority can change visibility when distributed authority mode is enabled!");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] Only the owner-authority can change visibility when distributed authority mode is enabled!");
}
return;
}
else
{
throw new NotServerException("Only the authority can change visibility");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] Only the authority can change visibility!");
}
return;
}
}

if (Observers.Contains(clientId))
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
Debug.LogError($"The object {name} is already visible to Client-{clientId}!");
return;
}
else
if (NetworkManagerOwner.LogLevel <= LogLevel.Developer)
{
throw new NotServerException("Only server can change visibility");
NetworkLog.LogWarning($"[{name}] {nameof(NetworkObject)} is already visible to Client-{clientId}! (ignoring)");
}
return;
}

if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId))
Expand Down Expand Up @@ -1556,8 +1567,8 @@ public static void NetworkShow(List<NetworkObject> networkObjects, ulong clientI
/// <remarks>
/// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for a currently visible <see cref="NetworkObject"/>.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be de-spawned and destroyed on the targeted client's side.<br />
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be de-spawned on the targeted client's side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
Expand All @@ -1568,18 +1579,30 @@ public void NetworkHide(ulong clientId)
{
if (!IsSpawned)
{
throw new SpawnStateException("Object is not spawned");
if (NetworkManager.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{name}] Attempted NetworkHide while {nameof(NetworkObject)} is not spawned.");
}
return;
}

if (!HasAuthority && !NetworkManagerOwner.DAHost)
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
throw new NotServerException($"Only the owner-authority can change visibility when distributed authority mode is enabled!");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] Only the owner-authority can change visibility when distributed authority mode is enabled!");
}
return;
}
else
{
throw new NotServerException("Only the authority can change visibility");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] Only the authority can change visibility!");
}
return;
}
}

Expand All @@ -1589,9 +1612,9 @@ public void NetworkHide(ulong clientId)
{
if (NetworkManagerOwner.LogLevel <= LogLevel.Developer)
{
Debug.LogWarning($"{name} is already hidden from Client-{clientId}! (ignoring)");
return;
NetworkLog.LogWarning($"[{name}] {nameof(NetworkObject)} already hidden from Client-{clientId}! (ignoring)");
}
return;
}
Observers.Remove(clientId);

Expand Down Expand Up @@ -1724,18 +1747,30 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla

if (!NetworkManagerOwner.IsListening)
{
throw new NotListeningException($"{nameof(NetworkManagerOwner)} is not listening, start a server or host before spawning objects");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] {nameof(NetworkManagerOwner)} is not listening, start a server or host before spawning objects.");
}
return;
}

if ((!NetworkManagerOwner.IsServer && !NetworkManagerOwner.DistributedAuthorityMode) || (NetworkManagerOwner.DistributedAuthorityMode && !NetworkManagerOwner.LocalClient.IsSessionOwner && NetworkManagerOwner.LocalClientId != ownerClientId))
{
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 {NetworkManagerOwner.LocalClientId} is not the same as the assigned owner id: {ownerClientId}!");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] 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}!");
}
return;
}
else
{
throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s");
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] Only server can spawn {nameof(NetworkObject)}s.");
}
return;
}
}

Expand Down Expand Up @@ -2256,7 +2291,10 @@ private void OnTransformParentChanged()
return;
}
transform.parent = m_CachedParent;
Debug.LogException(new NotListeningException($"[{name}] {nameof(networkManager)} is not listening, start a server or host before re-parenting"));
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"[{name}] {nameof(networkManager)} is not listening, start a server or host before re-parenting.");
}
return;
}

Expand All @@ -2273,7 +2311,10 @@ private void OnTransformParentChanged()
else
{
transform.parent = m_CachedParent;
Debug.LogException(new SpawnStateException($"[{name}] {nameof(NetworkObject)} can only be re-parented after being spawned"));
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{name}] {nameof(NetworkObject)} can only be re-parented after being spawned!");
}
}
return;
}
Expand All @@ -2293,7 +2334,7 @@ private void OnTransformParentChanged()
}
else
{
Debug.LogException(new NotServerException($"[{name}] Only the server can re-parent {nameof(NetworkObject)}s"));
NetworkLog.LogError($"[{name}] Only the server can re-parent {nameof(NetworkObject)}s.");
}
}
return;
Expand All @@ -2307,14 +2348,20 @@ private void OnTransformParentChanged()
{
transform.parent = m_CachedParent;
AuthorityAppliedParenting = false;
Debug.LogException(new InvalidParentException($"[{name}] Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent"));
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{name}] Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent");
}
return;
}
else if (!parentObject.IsSpawned)
{
transform.parent = m_CachedParent;
AuthorityAppliedParenting = false;
Debug.LogException(new SpawnStateException($"[{name}] {nameof(NetworkObject)} can only be re-parented under another spawned {nameof(NetworkObject)}"));
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{name}] {nameof(NetworkObject)} can only be re-parented under another spawned {nameof(NetworkObject)}.");
}
return;
}

Expand Down Expand Up @@ -2536,7 +2583,10 @@ internal void InvokeBehaviourNetworkSpawn()
{
if (!childBehaviour.gameObject.activeInHierarchy)
{
Debug.LogWarning($"{GenerateDisabledNetworkBehaviourWarning(childBehaviour)}");
if (NetworkManagerOwner.LogLevel <= LogLevel.Developer)
{
NetworkLog.LogWarning($"{GenerateDisabledNetworkBehaviourWarning(childBehaviour)}");
}
continue;
}
childBehaviour.InternalOnNetworkSpawn();
Expand Down Expand Up @@ -3327,7 +3377,11 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject,
// Spawn the NetworkObject
if (networkObject.IsSpawned)
{
throw new SpawnStateException($"[{networkObject.name}] Object-{networkObject.NetworkObjectId} is already spawned!");
if (NetworkManager.Singleton.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{networkObject.name}] Object-{networkObject.NetworkObjectId} is already spawned!");
}
return null;
}

// Invoke the non-authority local spawn method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Unity.Netcode
/// <summary>
/// Exception thrown when the operation require NetworkManager to be listening.
/// </summary>
[Obsolete("Not used anymore.")]
public class NotListeningException : Exception
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -892,5 +892,32 @@ public IEnumerator NetworkShowAndChangeOwnership()
AssertOnTimeout($"Timed out waiting for clients-{m_NewOwner.LocalClientId} to gain ownership of object {m_OwnershipNetworkObject.NetworkObjectId}!");
VerboseDebug($"Client {m_NewOwner.LocalClientId} now owns object {m_OwnershipNetworkObject.NetworkObjectId}!");
}

[UnityTest]
public IEnumerator DuplicateHideShowTest()
{
var authority = GetAuthorityNetworkManager();
var nonAuthority = GetNonAuthorityNetworkManager();
m_ClientId0 = nonAuthority.LocalClientId;
ShowHideObject.ClientTargetedNetworkObjects.Clear();
ShowHideObject.ClientIdToTarget = m_ClientId0;
ShowHideObject.Silent = true;

var spawnedObject1 = SpawnObject(m_PrefabToSpawn, authority);
m_NetSpawnedObject1 = spawnedObject1.GetComponent<NetworkObject>();

m_NetSpawnedObject1.GetComponent<ShowHideObject>().MyNetworkVariable.Value++;
m_NetSpawnedObject1.NetworkHide(m_ClientId0);
m_NetSpawnedObject1.NetworkHide(m_ClientId0);

yield return WaitForConditionOrTimeOut(() => !nonAuthority.SpawnManager.SpawnedObjects.ContainsKey(m_NetSpawnedObject1.NetworkObjectId));
AssertOnTimeout($"NetworkObject {m_NetSpawnedObject1.name} is still spawned on client-{nonAuthority.LocalClientId} after timeout!");

m_NetSpawnedObject1.NetworkShow(m_ClientId0);
m_NetSpawnedObject1.NetworkShow(m_ClientId0);

yield return WaitForConditionOrTimeOut(() => nonAuthority.SpawnManager.SpawnedObjects.ContainsKey(m_NetSpawnedObject1.NetworkObjectId));
AssertOnTimeout($"NetworkObject {m_NetSpawnedObject1.name} is not yet spawned on client-{nonAuthority.LocalClientId} after timeout!");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public void SetupSet(Transform rootTransform, int setIndex, NetworkManager netwo
Assert.That(m_Pickup_NetObjs[setIndex], Is.Not.Null);
Assert.That(m_Pickup_Back_NetObjs[setIndex], Is.Not.Null);

LogAssert.Expect(LogType.Exception, new Regex("start a server or host", RegexOptions.IgnoreCase));
LogAssert.Expect(LogType.Error, new Regex("start a server or host", RegexOptions.IgnoreCase));
var cachedParent = m_Cube_NetObjs[setIndex].parent;
m_Cube_NetObjs[setIndex].parent = m_Pickup_NetObjs[setIndex];
Assert.That(m_Cube_NetObjs[setIndex].parent, Is.EqualTo(cachedParent), $"Transform {m_Cube_NetObjs[setIndex].parent.name} is not equal to transform {cachedParent.name}");
Expand Down