com.unity.netcode.gameobjects@1.8.0

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.8.0] - 2023-12-12

### Added

- Added a new RPC attribute, which is simply `Rpc`. (#2762)
  - This is a generic attribute that can perform the functions of both Server and Client RPCs, as well as enabling client-to-client RPCs. Includes several default targets: `Server`, `NotServer`, `Owner`, `NotOwner`, `Me`, `NotMe`, `ClientsAndHost`, and `Everyone`. Runtime overrides are available for any of these targets, as well as for sending to a specific ID or groups of IDs.
  - This attribute also includes the ability to defer RPCs that are sent to the local process to the start of the next frame instead of executing them immediately, treating them as if they had gone across the network. The default behavior is to execute immediately.
  - This attribute effectively replaces `ServerRpc` and `ClientRpc`. `ServerRpc` and `ClientRpc` remain in their existing forms for backward compatibility, but `Rpc` will be the recommended and most supported option.
- Added `NetworkManager.OnConnectionEvent` as a unified connection event callback to notify clients and servers of all client connections and disconnections within the session (#2762)
- Added `NetworkManager.ServerIsHost` and `NetworkBehaviour.ServerIsHost` to allow a client to tell if it is connected to a host or to a dedicated server (#2762)
- Added `SceneEventProgress.SceneManagementNotEnabled` return status to be returned when a `NetworkSceneManager` method is invoked and scene management is not enabled. (#2735)
- Added `SceneEventProgress.ServerOnlyAction` return status to be returned when a `NetworkSceneManager` method is invoked by a client. (#2735)
- Added `NetworkObject.InstantiateAndSpawn` and `NetworkSpawnManager.InstantiateAndSpawn` methods to simplify prefab spawning by assuring that the prefab is valid and applies any override prior to instantiating the `GameObject` and spawning the `NetworkObject` instance. (#2710)

### Fixed

- Fixed issue where a client disconnected by a server-host would not receive a local notification. (#2789)
- Fixed issue where a server-host could shutdown during a relay connection but periodically the transport disconnect message sent to any connected clients could be dropped. (#2789)
- Fixed issue where a host could disconnect its local client but remain running as a server. (#2789)
- Fixed issue where `OnClientDisconnectedCallback` was not being invoked under certain conditions. (#2789)
- Fixed issue where `OnClientDisconnectedCallback` was always returning 0 as the client identifier. (#2789)
- Fixed issue where if a host or server shutdown while a client owned NetworkObjects (other than the player) it would throw an exception. (#2789)
- Fixed issue where setting values on a `NetworkVariable` or `NetworkList` within `OnNetworkDespawn` during a shutdown sequence would throw an exception. (#2789)
- Fixed issue where a teleport state could potentially be overridden by a previous unreliable delta state. (#2777)
- Fixed issue where `NetworkTransform` was using the `NetworkManager.ServerTime.Tick` as opposed to `NetworkManager.NetworkTickSystem.ServerTime.Tick` during the authoritative side's tick update where it performed a delta state check. (#2777)
- Fixed issue where a parented in-scene placed NetworkObject would be destroyed upon a client or server exiting a network session but not unloading the original scene in which the NetworkObject was placed. (#2737)
- Fixed issue where during client synchronization and scene loading, when client synchronization or the scene loading mode are set to `LoadSceneMode.Single`, a `CreateObjectMessage` could be received, processed, and the resultant spawned `NetworkObject` could be instantiated in the client's currently active scene that could, towards the end of the client synchronization or loading process, be unloaded and cause the newly created `NetworkObject` to be destroyed (and throw and exception). (#2735)
- Fixed issue where a `NetworkTransform` instance with interpolation enabled would result in wide visual motion gaps (stuttering) under above normal latency conditions and a 1-5% or higher packet are drop rate. (#2713)
- Fixed issue where  you could not have multiple source network prefab overrides targeting the same network prefab as their override. (#2710)

### Changed
- Changed the server or host shutdown so it will now perform a "soft shutdown" when `NetworkManager.Shutdown` is invoked. This will send a disconnect notification to all connected clients and the server-host will wait for all connected clients to disconnect or timeout after a 5 second period before completing the shutdown process. (#2789)
- Changed `OnClientDisconnectedCallback` will now return the assigned client identifier on the local client side if the client was approved and assigned one prior to being disconnected. (#2789)
- Changed `NetworkTransform.SetState` (and related methods) now are cumulative during a fractional tick period and sent on the next pending tick. (#2777)
- `NetworkManager.ConnectedClientsIds` is now accessible on the client side and will contain the list of all clients in the session, including the host client if the server is operating in host mode (#2762)
- Changed `NetworkSceneManager` to return a `SceneEventProgress` status and not throw exceptions for methods invoked when scene management is disabled and when a client attempts to access a `NetworkSceneManager` method by a client. (#2735)
- Changed `NetworkTransform` authoritative instance tick registration so a single `NetworkTransform` specific tick event update will update all authoritative instances to improve perofmance. (#2713)
- Changed `NetworkPrefabs.OverrideToNetworkPrefab` dictionary is no longer used/populated due to it ending up being related to a regression bug and not allowing more than one override to be assigned to a network prefab asset. (#2710)
- Changed in-scene placed `NetworkObject`s now store their source network prefab asset's `GlobalObjectIdHash` internally that is used, when scene management is disabled, by clients to spawn the correct prefab even if the `NetworkPrefab` entry has an override. This does not impact dynamically spawning the same prefab which will yield the override on both host and client. (#2710)
- Changed in-scene placed `NetworkObject`s no longer require a `NetworkPrefab` entry with `GlobalObjectIdHash` override in order for clients to properly synchronize. (#2710)
- Changed in-scene placed `NetworkObject`s now set their `IsSceneObject` value when generating their `GlobalObjectIdHash` value. (#2710)
- Changed the default `NetworkConfig.SpawnTimeout` value from 1.0s to 10.0s. (#2710)
This commit is contained in:
Unity Technologies
2023-12-12 00:00:00 +00:00
parent 514166e159
commit 07f206ff9e
99 changed files with 8667 additions and 1710 deletions

View File

@@ -26,6 +26,22 @@ namespace Unity.Netcode
[SerializeField]
internal uint GlobalObjectIdHash;
/// <summary>
/// Used to track the source GlobalObjectIdHash value of the associated network prefab.
/// When an override exists or it is in-scene placed, GlobalObjectIdHash and PrefabGlobalObjectIdHash
/// will be different. The PrefabGlobalObjectIdHash value is what is used when sending a <see cref="CreateObjectMessage"/>.
/// </summary>
internal uint PrefabGlobalObjectIdHash;
/// <summary>
/// This is the source prefab of an in-scene placed NetworkObject. This is not set for in-scene
/// placd NetworkObjects that are not prefab instances, dynamically spawned prefab instances,
/// or for network prefab assets.
/// </summary>
[HideInInspector]
[SerializeField]
internal uint InScenePlacedSourceGlobalObjectIdHash;
/// <summary>
/// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0
/// </summary>
@@ -34,15 +50,7 @@ namespace Unity.Netcode
{
get
{
foreach (var prefab in NetworkManager.NetworkConfig.Prefabs.Prefabs)
{
if (prefab.Prefab == gameObject)
{
return GlobalObjectIdHash;
}
}
return 0;
return GlobalObjectIdHash;
}
}
@@ -149,6 +157,9 @@ namespace Unity.Netcode
EditorUtility.SetDirty(this);
}
}
// Always check for in-scene placed to assure any previous version scene assets with in-scene place NetworkObjects gets updated
CheckForInScenePlaced();
}
private bool IsEditingPrefab()
@@ -164,6 +175,33 @@ namespace Unity.Netcode
return true;
}
/// <summary>
/// This checks to see if this NetworkObject is an in-scene placed prefab instance. If so it will
/// automatically find the source prefab asset's GlobalObjectIdHash value, assign it to
/// InScenePlacedSourceGlobalObjectIdHash and mark this as being in-scene placed.
/// </summary>
/// <remarks>
/// This NetworkObject is considered an in-scene placed prefab asset instance if it is:
/// - Part of a prefab
/// - Not being directly edited
/// - Within a valid scene that is part of the scenes in build list
/// (In-scene defined NetworkObjects that are not part of a prefab instance are excluded.)
/// </remarks>
private void CheckForInScenePlaced()
{
if (PrefabUtility.IsPartOfAnyPrefab(this) && !IsEditingPrefab() && gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0)
{
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
var assetPath = AssetDatabase.GetAssetPath(prefab);
var sourceAsset = AssetDatabase.LoadAssetAtPath<NetworkObject>(assetPath);
if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash)
{
InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash;
}
IsSceneObject = true;
}
}
private GlobalObjectId GetGlobalId()
{
var instanceGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(this);
@@ -703,7 +741,7 @@ namespace Unity.Netcode
// Since we still have a session connection, log locally and on the server to inform user of this issue.
if (NetworkManager.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
NetworkLog.LogErrorServer($"[Invalid Destroy][{gameObject.name}][NetworkObjectId:{NetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
}
return;
}
@@ -720,7 +758,7 @@ namespace Unity.Netcode
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject)
internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject)
{
if (!NetworkManager.IsListening)
{
@@ -743,6 +781,78 @@ namespace Unity.Netcode
}
}
/// <summary>
/// This invokes <see cref="NetworkSpawnManager.InstantiateAndSpawn(NetworkObject, ulong, bool, bool, bool, Vector3, Quaternion)"/>.
/// </summary>
/// <param name="networkPrefab">The NetworkPrefab to instantiate and spawn.</param>
/// <param name="networkManager">The local instance of the NetworkManager connected to an session in progress.</param>
/// <param name="ownerClientId">The owner of the <see cref="NetworkObject"/> instance (defaults to server).</param>
/// <param name="destroyWithScene">Whether the <see cref="NetworkObject"/> instance will be destroyed when the scene it is located within is unloaded (default is false).</param>
/// <param name="isPlayerObject">Whether the <see cref="NetworkObject"/> instance is a player object or not (default is false).</param>
/// <param name="forceOverride">Whether you want to force spawning the override when running as a host or server or if you want it to spawn the override for host mode and
/// the source prefab for server. If there is an override, clients always spawn that as opposed to the source prefab (defaults to false). </param>
/// <param name="position">The starting poisiton of the <see cref="NetworkObject"/> instance.</param>
/// <param name="rotation">The starting rotation of the <see cref="NetworkObject"/> instance.</param>
/// <returns>The newly instantiated and spawned <see cref="NetworkObject"/> prefab instance.</returns>
public static NetworkObject InstantiateAndSpawn(GameObject networkPrefab, NetworkManager networkManager, ulong ownerClientId = NetworkManager.ServerClientId, bool destroyWithScene = false, bool isPlayerObject = false, bool forceOverride = false, Vector3 position = default, Quaternion rotation = default)
{
var networkObject = networkPrefab.GetComponent<NetworkObject>();
if (networkObject == null)
{
Debug.LogError($"The {nameof(NetworkPrefab)} {networkPrefab.name} does not have a {nameof(NetworkObject)} component!");
return null;
}
return networkObject.InstantiateAndSpawn(networkManager, ownerClientId, destroyWithScene, isPlayerObject, forceOverride, position, rotation);
}
/// <summary>
/// This invokes <see cref="NetworkSpawnManager.InstantiateAndSpawn(NetworkObject, ulong, bool, bool, bool, Vector3, Quaternion)"/>.
/// </summary>
/// <param name="networkManager">The local instance of the NetworkManager connected to an session in progress.</param>
/// <param name="ownerClientId">The owner of the <see cref="NetworkObject"/> instance (defaults to server).</param>
/// <param name="destroyWithScene">Whether the <see cref="NetworkObject"/> instance will be destroyed when the scene it is located within is unloaded (default is false).</param>
/// <param name="isPlayerObject">Whether the <see cref="NetworkObject"/> instance is a player object or not (default is false).</param>
/// <param name="forceOverride">Whether you want to force spawning the override when running as a host or server or if you want it to spawn the override for host mode and
/// the source prefab for server. If there is an override, clients always spawn that as opposed to the source prefab (defaults to false). </param>
/// <param name="position">The starting poisiton of the <see cref="NetworkObject"/> instance.</param>
/// <param name="rotation">The starting rotation of the <see cref="NetworkObject"/> instance.</param>
/// <returns>The newly instantiated and spawned <see cref="NetworkObject"/> prefab instance.</returns>
public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ownerClientId = NetworkManager.ServerClientId, bool destroyWithScene = false, bool isPlayerObject = false, bool forceOverride = false, Vector3 position = default, Quaternion rotation = default)
{
if (networkManager == null)
{
Debug.LogError(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NetworkManagerNull]);
return null;
}
if (!networkManager.IsListening)
{
Debug.LogError(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NoActiveSession]);
return null;
}
if (!networkManager.IsServer)
{
Debug.LogError(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NotAuthority]);
return null;
}
if (NetworkManager.ShutdownInProgress)
{
Debug.LogWarning(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.InvokedWhenShuttingDown]);
return null;
}
// Verify it is actually a valid prefab
if (!NetworkManager.NetworkConfig.Prefabs.Contains(gameObject))
{
Debug.LogError(NetworkSpawnManager.InstantiateAndSpawnErrors[NetworkSpawnManager.InstantiateAndSpawnErrorTypes.NotRegisteredNetworkPrefab]);
return null;
}
return NetworkManager.SpawnManager.InstantiateAndSpawnNoParameterChecks(this, ownerClientId, destroyWithScene, isPlayerObject, forceOverride, position, rotation);
}
/// <summary>
/// Spawns this <see cref="NetworkObject"/> across the network. Can only be called from the Server
/// </summary>
@@ -969,20 +1079,21 @@ namespace Unity.Netcode
return false;
}
if (!NetworkManager.IsServer)
if (!NetworkManager.IsServer && !NetworkManager.ShutdownInProgress)
{
return false;
}
if (!IsSpawned)
// If the parent is not null fail only if either of the two is true:
// - This instance is spawned and the parent is not.
// - This instance is not spawned and the parent is.
// Basically, don't allow parenting when either the child or parent is not spawned.
// Caveat: if the parent is null then we can allow parenting whether the instance is or is not spawned.
if (parent != null && (IsSpawned ^ parent.IsSpawned))
{
return false;
}
if (parent != null && !parent.IsSpawned)
{
return false;
}
m_CachedWorldPositionStays = worldPositionStays;
if (parent == null)
@@ -1018,15 +1129,36 @@ namespace Unity.Netcode
if (!NetworkManager.IsServer)
{
transform.parent = m_CachedParent;
Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s"));
// Log exception if we are a client and not shutting down.
if (!NetworkManager.ShutdownInProgress)
{
transform.parent = m_CachedParent;
Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s"));
}
else // Otherwise, if we are removing a parent then go ahead and allow parenting to occur
if (transform.parent == null)
{
m_LatestParent = null;
m_CachedParent = null;
InvokeBehaviourOnNetworkObjectParentChanged(null);
}
return;
}
else // Otherwise, on the serer side if this instance is not spawned...
if (!IsSpawned)
{
transform.parent = m_CachedParent;
Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented after being spawned"));
// ,,,and we are removing the parent, then go ahead and allow parenting to occur
if (transform.parent == null)
{
m_LatestParent = null;
m_CachedParent = null;
InvokeBehaviourOnNetworkObjectParentChanged(null);
}
else
{
transform.parent = m_CachedParent;
Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented after being spawned"));
}
return;
}
var removeParent = false;
@@ -1869,16 +2001,39 @@ namespace Unity.Netcode
/// <returns></returns>
internal uint HostCheckForGlobalObjectIdHashOverride()
{
if (NetworkManager.IsHost)
if (NetworkManager.IsServer)
{
if (NetworkManager.PrefabHandler.ContainsHandler(this))
{
var globalObjectIdHash = NetworkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash);
return globalObjectIdHash == 0 ? GlobalObjectIdHash : globalObjectIdHash;
}
if (NetworkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab.TryGetValue(GlobalObjectIdHash, out uint hash))
// If scene management is disabled and this is an in-scene placed NetworkObject then go ahead
// and send the InScenePlacedSourcePrefab's GlobalObjectIdHash value (i.e. what to dynamically spawn)
if (!NetworkManager.NetworkConfig.EnableSceneManagement && IsSceneObject.Value && InScenePlacedSourceGlobalObjectIdHash != 0)
{
return hash;
return InScenePlacedSourceGlobalObjectIdHash;
}
// If the PrefabGlobalObjectIdHash is a non-zero value and the GlobalObjectIdHash value is
// different from the PrefabGlobalObjectIdHash value, then the NetworkObject instance is
// an override for the original network prefab (i.e. PrefabGlobalObjectIdHash)
if (!IsSceneObject.Value && GlobalObjectIdHash != PrefabGlobalObjectIdHash)
{
// If the PrefabGlobalObjectIdHash is already populated (i.e. InstantiateAndSpawn used), then return this
if (PrefabGlobalObjectIdHash != 0)
{
return PrefabGlobalObjectIdHash;
}
else
{
// For legacy manual instantiation and spawning, check the OverrideToNetworkPrefab for a possible match
if (NetworkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab.ContainsKey(GlobalObjectIdHash))
{
return NetworkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab[GlobalObjectIdHash];
}
}
}
}