Skip to content

Commit f2e776e

Browse files
fix: instantiate, spawn, and parent when spawning (#3401)
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) fix: #3100 ## 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 This requires a backport to v1.x as the `ParentSyncMessage` does not defer the message if the parent `NetworkObject` is not spawned yet.
1 parent f6a6c1f commit f2e776e

File tree

4 files changed

+179
-3
lines changed

4 files changed

+179
-3
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

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

1414
### Fixed
1515

16+
- 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. (#3401)
1617

1718
### Changed
1819

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ internal struct ParentSyncMessage : INetworkMessage
66
{
77
public int Version => 0;
88

9-
private const string k_Name = "DestroyObjectMessage";
9+
private const string k_Name = "ParentSyncMessage";
1010

1111
public ulong NetworkObjectId;
1212

@@ -87,12 +87,20 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int
8787
reader.ReadValueSafe(out Rotation);
8888
reader.ReadValueSafe(out Scale);
8989

90-
// If the target NetworkObject does not exist =or= the target latest parent does not exist then defer the message
91-
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId) || (LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(LatestParent.Value)))
90+
// If the target NetworkObject does not exist then defer this message until it does.
91+
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
9292
{
9393
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context, k_Name);
9494
return false;
9595
}
96+
97+
// If the target parent does not exist, then defer this message until it does.
98+
if (LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(LatestParent.Value))
99+
{
100+
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, LatestParent.Value, reader, ref context, k_Name);
101+
return false;
102+
}
103+
96104
return true;
97105
}
98106

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

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

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

0 commit comments

Comments
 (0)