com.unity.netcode.gameobjects@1.5.2

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.5.2] - 2023-07-24

### Added

### Fixed

- Fixed issue where `NetworkClient.OwnedObjects` was not returning any owned objects due to the `NetworkClient.IsConnected` not being properly set. (#2631)
- Fixed a crash when calling TrySetParent with a null Transform (#2625)
- Fixed issue where a `NetworkTransform` using full precision state updates was losing transform state updates when interpolation was enabled. (#2624)
- Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored for late joining clients. (#2623)
- Fixed issue where invoking `NetworkManager.Shutdown` multiple times, depending upon the timing, could cause an exception. (#2622)
- Fixed issue where removing ownership would not notify the server that it gained ownership. This also resolves the issue where an owner authoritative NetworkTransform would not properly initialize upon removing ownership from a remote client. (#2618)
- Fixed ILPP issues when using CoreCLR and for certain dedicated server builds. (#2614)
- Fixed an ILPP compile error when creating a generic NetworkBehaviour singleton with a static T instance. (#2603)

### Changed
This commit is contained in:
Unity Technologies
2023-07-24 00:00:00 +00:00
parent 4d70c198bd
commit 0581a42b70
24 changed files with 390 additions and 169 deletions

View File

@@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
## [1.5.2] - 2023-07-24
### Added
### Fixed
- Fixed issue where `NetworkClient.OwnedObjects` was not returning any owned objects due to the `NetworkClient.IsConnected` not being properly set. (#2631)
- Fixed a crash when calling TrySetParent with a null Transform (#2625)
- Fixed issue where a `NetworkTransform` using full precision state updates was losing transform state updates when interpolation was enabled. (#2624)
- Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored for late joining clients. (#2623)
- Fixed issue where invoking `NetworkManager.Shutdown` multiple times, depending upon the timing, could cause an exception. (#2622)
- Fixed issue where removing ownership would not notify the server that it gained ownership. This also resolves the issue where an owner authoritative NetworkTransform would not properly initialize upon removing ownership from a remote client. (#2618)
- Fixed ILPP issues when using CoreCLR and for certain dedicated server builds. (#2614)
- Fixed an ILPP compile error when creating a generic NetworkBehaviour singleton with a static T instance. (#2603)
### Changed
## [1.5.1] - 2023-06-07
### Added

View File

@@ -1159,8 +1159,11 @@ namespace Unity.Netcode.Components
// Non-Authoritative's current position, scale, and rotation that is used to assure the non-authoritative side cannot make adjustments to
// the portions of the transform being synchronized.
private Vector3 m_CurrentPosition;
private Vector3 m_TargetPosition;
private Vector3 m_CurrentScale;
private Vector3 m_TargetScale;
private Quaternion m_CurrentRotation;
private Vector3 m_TargetRotation;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -2009,6 +2012,7 @@ namespace Unity.Netcode.Components
}
m_CurrentPosition = currentPosition;
m_TargetPosition = currentPosition;
// Apply the position
if (newState.InLocalSpace)
@@ -2026,7 +2030,6 @@ namespace Unity.Netcode.Components
if (UseHalfFloatPrecision)
{
currentScale = newState.Scale;
m_CurrentScale = currentScale;
}
else
{
@@ -2049,6 +2052,7 @@ namespace Unity.Netcode.Components
}
m_CurrentScale = currentScale;
m_TargetScale = currentScale;
m_ScaleInterpolator.ResetTo(currentScale, sentTime);
// Apply the adjusted scale
@@ -2082,6 +2086,7 @@ namespace Unity.Netcode.Components
}
m_CurrentRotation = currentRotation;
m_TargetRotation = currentRotation.eulerAngles;
m_RotationInterpolator.ResetTo(currentRotation, sentTime);
if (InLocalSpace)
@@ -2158,28 +2163,29 @@ namespace Unity.Netcode.Components
}
else
{
var currentPosition = GetSpaceRelativePosition();
var newTargetPosition = m_TargetPosition;
if (m_LocalAuthoritativeNetworkState.HasPositionX)
{
currentPosition.x = m_LocalAuthoritativeNetworkState.PositionX;
newTargetPosition.x = m_LocalAuthoritativeNetworkState.PositionX;
}
if (m_LocalAuthoritativeNetworkState.HasPositionY)
{
currentPosition.y = m_LocalAuthoritativeNetworkState.PositionY;
newTargetPosition.y = m_LocalAuthoritativeNetworkState.PositionY;
}
if (m_LocalAuthoritativeNetworkState.HasPositionZ)
{
currentPosition.z = m_LocalAuthoritativeNetworkState.PositionZ;
newTargetPosition.z = m_LocalAuthoritativeNetworkState.PositionZ;
}
UpdatePositionInterpolator(currentPosition, sentTime);
UpdatePositionInterpolator(newTargetPosition, sentTime);
m_TargetPosition = newTargetPosition;
}
}
if (m_LocalAuthoritativeNetworkState.HasScaleChange)
{
var currentScale = transform.localScale;
var currentScale = m_TargetScale;
if (UseHalfFloatPrecision)
{
for (int i = 0; i < 3; i++)
@@ -2207,6 +2213,7 @@ namespace Unity.Netcode.Components
currentScale.z = m_LocalAuthoritativeNetworkState.ScaleZ;
}
}
m_TargetScale = currentScale;
m_ScaleInterpolator.AddMeasurement(currentScale, sentTime);
}
@@ -2221,7 +2228,9 @@ namespace Unity.Netcode.Components
}
else
{
currentEulerAngles = m_TargetRotation;
// Adjust based on which axis changed
// (both half precision and full precision apply Eulers to the RotAngle properties when reading the update)
if (m_LocalAuthoritativeNetworkState.HasRotAngleX)
{
currentEulerAngles.x = m_LocalAuthoritativeNetworkState.RotAngleX;
@@ -2236,6 +2245,7 @@ namespace Unity.Netcode.Components
{
currentEulerAngles.z = m_LocalAuthoritativeNetworkState.RotAngleZ;
}
m_TargetRotation = currentEulerAngles;
currentRotation.eulerAngles = currentEulerAngles;
}
@@ -2489,8 +2499,11 @@ namespace Unity.Netcode.Components
ResetInterpolatedStateToCurrentAuthoritativeState();
m_CurrentPosition = currentPosition;
m_TargetPosition = currentPosition;
m_CurrentScale = transform.localScale;
m_TargetScale = transform.localScale;
m_CurrentRotation = currentRotation;
m_TargetRotation = currentRotation.eulerAngles;
}
@@ -2649,7 +2662,7 @@ namespace Unity.Netcode.Components
var serverTime = NetworkManager.ServerTime;
var cachedDeltaTime = NetworkManager.RealTimeProvider.DeltaTime;
var cachedServerTime = serverTime.Time;
// TODO: Investigate Further
// With owner authoritative mode, non-authority clients can lag behind
// by more than 1 tick period of time. The current "solution" for now
// is to make their cachedRenderTime run 2 ticks behind.

View File

@@ -59,7 +59,7 @@ namespace Unity.Netcode.Editor.CodeGen
public static bool IsSubclassOf(this TypeDefinition typeDefinition, string classTypeFullName)
{
if (!typeDefinition.IsClass)
if (typeDefinition == null || !typeDefinition.IsClass)
{
return false;
}
@@ -154,6 +154,10 @@ namespace Unity.Netcode.Editor.CodeGen
public static bool IsSubclassOf(this TypeReference typeReference, TypeReference baseClass)
{
if (typeReference == null)
{
return false;
}
var type = typeReference.Resolve();
if (type?.BaseType == null || type.BaseType.Name == nameof(Object))
{

View File

@@ -396,6 +396,8 @@ namespace Unity.Netcode.Editor.CodeGen
#endif
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef;
private MethodReference m_RuntimeInitializeOnLoadAttribute_Ctor;
private MethodReference m_ExceptionCtorMethodReference;
private MethodReference m_List_NetworkVariableBase_Add;
@@ -509,6 +511,8 @@ namespace Unity.Netcode.Editor.CodeGen
}
}
m_RuntimeInitializeOnLoadAttribute_Ctor = moduleDefinition.ImportReference(typeof(RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new Type[] { }));
TypeDefinition networkManagerTypeDef = null;
TypeDefinition networkBehaviourTypeDef = null;
TypeDefinition networkVariableBaseTypeDef = null;
@@ -1200,19 +1204,14 @@ namespace Unity.Netcode.Editor.CodeGen
if (rpcHandlers.Count > 0 || rpcNames.Count > 0)
{
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
if (staticCtorMethodDef == null)
{
staticCtorMethodDef = new MethodDefinition(
".cctor", // Static Constructor (constant-constructor)
MethodAttributes.HideBySig |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName |
var staticCtorMethodDef = new MethodDefinition(
$"InitializeRPCS_{typeDefinition.Name}",
MethodAttributes.Assembly |
MethodAttributes.Static,
typeDefinition.Module.TypeSystem.Void);
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
typeDefinition.Methods.Add(staticCtorMethodDef);
}
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
staticCtorMethodDef.CustomAttributes.Add(new CustomAttribute(m_RuntimeInitializeOnLoadAttribute_Ctor));
typeDefinition.Methods.Add(staticCtorMethodDef);
var instructions = new List<Instruction>();
var processor = staticCtorMethodDef.Body.GetILProcessor();
@@ -1254,7 +1253,8 @@ namespace Unity.Netcode.Editor.CodeGen
baseGetTypeNameMethod.ReturnType)
{
ImplAttributes = baseGetTypeNameMethod.ImplAttributes,
SemanticsAttributes = baseGetTypeNameMethod.SemanticsAttributes
SemanticsAttributes = baseGetTypeNameMethod.SemanticsAttributes,
IsFamilyOrAssembly = true
};
var processor = newGetTypeNameMethod.Body.GetILProcessor();
@@ -2225,6 +2225,12 @@ namespace Unity.Netcode.Editor.CodeGen
}
field = new FieldReference(fieldDefinition.Name, fieldDefinition.FieldType, genericType);
}
if (field.FieldType.Resolve() == null)
{
continue;
}
if (!field.FieldType.IsArray && !field.FieldType.Resolve().IsArray && field.FieldType.IsSubclassOf(m_NetworkVariableBase_TypeRef))
{
// if({variable} == null) {

View File

@@ -98,6 +98,14 @@ namespace Unity.Netcode.Editor.CodeGen
fieldDefinition.IsPublic = true;
}
}
foreach (var nestedTypeDefinition in typeDefinition.NestedTypes)
{
if (nestedTypeDefinition.Name == nameof(NetworkManager.RpcReceiveHandler))
{
nestedTypeDefinition.IsNestedPublic = true;
}
}
}
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
@@ -114,7 +122,7 @@ namespace Unity.Netcode.Editor.CodeGen
{
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage) || fieldDefinition.Name == nameof(NetworkBehaviour.NetworkVariableFields))
{
fieldDefinition.IsFamily = true;
fieldDefinition.IsFamilyOrAssembly = true;
}
}
@@ -130,6 +138,11 @@ namespace Unity.Netcode.Editor.CodeGen
{
methodDefinition.IsFamily = true;
}
if (methodDefinition.Name == nameof(NetworkBehaviour.__getTypeName))
{
methodDefinition.IsFamilyOrAssembly = true;
}
}
}
}

View File

@@ -36,15 +36,11 @@ namespace Unity.Netcode
/// <summary>
/// The ClientId of the NetworkClient
/// </summary>
// TODO-2023-Q2: Determine if we want to make this property a public get and internal/private set
// There is no reason for a user to want to set this, but this will fail the package-validation-suite
public ulong ClientId;
/// <summary>
/// The PlayerObject of the Client
/// </summary>
// TODO-2023-Q2: Determine if we want to make this property a public get and internal/private set
// There is no reason for a user to want to set this, but this will fail the package-validation-suite
public NetworkObject PlayerObject;
/// <summary>

View File

@@ -17,7 +17,6 @@ namespace Unity.Netcode
/// - Processing <see cref="NetworkEvent"/>s.
/// - Client Disconnection
/// </summary>
// TODO 2023-Q2: Discuss what kind of public API exposure we want for this
public sealed class NetworkConnectionManager
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
@@ -628,6 +627,8 @@ namespace Unity.Netcode
};
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
{
// Update the observed spawned NetworkObjects for the newly connected player when scene management is disabled
NetworkManager.SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
if (NetworkManager.SpawnManager.SpawnedObjectsList.Count != 0)
{
message.SpawnedObjectsList = NetworkManager.SpawnManager.SpawnedObjectsList;
@@ -651,12 +652,13 @@ namespace Unity.Netcode
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
message.MessageVersions.Dispose();
// If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
// If scene management is disabled, then we are done and notify the local host-server the client is connected
if (!NetworkManager.NetworkConfig.EnableSceneManagement)
{
NetworkManager.ConnectedClients[ownerClientId].IsConnected = true;
InvokeOnClientConnectedCallback(ownerClientId);
}
else
else // Otherwise, let NetworkSceneManager handle the initial scene and NetworkObject synchronization
{
NetworkManager.SceneManager.SynchronizeNetworkObjects(ownerClientId);
}
@@ -665,6 +667,7 @@ namespace Unity.Netcode
{
LocalClient = client;
NetworkManager.SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
LocalClient.IsConnected = true;
}
if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkManager.NetworkConfig.PlayerPrefab == null))
@@ -730,12 +733,10 @@ namespace Unity.Netcode
internal NetworkClient AddClient(ulong clientId)
{
var networkClient = LocalClient;
if (clientId != NetworkManager.ServerClientId)
{
networkClient = new NetworkClient();
networkClient.SetRole(isServer: false, isClient: true, NetworkManager);
networkClient.ClientId = clientId;
}
networkClient = new NetworkClient();
networkClient.SetRole(clientId == NetworkManager.ServerClientId, isClient: true, NetworkManager);
networkClient.ClientId = clientId;
ConnectedClients.Add(clientId, networkClient);
ConnectedClientsList.Add(networkClient);
@@ -798,8 +799,7 @@ namespace Unity.Netcode
}
else
{
// Handle changing ownership and prefab handlers
// TODO-2023: Look into whether in-scene placed NetworkObjects could be destroyed if ownership changes to a client
// Handle changing ownership and prefab handlers
for (int i = clientOwnedObjects.Count - 1; i >= 0; i--)
{
var ownedObject = clientOwnedObjects[i];

View File

@@ -18,8 +18,6 @@ namespace Unity.Netcode
Server = 1,
Client = 2
}
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
@@ -98,7 +96,6 @@ namespace Unity.Netcode
}
bufferWriter.Dispose();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
{
@@ -230,7 +227,6 @@ namespace Unity.Netcode
}
bufferWriter.Dispose();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
{

View File

@@ -118,7 +118,7 @@ namespace Unity.Netcode
m_NetworkManager.NetworkTickSystem.Tick -= NetworkBehaviourUpdater_Tick;
}
// TODO 2023-Q2: Order of operations requires NetworkVariable updates first then showing NetworkObjects
// Order of operations requires NetworkVariable updates first then showing NetworkObjects
private void NetworkBehaviourUpdater_Tick()
{
// First update NetworkVariables

View File

@@ -59,13 +59,12 @@ namespace Unity.Netcode
// Metrics update needs to be driven by NetworkConnectionManager's update to assure metrics are dispatched after the send queue is processed.
MetricsManager.UpdateMetrics();
// TODO 2023-Q2: Determine a better way to handle this
// TODO: Determine a better way to handle this
NetworkObject.VerifyParentingStatus();
// This is "ok" to invoke when not processing messages since it is just cleaning up messages that never got handled within their timeout period.
DeferredMessageManager.CleanupStaleTriggers();
// TODO 2023-Q2: Determine a better way to handle this
if (m_ShuttingDown)
{
ShutdownInternal();
@@ -834,9 +833,7 @@ namespace Unity.Netcode
}
ConnectionManager.LocalClient.SetRole(true, true, this);
Initialize(true);
try
{
IsListening = NetworkConfig.NetworkTransport.StartServer();
@@ -942,10 +939,16 @@ namespace Unity.Netcode
if (IsServer || IsClient)
{
m_ShuttingDown = true;
MessageManager.StopProcessing = discardMessageQueue;
if (MessageManager != null)
{
MessageManager.StopProcessing = discardMessageQueue;
}
}
NetworkConfig.NetworkTransport.OnTransportEvent -= ConnectionManager.HandleNetworkEvent;
if (NetworkConfig != null && NetworkConfig.NetworkTransport != null)
{
NetworkConfig.NetworkTransport.OnTransportEvent -= ConnectionManager.HandleNetworkEvent;
}
}
// Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when unloading a scene with a NetworkManager
@@ -1029,6 +1032,8 @@ namespace Unity.Netcode
// Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when quitting the application.
private void OnApplicationQuit()
{
// Make sure ShutdownInProgress returns true during this time
m_ShuttingDown = true;
OnDestroy();
}

View File

@@ -733,6 +733,12 @@ namespace Unity.Netcode
/// <returns>Whether or not reparenting was successful.</returns>
public bool TrySetParent(Transform parent, bool worldPositionStays = true)
{
// If we are removing ourself from a parent
if (parent == null)
{
return TrySetParent((NetworkObject)null, worldPositionStays);
}
var networkObject = parent.GetComponent<NetworkObject>();
// If the parent doesn't have a NetworkObjet then return false, otherwise continue trying to parent
@@ -1192,7 +1198,6 @@ namespace Unity.Netcode
{
NetworkLog.LogError($"{nameof(NetworkBehaviour)} index {index} was out of bounds for {name}. NetworkBehaviours must be the same, and in the same order, between server and client.");
}
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
var currentKnownChildren = new System.Text.StringBuilder();
@@ -1205,7 +1210,6 @@ namespace Unity.Netcode
}
NetworkLog.LogInfo(currentKnownChildren.ToString());
}
return null;
}

View File

@@ -105,7 +105,6 @@ namespace Unity.Netcode
{
networkVariable.WriteDelta(writer);
}
NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
TargetClientId,
NetworkBehaviour.NetworkObject,
@@ -207,7 +206,6 @@ namespace Unity.Netcode
networkBehaviour.__getTypeName(),
context.MessageSize);
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize))

View File

@@ -72,6 +72,14 @@ namespace Unity.Netcode
catch (Exception ex)
{
Debug.LogException(new Exception("Unhandled RPC exception!", ex));
if (networkManager.LogLevel == LogLevel.Developer)
{
Debug.Log($"RPC Table Contents");
foreach (var entry in NetworkManager.__rpc_func_table)
{
Debug.Log($"{entry.Key} | {entry.Value.Method.Name}");
}
}
}
}
}

View File

@@ -2191,6 +2191,10 @@ namespace Unity.Netcode
ClientId = clientId
});
// At this point the client is considered fully "connected"
NetworkManager.ConnectedClients[clientId].IsConnected = true;
// All scenes are synchronized, let the server know we are done synchronizing
OnSynchronizeComplete?.Invoke(clientId);
// At this time the client is fully synchronized with all loaded scenes and

View File

@@ -113,12 +113,6 @@ namespace Unity.Netcode
// Remove the previous owner's entry
OwnershipToObjectsTable[previousOwner].Remove(networkObject.NetworkObjectId);
// Server or Host alway invokes the lost ownership notification locally
if (NetworkManager.IsServer)
{
networkObject.InvokeBehaviourOnLostOwnership();
}
// If we are removing the entry (i.e. despawning or client lost ownership)
if (isRemoving)
{
@@ -143,12 +137,6 @@ namespace Unity.Netcode
{
// Add the new ownership entry
OwnershipToObjectsTable[newOwner].Add(networkObject.NetworkObjectId, networkObject);
// Server or Host always invokes the gained ownership notification locally
if (NetworkManager.IsServer)
{
networkObject.InvokeBehaviourOnGainedOwnership();
}
}
else if (isRemoving)
{
@@ -227,43 +215,6 @@ namespace Unity.Netcode
return null;
}
internal void RemoveOwnership(NetworkObject networkObject)
{
if (!NetworkManager.IsServer)
{
throw new NotServerException("Only the server can change ownership");
}
if (!networkObject.IsSpawned)
{
throw new SpawnStateException("Object is not spawned");
}
// If we made it here then we are the server and if the server is determined to already be the owner
// then ignore the RemoveOwnership invocation.
if (networkObject.OwnerClientId == NetworkManager.ServerClientId)
{
return;
}
networkObject.OwnerClientId = NetworkManager.ServerClientId;
// Server removes the entry and takes over ownership before notifying
UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true);
var message = new ChangeOwnershipMessage
{
NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId
};
var size = NetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
foreach (var client in NetworkManager.ConnectedClients)
{
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
}
}
/// <summary>
/// Helper function to get a network client for a clientId from the NetworkManager.
/// On the server this will check the <see cref="NetworkManager.ConnectedClients"/> list.
@@ -289,6 +240,11 @@ namespace Unity.Netcode
return false;
}
internal void RemoveOwnership(NetworkObject networkObject)
{
ChangeOwnership(networkObject, NetworkManager.ServerClientId);
}
internal void ChangeOwnership(NetworkObject networkObject, ulong clientId)
{
if (!NetworkManager.IsServer)
@@ -301,14 +257,21 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is not spawned");
}
// Assign the new owner
networkObject.OwnerClientId = clientId;
// Always notify locally on the server when ownership is lost
networkObject.InvokeBehaviourOnLostOwnership();
networkObject.MarkVariablesDirty(true);
NetworkManager.BehaviourUpdater.AddForUpdate(networkObject);
// Server adds entries for all client ownership
UpdateOwnershipTable(networkObject, networkObject.OwnerClientId);
// Always notify locally on the server when a new owner is assigned
networkObject.InvokeBehaviourOnGainedOwnership();
var message = new ChangeOwnershipMessage
{
NetworkObjectId = networkObject.NetworkObjectId,
@@ -952,27 +915,35 @@ namespace Unity.Netcode
}
/// <summary>
/// Updates all spawned <see cref="NetworkObject.Observers"/> for the specified client
/// Updates all spawned <see cref="NetworkObject.Observers"/> for the specified newly connected client
/// Note: if the clientId is the server then it is observable to all spawned <see cref="NetworkObject"/>'s
/// </summary>
/// <remarks>
/// This method is to only to be used for newly connected clients in order to update the observers list for
/// each NetworkObject instance.
/// </remarks>
internal void UpdateObservedNetworkObjects(ulong clientId)
{
foreach (var sobj in SpawnedObjectsList)
{
// If the NetworkObject has no visibility check then prepare to add this client as an observer
if (sobj.CheckObjectVisibility == null)
{
if (!sobj.Observers.Contains(clientId))
// If the client is not part of the observers and spawn with observers is enabled on this instance or the clientId is the server
if (!sobj.Observers.Contains(clientId) && (sobj.SpawnWithObservers || clientId == NetworkManager.ServerClientId))
{
sobj.Observers.Add(clientId);
}
}
else
{
// CheckObject visibility overrides SpawnWithObservers under this condition
if (sobj.CheckObjectVisibility(clientId))
{
sobj.Observers.Add(clientId);
}
else if (sobj.Observers.Contains(clientId))
else // Otherwise, if the observers contains the clientId (shouldn't happen) then remove it since CheckObjectVisibility returned false
if (sobj.Observers.Contains(clientId))
{
sobj.Observers.Remove(clientId);
}

View File

@@ -9,9 +9,6 @@ namespace Unity.Netcode
/// </summary>
public class NetworkTimeSystem
{
/// <summary>
/// TODO 2023-Q2: Not sure if this just needs to go away, but there is nothing that ever replaces this
/// </summary>
/// <remarks>
/// This was the original comment when it lived in NetworkManager:
/// todo talk with UX/Product, find good default value for this

View File

@@ -121,9 +121,7 @@ namespace Unity.Netcode.EditorTests
LogAssert.Expect(LogType.Error, "Invalid network endpoint: 127.0.0.:4242.");
LogAssert.Expect(LogType.Error, "Network listen address (127.0.0.) is Invalid!");
#if UTP_TRANSPORT_2_0_ABOVE
LogAssert.Expect(LogType.Error, "Socket creation failed (error Unity.Baselib.LowLevel.Binding+Baselib_ErrorState: Invalid argument (0x01000003) <argument name stripped>");
#endif
transport.SetConnectionData("127.0.0.1", 4242, "127.0.0.1");
Assert.True(transport.StartServer());

View File

@@ -67,7 +67,6 @@ namespace Unity.Netcode.RuntimeTests
// Set the child object to be inactive in the hierarchy
childObject.SetActive(false);
LogAssert.Expect(LogType.Warning, $"{childObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during ownership assignment!");
LogAssert.Expect(LogType.Warning, $"{childObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during spawn!");
parentNetworkObject.Spawn();

View File

@@ -61,8 +61,26 @@ namespace Unity.Netcode.RuntimeTests
return true;
}
/// <summary>
/// Assures the <see cref="ObserverSpawnTests"/> late joining client has all
/// NetworkPrefabs required to connect.
/// </summary>
protected override void OnNewClientCreated(NetworkManager networkManager)
{
foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs)
{
if (!networkManager.NetworkConfig.Prefabs.Contains(networkPrefab.Prefab))
{
networkManager.NetworkConfig.Prefabs.Add(networkPrefab);
}
}
base.OnNewClientCreated(networkManager);
}
/// <summary>
/// This test validates <see cref="NetworkObject.SpawnWithObservers"/> property
/// </summary>
/// <param name="observerTestTypes">whether to spawn with or without observers</param>
[UnityTest]
public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTypes)
{
@@ -92,6 +110,23 @@ namespace Unity.Netcode.RuntimeTests
m_ObserverTestType = ObserverTestTypes.WithObservers;
yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj);
AssertOnTimeout($"{k_WithObserversError} {k_ObserverTestObjName} object!");
// Validate that a late joining client does not see the NetworkObject when it spawns
yield return CreateAndStartNewClient();
m_ObserverTestType = ObserverTestTypes.WithoutObservers;
// Just give a little time to make sure nothing spawned
yield return s_DefaultWaitForTick;
yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj);
AssertOnTimeout($"{(withoutObservers ? k_WithoutObserversError : k_WithObserversError)} {k_ObserverTestObjName} object!");
// Now validate that we can make the NetworkObject visible to the newly joined client
m_ObserverTestNetworkObject.NetworkShow(m_ClientNetworkManagers[NumberOfClients].LocalClientId);
// Validate the NetworkObject is visible to all connected clients (including the recently joined client)
m_ObserverTestType = ObserverTestTypes.WithObservers;
yield return WaitForConditionOrTimeOut(CheckClientsSideObserverTestObj);
AssertOnTimeout($"{k_WithObserversError} {k_ObserverTestObjName} object!");
}
}
/// <summary>

View File

@@ -42,6 +42,12 @@ namespace Unity.Netcode.RuntimeTests
public NetworkObjectOwnershipTests(HostOrServer hostOrServer) : base(hostOrServer) { }
public enum OwnershipChecks
{
Change,
Remove
}
protected override void OnServerAndClientsCreated()
{
m_OwnershipPrefab = CreateNetworkObjectPrefab("OnwershipPrefab");
@@ -62,7 +68,7 @@ namespace Unity.Netcode.RuntimeTests
}
[UnityTest]
public IEnumerator TestOwnershipCallbacks()
public IEnumerator TestOwnershipCallbacks([Values] OwnershipChecks ownershipChecks)
{
m_OwnershipObject = SpawnObject(m_OwnershipPrefab, m_ServerNetworkManager);
m_OwnershipNetworkObject = m_OwnershipObject.GetComponent<NetworkObject>();
@@ -109,7 +115,17 @@ namespace Unity.Netcode.RuntimeTests
serverComponent.ResetFlags();
clientComponent.ResetFlags();
serverObject.ChangeOwnership(NetworkManager.ServerClientId);
if (ownershipChecks == OwnershipChecks.Change)
{
// Validates that when ownership is changed back to the server it will get an OnGainedOwnership notification
serverObject.ChangeOwnership(NetworkManager.ServerClientId);
}
else
{
// Validates that when ownership is removed the server gets an OnGainedOwnership notification
serverObject.RemoveOwnership();
}
yield return s_DefaultWaitForTick;
Assert.That(serverComponent.OnGainedOwnershipFired);
@@ -125,7 +141,7 @@ namespace Unity.Netcode.RuntimeTests
/// Verifies that switching ownership between several clients works properly
/// </summary>
[UnityTest]
public IEnumerator TestOwnershipCallbacksSeveralClients()
public IEnumerator TestOwnershipCallbacksSeveralClients([Values] OwnershipChecks ownershipChecks)
{
// Build our message hook entries tables so we can determine if all clients received spawn or ownership messages
var messageHookEntriesForSpawn = new List<MessageHookEntry>();
@@ -247,8 +263,17 @@ namespace Unity.Netcode.RuntimeTests
previousClientComponent = currentClientComponent;
}
// Now change ownership back to the server
serverObject.ChangeOwnership(NetworkManager.ServerClientId);
if (ownershipChecks == OwnershipChecks.Change)
{
// Validates that when ownership is changed back to the server it will get an OnGainedOwnership notification
serverObject.ChangeOwnership(NetworkManager.ServerClientId);
}
else
{
// Validates that when ownership is removed the server gets an OnGainedOwnership notification
serverObject.RemoveOwnership();
}
yield return WaitForConditionOrTimeOut(ownershipMessageHooks);
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for all clients to receive the {nameof(ChangeOwnershipMessage)} message (back to server).");
@@ -269,5 +294,69 @@ namespace Unity.Netcode.RuntimeTests
}
serverComponent.ResetFlags();
}
private const int k_NumberOfSpawnedObjects = 5;
private bool AllClientsHaveCorrectObjectCount()
{
foreach (var clientNetworkManager in m_ClientNetworkManagers)
{
if (clientNetworkManager.LocalClient.OwnedObjects.Count < k_NumberOfSpawnedObjects)
{
return false;
}
}
return true;
}
private bool ServerHasCorrectClientOwnedObjectCount()
{
// Only check when we are the host
if (m_ServerNetworkManager.IsHost)
{
if (m_ServerNetworkManager.LocalClient.OwnedObjects.Count < k_NumberOfSpawnedObjects)
{
return false;
}
}
foreach (var connectedClient in m_ServerNetworkManager.ConnectedClients)
{
if (connectedClient.Value.OwnedObjects.Count < k_NumberOfSpawnedObjects)
{
return false;
}
}
return true;
}
[UnityTest]
public IEnumerator TestOwnedObjectCounts()
{
if (m_ServerNetworkManager.IsHost)
{
for (int i = 0; i < 5; i++)
{
SpawnObject(m_OwnershipPrefab, m_ServerNetworkManager);
}
}
foreach (var clientNetworkManager in m_ClientNetworkManagers)
{
for (int i = 0; i < 5; i++)
{
SpawnObject(m_OwnershipPrefab, clientNetworkManager);
}
}
yield return WaitForConditionOrTimeOut(AllClientsHaveCorrectObjectCount);
AssertOnTimeout($"Not all clients spawned {k_NumberOfSpawnedObjects} {nameof(NetworkObject)}s!");
yield return WaitForConditionOrTimeOut(ServerHasCorrectClientOwnedObjectCount);
AssertOnTimeout($"Server does not have the correct count for all clients spawned {k_NumberOfSpawnedObjects} {nameof(NetworkObject)}s!");
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using Unity.Netcode.Components;
using Unity.Netcode.TestHelpers.Runtime;
@@ -378,7 +379,7 @@ namespace Unity.Netcode.RuntimeTests
{
var success = WaitForConditionOrTimeOutWithTimeTravel(AllInstancesKeptLocalTransformValues);
//TimeTravelToNextTick();
var infoMessage = new System.Text.StringBuilder($"Timed out waiting for all children to have the correct local space values:\n");
var infoMessage = new StringBuilder($"Timed out waiting for all children to have the correct local space values:\n");
var authorityObjectLocalPosition = m_AuthorityChildObject.transform.localPosition;
var authorityObjectLocalRotation = m_AuthorityChildObject.transform.localRotation.eulerAngles;
var authorityObjectLocalScale = m_AuthorityChildObject.transform.localScale;
@@ -567,9 +568,9 @@ namespace Unity.Netcode.RuntimeTests
}
}
// The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime
// Note: this was reduced from 8 iterations to 3 due to the number of tests based on all of the various parameter combinations
// The number of iterations to change position, rotation, and scale for NetworkTransformMultipleChangesOverTime
private const int k_PositionRotationScaleIterations = 3;
private const int k_PositionRotationScaleIterations3Axis = 8;
protected override void OnNewClientCreated(NetworkManager networkManager)
{
@@ -594,22 +595,69 @@ namespace Unity.Netcode.RuntimeTests
private Axis m_CurrentAxis;
private bool m_AxisExcluded;
/// <summary>
/// Randomly determine if an axis should be excluded.
/// If so, then randomly pick one of the axis to be excluded.
/// </summary>
private Vector3 RandomlyExcludeAxis(Vector3 delta)
{
if (Random.Range(0.0f, 1.0f) >= 0.5f)
{
m_AxisExcluded = true;
var axisToIgnore = Random.Range(0, 2);
switch (axisToIgnore)
{
case 0:
{
delta.x = 0;
break;
}
case 1:
{
delta.y = 0;
break;
}
case 2:
{
delta.z = 0;
break;
}
}
}
return delta;
}
/// <summary>
/// This validates that multiple changes can occur within the same tick or over
/// several ticks while still keeping non-authoritative instances synchronized.
/// </summary>
/// <remarks>
/// When testing < 3 axis: Interpolation is disabled and only 3 delta updates are applied per unique test
/// When testing 3 axis: Interpolation is enabled, sometimes an axis is intentionally excluded during a
/// delta update, and it runs through 8 delta updates per unique test.
/// </remarks>
[Test]
public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] OverrideState overideState,
[Values] Precision precision, [Values] Rotation rotationSynch, [Values] Axis axis)
{
// In the name of reducing the very long time it takes to interpolate and run all of the possible combinations,
// we only interpolate when the second client joins
m_AuthoritativeTransform.Interpolate = false;
m_AuthoritativeTransform.InLocalSpace = testLocalTransform == TransformSpace.Local;
bool axisX = axis == Axis.X || axis == Axis.XY || axis == Axis.XZ || axis == Axis.XYZ;
bool axisY = axis == Axis.Y || axis == Axis.XY || axis == Axis.YZ || axis == Axis.XYZ;
bool axisZ = axis == Axis.Z || axis == Axis.XZ || axis == Axis.YZ || axis == Axis.XYZ;
var axisCount = axisX ? 1 : 0;
axisCount += axisY ? 1 : 0;
axisCount += axisZ ? 1 : 0;
// Enable interpolation when all 3 axis are selected to make sure we are synchronizing properly
// when interpolation is enabled.
m_AuthoritativeTransform.Interpolate = axisCount == 3 ? true : false;
m_CurrentAxis = axis;
// Authority dictates what is synchronized and what the precision is going to be
// so we only need to set this on the authoritative side.
m_AuthoritativeTransform.UseHalfFloatPrecision = precision == Precision.Half;
@@ -640,29 +688,49 @@ namespace Unity.Netcode.RuntimeTests
m_AuthoritativeTransform.SyncScaleY = axisY;
m_AuthoritativeTransform.SyncScaleZ = axisZ;
var positionStart = GetRandomVector3(0.25f, 1.75f);
var rotationStart = GetRandomVector3(1f, 15f);
var scaleStart = GetRandomVector3(0.25f, 2.0f);
var position = positionStart;
var rotation = rotationStart;
var scale = scaleStart;
var success = false;
m_AuthoritativeTransform.StatePushed = false;
// Wait for the deltas to be pushed
WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed);
// Allow the precision settings to propagate first as changing precision
// causes a teleport event to occur
WaitForNextTick();
var iterations = axisCount == 3 ? k_PositionRotationScaleIterations3Axis : k_PositionRotationScaleIterations;
// Move and rotate within the same tick, validate the non-authoritative instance updates
// to each set of changes. Repeat several times.
for (int i = 0; i < k_PositionRotationScaleIterations; i++)
for (int i = 0; i < iterations; i++)
{
// Always reset this per delta update pass
m_AxisExcluded = false;
var deltaPositionDelta = GetRandomVector3(-1.5f, 1.5f);
var deltaRotationDelta = GetRandomVector3(-3.5f, 3.5f);
var deltaScaleDelta = GetRandomVector3(-0.5f, 0.5f);
m_NonAuthoritativeTransform.StateUpdated = false;
m_AuthoritativeTransform.StatePushed = false;
position = positionStart * i;
rotation = rotationStart * i;
scale = scaleStart * i;
// With two or more axis, excluding one of them while chaging another will validate that
// full precision updates are maintaining their target state value(s) to interpolate towards
if (axisCount == 3)
{
position += RandomlyExcludeAxis(deltaPositionDelta);
rotation += RandomlyExcludeAxis(deltaRotationDelta);
scale += RandomlyExcludeAxis(deltaScaleDelta);
}
else
{
position += deltaPositionDelta;
rotation += deltaRotationDelta;
scale += deltaScaleDelta;
}
// Apply delta between ticks
MoveRotateAndScaleAuthority(position, rotation, scale, overideState);
@@ -670,54 +738,37 @@ namespace Unity.Netcode.RuntimeTests
// Wait for the deltas to be pushed
Assert.True(WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed && m_NonAuthoritativeTransform.StateUpdated), $"[Non-Interpolate {i}] Timed out waiting for state to be pushed ({m_AuthoritativeTransform.StatePushed}) or state to be updated ({m_NonAuthoritativeTransform.StateUpdated})!");
// Wait for deltas to synchronize on non-authoritative side
var success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
// Provide additional debug info about what failed (if it fails)
if (!success)
// For 3 axis, we will skip validating that the non-authority interpolates to its target point at least once.
// This will validate that non-authoritative updates are maintaining their target state axis values if only 2
// of the axis are being updated to assure interpolation maintains the targeted axial value per axis.
// For 2 and 1 axis tests we always validate per delta update
if (m_AxisExcluded || axisCount < 3)
{
m_EnableVerboseDebug = true;
PositionRotationScaleMatches();
m_EnableVerboseDebug = false;
// Wait for deltas to synchronize on non-authoritative side
success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
// Provide additional debug info about what failed (if it fails)
if (!success)
{
m_EnableVerboseDebug = true;
success = PositionRotationScaleMatches();
m_EnableVerboseDebug = false;
}
Assert.True(success, $"[Non-Interpolate {i}] Timed out waiting for non-authority to match authority's position or rotation");
}
Assert.True(success, $"[Non-Interpolate {i}] Timed out waiting for non-authority to match authority's position or rotation");
}
// Only enable interpolation when all axis are set (to reduce the test times)
if (axis == Axis.XYZ)
if (axisCount == 3)
{
// Now, enable interpolation
m_AuthoritativeTransform.Interpolate = true;
m_NonAuthoritativeTransform.StateUpdated = false;
m_AuthoritativeTransform.StatePushed = false;
// Wait for the delta (change in interpolation) to be pushed
var success = WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed && m_NonAuthoritativeTransform.StateUpdated);
Assert.True(success, $"[Interpolation Enable] Timed out waiting for state to be pushed ({m_AuthoritativeTransform.StatePushed}) or state to be updated ({m_NonAuthoritativeTransform.StateUpdated})!");
// Continue for one more update with interpolation enabled
// Note: We are just verifying one update with interpolation enabled due to the number of tests this integration test has to run
// and since the NestedNetworkTransformTests already tests interpolation under the same number of conditions (excluding Axis).
// This is just to verify selecting specific axis doesn't cause issues when interpolating as well.
m_NonAuthoritativeTransform.StateUpdated = false;
m_AuthoritativeTransform.StatePushed = false;
position = positionStart * k_PositionRotationScaleIterations;
rotation = rotationStart * k_PositionRotationScaleIterations;
scale = scaleStart * k_PositionRotationScaleIterations;
MoveRotateAndScaleAuthority(position, rotation, scale, overideState);
// Wait for the deltas to be pushed and updated
success = WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed && m_NonAuthoritativeTransform.StateUpdated);
Assert.True(success, $"[Interpolation {k_PositionRotationScaleIterations}] Timed out waiting for state to be pushed ({m_AuthoritativeTransform.StatePushed}) or state to be updated ({m_NonAuthoritativeTransform.StateUpdated})!");
success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches, 120);
// As a final test, wait for deltas to synchronize on non-authoritative side to assure it interpolates to th
success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
// Provide additional debug info about what failed (if it fails)
if (!success)
{
m_EnableVerboseDebug = true;
PositionRotationScaleMatches();
success = PositionRotationScaleMatches();
m_EnableVerboseDebug = false;
}
Assert.True(success, $"[Interpolation {k_PositionRotationScaleIterations}] Timed out waiting for non-authority to match authority's position or rotation");
Assert.True(success, $"Timed out waiting for non-authority to match authority's position or rotation");
}
}

10
ValidationExceptions.json Normal file
View File

@@ -0,0 +1,10 @@
{
"ErrorExceptions": [
{
"ValidationTest": "API Validation",
"ExceptionMessage": "Additions require a new minor or major version.",
"PackageVersion": "1.5.2"
}
],
"WarningExceptions": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2a43005be301c9043aab7034757d4868
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -2,23 +2,23 @@
"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": "1.5.1",
"version": "1.5.2",
"unity": "2020.3",
"dependencies": {
"com.unity.nuget.mono-cecil": "1.10.1",
"com.unity.transport": "1.3.4"
},
"_upm": {
"changelog": "### Added\n\n- Added support for serializing `NativeArray<>` and `NativeList<>` in `FastBufferReader`/`FastBufferWriter`, `BufferSerializer`, `NetworkVariable`, and RPCs. (To use `NativeList<>`, add `UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT` to your Scripting Define Symbols in `Project Settings > Player`) (#2375)\n- The location of the automatically-created default network prefab list can now be configured (#2544)\n- Added: Message size limits (max single message and max fragmented message) can now be set using NetworkManager.MaximumTransmissionUnitSize and NetworkManager.MaximumFragmentedMessageSize for transports that don't work with the default values (#2530)\n- Added `NetworkObject.SpawnWithObservers` property (default is true) that when set to false will spawn a `NetworkObject` with no observers and will not be spawned on any client until `NetworkObject.NetworkShow` is invoked. (#2568)\n\n### Fixed\n\n- Fixed: Fixed a null reference in codegen in some projects (#2581)\n- Fixed issue where the `OnClientDisconnected` client identifier was incorrect after a pending client connection was denied. (#2569)\n- Fixed warning \"Runtime Network Prefabs was not empty at initialization time.\" being erroneously logged when no runtime network prefabs had been added (#2565)\n- Fixed issue where some temporary debug console logging was left in a merged PR. (#2562)\n- Fixed the \"Generate Default Network Prefabs List\" setting not loading correctly and always reverting to being checked. (#2545)\n- Fixed issue where users could not use NetworkSceneManager.VerifySceneBeforeLoading to exclude runtime generated scenes from client synchronization. (#2550)\n- Fixed missing value on `NetworkListEvent` for `EventType.RemoveAt` events. (#2542,#2543)\n- Fixed issue where parenting a NetworkTransform under a transform with a scale other than Vector3.one would result in incorrect values on non-authoritative instances. (#2538)\n- Fixed issue where a server would include scene migrated and then despawned NetworkObjects to a client that was being synchronized. (#2532)\n- Fixed the inspector throwing exceptions when attempting to render `NetworkVariable`s of enum types. (#2529)\n- Making a `NetworkVariable` with an `INetworkSerializable` type that doesn't meet the `new()` constraint will now create a compile-time error instead of an editor crash (#2528)\n- Fixed Multiplayer Tools package installation docs page link on the NetworkManager popup. (#2526)\n- Fixed an exception and error logging when two different objects are shown and hidden on the same frame (#2524)\n- Fixed a memory leak in `UnityTransport` that occurred if `StartClient` failed. (#2518)\n- Fixed issue where a client could throw an exception if abruptly disconnected from a network session with one or more spawned `NetworkObject`(s). (#2510)\n- Fixed issue where invalid endpoint addresses were not being detected and returning false from NGO UnityTransport. (#2496)\n- Fixed some errors that could occur if a connection is lost and the loss is detected when attempting to write to the socket. (#2495)\n\n## Changed\n\n- Adding network prefabs before NetworkManager initialization is now supported. (#2565)\n- Connecting clients being synchronized now switch to the server's active scene before spawning and synchronizing NetworkObjects. (#2532)\n- Updated `UnityTransport` dependency on `com.unity.transport` to 1.3.4. (#2533)\n- Improved performance of NetworkBehaviour initialization by replacing reflection when initializing NetworkVariables with compile-time code generation, which should help reduce hitching during additive scene loads. (#2522)"
"changelog": "### Added\n\n### Fixed\n\n- Fixed issue where `NetworkClient.OwnedObjects` was not returning any owned objects due to the `NetworkClient.IsConnected` not being properly set. (#2631)\n- Fixed a crash when calling TrySetParent with a null Transform (#2625)\n- Fixed issue where a `NetworkTransform` using full precision state updates was losing transform state updates when interpolation was enabled. (#2624)\n- Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored for late joining clients. (#2623)\n- Fixed issue where invoking `NetworkManager.Shutdown` multiple times, depending upon the timing, could cause an exception. (#2622)\n- Fixed issue where removing ownership would not notify the server that it gained ownership. This also resolves the issue where an owner authoritative NetworkTransform would not properly initialize upon removing ownership from a remote client. (#2618)\n- Fixed ILPP issues when using CoreCLR and for certain dedicated server builds. (#2614)\n- Fixed an ILPP compile error when creating a generic NetworkBehaviour singleton with a static T instance. (#2603)\n\n### Changed"
},
"upmCi": {
"footprint": "35c5325acc3edf18c37ef8c9d19e0944fae0d42a"
"footprint": "e7549ba358ade416ab85285cdf53c5a6aac35cef"
},
"documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@1.5/manual/index.html",
"repository": {
"url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git",
"type": "git",
"revision": "7a969f89d6dda65ac373ce552c0c997c9116f21a"
"revision": "36368846c5bfe6cfb93adc36282507614955955c"
},
"samples": [
{