diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index 4a03809c92..3903b2edca 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -18,6 +18,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Fixed
+- Issue where every `NetworkObject` that started before a session logged a "Did not register because there is no session in progress!" warning when `NetworkManager.LogLevel` was `Developer`. Only `GhostObject` instances log it now. (#4245)
- Ensured a useful and human readable error is logged when a NetworkVariable is trying to serialize a private or protected type. (#4239)
- Issue where `NetworkAnimator` logged an error for a trigger-conditioned transition into an Exit node, and a client that joined during that transition, or during one into a sub-state machine, did not synchronize with it. Transitions that pass through Exit, Entry or sub-state machine nodes now resolve to the state they reach, and a late-joining client cross-fades from the matching originating state when several states transition into the same destination. (#4238)
- Ensured all callbacks are wrapped with exception handling to avoid silent errors. (#4161)
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
index 028e508982..e9815947e0 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
@@ -4208,6 +4208,8 @@ private void Start()
[HideInInspector]
internal NetworkObjectBridge NetworkObjectBridge;
+ internal const string NotRegisteredWithoutSessionWarning = "Did not register because there is no session in progress!";
+
private void InitGhost()
{
// Note: If hybrid prefabs are created prior to any NetworkManager instances,
@@ -4220,22 +4222,21 @@ private void InitGhost()
return;
}
+ if (!HasGhost || !NetworkObjectBridge || GhostObject.IsPrefab())
+ {
+ // Nothing to register
+ return;
+ }
if (!NetworkManager.IsListening)
{
if (NetworkManager.LogLevel == LogLevel.Developer)
{
- Debug.LogWarning($"[{nameof(NetworkObject)}] Did not register because there is no session in progress!");
+ Debug.LogWarning($"[{nameof(NetworkObject)}][{name}] {NotRegisteredWithoutSessionWarning}");
}
return;
}
- if (!HasGhost || !NetworkObjectBridge || GhostObject.IsPrefab())
- {
- // Nothing to register
- return;
- }
-
// All instances with Ghosts are automatically registered
if (NetworkManager.LogLevel == LogLevel.Developer)
{
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/UnifiedHybridPrefabValidationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/UnifiedHybridPrefabValidationTests.cs
index 2cf756995d..f4bb04ac50 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/UnifiedHybridPrefabValidationTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/UnifiedHybridPrefabValidationTests.cs
@@ -1,6 +1,7 @@
#if UNIFIED_NETCODE
using System;
using System.Collections;
+using System.Collections.Generic;
using System.Text.RegularExpressions;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
@@ -17,6 +18,7 @@ namespace Unity.Netcode.RuntimeTests
/// - A hybrid prefab instance spawned during a session that started without hybrid prefabs. It is not spawned.
/// - A hybrid player prefab counts toward hybrid mode before the session starts, so distributed authority rejects it.
/// - A NetworkPrefab override with a hybrid source or target prefab, in any topology. The entry is ignored.
+ /// - A hybrid instance created before the session starts. With developer logging it warns, and a non-hybrid instance does not.
///
///
/// The fixture's managers are not started. Each test creates its own that registers only the prefabs it needs.
@@ -215,6 +217,58 @@ public void PrefabListAddDuringSessionRegistersOnce()
UnityEngine.Object.Destroy(prefabList);
}
+ ///
+ /// Validates that only a hybrid instance created before the session starts warns that it was not registered.
+ ///
+ [UnityTest]
+ public IEnumerator PreSessionInstanceWarnsOnlyWhenHybrid()
+ {
+ var networkManager = CreateNetworkManager(NetworkTopologyTypes.ClientServer);
+ var cases = new (GameObject Prefab, LogLevel LogLevel, bool ExpectWarning)[]
+ {
+ (m_Prefab, LogLevel.Developer, false),
+ (m_HybridPrefab, LogLevel.Normal, false),
+ (m_HybridPrefab, LogLevel.Developer, true),
+ };
+
+ var warnings = new List();
+ Application.LogCallback onLogMessage = (message, stackTrace, logType) =>
+ {
+ if (logType == LogType.Warning && message.Contains(NetworkObject.NotRegisteredWithoutSessionWarning))
+ {
+ warnings.Add(message);
+ }
+ };
+ Application.logMessageReceived += onLogMessage;
+ try
+ {
+ foreach (var testCase in cases)
+ {
+ warnings.Clear();
+ networkManager.LogLevel = testCase.LogLevel;
+ var instance = UnityEngine.Object.Instantiate(testCase.Prefab).GetComponent();
+ instance.NetworkManagerOwner = networkManager;
+ var instanceName = instance.name;
+ var caseName = $"{instanceName}][{testCase.LogLevel}";
+
+ // Start, where the instance is registered, runs on the next frame.
+ yield return null;
+ UnityEngine.Object.Destroy(instance.gameObject);
+ yield return null;
+
+ Assert.AreEqual(testCase.ExpectWarning ? 1 : 0, warnings.Count, $"[{caseName}] Unexpected number of not registered warnings!");
+ if (testCase.ExpectWarning)
+ {
+ StringAssert.Contains(instanceName, warnings[0], $"[{caseName}] The warning does not name the instance!");
+ }
+ }
+ }
+ finally
+ {
+ Application.logMessageReceived -= onLogMessage;
+ }
+ }
+
///
/// Validates that an override with a hybrid source or target is ignored, with an error naming it.
///