Skip to content

Commit 9b620fb

Browse files
NoelStephensUnityEmandMjabbacakes
authored
fix: spawn disabled in scene placed and network prefab registration (#4093)
* fix - Issue where disabled in-scene placed NetworkObjects could not be spawned after starting a session. - Issue where users are allowed to spawn a NetworkObject with a GlobalObjectIdHash value of 0 (zero). * update NetworkAnimator not pointing to an animator within awake now just logs a warning as opposed to an error. * test Updating tests based on the fixes applied to this branch. A large portion is related to some integration tests trying to spawn NetworkObjects with a GlobalObjectIdHash value of zero(0). * update Style and providing internal write access to the prefabs list. * test Potentially last iteration on fixing/updating integration tests. * test - fix Using the fixed version of NetworkPrefabHandlerSpawnAndSynchronizeTests. Fixing some issues with prefab and handler creation. * style Fixing formatting issues. * style Fixing some standards formatting related issues. * style removing trailing spaces. * style Super weird one... but using ? as opposed to an if null check. * style Removing commented out code that is no longer needed/used. * update Committing suggested changes. Co-authored-by: Emma <emma.mcmillan@unity3d.com> * update Applying changes based on review discussion. * update Making adjustments based on review discussion. * style Removing field that is no longer valid. Moving Awake higher in NetworkObject as it has become wedged in the middle of the code. * update Adding change log entries. * update Inverting logic (oops) * doc Updating NetworkObject documentation and in-scene placed documentation. * Update documentation for the two types of NetworkObjects Clarified the categories of NetworkObjects and their requirements. Added details for dynamically instantiated and in-scene placed NetworkObjects. * style White spaces! * Apply suggestions from code review Co-authored-by: Amy Reeve <amy.reeve@unity3d.com> --------- Co-authored-by: Emma <emma.mcmillan@unity3d.com> Co-authored-by: Amy Reeve <amy.reeve@unity3d.com>
1 parent 3a09971 commit 9b620fb

26 files changed

Lines changed: 1192 additions & 826 deletions

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
1414
### Changed
1515

1616

17+
18+
1719
### Deprecated
1820

1921

@@ -22,6 +24,10 @@ Additional documentation and release notes are available at [Multiplayer Documen
2224

2325
### Fixed
2426

27+
- Issue with not being able to spawn initially disabled in-scene placed objects. (#4093)
28+
- Issue with pre-instantiated network prefab instances being marked as in-scene placed. Now pre-instantiated network prefabs are dynamically spawned. (#4093)
29+
- Issue where a user could spawn runtime created `NetworkObject` that has a GlobalObjectIdHash of zero. These are not valid instances and will no longer be allowed to spawn. (#4093)
30+
2531

2632
### Security
2733

com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,13 @@ public class MyInSceneNetworkObjectBehaviour : NetworkBehaviour
184184
> [!NOTE]
185185
> You only need to enable the NetworkObject on the server-side to be able to respawn it. Netcode for GameObjects only enables a disabled in-scene placed NetworkObject on the client-side if the server-side spawns it. This **does not** apply to dynamically spawned `NetworkObjects`. Refer to [the object pooling page](../../advanced-topics/object-pooling.md) for an example of recycling dynamically spawned NetworkObjects.
186186
187+
### Pre-disabled in-scene placed NetworkObjects
188+
189+
To initialize an in-scene placed NetworkObject in a disabled state and spawn it later, set its GameObject to inactive in the Editor while the respective scene is open. Once a networked session begins, you can re-enable and spawn it at any time.
190+
187191
### Setting an in-scene placed NetworkObject to a despawned state when instantiating
188192

189-
Since in-scene placed NetworkObjects are automatically spawned when their respective scene has finished loading during a network session, you might run into the scenario where you want it to start in a despawned state until a certain condition has been met. To do this, you need to add some additional code in the `OnNetworkSpawn` part of your NetworkBehaviour component:
193+
To programmatically disable an in-scene placed NetworkObject, add some additional code in the `OnNetworkSpawn` part of a NetworkBehaviour component:
190194

191195
```csharp
192196
using UnityEngine;

com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,32 @@ When spawning a NetworkObject, the `NetworkObject.GlobalObjectIdHash` value init
1919

2020
You can use [NetworkBehaviours](networkbehaviour.md) to add your own custom Netcode logic to the associated NetworkObject.
2121

22+
### What is a valid NetworkObject?
23+
24+
There are two categories of NetworkObjects:
25+
* Dynamically instantiated
26+
* [In-scene placed](../../basics/scenemanagement/inscene-placed-networkobjects.md)
27+
28+
The following provides the validity requirements for both types.
29+
30+
#### Dynamically instantiated network prefabs
31+
32+
Dynamically instantiated network prefabs must:
33+
34+
* Be a valid [network prefab](./networkobject.md#network-prefabs) created within the Editor.
35+
* Be registered in a network prefab list that's assigned to your NetworkManager.
36+
37+
#### In-scene placed network prefabs
38+
39+
In-scene placed network prefabs must:
40+
41+
* Be a valid network prefab instance within a scene.
42+
* Be a GameObject with a NetworkObject component created within the scene while in the Editor.
43+
44+
### What is an invalid NetworkObject?
45+
46+
GameObjects that have NetworkObject components added to them during runtime are **not supported** and will result in the NetworkObject's `GlobalObjectIdHash` being zero, which causes synchronization issues. In the event you make this mistake, a warning message will be logged and the NetworkObject won't be spawned.
47+
2248
### Component order
2349

2450
The order of components on a networked GameObject matters. When adding netcode components to a GameObject, ensure that the NetworkObject component is ordered before any NetworkBehaviour components.

com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ public void OnProcessScene(Scene scene, BuildReport report)
2424
log.AddInfo(scene.name, scene.handle);
2525
foreach (var networkObject in FindObjects.FromSceneByType<NetworkObject>(scene, true))
2626
{
27+
// Trap for users just creating things during runtime where this will be zero.
28+
if (networkObject.GlobalObjectIdHash == 0)
29+
{
30+
log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s GlobalObjectIdHash value is zero! Runtime creating of {nameof(NetworkObject)}s is not supported. Skipping processing.").AddNetworkObject(networkObject));
31+
continue;
32+
}
2733
if (networkObject.SceneOrigin.IsValid() && networkObject.SceneOrigin.handle != scene.handle)
2834
{
2935
log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s SceneOrigin doesn't match current scene being processed! Skipping processing.").AddInfo("SceneOrigin", networkObject.SceneOriginHandle).AddNetworkObject(networkObject));
@@ -36,7 +42,15 @@ public void OnProcessScene(Scene scene, BuildReport report)
3642
continue;
3743
}
3844

45+
// If already marked, the do nothing.
46+
if (networkObject.InScenePlaced)
47+
{
48+
continue;
49+
}
50+
3951
networkObject.InScenePlaced = true;
52+
// Will not be true when making a build and the values are serialized.
53+
networkObject.InScenePlacedPostProcessorMarkedDuringRuntime = Application.isPlaying;
4054
}
4155
}
4256
}

com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,7 @@ protected virtual void Awake()
768768
if (!m_Animator)
769769
{
770770
#if !UNITY_EDITOR
771-
Debug.LogError($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly.");
771+
Debug.LogWarning($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly.");
772772
#endif
773773
return;
774774
}

com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs

Lines changed: 100 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,110 @@ public class NetworkPrefabs
4747
[NonSerialized]
4848
private List<NetworkPrefab> m_Prefabs = new List<NetworkPrefab>();
4949

50+
/// <summary>
51+
/// Returns the last registered prefab.
52+
/// </summary>
53+
internal NetworkPrefab GetLastRegisteredPrefab()
54+
{
55+
if (m_Prefabs.Count == 0)
56+
{
57+
return null;
58+
}
59+
return m_Prefabs[m_Prefabs.Count - 1];
60+
}
61+
62+
/// <summary>
63+
/// Applies a network prefab at a specific index
64+
/// </summary>
65+
/// <param name="index">index to apply</param>
66+
/// <param name="networkPrefab">network prefab to be applied</param>
67+
/// <returns></returns>
68+
internal bool AssignPrefabAtIndex(int index, NetworkPrefab networkPrefab)
69+
{
70+
if (index >= m_Prefabs.Count)
71+
{
72+
NetworkManager.Singleton.Log.Error(new Logging.Context(LogLevel.Normal, $"[{nameof(NetworkPrefabs)}][{nameof(AssignPrefabAtIndex)}] Cannot apply prefab to index {index} when the {nameof(m_Prefabs)} count is only {m_Prefabs.Count}!"));
73+
return false;
74+
}
75+
m_Prefabs[index] = networkPrefab;
76+
return true;
77+
}
78+
79+
[NonSerialized]
80+
private Dictionary<uint, NetworkPrefab> m_PrefabHashIds = new Dictionary<uint, NetworkPrefab>();
81+
5082
[NonSerialized]
5183
private List<NetworkPrefab> m_RuntimeAddedPrefabs = new List<NetworkPrefab>();
5284

53-
private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
85+
private bool InternalAddPrefab(NetworkPrefab networkPrefab)
5486
{
5587
if (AddPrefabRegistration(networkPrefab))
5688
{
5789
// Don't add this to m_RuntimeAddedPrefabs
5890
// This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there.
5991
m_Prefabs.Add(networkPrefab);
92+
93+
// We are not getting all potential overrides but just determining if the prefab has been registered.
94+
if (!m_PrefabHashIds.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash))
95+
{
96+
m_PrefabHashIds.Add(networkPrefab.SourcePrefabGlobalObjectIdHash, networkPrefab);
97+
}
98+
if (!m_PrefabHashIds.ContainsKey(networkPrefab.TargetPrefabGlobalObjectIdHash))
99+
{
100+
m_PrefabHashIds.Add(networkPrefab.TargetPrefabGlobalObjectIdHash, networkPrefab);
101+
}
102+
return true;
60103
}
104+
return false;
61105
}
62106

63-
private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
107+
private void InternalRemovePrefab(NetworkPrefab networkPrefab)
64108
{
65109
m_Prefabs.Remove(networkPrefab);
110+
m_PrefabHashIds.Remove(networkPrefab.SourcePrefabGlobalObjectIdHash);
111+
}
112+
113+
internal bool IsBasedOnRegisteredPrefab(NetworkObject networkObject)
114+
{
115+
116+
117+
return m_PrefabHashIds.ContainsKey(networkObject.GlobalObjectIdHash);
118+
}
119+
120+
internal bool IsActualPrefabAsset(NetworkObject networkObject)
121+
{
122+
var isActualPrefabAsset = false;
123+
if (m_PrefabHashIds.TryGetValue(networkObject.GlobalObjectIdHash, out NetworkPrefab networkPrefab))
124+
{
125+
switch (networkPrefab.Override)
126+
{
127+
case NetworkPrefabOverride.Prefab:
128+
case NetworkPrefabOverride.None:
129+
{
130+
isActualPrefabAsset = networkPrefab.Prefab != null && networkObject.gameObject == networkPrefab.Prefab;
131+
break;
132+
}
133+
case NetworkPrefabOverride.Hash:
134+
{
135+
isActualPrefabAsset = networkPrefab.SourceHashToOverride == networkObject.GlobalObjectIdHash;
136+
break;
137+
}
138+
}
139+
}
140+
return isActualPrefabAsset;
141+
}
142+
143+
private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
144+
{
145+
// Don't add this to m_RuntimeAddedPrefabs
146+
// This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there.
147+
InternalAddPrefab(networkPrefab);
148+
// Log warning if this returns false?
149+
}
150+
151+
private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
152+
{
153+
InternalRemovePrefab(networkPrefab);
66154
}
67155

68156
/// <summary>
@@ -93,6 +181,7 @@ internal void Shutdown()
93181
/// <param name="warnInvalid">When true, logs warnings about invalid prefabs that are removed during initialization</param>
94182
public void Initialize(bool warnInvalid = true)
95183
{
184+
m_PrefabHashIds.Clear();
96185
m_Prefabs.Clear();
97186
NetworkPrefabsLists.RemoveAll(x => x == null);
98187
foreach (var list in NetworkPrefabsLists)
@@ -113,7 +202,7 @@ public void Initialize(bool warnInvalid = true)
113202
prefabs.AddRange(list.PrefabList);
114203
}
115204
}
116-
205+
m_PrefabHashIds = new Dictionary<uint, NetworkPrefab>();
117206
m_Prefabs = new List<NetworkPrefab>();
118207

119208
List<NetworkPrefab> removeList = null;
@@ -124,23 +213,15 @@ public void Initialize(bool warnInvalid = true)
124213

125214
foreach (var networkPrefab in prefabs)
126215
{
127-
if (AddPrefabRegistration(networkPrefab))
128-
{
129-
m_Prefabs.Add(networkPrefab);
130-
}
131-
else
216+
if (!InternalAddPrefab(networkPrefab))
132217
{
133218
removeList?.Add(networkPrefab);
134219
}
135220
}
136221

137222
foreach (var networkPrefab in m_RuntimeAddedPrefabs)
138223
{
139-
if (AddPrefabRegistration(networkPrefab))
140-
{
141-
m_Prefabs.Add(networkPrefab);
142-
}
143-
else
224+
if (!InternalAddPrefab(networkPrefab))
144225
{
145226
removeList?.Add(networkPrefab);
146227
}
@@ -171,14 +252,12 @@ public void Initialize(bool warnInvalid = true)
171252
/// </remarks>
172253
public bool Add(NetworkPrefab networkPrefab)
173254
{
174-
if (AddPrefabRegistration(networkPrefab))
255+
var added = InternalAddPrefab(networkPrefab);
256+
if (added)
175257
{
176-
m_Prefabs.Add(networkPrefab);
177258
m_RuntimeAddedPrefabs.Add(networkPrefab);
178-
return true;
179259
}
180-
181-
return false;
260+
return added;
182261
}
183262

184263
/// <summary>
@@ -197,8 +276,7 @@ public void Remove(NetworkPrefab prefab)
197276
{
198277
throw new ArgumentNullException(nameof(prefab));
199278
}
200-
201-
m_Prefabs.Remove(prefab);
279+
InternalRemovePrefab(prefab);
202280
m_RuntimeAddedPrefabs.Remove(prefab);
203281
OverrideToNetworkPrefab.Remove(prefab.TargetPrefabGlobalObjectIdHash);
204282
NetworkPrefabOverrideLinks.Remove(prefab.SourcePrefabGlobalObjectIdHash);
@@ -294,14 +372,12 @@ private bool AddPrefabRegistration(NetworkPrefab networkPrefab)
294372

295373
uint source = networkPrefab.SourcePrefabGlobalObjectIdHash;
296374
uint target = networkPrefab.TargetPrefabGlobalObjectIdHash;
297-
298375
// Make sure the prefab isn't already registered.
299376
if (NetworkPrefabOverrideLinks.ContainsKey(source))
300377
{
301-
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
302-
378+
var nameOrHashOverride = networkPrefab.Override == NetworkPrefabOverride.Hash ? $"Hash: {networkPrefab.SourcePrefabGlobalObjectIdHash}" : networkPrefab.Prefab?.name;
303379
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry
304-
Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!");
380+
Debug.LogError($"{nameof(NetworkPrefab)} ({nameOrHashOverride}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!");
305381
return false;
306382
}
307383

0 commit comments

Comments
 (0)