diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index ea9b77f778..d8942f47cd 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -16,6 +16,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issues with the `NetworkBehaviour` and `NetworkVariable` length safety checks. (#3415) - Fixed issue where during a `NetworkObject`'s spawn if you instantiated, spawned, and parented another network prefab under the currently spawning `NetworkObject` the parenting message would not properly defer until the parent `NetworkObject` was spawned. (#3403) - Fixed issue where in-scene placed `NetworkObjects` could fail to synchronize its transform properly (especially without a `NetworkTransform`) if their parenting changes from the default when the scene is loaded and if the same scene remains loaded between network sessions while the parenting is completely different from the original hierarchy. (#3388) - Fixed an issue in `UnityTransport` where the transport would accept sends on invalid connections, leading to a useless memory allocation and confusing error message. (#3383) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index ba8e981795..97e446de4b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1112,49 +1112,33 @@ internal void MarkOwnerReadVariablesDirty() /// internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId) { - if (NetworkVariableFields.Count == 0) + foreach (var field in NetworkVariableFields) { - return; - } - - for (int j = 0; j < NetworkVariableFields.Count; j++) - { - - if (NetworkVariableFields[j].CanClientRead(targetClientId)) + if (field.CanClientRead(targetClientId)) { if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { var writePos = writer.Position; // Note: This value can't be packed because we don't know how large it will be in advance - // we reserve space for it, then write the data, then come back and fill in the space - // to pack here, we'd have to write data to a temporary buffer and copy it in - which - // isn't worth possibly saving one byte if and only if the data is less than 63 bytes long... - // The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent. - writer.WriteValueSafe((ushort)0); + writer.WriteValueSafe(0); var startPos = writer.Position; // Write the NetworkVariable field value - // WriteFieldSynchronization will write the current value only if there are no pending changes. - // Otherwise, it will write the previous value if there are pending changes since the pending - // changes will be sent shortly after the client's synchronization. - NetworkVariableFields[j].WriteFieldSynchronization(writer); + field.WriteFieldSynchronization(writer); var size = writer.Position - startPos; writer.Seek(writePos); - writer.WriteValueSafe((ushort)size); + writer.WriteValueSafe(size); writer.Seek(startPos + size); } else { // Write the NetworkVariable field value - // WriteFieldSynchronization will write the current value only if there are no pending changes. - // Otherwise, it will write the previous value if there are pending changes since the pending - // changes will be sent shortly after the client's synchronization. - NetworkVariableFields[j].WriteFieldSynchronization(writer); + field.WriteFieldSynchronization(writer); } } else // Only if EnsureNetworkVariableLengthSafety, otherwise just skip if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { - writer.WriteValueSafe((ushort)0); + writer.WriteValueSafe(0); } } } @@ -1169,51 +1153,37 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie /// internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) { - if (NetworkVariableFields.Count == 0) + foreach (var field in NetworkVariableFields) { - return; - } - - for (int j = 0; j < NetworkVariableFields.Count; j++) - { - var varSize = (ushort)0; + int expectedBytesToRead = 0; var readStartPos = 0; if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { - reader.ReadValueSafe(out varSize); - if (varSize == 0) + reader.ReadValueSafe(out expectedBytesToRead); + if (expectedBytesToRead == 0) { continue; } readStartPos = reader.Position; } else // If the client cannot read this field, then skip it - if (!NetworkVariableFields[j].CanClientRead(clientId)) + if (!field.CanClientRead(clientId)) { continue; } - NetworkVariableFields[j].ReadField(reader); + field.ReadField(reader); if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { - if (reader.Position > (readStartPos + varSize)) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"Var data read too far. {reader.Position - (readStartPos + varSize)} bytes."); - } - - reader.Seek(readStartPos + varSize); - } - else if (reader.Position < (readStartPos + varSize)) + var totalBytesRead = reader.Position - readStartPos; + if (totalBytesRead != expectedBytesToRead) { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + if (NetworkManager.LogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"Var data read too little. {(readStartPos + varSize) - reader.Position} bytes."); + NetworkLog.LogWarning($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}][{field.Name}] NetworkVariable read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes during synchronization deserialization!"); } - - reader.Seek(readStartPos + varSize); + reader.Seek(readStartPos + expectedBytesToRead); } } } @@ -1295,7 +1265,7 @@ internal bool Synchronize(ref BufferSerializer serializer, ulong targetCli // Save our position where we will write the final size being written so we can skip over it in the // event an exception occurs when deserializing. var sizePosition = writer.Position; - writer.WriteValueSafe((ushort)0); + writer.WriteValueSafe(0); // Save our position before synchronizing to determine how much was written var positionBeforeSynchronize = writer.Position; @@ -1333,7 +1303,7 @@ internal bool Synchronize(ref BufferSerializer serializer, ulong targetCli // Write the number of bytes serialized to handle exceptions on the deserialization side var bytesWritten = finalPosition - positionBeforeSynchronize; writer.Seek(sizePosition); - writer.WriteValueSafe((ushort)bytesWritten); + writer.WriteValueSafe(bytesWritten); writer.Seek(finalPosition); } return true; @@ -1342,7 +1312,7 @@ internal bool Synchronize(ref BufferSerializer serializer, ulong targetCli { var reader = serializer.GetFastBufferReader(); // We will always read the expected byte count - reader.ReadValueSafe(out ushort expectedBytesToRead); + reader.ReadValueSafe(out int expectedBytesToRead); // Save our position before we begin synchronization deserialization var positionBeforeSynchronize = reader.Position; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index 1453871247..22b8c2d09c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -108,11 +108,15 @@ internal void NetworkBehaviourUpdate(bool forceSend = false) } // Now, reset all the no-longer-dirty variables - foreach (var dirtyobj in m_DirtyNetworkObjects) + foreach (var dirtyObj in m_DirtyNetworkObjects) { - dirtyobj.PostNetworkVariableWrite(forceSend); + foreach (var behaviour in dirtyObj.ChildNetworkBehaviours) + { + behaviour.PostNetworkVariableWrite(forceSend); + } + // Once done processing, we set the previous owner id to the current owner id - dirtyobj.PreviousOwnerId = dirtyobj.OwnerClientId; + dirtyObj.PreviousOwnerId = dirtyObj.OwnerClientId; } m_DirtyNetworkObjects.Clear(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index ae7d846e90..d1942f6df0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1476,16 +1476,6 @@ internal List ChildNetworkBehaviours } } - internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId) - { - for (int i = 0; i < ChildNetworkBehaviours.Count; i++) - { - var behavior = ChildNetworkBehaviours[i]; - behavior.InitializeVariables(); - behavior.WriteNetworkVariableData(writer, targetClientId); - } - } - internal void MarkVariablesDirty(bool dirty) { for (int i = 0; i < ChildNetworkBehaviours.Count; i++) @@ -1525,18 +1515,6 @@ internal static void VerifyParentingStatus() } } - /// - /// Only invoked during first synchronization of a NetworkObject (late join or newly spawned) - /// - internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId) - { - for (int i = 0; i < ChildNetworkBehaviours.Count; i++) - { - var behaviour = ChildNetworkBehaviours[i]; - behaviour.InitializeVariables(); - behaviour.SetNetworkVariableData(reader, clientId); - } - } internal ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour instance) { @@ -1744,14 +1722,6 @@ public void Deserialize(FastBufferReader reader) } } - internal void PostNetworkVariableWrite(bool forceSend) - { - for (int k = 0; k < ChildNetworkBehaviours.Count; k++) - { - ChildNetworkBehaviours[k].PostNetworkVariableWrite(forceSend); - } - } - /// /// Handles synchronizing NetworkVariables and custom synchronization data for NetworkBehaviours. /// @@ -1765,11 +1735,16 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer { var writer = serializer.GetFastBufferWriter(); var positionBeforeSynchronizing = writer.Position; - writer.WriteValueSafe((ushort)0); + writer.WriteValueSafe(0); var sizeToSkipCalculationPosition = writer.Position; // Synchronize NetworkVariables - WriteNetworkVariableData(writer, targetClientId); + foreach (var behavior in ChildNetworkBehaviours) + { + behavior.InitializeVariables(); + behavior.WriteNetworkVariableData(writer, targetClientId); + } + // Reserve the NetworkBehaviour synchronization count position var networkBehaviourCountPosition = writer.Position; writer.WriteValueSafe((byte)0); @@ -1791,7 +1766,7 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer // synchronization. writer.Seek(positionBeforeSynchronizing); // We want the size of everything after our size to skip calculation position - var size = (ushort)(currentPosition - sizeToSkipCalculationPosition); + var size = currentPosition - sizeToSkipCalculationPosition; writer.WriteValueSafe(size); // Write the number of NetworkBehaviours synchronized writer.Seek(networkBehaviourCountPosition); @@ -1803,23 +1778,34 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer else { var reader = serializer.GetFastBufferReader(); - - reader.ReadValueSafe(out ushort sizeOfSynchronizationData); + reader.ReadValueSafe(out int sizeOfSynchronizationData); var seekToEndOfSynchData = reader.Position + sizeOfSynchronizationData; - // Apply the network variable synchronization data - SetNetworkVariableData(reader, targetClientId); - // Read the number of NetworkBehaviours to synchronize - reader.ReadValueSafe(out byte numberSynchronized); - var networkBehaviourId = (ushort)0; - // If a NetworkBehaviour writes synchronization data, it will first - // write its NetworkBehaviourId so when deserializing the client-side - // can find the right NetworkBehaviour to deserialize the synchronization data. - for (int i = 0; i < numberSynchronized; i++) + try + { + // Apply the network variable synchronization data + foreach (var behaviour in ChildNetworkBehaviours) + { + behaviour.InitializeVariables(); + behaviour.SetNetworkVariableData(reader, targetClientId); + } + + // Read the number of NetworkBehaviours to synchronize + reader.ReadValueSafe(out byte numberSynchronized); + + // If a NetworkBehaviour writes synchronization data, it will first + // write its NetworkBehaviourId so when deserializing the client-side + // can find the right NetworkBehaviour to deserialize the synchronization data. + for (int i = 0; i < numberSynchronized; i++) + { + reader.ReadValueSafe(out ushort networkBehaviourId); + var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId); + networkBehaviour.Synchronize(ref serializer, targetClientId); + } + } + catch { - serializer.SerializeValue(ref networkBehaviourId); - var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId); - networkBehaviour.Synchronize(ref serializer, targetClientId); + reader.Seek(seekToEndOfSynchData); } } } @@ -1916,7 +1902,7 @@ internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBuf try { // If we failed to load this NetworkObject, then skip past the Network Variable and (if any) synchronization data - reader.ReadValueSafe(out ushort networkBehaviourSynchronizationDataLength); + reader.ReadValueSafe(out int networkBehaviourSynchronizationDataLength); reader.Seek(reader.Position + networkBehaviourSynchronizationDataLength); } catch (Exception ex) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index fed46a1337..7331c28e4c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -107,7 +107,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { if (!shouldWrite) { - BytePacker.WriteValueBitPacked(writer, (ushort)0); + BytePacker.WriteValueBitPacked(writer, 0); } } else @@ -131,7 +131,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { if (ensureNetworkVariableLengthSafety) { - BytePacker.WriteValueBitPacked(writer, (ushort)0); + BytePacker.WriteValueBitPacked(writer, 0); } else { @@ -170,7 +170,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { if (!shouldWrite) { - BytePacker.WriteValueBitPacked(writer, (ushort)0); + BytePacker.WriteValueBitPacked(writer, 0); } } else @@ -241,13 +241,13 @@ public void Handle(ref NetworkContext context) // Update NetworkVariable Fields for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++) { - int varSize = 0; + int expectedBytesToRead = 0; var networkVariable = networkBehaviour.NetworkVariableFields[i]; if (ensureNetworkVariableLengthSafety) { - ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize); - if (varSize == 0) + ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out expectedBytesToRead); + if (expectedBytesToRead == 0) { continue; } @@ -272,7 +272,7 @@ public void Handle(ref NetworkContext context) NetworkLog.LogError($"[{networkVariable.GetType().Name}]"); } - m_ReceivedNetworkVariableData.Seek(m_ReceivedNetworkVariableData.Position + varSize); + m_ReceivedNetworkVariableData.Seek(m_ReceivedNetworkVariableData.Position + expectedBytesToRead); continue; } @@ -295,9 +295,9 @@ public void Handle(ref NetworkContext context) if (ensureNetworkVariableLengthSafety) { var remainingBufferSize = m_ReceivedNetworkVariableData.Length - m_ReceivedNetworkVariableData.Position; - if (varSize > (remainingBufferSize)) + if (expectedBytesToRead > remainingBufferSize) { - UnityEngine.Debug.LogError($"[{networkBehaviour.name}][Delta State Read Error] Expecting to read {varSize} but only {remainingBufferSize} remains!"); + UnityEngine.Debug.LogError($"[{networkBehaviour.name}][Delta State Read Error] Expecting to read {expectedBytesToRead} but only {remainingBufferSize} remains!"); return; } } @@ -317,6 +317,19 @@ public void Handle(ref NetworkContext context) return; } + if (ensureNetworkVariableLengthSafety) + { + var totalBytesRead = m_ReceivedNetworkVariableData.Position - readStartPos; + if (totalBytesRead != expectedBytesToRead) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"[{nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}][Delta State Read] NetworkVariable read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes!"); + } + m_ReceivedNetworkVariableData.Seek(readStartPos + expectedBytesToRead); + } + } + // (For client-server) As opposed to worrying about adding additional processing on the server to send NetworkVariable // updates at the end of the frame, we now track all NetworkVariable state updates, per client, that need to be forwarded // to the client. This happens once the server is finished processing all state updates for this message. @@ -346,28 +359,6 @@ public void Handle(ref NetworkContext context) networkVariable.Name, networkBehaviour.__getTypeName(), context.MessageSize); - - if (ensureNetworkVariableLengthSafety) - { - if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize)) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"Var delta read too far. {m_ReceivedNetworkVariableData.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}"); - } - - m_ReceivedNetworkVariableData.Seek(readStartPos + varSize); - } - else if (m_ReceivedNetworkVariableData.Position < (readStartPos + varSize)) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"Var delta read too little. {readStartPos + varSize - m_ReceivedNetworkVariableData.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}"); - } - - m_ReceivedNetworkVariableData.Seek(readStartPos + varSize); - } - } } // If we are using the version of this message that includes network delivery, then diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index c6db156f81..97b13192d6 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -97,7 +97,7 @@ public void Initialize(NetworkBehaviour networkBehaviour) if (!m_NetworkBehaviour.NetworkObject.NetworkManagerOwner) { // Exit early if there has yet to be a NetworkManagerOwner assigned - // to the NetworkObject. This is ok because Initialize is invoked + // to the NetworkObject. This is ok because Initialize is invoked // multiple times until it is considered "initialized". return; } @@ -374,7 +374,7 @@ internal ulong OwnerClientId() /// This should be always invoked (client & server) to assure the previous values are set /// !! IMPORTANT !! /// When a server forwards delta updates to connected clients, it needs to preserve the previous dirty value(s) - /// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This is invoked + /// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This is invoked /// after it is done forwarding the deltas at the end of the method. /// internal virtual void PostDeltaRead() @@ -382,12 +382,16 @@ internal virtual void PostDeltaRead() } /// + /// WriteFieldSynchronization will write the current value only if there are no pending changes. + /// Otherwise, it will write the previous value if there are pending changes since the pending + /// changes will be sent shortly after the client's synchronization. + ///

/// There are scenarios, specifically with collections, where a client could be synchronizing and /// some NetworkVariables have pending updates. To avoid duplicating entries, this is invoked only /// when sending the full synchronization information. ///
/// - /// Derrived classes should send the previous value for synchronization so when the updated value + /// Derived classes should send the previous value for synchronization so when the updated value /// is sent (after synchronizing the client) it will apply the updates. /// /// diff --git a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs index fe377a6d01..f0056538ba 100644 --- a/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -1466,6 +1466,21 @@ public bool WaitForConditionOrTimeOutWithTimeTravel(IConditionalPredicate condit return success; } + /// + /// Validation for clients connected. + /// + private bool CheckClientsConnected(NetworkManager[] clientsToCheck) + { + if (clientsToCheck.Any(client => !client.IsConnectedClient)) + { + return false; + } + var expectedCount = m_ServerNetworkManager.IsHost ? clientsToCheck.Length + 1 : clientsToCheck.Length; + var currentCount = m_ServerNetworkManager.ConnectedClients.Count; + + return currentCount == expectedCount; + } + /// /// Validates that all remote clients (i.e. non-server) detect they are connected /// to the server and that the server reflects the appropriate number of clients @@ -1475,11 +1490,7 @@ public bool WaitForConditionOrTimeOutWithTimeTravel(IConditionalPredicate condit /// An IEnumerator for use with Unity's coroutine system. protected IEnumerator WaitForClientsConnectedOrTimeOut(NetworkManager[] clientsToCheck) { - var remoteClientCount = clientsToCheck.Length; - var serverClientCount = m_ServerNetworkManager.IsHost ? remoteClientCount + 1 : remoteClientCount; - - yield return WaitForConditionOrTimeOut(() => clientsToCheck.Where((c) => c.IsConnectedClient).Count() == remoteClientCount && - m_ServerNetworkManager.ConnectedClients.Count == serverClientCount); + yield return WaitForConditionOrTimeOut(() => CheckClientsConnected(clientsToCheck)); } /// @@ -1492,11 +1503,7 @@ protected IEnumerator WaitForClientsConnectedOrTimeOut(NetworkManager[] clientsT /// True if all clients are connected within the specified number of frames; otherwise, false. protected bool WaitForClientsConnectedOrTimeOutWithTimeTravel(NetworkManager[] clientsToCheck) { - var remoteClientCount = clientsToCheck.Length; - var serverClientCount = m_ServerNetworkManager.IsHost ? remoteClientCount + 1 : remoteClientCount; - - return WaitForConditionOrTimeOutWithTimeTravel(() => clientsToCheck.Where((c) => c.IsConnectedClient).Count() == remoteClientCount && - m_ServerNetworkManager.ConnectedClients.Count == serverClientCount); + return WaitForConditionOrTimeOutWithTimeTravel(() => CheckClientsConnected(clientsToCheck)); } /// /// Overloaded method that just passes in all clients to diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs index 67d54cd20d..ab0f65d022 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTestsHelperTypes.cs @@ -889,12 +889,12 @@ public class NetVarILPPClassForTests : NetworkBehaviour public class TemplateNetworkBehaviourType : NetworkBehaviour { - public NetworkVariable TheVar; + public NetworkVariable TheVar = new NetworkVariable(); } public class IntermediateNetworkBehavior : TemplateNetworkBehaviourType { - public NetworkVariable TheVar2; + public NetworkVariable TheVar2 = new NetworkVariable(); } public class ClassHavingNetworkBehaviour : IntermediateNetworkBehavior