Skip to content

Commit 64c4331

Browse files
NoelStephensUnityJesse Olmernetcode-ci-service
authored
fix: Parenting does not preserve WorldPositionStays option and other parenting related issues (#2146)
* fix Parenting: Fixed the issue where WorldPositionStays was not being synchronized with clients. Removed m_IsReparented and associated properties. Added world position stays parameter to CreateLocalNetworkObject to preserve worldPostionStays. This fixes some edge case scenarios and issues with parenting in-scene placed NetworkObjects. One in particular is to provide the local space position and rotation in ParentSyncMessage if the WorldPositionStays value is false. The server might maintain the original local space values and upon being reparented it re-applies the local space values. This is only pertinent if the user decides they don't want to use a NetworkTransform for the NetworkObject in question. Now, a users can control what the final position, rotation, and scale of a NetworkObject is when being added/removed to/from a parent's sibling list ("parented or de-parented"). Parenting and Scale: Fixed issue where GetMessageSceneObject wasn't using the NetworkObject's lossy scale value. This would result in improper scaling of parented (WorldPositionStays = true) child NetworkObjects for late joining clients. Added comments with details on this. Fixed edge case scenario where nested in-scene placed NetworkObject children are de-parented and then a client late joins. Under this scenario (only for in-scene placed NetworkObjects) we want the late joining client to remove its parent, then set the transform values, and then get spawned. NetworkTransform: Fixed issue where TryCommitTransformToServer was not honoring the NetworkTransform.InLocalSpace property. Fixed issue where teleporting did not honor the NetworkTransforms's current synchronize axis settings. Removed the private m_TickFrequency property as it is no longer being used. * refactor CreateLocalNetworkObject now just accepts the NetworkObject.SceneObject structure as opposed to the 10 properties of the NetworkObject.SceneObject structure passed in as parameters. Provided the ability to invoke NetworkObject.TrySetParent with a null value in order to be able to remove a parent while also selecting whether they want WorldPositionStay to be true or false (when removing a parent WorldPositionStays impacts the final transform values and can lead to scale issue if you parent with WorldPositionStays set to false and then it remove the parent with WorldPositionStays set to true). Consolidated the CreateLocalNetworkObject's assignment of the transform values, parenting, and DDOL migration. Removed parenting from CreateLocalNetworkObject Removed setting the cached parent directly within SpawnNetworkObjectLocallyCommon as this is done within NetworkObject.ApplyNetworkParenting. * add Added RemoveParent to the ParentSync message. Added applying scale to CreateLocalNetworkObject. Added parent child sorting to the SceneEventData's synchronization serialization process. Added the TryRemoveParent helper method to make removing a parent easier. Without this method users will have to use TrySetParent and cast the null value to either GameObject or NetworkObject. * test Added ParentingWorldPositionStaysTest: Testing with and without WorldPositionStays while also varying child position, rotation, and scale values. Validates scale with multi-generation nested children where most of the parents have a scale other than Vector3.one. Created abstract IntegrationTestWithApproximation to avoid replicating the exact same set of Approximately methods. Added ParentingInSceneObjectsTests: This includes all of the scripts and assets that the ParentingInSceneObjectsTests needs. It includes adding the ParentingInSceneObjects scene to the scenes in build list as well. This also verifies an in-scene placed NetworkObject parented under a GameObject will preserve that hierarchy. NetworkTransformStateTest: Updated the bool parameters to be more meaningful named enums so you know what exactly is being tested. Added additional NetworkTansformState.IsTeleportingNextFrame logic to the existing test. Added an additional "step" which verifies the NetworkTransformState is additively collapsing for each detected axial delta over the relative threshold value. Co-authored-by: Jesse Olmer <[email protected]> Co-authored-by: Unity Netcode CI <[email protected]>
1 parent 41ef88b commit 64c4331

25 files changed

+3491
-266
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
1313

1414
- IPv6 is now supported for direct connections when using `UnityTransport`. (#2232)
1515
- Added WebSocket support when using UTP 2.0 with `UseWebSockets` property in the `UnityTransport` component of the `NetworkManager` allowing to pick WebSockets for communication. When building for WebGL, this selection happens automatically. (#2201)
16+
- Added position, rotation, and scale to the `ParentSyncMessage` which provides users the ability to specify the final values on the server-side when `OnNetworkObjectParentChanged` is invoked just before the message is created (when the `Transform` values are applied to the message). (#2146)
17+
- Added `NetworkObject.TryRemoveParent` method for convenience purposes opposed to having to cast null to either `GameObject` or `NetworkObject`. (#2146)
1618

1719
### Changed
1820

@@ -33,6 +35,11 @@ Additional documentation and release notes are available at [Multiplayer Documen
3335
- Implicit conversion of NetworkObjectReference to GameObject will now return null instead of throwing an exception if the referenced object could not be found (i.e., was already despawned) (#2158)
3436
- Fixed warning resulting from a stray NetworkAnimator.meta file (#2153)
3537
- Fixed Connection Approval Timeout not working client side. (#2164)
38+
- Fixed issue where the `WorldPositionStays` parenting parameter was not being synchronized with clients. (#2146)
39+
- Fixed issue where parented in-scene placed `NetworkObject`s would fail for late joining clients. (#2146)
40+
- Fixed issue where scale was not being synchronized which caused issues with nested parenting and scale when `WorldPositionStays` was true. (#2146)
41+
- Fixed issue with `NetworkTransform.ApplyTransformToNetworkStateWithInfo` where it was not honoring axis sync settings when `NetworkTransformState.IsTeleportingNextFrame` was true. (#2146)
42+
- Fixed issue with `NetworkTransform.TryCommitTransformToServer` where it was not honoring the `InLocalSpace` setting. (#2146)
3643
- Fixed ClientRpcs always reporting in the profiler view as going to all clients, even when limited to a subset of clients by `ClientRpcParams`. (#2144)
3744
- Fixed RPC codegen failing to choose the correct extension methods for `FastBufferReader` and `FastBufferWriter` when the parameters were a generic type (i.e., List<int>) and extensions for multiple instantiations of that type have been defined (i.e., List<int> and List<string>) (#2142)
3845
- Fixed the issue where running a server (i.e. not host) the second player would not receive updates (unless a third player joined). (#2127)

com.unity.netcode.gameobjects/Components/NetworkTransform.cs

Lines changed: 21 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -451,19 +451,6 @@ internal NetworkTransformState GetLastSentState()
451451
return m_LastSentState;
452452
}
453453

454-
/// <summary>
455-
/// Calculated when spawned, this is used to offset a newly received non-authority side state by 1 tick duration
456-
/// in order to end the extrapolation for that state's values.
457-
/// </summary>
458-
/// <remarks>
459-
/// Example:
460-
/// NetworkState-A is received, processed, and measurements added
461-
/// NetworkState-A is duplicated (NetworkState-A-Post) and its sent time is offset by the tick frequency
462-
/// One tick later, NetworkState-A-Post is applied to end that delta's extrapolation.
463-
/// <see cref="OnNetworkStateChanged"/> to see how NetworkState-A-Post doesn't get excluded/missed
464-
/// </remarks>
465-
private double m_TickFrequency;
466-
467454
/// <summary>
468455
/// This will try to send/commit the current transform delta states (if any)
469456
/// </summary>
@@ -488,14 +475,16 @@ protected void TryCommitTransformToServer(Transform transformToCommit, double di
488475
}
489476
else // Non-Authority
490477
{
478+
var position = InLocalSpace ? transformToCommit.localPosition : transformToCommit.position;
479+
var rotation = InLocalSpace ? transformToCommit.localRotation : transformToCommit.rotation;
491480
// We are an owner requesting to update our state
492481
if (!m_CachedIsServer)
493482
{
494-
SetStateServerRpc(transformToCommit.position, transformToCommit.rotation, transformToCommit.localScale, false);
483+
SetStateServerRpc(position, rotation, transformToCommit.localScale, false);
495484
}
496485
else // Server is always authoritative (including owner authoritative)
497486
{
498-
SetStateClientRpc(transformToCommit.position, transformToCommit.rotation, transformToCommit.localScale, false);
487+
SetStateClientRpc(position, rotation, transformToCommit.localScale, false);
499488
}
500489
}
501490
}
@@ -521,37 +510,24 @@ private void TryCommitTransform(Transform transformToCommit, double dirtyTime)
521510
}
522511
}
523512

513+
/// <summary>
514+
/// Initializes the interpolators with the current transform values
515+
/// </summary>
524516
private void ResetInterpolatedStateToCurrentAuthoritativeState()
525517
{
526518
var serverTime = NetworkManager.ServerTime.Time;
527-
528-
// TODO: Look into a better way to communicate the entire state for late joining clients.
529-
// Since the replicated network state will just be the most recent deltas and not the entire state.
530-
//m_PositionXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionX, serverTime);
531-
//m_PositionYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionY, serverTime);
532-
//m_PositionZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionZ, serverTime);
533-
534-
//m_RotationInterpolator.ResetTo(Quaternion.Euler(m_LocalAuthoritativeNetworkState.RotAngleX, m_LocalAuthoritativeNetworkState.RotAngleY, m_LocalAuthoritativeNetworkState.RotAngleZ), serverTime);
535-
536-
//m_ScaleXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleX, serverTime);
537-
//m_ScaleYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleY, serverTime);
538-
//m_ScaleZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleZ, serverTime);
539-
540-
// NOTE ABOUT THIS CHANGE:
541-
// !!! This will exclude any scale changes because we currently do not spawn network objects with scale !!!
542-
// Regarding Scale: It will be the same scale as the default scale for the object being spawned.
543519
var position = InLocalSpace ? transform.localPosition : transform.position;
544520
m_PositionXInterpolator.ResetTo(position.x, serverTime);
545521
m_PositionYInterpolator.ResetTo(position.y, serverTime);
546522
m_PositionZInterpolator.ResetTo(position.z, serverTime);
523+
547524
var rotation = InLocalSpace ? transform.localRotation : transform.rotation;
548525
m_RotationInterpolator.ResetTo(rotation, serverTime);
549526

550-
// TODO: (Create Jira Ticket) Synchronize local scale during NetworkObject synchronization
551-
// (We will probably want to byte pack TransformData to offset the 3 float addition)
552-
m_ScaleXInterpolator.ResetTo(transform.localScale.x, serverTime);
553-
m_ScaleYInterpolator.ResetTo(transform.localScale.y, serverTime);
554-
m_ScaleZInterpolator.ResetTo(transform.localScale.z, serverTime);
527+
var scale = transform.localScale;
528+
m_ScaleXInterpolator.ResetTo(scale.x, serverTime);
529+
m_ScaleYInterpolator.ResetTo(scale.y, serverTime);
530+
m_ScaleZInterpolator.ResetTo(scale.z, serverTime);
555531
}
556532

557533
/// <summary>
@@ -602,63 +578,63 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw
602578
isDirty = true;
603579
}
604580

605-
if (SyncPositionX && Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame)
581+
if (SyncPositionX && (Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame))
606582
{
607583
networkState.PositionX = position.x;
608584
networkState.HasPositionX = true;
609585
isPositionDirty = true;
610586
}
611587

612-
if (SyncPositionY && Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold || networkState.IsTeleportingNextFrame)
588+
if (SyncPositionY && (Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold || networkState.IsTeleportingNextFrame))
613589
{
614590
networkState.PositionY = position.y;
615591
networkState.HasPositionY = true;
616592
isPositionDirty = true;
617593
}
618594

619-
if (SyncPositionZ && Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold || networkState.IsTeleportingNextFrame)
595+
if (SyncPositionZ && (Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold || networkState.IsTeleportingNextFrame))
620596
{
621597
networkState.PositionZ = position.z;
622598
networkState.HasPositionZ = true;
623599
isPositionDirty = true;
624600
}
625601

626-
if (SyncRotAngleX && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame)
602+
if (SyncRotAngleX && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame))
627603
{
628604
networkState.RotAngleX = rotAngles.x;
629605
networkState.HasRotAngleX = true;
630606
isRotationDirty = true;
631607
}
632608

633-
if (SyncRotAngleY && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame)
609+
if (SyncRotAngleY && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame))
634610
{
635611
networkState.RotAngleY = rotAngles.y;
636612
networkState.HasRotAngleY = true;
637613
isRotationDirty = true;
638614
}
639615

640-
if (SyncRotAngleZ && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame)
616+
if (SyncRotAngleZ && (Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame))
641617
{
642618
networkState.RotAngleZ = rotAngles.z;
643619
networkState.HasRotAngleZ = true;
644620
isRotationDirty = true;
645621
}
646622

647-
if (SyncScaleX && Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame)
623+
if (SyncScaleX && (Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame))
648624
{
649625
networkState.ScaleX = scale.x;
650626
networkState.HasScaleX = true;
651627
isScaleDirty = true;
652628
}
653629

654-
if (SyncScaleY && Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame)
630+
if (SyncScaleY && (Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame))
655631
{
656632
networkState.ScaleY = scale.y;
657633
networkState.HasScaleY = true;
658634
isScaleDirty = true;
659635
}
660636

661-
if (SyncScaleZ && Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame)
637+
if (SyncScaleZ && (Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame))
662638
{
663639
networkState.ScaleZ = scale.z;
664640
networkState.HasScaleZ = true;
@@ -1007,7 +983,6 @@ public override void OnNetworkSpawn()
1007983
{
1008984
m_CachedIsServer = IsServer;
1009985
m_CachedNetworkManager = NetworkManager;
1010-
m_TickFrequency = 1.0 / NetworkManager.NetworkConfig.TickRate;
1011986

1012987
Initialize();
1013988

com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,14 +2070,31 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe
20702070

20712071
if (response.CreatePlayerObject)
20722072
{
2073-
var networkObject = SpawnManager.CreateLocalNetworkObject(
2074-
isSceneObject: false,
2075-
response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash,
2076-
ownerClientId,
2077-
parentNetworkId: null,
2078-
networkSceneHandle: null,
2079-
response.Position,
2080-
response.Rotation);
2073+
var playerPrefabHash = response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
2074+
2075+
// Generate a SceneObject for the player object to spawn
2076+
var sceneObject = new NetworkObject.SceneObject
2077+
{
2078+
Header = new NetworkObject.SceneObject.HeaderData
2079+
{
2080+
IsPlayerObject = true,
2081+
OwnerClientId = ownerClientId,
2082+
IsSceneObject = false,
2083+
HasTransform = true,
2084+
Hash = playerPrefabHash,
2085+
},
2086+
TargetClientId = ownerClientId,
2087+
Transform = new NetworkObject.SceneObject.TransformData
2088+
{
2089+
Position = response.Position.GetValueOrDefault(),
2090+
Rotation = response.Rotation.GetValueOrDefault()
2091+
}
2092+
};
2093+
2094+
// Create the player NetworkObject locally
2095+
var networkObject = SpawnManager.CreateLocalNetworkObject(sceneObject);
2096+
2097+
// Spawn the player NetworkObject locally
20812098
SpawnManager.SpawnNetworkObjectLocally(
20822099
networkObject,
20832100
SpawnManager.GetNetworkObjectId(),

0 commit comments

Comments
 (0)