com.unity.netcode.gameobjects@2.2.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). ## [2.2.0] - 2024-12-12 ### Added - Added `NetworkObject.OwnershipStatus.SessionOwner` to allow Network Objects to be distributable and only owned by the Session Owner. This flag will override all other `OwnershipStatus` flags. (#3175) - Added `UnityTransport.GetEndpoint` method to provide a way to obtain `NetworkEndpoint` information of a connection via client identifier. (#3130) - Added `NetworkTransport.OnEarlyUpdate` and `NetworkTransport.OnPostLateUpdate` methods to provide more control over handling transport related events at the start and end of each frame. (#3113) ### Fixed - Fixed issue where the server, host, or session owner would not populate the in-scene place `NetworkObject` table if the scene was loaded prior to starting the `NetworkManager`. (#3177) - Fixed issue where the `NetworkObjectIdHash` value could be incorrect when entering play mode while still in prefab edit mode with pending changes and using MPPM. (#3162) - Fixed issue where a sever only `NetworkManager` instance would spawn the actual `NetworkPrefab`'s `GameObject` as opposed to creating an instance of it. (#3160) - Fixed issue where only the session owner (as opposed to all clients) would handle spawning prefab overrides properly when using a distributed authority network topology. (#3160) - Fixed issue where an exception was thrown when calling `NetworkManager.Shutdown` after calling `UnityTransport.Shutdown`. (#3118) - Fixed issue where `NetworkList` properties on in-scene placed `NetworkObject`s could cause small memory leaks when entering playmode. (#3147) - Fixed in-scene `NertworkObject` synchronization issue when loading a scene with currently connected clients connected to a session created by a `NetworkManager` started as a server (i.e. not as a host). (#3133) - Fixed issue where a `NetworkManager` started as a server would not add itself as an observer to in-scene placed `NetworkObject`s instantiated and spawned by a scene loading event. (#3133) - Fixed issue where spawning a player using `NetworkObject.InstantiateAndSpawn` or `NetworkSpawnManager.InstantiateAndSpawn` would not update the `NetworkSpawnManager.PlayerObjects` or assign the newly spawned player to the `NetworkClient.PlayerObject`. (#3122) - Fixed issue where queued UnitTransport (NetworkTransport) message batches were being sent on the next frame. They are now sent at the end of the frame during `PostLateUpdate`. (#3113) - Fixed issue where `NotOwnerRpcTarget` or `OwnerRpcTarget` were not using their replacements `NotAuthorityRpcTarget` and `AuthorityRpcTarget` which would invoke a warning. (#3111) - Fixed issue where client is removed as an observer from spawned objects when their player instance is despawned. (#3110) - Fixed issue where `NetworkAnimator` would statically allocate write buffer space for `Animator` parameters that could cause a write error if the number of parameters exceeded the space allocated. (#3108) ### Changed - In-scene placed `NetworkObject`s have been made distributable when balancing object distribution after a connection event. (#3175) - Optimised `NetworkVariable` and `NetworkTransform` related packets when in Distributed Authority mode. - The Debug Simulator section of the Unity Transport component was removed. This section was not functional anymore and users are now recommended to use the more featureful [Network Simulator](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) tool from the Multiplayer Tools package instead. (#3121)
This commit is contained in:
@@ -826,7 +826,7 @@ namespace Unity.Netcode
|
||||
internal void InternalOnGainedOwnership()
|
||||
{
|
||||
UpdateNetworkProperties();
|
||||
// New owners need to assure any NetworkVariables they have write permissions
|
||||
// New owners need to assure any NetworkVariables they have write permissions
|
||||
// to are updated so the previous and original values are aligned with the
|
||||
// current value (primarily for collections).
|
||||
if (OwnerClientId == NetworkManager.LocalClientId)
|
||||
@@ -1181,14 +1181,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
// Create any values that require accessing the NetworkManager locally (it is expensive to access it in NetworkBehaviour)
|
||||
var networkManager = NetworkManager;
|
||||
var distributedAuthority = networkManager.DistributedAuthorityMode;
|
||||
var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;
|
||||
|
||||
// Always write the NetworkVariable count even if zero for distributed authority (used by comb server)
|
||||
if (distributedAuthority)
|
||||
{
|
||||
writer.WriteValueSafe((ushort)NetworkVariableFields.Count);
|
||||
}
|
||||
|
||||
// Exit early if there are no NetworkVariables
|
||||
if (NetworkVariableFields.Count == 0)
|
||||
@@ -1203,14 +1197,8 @@ namespace Unity.Netcode
|
||||
if (NetworkVariableFields[j].CanClientRead(targetClientId))
|
||||
{
|
||||
// Write additional NetworkVariable information when length safety is enabled or when in distributed authority mode
|
||||
if (ensureLengthSafety || distributedAuthority)
|
||||
if (ensureLengthSafety)
|
||||
{
|
||||
// Write the type being serialized for distributed authority (only for comb-server)
|
||||
if (distributedAuthority)
|
||||
{
|
||||
writer.WriteValueSafe(NetworkVariableFields[j].Type);
|
||||
}
|
||||
|
||||
var writePos = writer.Position;
|
||||
// Note: This value can't be packed because we don't know how large it will be in advance
|
||||
// we reserve space for it, then write the data, then come back and fill in the space
|
||||
@@ -1261,20 +1249,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
// Stack cache any values that requires accessing the NetworkManager (it is expensive to access it in NetworkBehaviour)
|
||||
var networkManager = NetworkManager;
|
||||
var distributedAuthority = networkManager.DistributedAuthorityMode;
|
||||
var ensureLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;
|
||||
|
||||
// Always read the NetworkVariable count when in distributed authority (sanity check if comb-server matches what client has locally)
|
||||
if (distributedAuthority)
|
||||
{
|
||||
reader.ReadValueSafe(out ushort variableCount);
|
||||
if (variableCount != NetworkVariableFields.Count)
|
||||
{
|
||||
Debug.LogError($"[{name}][NetworkObjectId: {NetworkObjectId}][NetworkBehaviourId: {NetworkBehaviourId}] NetworkVariable count mismatch! (Read: {variableCount} vs. Expected: {NetworkVariableFields.Count})");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit early if nothing else to read
|
||||
if (NetworkVariableFields.Count == 0)
|
||||
{
|
||||
@@ -1289,14 +1265,8 @@ namespace Unity.Netcode
|
||||
// Distributed Authority: All clients have read permissions, always try to read the value
|
||||
if (NetworkVariableFields[j].CanClientRead(clientId))
|
||||
{
|
||||
if (ensureLengthSafety || distributedAuthority)
|
||||
if (ensureLengthSafety)
|
||||
{
|
||||
// Read the type being serialized and discard it (for now) when in a distributed authority network topology (only used by comb-server)
|
||||
if (distributedAuthority)
|
||||
{
|
||||
reader.ReadValueSafe(out NetworkVariableType _);
|
||||
}
|
||||
|
||||
reader.ReadValueSafe(out varSize);
|
||||
if (varSize == 0)
|
||||
{
|
||||
@@ -1320,11 +1290,11 @@ namespace Unity.Netcode
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read the NetworkVarible value
|
||||
// Read the NetworkVariable value
|
||||
NetworkVariableFields[j].ReadField(reader);
|
||||
|
||||
// When EnsureNetworkVariableLengthSafety or DistributedAuthorityMode always do a bounds check
|
||||
if (ensureLengthSafety || distributedAuthority)
|
||||
// When EnsureNetworkVariableLengthSafety always do a bounds check
|
||||
if (ensureLengthSafety)
|
||||
{
|
||||
if (reader.Position > (readStartPos + varSize))
|
||||
{
|
||||
|
||||
@@ -141,6 +141,9 @@ namespace Unity.Netcode
|
||||
|
||||
// Then show any NetworkObjects queued to be made visible/shown
|
||||
m_NetworkManager.SpawnManager.HandleNetworkObjectShow();
|
||||
|
||||
// Handle object redistribution (DA + disabled scene management only)
|
||||
m_NetworkManager.HandleRedistributionToClients();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,18 @@ namespace Unity.Netcode
|
||||
}
|
||||
#endif
|
||||
|
||||
internal SessionConfig SessionConfig;
|
||||
|
||||
/// <summary>
|
||||
/// Used for internal testing purposes
|
||||
/// </summary>
|
||||
internal delegate SessionConfig OnGetSessionConfigHandler();
|
||||
internal OnGetSessionConfigHandler OnGetSessionConfig;
|
||||
private SessionConfig GetSessionConfig()
|
||||
{
|
||||
return OnGetSessionConfig != null ? OnGetSessionConfig.Invoke() : new SessionConfig();
|
||||
}
|
||||
|
||||
internal static bool IsDistributedAuthority;
|
||||
|
||||
/// <summary>
|
||||
@@ -162,10 +174,30 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
// DANGO-TODO-MVP: Remove these properties once the service handles object distribution
|
||||
internal ulong ClientToRedistribute;
|
||||
internal bool RedistributeToClient;
|
||||
internal int TickToRedistribute;
|
||||
// DANGO-TODO: Determine if this needs to be removed once the service handles object distribution
|
||||
internal List<ulong> ClientsToRedistribute = new List<ulong>();
|
||||
internal bool RedistributeToClients;
|
||||
|
||||
/// <summary>
|
||||
/// Handles object redistribution when scene management is disabled.
|
||||
/// <see cref="NetworkBehaviourUpdater.NetworkBehaviourUpdater_Tick"/>
|
||||
/// DANGO-TODO: Determine if this needs to be removed once the service handles object distribution
|
||||
/// </summary>
|
||||
internal void HandleRedistributionToClients()
|
||||
{
|
||||
if (!DistributedAuthorityMode || !RedistributeToClients || NetworkConfig.EnableSceneManagement || ShutdownInProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var clientId in ClientsToRedistribute)
|
||||
{
|
||||
SpawnManager.DistributeNetworkObjects(clientId);
|
||||
}
|
||||
RedistributeToClients = false;
|
||||
ClientsToRedistribute.Clear();
|
||||
}
|
||||
|
||||
|
||||
internal List<NetworkObject> DeferredDespawnObjects = new List<NetworkObject>();
|
||||
|
||||
@@ -193,11 +225,7 @@ namespace Unity.Netcode
|
||||
foreach (var networkObjectEntry in SpawnManager.SpawnedObjects)
|
||||
{
|
||||
var networkObject = networkObjectEntry.Value;
|
||||
if (networkObject.IsSceneObject == null || !networkObject.IsSceneObject.Value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (networkObject.OwnerClientId != LocalClientId)
|
||||
if (networkObject.IsOwnershipSessionOwner && LocalClient.IsSessionOwner)
|
||||
{
|
||||
SpawnManager.ChangeOwnership(networkObject, LocalClientId, true);
|
||||
}
|
||||
@@ -291,6 +319,10 @@ namespace Unity.Netcode
|
||||
case NetworkUpdateStage.EarlyUpdate:
|
||||
{
|
||||
UpdateTopology();
|
||||
|
||||
// Handle processing any new connections or transport events
|
||||
NetworkConfig.NetworkTransport.EarlyUpdate();
|
||||
|
||||
ConnectionManager.ProcessPendingApprovals();
|
||||
ConnectionManager.PollAndHandleNetworkEvents();
|
||||
|
||||
@@ -298,6 +330,7 @@ namespace Unity.Netcode
|
||||
|
||||
AnticipationSystem.SetupForUpdate();
|
||||
MessageManager.ProcessIncomingMessageQueue();
|
||||
|
||||
MessageManager.CleanupDisconnectedClients();
|
||||
AnticipationSystem.ProcessReanticipation();
|
||||
}
|
||||
@@ -379,22 +412,15 @@ namespace Unity.Netcode
|
||||
// Metrics update needs to be driven by NetworkConnectionManager's update to assure metrics are dispatched after the send queue is processed.
|
||||
MetricsManager.UpdateMetrics();
|
||||
|
||||
// Handle sending any pending transport messages
|
||||
NetworkConfig.NetworkTransport.PostLateUpdate();
|
||||
|
||||
// TODO: Determine a better way to handle this
|
||||
NetworkObject.VerifyParentingStatus();
|
||||
|
||||
// This is "ok" to invoke when not processing messages since it is just cleaning up messages that never got handled within their timeout period.
|
||||
DeferredMessageManager.CleanupStaleTriggers();
|
||||
|
||||
// DANGO-TODO-MVP: Remove this once the service handles object distribution
|
||||
// NOTE: This needs to be the last thing done and should happen exactly at this point
|
||||
// in the update
|
||||
if (RedistributeToClient && ServerTime.Tick <= TickToRedistribute)
|
||||
{
|
||||
RedistributeToClient = false;
|
||||
SpawnManager.DistributeNetworkObjects(ClientToRedistribute);
|
||||
ClientToRedistribute = 0;
|
||||
}
|
||||
|
||||
if (m_ShuttingDown)
|
||||
{
|
||||
// Host-server will disconnect any connected clients prior to finalizing its shutdown
|
||||
@@ -925,6 +951,9 @@ namespace Unity.Netcode
|
||||
return; // May occur when the component is added
|
||||
}
|
||||
|
||||
// Do a validation pass on NetworkConfig properties
|
||||
NetworkConfig.OnValidate();
|
||||
|
||||
if (GetComponentInChildren<NetworkObject>() != null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
@@ -993,8 +1022,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (IsListening && change == PlayModeStateChange.ExitingPlayMode)
|
||||
{
|
||||
// Make sure we are not holding onto anything in case domain reload is disabled
|
||||
ShutdownInternal();
|
||||
OnApplicationQuit();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1161,6 +1189,12 @@ namespace Unity.Netcode
|
||||
|
||||
UpdateTopology();
|
||||
|
||||
// Always create a default session config when starting a NetworkManager instance
|
||||
if (DistributedAuthorityMode)
|
||||
{
|
||||
SessionConfig = GetSessionConfig();
|
||||
}
|
||||
|
||||
// Make sure the ServerShutdownState is reset when initializing
|
||||
if (server)
|
||||
{
|
||||
|
||||
@@ -102,10 +102,22 @@ namespace Unity.Netcode
|
||||
private const int k_SceneObjectType = 2;
|
||||
private const int k_SourceAssetObjectType = 3;
|
||||
|
||||
// Used to track any InContext or InIsolation prefab being edited.
|
||||
private static PrefabStage s_PrefabStage;
|
||||
// The network prefab asset that the edit mode scene has created an instance of (s_PrefabInstance).
|
||||
private static NetworkObject s_PrefabAsset;
|
||||
// The InContext or InIsolation edit mode network prefab scene instance of the prefab asset (s_PrefabAsset).
|
||||
private static NetworkObject s_PrefabInstance;
|
||||
|
||||
private static bool s_DebugPrefabIdGeneration;
|
||||
|
||||
|
||||
[ContextMenu("Refresh In-Scene Prefab Instances")]
|
||||
internal void RefreshAllPrefabInstances()
|
||||
{
|
||||
var instanceGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(this);
|
||||
// Assign the currently selected instance to be updated
|
||||
NetworkObjectRefreshTool.PrefabNetworkObject = this;
|
||||
if (!PrefabUtility.IsPartOfAnyPrefab(this) || instanceGlobalId.identifierType != k_ImportedAssetObjectType)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Network Prefab Assets Only", "This action can only be performed on a network prefab asset.", "Ok");
|
||||
@@ -132,25 +144,119 @@ namespace Unity.Netcode
|
||||
NetworkObjectRefreshTool.ProcessScenes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register for <see cref="PrefabStage"/> opened and closing event notifications.
|
||||
/// </summary>
|
||||
[InitializeOnLoadMethod]
|
||||
private static void OnApplicationStart()
|
||||
{
|
||||
PrefabStage.prefabStageOpened -= PrefabStageOpened;
|
||||
PrefabStage.prefabStageOpened += PrefabStageOpened;
|
||||
PrefabStage.prefabStageClosing -= PrefabStageClosing;
|
||||
PrefabStage.prefabStageClosing += PrefabStageClosing;
|
||||
}
|
||||
|
||||
private static void PrefabStageClosing(PrefabStage prefabStage)
|
||||
{
|
||||
// If domain reloading is enabled, then this will be null when we return from playmode.
|
||||
if (s_PrefabStage == null)
|
||||
{
|
||||
// Determine if we have a network prefab opened in edit mode or not.
|
||||
CheckPrefabStage(prefabStage);
|
||||
}
|
||||
|
||||
s_PrefabStage = null;
|
||||
s_PrefabInstance = null;
|
||||
s_PrefabAsset = null;
|
||||
}
|
||||
|
||||
private static void PrefabStageOpened(PrefabStage prefabStage)
|
||||
{
|
||||
// Determine if we have a network prefab opened in edit mode or not.
|
||||
CheckPrefabStage(prefabStage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if we have opened a network prefab in edit mode (InContext or InIsolation)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// InContext: Typically means a are in prefab edit mode for an in-scene placed network prefab instance.
|
||||
/// (currently no such thing as a network prefab with nested network prefab instances)
|
||||
///
|
||||
/// InIsolation: Typically means we are in prefb edit mode for a prefab asset.
|
||||
/// </remarks>
|
||||
/// <param name="prefabStage"></param>
|
||||
private static void CheckPrefabStage(PrefabStage prefabStage)
|
||||
{
|
||||
s_PrefabStage = prefabStage;
|
||||
s_PrefabInstance = prefabStage.prefabContentsRoot?.GetComponent<NetworkObject>();
|
||||
if (s_PrefabInstance)
|
||||
{
|
||||
// We acquire the source prefab that the prefab edit mode scene instance was instantiated from differently for InContext than InSolation.
|
||||
if (s_PrefabStage.mode == PrefabStage.Mode.InContext && s_PrefabStage.openedFromInstanceRoot != null)
|
||||
{
|
||||
// This is needed to handle the scenario where a user completely loads a new scene while in an InContext prefab edit mode.
|
||||
try
|
||||
{
|
||||
s_PrefabAsset = s_PrefabStage.openedFromInstanceRoot?.GetComponent<NetworkObject>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
s_PrefabAsset = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// When editing in InIsolation mode, load the original prefab asset from the provided path.
|
||||
s_PrefabAsset = AssetDatabase.LoadAssetAtPath<NetworkObject>(s_PrefabStage.assetPath);
|
||||
}
|
||||
|
||||
if (s_PrefabInstance.GlobalObjectIdHash != s_PrefabAsset.GlobalObjectIdHash)
|
||||
{
|
||||
s_PrefabInstance.GlobalObjectIdHash = s_PrefabAsset.GlobalObjectIdHash;
|
||||
// For InContext mode, we don't want to record these modifications (the in-scene GlobalObjectIdHash is serialized with the scene).
|
||||
if (s_PrefabStage.mode == PrefabStage.Mode.InIsolation)
|
||||
{
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(s_PrefabAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
s_PrefabStage = null;
|
||||
s_PrefabInstance = null;
|
||||
s_PrefabAsset = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GlobalObjectIdHash values are generated during validation.
|
||||
/// </summary>
|
||||
internal void OnValidate()
|
||||
{
|
||||
// do NOT regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in PlayMode
|
||||
// Always exit early if we are in prefab edit mode and this instance is the
|
||||
// prefab instance within the InContext or InIsolation edit scene.
|
||||
if (s_PrefabInstance == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in play mode.
|
||||
if (EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// do NOT regenerate GlobalObjectIdHash if Editor is transitioning into or out of PlayMode
|
||||
// Do not regenerate GlobalObjectIdHash if Editor is transitioning into or out of play mode.
|
||||
if (!EditorApplication.isPlaying && EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a global object identifier for this network prefab
|
||||
var globalId = GetGlobalId();
|
||||
// Get a global object identifier for this network prefab.
|
||||
var globalId = GlobalObjectId.GetGlobalObjectIdSlow(this);
|
||||
|
||||
|
||||
// if the identifier type is 0, then don't update the GlobalObjectIdHash
|
||||
// if the identifier type is 0, then don't update the GlobalObjectIdHash.
|
||||
if (globalId.identifierType == k_NullObjectType)
|
||||
{
|
||||
return;
|
||||
@@ -159,47 +265,34 @@ namespace Unity.Netcode
|
||||
var oldValue = GlobalObjectIdHash;
|
||||
GlobalObjectIdHash = globalId.ToString().Hash32();
|
||||
|
||||
// If the GlobalObjectIdHash value changed, then mark the asset dirty
|
||||
// Always check for in-scene placed to assure any previous version scene assets with in-scene place NetworkObjects gets updated.
|
||||
CheckForInScenePlaced();
|
||||
|
||||
// If the GlobalObjectIdHash value changed, then mark the asset dirty.
|
||||
if (GlobalObjectIdHash != oldValue)
|
||||
{
|
||||
// Check if this is an in-scnee placed NetworkObject (Special Case for In-Scene Placed)
|
||||
if (!IsEditingPrefab() && gameObject.scene.name != null && gameObject.scene.name != gameObject.name)
|
||||
// Check if this is an in-scnee placed NetworkObject (Special Case for In-Scene Placed).
|
||||
if (IsSceneObject.HasValue && IsSceneObject.Value)
|
||||
{
|
||||
// Sanity check to make sure this is a scene placed object
|
||||
// Sanity check to make sure this is a scene placed object.
|
||||
if (globalId.identifierType != k_SceneObjectType)
|
||||
{
|
||||
// This should never happen, but in the event it does throw and error
|
||||
// This should never happen, but in the event it does throw and error.
|
||||
Debug.LogError($"[{gameObject.name}] is detected as an in-scene placed object but its identifier is of type {globalId.identifierType}! **Report this error**");
|
||||
}
|
||||
|
||||
// If this is a prefab instance
|
||||
// If this is a prefab instance, then we want to mark it as having been updated in order for the udpated GlobalObjectIdHash value to be saved.
|
||||
if (PrefabUtility.IsPartOfAnyPrefab(this))
|
||||
{
|
||||
// We must invoke this in order for the modifications to get saved with the scene (does not mark scene as dirty)
|
||||
// We must invoke this in order for the modifications to get saved with the scene (does not mark scene as dirty).
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
|
||||
}
|
||||
}
|
||||
else // Otherwise, this is a standard network prefab asset so we just mark it dirty for the AssetDatabase to update it
|
||||
else // Otherwise, this is a standard network prefab asset so we just mark it dirty for the AssetDatabase to update it.
|
||||
{
|
||||
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()
|
||||
{
|
||||
// Check if we are directly editing the prefab
|
||||
var stage = PrefabStageUtility.GetPrefabStage(gameObject);
|
||||
|
||||
// if we are not editing the prefab directly (or a sub-prefab), then return the object identifier
|
||||
if (stage == null || stage.assetPath == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -210,13 +303,12 @@ namespace Unity.Netcode
|
||||
/// <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)
|
||||
if (PrefabUtility.IsPartOfAnyPrefab(this) && gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0)
|
||||
{
|
||||
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
|
||||
var assetPath = AssetDatabase.GetAssetPath(prefab);
|
||||
@@ -229,55 +321,6 @@ namespace Unity.Netcode
|
||||
IsSceneObject = true;
|
||||
}
|
||||
}
|
||||
|
||||
private GlobalObjectId GetGlobalId()
|
||||
{
|
||||
var instanceGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(this);
|
||||
|
||||
// If not editing a prefab, then just use the generated id
|
||||
if (!IsEditingPrefab())
|
||||
{
|
||||
return instanceGlobalId;
|
||||
}
|
||||
|
||||
// If the asset doesn't exist at the given path, then return the object identifier
|
||||
var prefabStageAssetPath = PrefabStageUtility.GetPrefabStage(gameObject).assetPath;
|
||||
// If (for some reason) the asset path is null return the generated id
|
||||
if (prefabStageAssetPath == null)
|
||||
{
|
||||
return instanceGlobalId;
|
||||
}
|
||||
|
||||
var theAsset = AssetDatabase.LoadAssetAtPath<NetworkObject>(prefabStageAssetPath);
|
||||
// If there is no asset at that path (for some odd/edge case reason), return the generated id
|
||||
if (theAsset == null)
|
||||
{
|
||||
return instanceGlobalId;
|
||||
}
|
||||
|
||||
// If we can't get the asset GUID and/or the file identifier, then return the object identifier
|
||||
if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(theAsset, out var guid, out long localFileId))
|
||||
{
|
||||
return instanceGlobalId;
|
||||
}
|
||||
|
||||
// Note: If we reached this point, then we are most likely opening a prefab to edit.
|
||||
// The instanceGlobalId will be constructed as if it is a scene object, however when it
|
||||
// is serialized its value will be treated as a file asset (the "why" to the below code).
|
||||
|
||||
// Construct an imported asset identifier with the type being a source asset object type
|
||||
var prefabGlobalIdText = string.Format(k_GlobalIdTemplate, k_SourceAssetObjectType, guid, (ulong)localFileId, 0);
|
||||
|
||||
// If we can't parse the result log an error and return the instanceGlobalId
|
||||
if (!GlobalObjectId.TryParse(prefabGlobalIdText, out var prefabGlobalId))
|
||||
{
|
||||
Debug.LogError($"[GlobalObjectId Gen] Failed to parse ({prefabGlobalIdText}) returning default ({instanceGlobalId})! ** Please Report This Error **");
|
||||
return instanceGlobalId;
|
||||
}
|
||||
|
||||
// Otherwise, return the constructed identifier for the source prefab asset
|
||||
return prefabGlobalId;
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
|
||||
/// <summary>
|
||||
@@ -396,6 +439,13 @@ namespace Unity.Netcode
|
||||
/// </remarks>
|
||||
public bool IsOwnershipDistributable => Ownership.HasFlag(OwnershipStatus.Distributable);
|
||||
|
||||
/// <summary>
|
||||
/// When true, the <see cref="NetworkObject"/> can only be owned by the current Session Owner.
|
||||
/// To set <see cref="OwnershipStatus.SessionOwner"/> during runtime, use <see cref="ChangeOwnership(ulong)"/> to ensure the session owner owns the object.
|
||||
/// Once the session owner owns the object, then use <see cref="SetOwnershipStatus(OwnershipStatus, bool, OwnershipLockActions)"/>.
|
||||
/// </summary>
|
||||
public bool IsOwnershipSessionOwner => Ownership.HasFlag(OwnershipStatus.SessionOwner);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the <see cref="NetworkObject"/> is has ownership locked.
|
||||
/// When locked, the <see cref="NetworkObject"/> cannot be redistributed nor can it be transferred by another client.
|
||||
@@ -438,7 +488,8 @@ namespace Unity.Netcode
|
||||
/// <see cref="None"/>: If nothing is set, then ownership is considered "static" and cannot be redistributed, requested, or transferred (i.e. a Player would have this).
|
||||
/// <see cref="Distributable"/>: When set, this instance will be automatically redistributed when a client joins (if not locked or no request is pending) or leaves.
|
||||
/// <see cref="Transferable"/>: When set, a non-owner can obtain ownership immediately (without requesting and as long as it is not locked).
|
||||
/// <see cref="RequestRequired"/>: When set, When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred).
|
||||
/// <see cref="RequestRequired"/>: When set, a non-owner must request ownership from the owner (will always get locked once ownership is transferred).
|
||||
/// <see cref="SessionOwner"/>: When set, only the current session owner may have ownership over this object.
|
||||
/// </summary>
|
||||
// Ranges from 1 to 8 bits
|
||||
[Flags]
|
||||
@@ -448,6 +499,7 @@ namespace Unity.Netcode
|
||||
Distributable = 1 << 0,
|
||||
Transferable = 1 << 1,
|
||||
RequestRequired = 1 << 2,
|
||||
SessionOwner = 1 << 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -506,7 +558,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
// If we don't have the Transferable flag set and it is not a player object, then it is the same as having a static lock on ownership
|
||||
if (!IsOwnershipTransferable && !IsPlayerObject)
|
||||
if (!(IsOwnershipTransferable || IsPlayerObject) || IsOwnershipSessionOwner)
|
||||
{
|
||||
NetworkLog.LogWarning($"Trying to add or remove ownership lock on [{name}] which does not have the {nameof(OwnershipStatus.Transferable)} flag set!");
|
||||
return false;
|
||||
@@ -539,13 +591,15 @@ namespace Unity.Netcode
|
||||
/// <see cref="RequestRequired"/>: The <see cref="NetworkObject"/> requires an ownership request via <see cref="RequestOwnership"/>.
|
||||
/// <see cref="RequestInProgress"/>: The <see cref="NetworkObject"/> is already processing an ownership request and ownership cannot be acquired at this time.
|
||||
/// <see cref="NotTransferrable"/>: The <see cref="NetworkObject"/> does not have the <see cref="OwnershipStatus.Transferable"/> flag set and ownership cannot be acquired.
|
||||
/// <see cref="SessionOwnerOnly"/>: The <see cref="NetworkObject"/> has the <see cref="OwnershipStatus.SessionOwner"/> flag set and ownership cannot be acquired.
|
||||
/// </summary>
|
||||
public enum OwnershipPermissionsFailureStatus
|
||||
{
|
||||
Locked,
|
||||
RequestRequired,
|
||||
RequestInProgress,
|
||||
NotTransferrable
|
||||
NotTransferrable,
|
||||
SessionOwnerOnly
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -567,6 +621,7 @@ namespace Unity.Netcode
|
||||
/// <see cref="RequestRequiredNotSet"/>: The <see cref="OwnershipStatus.RequestRequired"/> flag is not set on this <see cref="NetworkObject"/>
|
||||
/// <see cref="Locked"/>: The current owner has locked ownership which means requests are not available at this time.
|
||||
/// <see cref="RequestInProgress"/>: There is already a known request in progress. You can scan for ownership changes and try upon
|
||||
/// <see cref="SessionOwnerOnly"/>: This object is marked as SessionOwnerOnly and therefore cannot be requested
|
||||
/// a change in ownership or just try again after a specific period of time or no longer attempt to request ownership.
|
||||
/// </summary>
|
||||
public enum OwnershipRequestStatus
|
||||
@@ -576,6 +631,7 @@ namespace Unity.Netcode
|
||||
RequestRequiredNotSet,
|
||||
Locked,
|
||||
RequestInProgress,
|
||||
SessionOwnerOnly,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -588,6 +644,7 @@ namespace Unity.Netcode
|
||||
/// <see cref="OwnershipRequestStatus.RequestRequiredNotSet"/>: The <see cref="OwnershipStatus.RequestRequired"/> flag is not set on this <see cref="NetworkObject"/>
|
||||
/// <see cref="OwnershipRequestStatus.Locked"/>: The current owner has locked ownership which means requests are not available at this time.
|
||||
/// <see cref="OwnershipRequestStatus.RequestInProgress"/>: There is already a known request in progress. You can scan for ownership changes and try upon
|
||||
/// <see cref="OwnershipRequestStatus.SessionOwnerOnly"/>: This object can only belong the the session owner and so cannot be requested
|
||||
/// a change in ownership or just try again after a specific period of time or no longer attempt to request ownership.
|
||||
/// </remarks>
|
||||
/// <returns><see cref="OwnershipRequestStatus"/></returns>
|
||||
@@ -617,6 +674,12 @@ namespace Unity.Netcode
|
||||
return OwnershipRequestStatus.RequestInProgress;
|
||||
}
|
||||
|
||||
// Exit early if it has the SessionOwner flag
|
||||
if (IsOwnershipSessionOwner)
|
||||
{
|
||||
return OwnershipRequestStatus.SessionOwnerOnly;
|
||||
}
|
||||
|
||||
// Otherwise, send the request ownership message
|
||||
var changeOwnership = new ChangeOwnershipMessage
|
||||
{
|
||||
@@ -673,7 +736,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
response = OwnershipRequestResponseStatus.RequestInProgress;
|
||||
}
|
||||
else if (!IsOwnershipRequestRequired && !IsOwnershipTransferable)
|
||||
else if (!(IsOwnershipRequestRequired || IsOwnershipTransferable) || IsOwnershipSessionOwner)
|
||||
{
|
||||
response = OwnershipRequestResponseStatus.CannotRequest;
|
||||
}
|
||||
@@ -793,6 +856,12 @@ namespace Unity.Netcode
|
||||
/// </remarks>
|
||||
public bool SetOwnershipStatus(OwnershipStatus status, bool clearAndSet = false, OwnershipLockActions lockAction = OwnershipLockActions.None)
|
||||
{
|
||||
if (status.HasFlag(OwnershipStatus.SessionOwner) && !NetworkManager.LocalClient.IsSessionOwner)
|
||||
{
|
||||
NetworkLog.LogWarning("Only the session owner is allowed to set the ownership status to session owner only.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it already has the flag do nothing
|
||||
if (!clearAndSet && Ownership.HasFlag(status))
|
||||
{
|
||||
@@ -804,13 +873,25 @@ namespace Unity.Netcode
|
||||
Ownership = OwnershipStatus.None;
|
||||
}
|
||||
|
||||
// Faster to just OR a None status than to check
|
||||
// if it is !None before "OR'ing".
|
||||
Ownership |= status;
|
||||
|
||||
if (lockAction != OwnershipLockActions.None)
|
||||
if (status.HasFlag(OwnershipStatus.SessionOwner))
|
||||
{
|
||||
SetOwnershipLock(lockAction == OwnershipLockActions.SetAndLock);
|
||||
Ownership = OwnershipStatus.SessionOwner;
|
||||
}
|
||||
else if (Ownership.HasFlag(OwnershipStatus.SessionOwner))
|
||||
{
|
||||
NetworkLog.LogWarning("No other ownership statuses may be set while SessionOwner is set.");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Faster to just OR a None status than to check
|
||||
// if it is !None before "OR'ing".
|
||||
Ownership |= status;
|
||||
|
||||
if (lockAction != OwnershipLockActions.None)
|
||||
{
|
||||
SetOwnershipLock(lockAction == OwnershipLockActions.SetAndLock);
|
||||
}
|
||||
}
|
||||
|
||||
SendOwnershipStatusUpdate();
|
||||
@@ -1586,7 +1667,7 @@ namespace Unity.Netcode
|
||||
// DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set
|
||||
if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogWarning("DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set. For now, if the NetworkObject does not destroy with the owner it will automatically set DistributeOwnership.");
|
||||
NetworkLog.LogWarning("DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set. For now, if the NetworkObject does not destroy with the owner it will set ownership to SessionOwner.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1964,12 +2045,14 @@ namespace Unity.Netcode
|
||||
|
||||
internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true)
|
||||
{
|
||||
if (parent != null && (IsSpawned ^ parent.IsSpawned))
|
||||
if (parent != null && (IsSpawned ^ parent.IsSpawned) && NetworkManager != null && !NetworkManager.ShutdownInProgress)
|
||||
{
|
||||
if (NetworkManager != null && !NetworkManager.ShutdownInProgress)
|
||||
if (NetworkManager.LogLevel <= LogLevel.Developer)
|
||||
{
|
||||
return false;
|
||||
var nameOfNotSpawnedObject = IsSpawned ? $" the parent ({parent.name})" : $"the child ({name})";
|
||||
NetworkLog.LogWarning($"Parenting failed because {nameOfNotSpawnedObject} is not spawned!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
m_CachedWorldPositionStays = worldPositionStays;
|
||||
@@ -2892,7 +2975,7 @@ namespace Unity.Netcode
|
||||
SyncObservers = syncObservers,
|
||||
Observers = syncObservers ? Observers.ToArray() : null,
|
||||
NetworkSceneHandle = NetworkSceneHandle,
|
||||
Hash = HostCheckForGlobalObjectIdHashOverride(),
|
||||
Hash = CheckForGlobalObjectIdHashOverride(),
|
||||
OwnerObject = this,
|
||||
TargetClientId = targetClientId
|
||||
};
|
||||
@@ -3244,14 +3327,15 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only applies to Host mode.
|
||||
/// Client-Server: Only applies to spawn authority (i.e. Server)
|
||||
/// Distributed Authority: Applies to all clients since they all have spawn authority.
|
||||
/// Will return the registered source NetworkPrefab's GlobalObjectIdHash if one exists.
|
||||
/// Server and Clients will always return the NetworkObject's GlobalObjectIdHash.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal uint HostCheckForGlobalObjectIdHashOverride()
|
||||
/// <returns>appropriate hash value</returns>
|
||||
internal uint CheckForGlobalObjectIdHashOverride()
|
||||
{
|
||||
if (NetworkManager.IsServer)
|
||||
if (NetworkManager.IsServer || NetworkManager.DistributedAuthorityMode)
|
||||
{
|
||||
if (NetworkManager.PrefabHandler.ContainsHandler(this))
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ using UnityEngine.SceneManagement;
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a helper tool to update all in-scene placed instances of a prefab that
|
||||
/// This is a helper tool to update all in-scene placed instances of a prefab that
|
||||
/// originally did not have a NetworkObject component but one was added to the prefab
|
||||
/// later.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user