Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Changed




### Deprecated


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

### Fixed

- Issue with not being able to spawn initially disabled in-scene placed objects. (#4093)
- Issue with pre-instantiated network prefab instances being marked as in-scene placed. Now pre-instantiated network prefabs are dynamically spawned. (#4093)
- 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)


### Security

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,13 @@ public class MyInSceneNetworkObjectBehaviour : NetworkBehaviour
> [!NOTE]
> 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.

### Pre-disabled in-scene placed NetworkObjects

If you want to always have an in-scene placed NetworkObject start off as disabled and then spawn it at a later time, you only need to set its GameObject to inactive in the editor while the respective scene is open. Once you have started a networked session, you can re-enable it and then spawn it any time.

### Setting an in-scene placed NetworkObject to a despawned state when instantiating

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:
If you want to programmatically disable an in-scene placed NetworkObject you can add some additional code in the `OnNetworkSpawn` part of a NetworkBehaviour component:

```csharp
using UnityEngine;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,32 @@ When spawning a NetworkObject, the `NetworkObject.GlobalObjectIdHash` value init

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

### What is a valid NetworkObject?

There are two categories of NetworkObjects:
* Dynamically intantiated
* [In-scene placed](../../basics/scenemanagement/inscene-placed-networkobjects.md)

The following provides the validity requirements for both types.

#### Dynamically instantiated network prefabs.

Requirements

* It must be a valid [network prefab](./networkobject.md#network-prefabs) created within the editor.
* It must be registered within a network prefab list that is assigned to your NetworkManager.

#### In-scene placed

Requirements

* It can be a valid network prefab instance within a scene.
* It can be a GameObject with a `NetworkObject` component created within the scene while in the editor.

### What is an invalid NetworkObject?

Any GameObjects that have `NetworkObject` components added to them during runtime is **not supported** and will result in the NetworkObject's `GlobalObjectIdHash` being zero which would eventually result in synchronization issues. In the event you make this mistake, a warning message will be logged and the NetworkObject will not get spawned.

### Component order

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.
Expand Down
14 changes: 14 additions & 0 deletions com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ public void OnProcessScene(Scene scene, BuildReport report)
log.AddInfo(scene.name, scene.handle);
foreach (var networkObject in FindObjects.FromSceneByType<NetworkObject>(scene, true))
{
// Trap for users just creating things during runtime where this will be zero.
if (networkObject.GlobalObjectIdHash == 0)
{
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));
continue;
}
if (networkObject.SceneOrigin.IsValid() && networkObject.SceneOrigin.handle != scene.handle)
{
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));
Expand All @@ -36,7 +42,15 @@ public void OnProcessScene(Scene scene, BuildReport report)
continue;
}

// If already marked, the do nothing.
if (networkObject.InScenePlaced)
{
continue;
}

networkObject.InScenePlaced = true;
// Will not be true when making a build and the values are serialized.
networkObject.InScenePlacedPostProcessorMarkedDuringRuntime = Application.isPlaying;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ protected virtual void Awake()
if (!m_Animator)
{
#if !UNITY_EDITOR
Debug.LogError($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly.");
Debug.LogWarning($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly.");
#endif
return;
}
Expand Down
124 changes: 100 additions & 24 deletions com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,110 @@ public class NetworkPrefabs
[NonSerialized]
private List<NetworkPrefab> m_Prefabs = new List<NetworkPrefab>();

/// <summary>
/// Returns the last registered prefab.
/// </summary>
internal NetworkPrefab GetLastRegisteredPrefab()
{
if (m_Prefabs.Count == 0)
{
return null;
}
return m_Prefabs[m_Prefabs.Count - 1];
}

/// <summary>
/// Applies a network prefab at a specific index
/// </summary>
/// <param name="index">index to apply</param>
/// <param name="networkPrefab">network prefab to be applied</param>
/// <returns></returns>
internal bool AssignPrefabAtIndex(int index, NetworkPrefab networkPrefab)
{
if (index >= m_Prefabs.Count)
{
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}!"));
return false;
}
m_Prefabs[index] = networkPrefab;
return true;
}

[NonSerialized]
private Dictionary<uint, NetworkPrefab> m_PrefabHashIds = new Dictionary<uint, NetworkPrefab>();

[NonSerialized]
private List<NetworkPrefab> m_RuntimeAddedPrefabs = new List<NetworkPrefab>();

private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
private bool InternalAddPrefab(NetworkPrefab networkPrefab)
{
if (AddPrefabRegistration(networkPrefab))
{
// Don't add this to m_RuntimeAddedPrefabs
// This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there.
m_Prefabs.Add(networkPrefab);

// We are not getting all potential overrides but just determining if the prefab has been registered.
if (!m_PrefabHashIds.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash))
{
m_PrefabHashIds.Add(networkPrefab.SourcePrefabGlobalObjectIdHash, networkPrefab);
}
if (!m_PrefabHashIds.ContainsKey(networkPrefab.TargetPrefabGlobalObjectIdHash))
{
m_PrefabHashIds.Add(networkPrefab.TargetPrefabGlobalObjectIdHash, networkPrefab);
}
return true;
}
return false;
}

private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
private void InternalRemovePrefab(NetworkPrefab networkPrefab)
{
m_Prefabs.Remove(networkPrefab);
m_PrefabHashIds.Remove(networkPrefab.SourcePrefabGlobalObjectIdHash);
}

internal bool IsBasedOnRegisteredPrefab(NetworkObject networkObject)
{


return m_PrefabHashIds.ContainsKey(networkObject.GlobalObjectIdHash);
}

internal bool IsActualPrefabAsset(NetworkObject networkObject)
{
var isActualPrefabAsset = false;
Comment thread
NoelStephensUnity marked this conversation as resolved.
if (m_PrefabHashIds.TryGetValue(networkObject.GlobalObjectIdHash, out NetworkPrefab networkPrefab))
{
switch (networkPrefab.Override)
{
case NetworkPrefabOverride.Prefab:
case NetworkPrefabOverride.None:
{
isActualPrefabAsset = networkPrefab.Prefab != null && networkObject.gameObject == networkPrefab.Prefab;
break;
}
case NetworkPrefabOverride.Hash:
{
isActualPrefabAsset = networkPrefab.SourceHashToOverride == networkObject.GlobalObjectIdHash;
break;
}
}
}
return isActualPrefabAsset;
}

private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
{
// Don't add this to m_RuntimeAddedPrefabs
// This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there.
InternalAddPrefab(networkPrefab);
// Log warning if this returns false?
}

private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
{
InternalRemovePrefab(networkPrefab);
}

/// <summary>
Expand Down Expand Up @@ -93,6 +181,7 @@ internal void Shutdown()
/// <param name="warnInvalid">When true, logs warnings about invalid prefabs that are removed during initialization</param>
public void Initialize(bool warnInvalid = true)
{
m_PrefabHashIds.Clear();
m_Prefabs.Clear();
NetworkPrefabsLists.RemoveAll(x => x == null);
foreach (var list in NetworkPrefabsLists)
Expand All @@ -113,7 +202,7 @@ public void Initialize(bool warnInvalid = true)
prefabs.AddRange(list.PrefabList);
}
}

m_PrefabHashIds = new Dictionary<uint, NetworkPrefab>();
m_Prefabs = new List<NetworkPrefab>();

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

foreach (var networkPrefab in prefabs)
{
if (AddPrefabRegistration(networkPrefab))
{
m_Prefabs.Add(networkPrefab);
}
else
if (!InternalAddPrefab(networkPrefab))
{
removeList?.Add(networkPrefab);
}
}

foreach (var networkPrefab in m_RuntimeAddedPrefabs)
{
if (AddPrefabRegistration(networkPrefab))
{
m_Prefabs.Add(networkPrefab);
}
else
if (!InternalAddPrefab(networkPrefab))
{
removeList?.Add(networkPrefab);
}
Expand Down Expand Up @@ -171,14 +252,12 @@ public void Initialize(bool warnInvalid = true)
/// </remarks>
public bool Add(NetworkPrefab networkPrefab)
{
if (AddPrefabRegistration(networkPrefab))
var added = InternalAddPrefab(networkPrefab);
if (added)
{
m_Prefabs.Add(networkPrefab);
m_RuntimeAddedPrefabs.Add(networkPrefab);
return true;
}

return false;
return added;
}

/// <summary>
Expand All @@ -197,8 +276,7 @@ public void Remove(NetworkPrefab prefab)
{
throw new ArgumentNullException(nameof(prefab));
}

m_Prefabs.Remove(prefab);
InternalRemovePrefab(prefab);
m_RuntimeAddedPrefabs.Remove(prefab);
OverrideToNetworkPrefab.Remove(prefab.TargetPrefabGlobalObjectIdHash);
NetworkPrefabOverrideLinks.Remove(prefab.SourcePrefabGlobalObjectIdHash);
Expand Down Expand Up @@ -294,14 +372,12 @@ private bool AddPrefabRegistration(NetworkPrefab networkPrefab)

uint source = networkPrefab.SourcePrefabGlobalObjectIdHash;
uint target = networkPrefab.TargetPrefabGlobalObjectIdHash;

// Make sure the prefab isn't already registered.
if (NetworkPrefabOverrideLinks.ContainsKey(source))
{
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();

var nameOrHashOverride = networkPrefab.Override == NetworkPrefabOverride.Hash ? $"Hash: {networkPrefab.SourcePrefabGlobalObjectIdHash}" : networkPrefab.Prefab?.name;
Comment thread
NoelStephensUnity marked this conversation as resolved.
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry
Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!");
Debug.LogError($"{nameof(NetworkPrefab)} ({nameOrHashOverride}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!");
return false;
}

Expand Down
Loading