diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index 8b68a5b22b..df8bec4dd2 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -23,6 +23,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Fixed
- 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. (#4240)
+- Fixed a NullReferenceException that would be thrown if a MonoBehaviour derived from NetworkManager defines it's own Awake or OnEnable. (#4237)
- Issue where `NetworkTime` reported a `TickOffset` of almost a whole tick, instead of zero, for some times that land on a tick (for example 0.3 at a tick rate of 10), which put `TickWithPartial` one tick ahead or, in long sessions, `Tick` one tick behind. Times between minus one tick and zero now report tick -1 with a positive `TickOffset`, as other negative times do. (#4227)
- Issue where bits written with `BitWriter` were lost when a later `BitWriter.TryBeginWriteBits` call in the same bitwise context grew the `FastBufferWriter`. The bytes written earlier in that context were replaced with zeros or uninitialized memory. (#4225)
- Issue where a growable `FastBufferWriter` created with a size of 0 never returned from its first write or `TryBeginWrite` call that needed the buffer to grow. (#4223)
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
index 25b86e3da0..36bb40d760 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
@@ -1071,11 +1071,18 @@ public void SetSingleton()
OnSingletonReady?.Invoke();
}
- private void Awake()
+ ///
+ /// Unity calls Awake when loading an instance of a script component.
+ ///
+ ///
+ /// Override this method to implement custom initialization logic for a derived .
+ /// Ensure you call base.Awake() in your override to allow the base initialization to happen.
+ ///
+ protected virtual void Awake()
{
if (Log == null)
{
- Log = new ContextualLogger(this, false);
+ Log = new ContextualLogger(this);
}
NetworkConfig?.InitializePrefabs();
@@ -1088,7 +1095,14 @@ private void Awake()
OnInstantiated?.Invoke(this);
}
- private void OnEnable()
+ ///
+ /// Called when a component of an active GameObject is first enabled.
+ ///
+ ///
+ /// Override this method to implement custom initialization logic for a derived .
+ /// Ensure you call base.OnEnable() in your override to allow the base initialization to happen.
+ ///
+ protected virtual void OnEnable()
{
if (RunInBackground)
{
@@ -1337,6 +1351,11 @@ private bool CanStart(StartType type)
/// returns true if started in server mode successfully; otherwise false
public bool StartServer()
{
+ if (Log == null)
+ {
+ Log = new ContextualLogger(this);
+ Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable() is called in any derived NetworkManager classes."));
+ }
Log.CaptureFunctionCall();
if (!CanStart(StartType.Server))
@@ -1397,6 +1416,11 @@ public bool StartServer()
/// (/) returns true if started in client mode successfully.
public bool StartClient()
{
+ if (Log == null)
+ {
+ Log = new ContextualLogger(this);
+ Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable() is called in any derived NetworkManager classes."));
+ }
Log.CaptureFunctionCall();
if (!CanStart(StartType.Client))
@@ -1450,6 +1474,11 @@ public bool StartClient()
/// (/) returns true if started in host mode successfully.
public bool StartHost()
{
+ if (Log == null)
+ {
+ Log = new ContextualLogger(this);
+ Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable() is called in any derived NetworkManager classes."));
+ }
Log.CaptureFunctionCall();
if (!CanStart(StartType.Host))
@@ -1613,7 +1642,7 @@ private void OnSceneUnloaded(Scene scene)
{
if (gameObject != null && scene == gameObject.scene)
{
- OnDestroy();
+ DestroyInternal();
}
}
@@ -1728,7 +1757,7 @@ internal void ShutdownInternal()
OnStopped?.Invoke();
}
- // Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when quitting the application.
+ // Ensures that the NetworkManager is cleaned up before DestroyInternal is run on NetworkObjects and NetworkBehaviours when quitting the application.
private void OnApplicationQuit()
{
// Abrupt shutdown (or immediate exit of play mode).
@@ -1742,7 +1771,7 @@ private void OnApplicationQuit()
{
return;
}
- OnDestroy();
+ DestroyInternal();
#if UNITY_EDITOR
if (Singleton != null)
{
@@ -1751,9 +1780,28 @@ private void OnApplicationQuit()
#endif
}
+ ///
+ /// Called when a GameObject or component is about to be destroyed.
+ ///
+ ///
+ /// Override this method to implement custom teardown logic for a derived .
+ /// Ensure you call base.OnDestroy() in your override to allow the base teardown to happen.
+ ///
+ protected virtual void OnDestroy()
+ {
+ DestroyInternal();
+ }
+
+ // Internal destroy method that can't be overridden by derived classes.
// Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit
- private void OnDestroy()
+ private void DestroyInternal()
{
+ if (Log == null)
+ {
+ Log = new ContextualLogger(this);
+ Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable() is called in any derived NetworkManager classes."));
+ }
+
try
{
ShutdownInternal();
@@ -1849,7 +1897,7 @@ internal void OnValidate()
if (Log == null)
{
- Log = new ContextualLogger(this, false);
+ Log = new ContextualLogger(this);
}
// Do a validation pass on NetworkConfig properties
diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json
index e257255fcc..124d7ce7fa 100644
--- a/com.unity.netcode.gameobjects/package.json
+++ b/com.unity.netcode.gameobjects/package.json
@@ -2,7 +2,7 @@
"name": "com.unity.netcode.gameobjects",
"displayName": "Netcode for GameObjects",
"description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.",
- "version": "2.13.4",
+ "version": "2.14.0",
"unity": "6000.0",
"dependencies": {
"com.unity.nuget.mono-cecil": "1.11.4",
@@ -15,4 +15,4 @@
"path": "Samples~/Bootstrap"
}
]
-}
\ No newline at end of file
+}
diff --git a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs
index ccf46cb61c..5dd74a95ba 100644
--- a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs
+++ b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs
@@ -198,4 +198,45 @@ private void OnServerStopped(bool obj)
m_ServerNetworkManager.Shutdown();
}
}
+
+ internal class DerivedNetworkManagerTests
+ {
+ private class TestNetworkManager : NetworkManager
+ {
+ public void Awake() { }
+ public void OnEnable() { }
+ }
+
+ [UnityTest]
+ public IEnumerator ValidateDerivedNetworkManager()
+ {
+ var gameObject = new GameObject("TestNetworkManager");
+ var server = gameObject.AddComponent();
+ var mockTransport = server.gameObject.AddComponent();
+ server.NetworkConfig ??= new NetworkConfig();
+ server.NetworkConfig.NetworkTransport = mockTransport;
+ var client = Object.Instantiate(server);
+
+ var isListening = server.StartServer();
+ Assert.That(isListening, Is.True, "Server failed to start!");
+
+ client.StartClient();
+
+ var timeoutHelper = new TimeoutHelper();
+ yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => client.IsListening, timeoutHelper);
+ Assert.That(timeoutHelper.TimedOut, Is.False, "Client timed out waiting to connect!");
+
+ Object.Destroy(client.gameObject);
+
+ server.Shutdown();
+ timeoutHelper = new TimeoutHelper();
+ yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => !server.IsListening, timeoutHelper);
+ Assert.That(timeoutHelper.TimedOut, Is.False, "Timed out waiting for server to shutdown!");
+
+ isListening = server.StartHost();
+ Assert.That(isListening, Is.True, "Host failed to start!");
+
+ Object.Destroy(gameObject);
+ }
+ }
}