Skip to content

Commit c59035b

Browse files
fix: instantiate, spawn, and parent when spawning [Backport] (#3403)
This PR resolves the issue where instantiating and spawning a `NetworkObject` (A) during another `NetworkObject`'s spawn process (B) and then parenting the newly spawned `NetworkObject` (A) under the `NetworkObject` (B) would end up causing the parenting message to not properly defer the parenting message until the parent (B) was fully spawned on other already connected and synchronized clients (it would properly parent if another client late joined shortly after). [MTTB-1209](https://jira.unity3d.com/browse/MTTB-1209) ## Changelog - Fixed: Issue where during a `NetworkObject` 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. ## Testing and Documentation - Includes new integration ParentingDuringSpawnTests test. - No documentation changes or additions were necessary. <!-- Uncomment and mark items off with a * if this PR deprecates any API: ### Deprecated API - [ ] An `[Obsolete]` attribute was added along with a `(RemovedAfter yyyy-mm-dd)` entry. - [ ] An [api updater] was added. - [ ] Deprecation of the API is explained in the CHANGELOG. - [ ] The users can understand why this API was removed and what they should use instead. --> ## Backport #3401
1 parent c23350b commit c59035b

File tree

6 files changed

+200
-4
lines changed

6 files changed

+200
-4
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1616

1717
### Fixed
1818

19+
- 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)
1920
- 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)
2021
- 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)
2122
- Fixed issue where `NetworkAnimator` would log an error if there was no destination transition information. (#3384)

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ParentSyncMessage.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int
8888
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
8989
return false;
9090
}
91+
92+
// If the target parent does not exist, then defer this message until it does.
93+
if (LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(LatestParent.Value))
94+
{
95+
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, LatestParent.Value, reader, ref context);
96+
return false;
97+
}
98+
9199
return true;
92100
}
93101

com.unity.netcode.gameobjects/TestHelpers/Runtime/IntegrationTestWithApproximation.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@ public abstract class IntegrationTestWithApproximation : NetcodeIntegrationTest
88
{
99
private const float k_AproximateDeltaVariance = 0.01f;
1010

11+
/// <summary>
12+
/// Returns a <see cref="Vector3"/> as a formatted string.
13+
/// </summary>
14+
/// <param name="vector3">reference of <see cref="Vector3"/> to return as a formatted string.</param>
15+
/// <returns><see cref="string"/></returns>
16+
protected string GetVector3Values(ref Vector3 vector3)
17+
{
18+
return $"({vector3.x:F6},{vector3.y:F6},{vector3.z:F6})";
19+
}
20+
21+
/// <summary>
22+
/// Returns a <see cref="Vector3"/> as a formatted string.
23+
/// </summary>
24+
/// <param name="vector3"><see cref="Vector3"/> to return as a formatted string.</param>
25+
/// <returns><see cref="string"/></returns>
26+
protected string GetVector3Values(Vector3 vector3)
27+
{
28+
return GetVector3Values(ref vector3);
29+
}
30+
1131
protected virtual float GetDeltaVarianceThreshold()
1232
{
1333
return k_AproximateDeltaVariance;
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using NUnit.Framework;
5+
using Unity.Netcode.TestHelpers.Runtime;
6+
using UnityEngine;
7+
using UnityEngine.TestTools;
8+
9+
namespace Unity.Netcode.RuntimeTests
10+
{
11+
[TestFixture(NetworkSpawnTypes.OnNetworkSpawn)]
12+
[TestFixture(NetworkSpawnTypes.OnNetworkPostSpawn)]
13+
internal class ParentingDuringSpawnTests : IntegrationTestWithApproximation
14+
{
15+
protected override int NumberOfClients => 2;
16+
17+
public enum NetworkSpawnTypes
18+
{
19+
OnNetworkSpawn,
20+
OnNetworkPostSpawn,
21+
}
22+
23+
private NetworkSpawnTypes m_NetworkSpawnType;
24+
25+
private GameObject m_ParentPrefab;
26+
private GameObject m_ChildPrefab;
27+
private NetworkObject m_AuthorityInstance;
28+
private List<NetworkManager> m_NetworkManagers = new List<NetworkManager>();
29+
private StringBuilder m_Errors = new StringBuilder();
30+
31+
public class ParentDuringSpawnBehaviour : NetworkBehaviour
32+
{
33+
public GameObject ChildToSpawn;
34+
35+
public NetworkSpawnTypes NetworkSpawnType;
36+
37+
public Transform ChildSpawnPoint;
38+
39+
private void SpawnThenParent()
40+
{
41+
var child = NetworkObject.InstantiateAndSpawn(ChildToSpawn, NetworkManager, position: ChildSpawnPoint.position, rotation: ChildSpawnPoint.rotation);
42+
if (!child.TrySetParent(NetworkObject))
43+
{
44+
var errorMessage = $"[{ChildToSpawn}] Failed to parent child {child.name} under parent {gameObject.name}!";
45+
Debug.LogError(errorMessage);
46+
}
47+
}
48+
49+
public override void OnNetworkSpawn()
50+
{
51+
if (IsServer && NetworkSpawnType == NetworkSpawnTypes.OnNetworkSpawn)
52+
{
53+
SpawnThenParent();
54+
}
55+
56+
base.OnNetworkSpawn();
57+
}
58+
59+
protected override void OnNetworkPostSpawn()
60+
{
61+
if (IsServer && NetworkSpawnType == NetworkSpawnTypes.OnNetworkPostSpawn)
62+
{
63+
SpawnThenParent();
64+
}
65+
base.OnNetworkPostSpawn();
66+
}
67+
}
68+
69+
public ParentingDuringSpawnTests(NetworkSpawnTypes networkSpawnType) : base()
70+
{
71+
m_NetworkSpawnType = networkSpawnType;
72+
}
73+
74+
protected override void OnServerAndClientsCreated()
75+
{
76+
m_ParentPrefab = CreateNetworkObjectPrefab("Parent");
77+
m_ChildPrefab = CreateNetworkObjectPrefab("Child");
78+
var parentComponet = m_ParentPrefab.AddComponent<ParentDuringSpawnBehaviour>();
79+
parentComponet.ChildToSpawn = m_ChildPrefab;
80+
var spawnPoint = new GameObject();
81+
parentComponet.ChildSpawnPoint = spawnPoint.transform;
82+
parentComponet.ChildSpawnPoint.position = GetRandomVector3(-5.0f, 5.0f);
83+
var rotation = parentComponet.ChildSpawnPoint.rotation;
84+
rotation.eulerAngles = GetRandomVector3(-180.0f, 180.0f);
85+
parentComponet.ChildSpawnPoint.rotation = rotation;
86+
base.OnServerAndClientsCreated();
87+
}
88+
89+
private bool NonAuthorityInstancesSpawnedParent()
90+
{
91+
foreach (var networkManager in m_NetworkManagers)
92+
{
93+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_AuthorityInstance.NetworkObjectId))
94+
{
95+
return false;
96+
}
97+
}
98+
return true;
99+
}
100+
101+
private bool NonAuthorityInstancesParentedChild()
102+
{
103+
m_Errors.Clear();
104+
if (m_AuthorityInstance.transform.childCount == 0)
105+
{
106+
return false;
107+
}
108+
var authorityChildObject = m_AuthorityInstance.transform.GetChild(0).GetComponent<NetworkObject>();
109+
110+
foreach (var networkManager in m_NetworkManagers)
111+
{
112+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(authorityChildObject.NetworkObjectId))
113+
{
114+
m_Errors.AppendLine($"{networkManager.name} has not spawned the child {authorityChildObject.name}!");
115+
return false;
116+
}
117+
var childObject = networkManager.SpawnManager.SpawnedObjects[authorityChildObject.NetworkObjectId];
118+
119+
if (childObject.transform.parent == null)
120+
{
121+
m_Errors.AppendLine($"{childObject.name} does not have a parent!");
122+
return false;
123+
}
124+
125+
if (!Approximately(authorityChildObject.transform.position, childObject.transform.position))
126+
{
127+
m_Errors.AppendLine($"{childObject.name} position {GetVector3Values(childObject.transform.position)} does " +
128+
$"not match the authority's position {GetVector3Values(authorityChildObject.transform.position)}!");
129+
return false;
130+
}
131+
132+
if (!Approximately(authorityChildObject.transform.rotation, childObject.transform.rotation))
133+
{
134+
m_Errors.AppendLine($"{childObject.name} rotation {GetVector3Values(childObject.transform.rotation.eulerAngles)} does " +
135+
$"not match the authority's position {GetVector3Values(authorityChildObject.transform.rotation.eulerAngles)}!");
136+
return false;
137+
}
138+
}
139+
return true;
140+
}
141+
142+
[UnityTest]
143+
public IEnumerator ParentDuringSpawn()
144+
{
145+
m_NetworkManagers.Clear();
146+
var authorityNetworkManager = m_ServerNetworkManager;
147+
148+
m_NetworkManagers.AddRange(m_ClientNetworkManagers);
149+
m_NetworkManagers.Add(m_ServerNetworkManager);
150+
151+
m_AuthorityInstance = SpawnObject(m_ParentPrefab, authorityNetworkManager).GetComponent<NetworkObject>();
152+
153+
yield return WaitForConditionOrTimeOut(NonAuthorityInstancesSpawnedParent);
154+
AssertOnTimeout($"Not all clients spawned the parent {nameof(NetworkObject)}!");
155+
156+
yield return WaitForConditionOrTimeOut(NonAuthorityInstancesParentedChild);
157+
AssertOnTimeout($"Non-Authority instance had a mismatched value: \n {m_Errors}");
158+
}
159+
}
160+
}

com.unity.netcode.gameobjects/Tests/Runtime/ParentingDuringSpawnTests.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

testproject/Assets/Tests/Runtime/NetworkTransform/NestedNetworkTransformTests.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,6 @@ protected override float GetDeltaVarianceThreshold()
181181

182182
private StringBuilder m_ValidationErrors;
183183

184-
private string GetVector3Values(ref Vector3 vector3)
185-
{
186-
return $"({vector3.x:F6},{vector3.y:F6},{vector3.z:F6})";
187-
}
188184

189185
/// <summary>
190186
/// Validates all transform instance values match the authority's

0 commit comments

Comments
 (0)