Skip to content
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- Fixed issue where either an `AttachableBehaviour` or an `AttachableNode` can throw an exception if they are attached during a scene unload where one of the two persists the scene unload event and the other does not. (#3931)
- Fixed issue where attempts to use `NetworkLog` when there is no `NetworkManager` instance would result in an exception. (#3917)

### Security
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ public enum AttachState
private Vector3 m_OriginalLocalPosition;
private Quaternion m_OriginalLocalRotation;

internal bool IsDestroying { get; private set; }

/// <inheritdoc/>
protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
{
Expand Down Expand Up @@ -238,6 +240,42 @@ protected virtual void Awake()
OnAwake();
}

/// <inheritdoc/>
protected override void OnNetworkPreSpawn(ref NetworkManager networkManager)
{
IsDestroying = false;
// When attached to something else, the attachable needs to know if the
// default parent has been destroyed in order to not attempt to re-parent
// when detached (especially if it is being detached because it should be destroyed).
NetworkObject.OnDestroying += OnDefaultParentDestroying;

base.OnNetworkPreSpawn(ref networkManager);
}

private void OnDefaultParentDestroying()
{
NetworkObject.OnDestroying -= OnDefaultParentDestroying;
// Exit early if we are already being destroyed
if (IsDestroying)
{
return;
}
IsDestroying = true;
// If not completely detached, then destroy the GameObject for
// this attachable since the associated NetworkObject is being
// destroyed.
if (m_AttachState != AttachState.Detached)
{
Destroy(gameObject);
}
}

internal override void InternalOnDestroy()
{
IsDestroying = true;
base.InternalOnDestroy();
}

/// <inheritdoc/>
/// <remarks>
/// If you create a custom <see cref="AttachableBehaviour"/> and override this method, you will want to
Expand Down Expand Up @@ -286,7 +324,7 @@ public override void OnNetworkPreDespawn()
}

/// <summary>
/// This will apply the final attach or detatch state based on the current value of <see cref="m_AttachedNodeReference"/>.
/// This will apply the final attach or detach state based on the current value of <see cref="m_AttachedNodeReference"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UpdateAttachedState()
Expand Down Expand Up @@ -392,7 +430,8 @@ internal void ForceComponentChange(bool isAttaching, bool forcedChange)

foreach (var componentControllerEntry in ComponentControllers)
{
if (componentControllerEntry.AutoTrigger.HasFlag(triggerType))
// Only if the component controller still exists and has the appropriate flag.
if (componentControllerEntry.ComponentController && componentControllerEntry.AutoTrigger.HasFlag(triggerType))
Comment thread
NoelStephensUnity marked this conversation as resolved.
{
componentControllerEntry.ComponentController.ForceChangeEnabled(componentControllerEntry.EnableOnAttach ? isAttaching : !isAttaching, forcedChange);
}
Expand Down Expand Up @@ -457,7 +496,7 @@ public void Attach(AttachableNode attachableNode)
/// </summary>
internal void InternalDetach()
{
if (m_AttachableNode)
if (!IsDestroying && m_AttachableNode && !m_AttachableNode.IsDestroying)
{
if (m_DefaultParent)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class AttachableNode : NetworkBehaviour
/// </summary>
public bool DetachOnDespawn = true;

internal bool IsDestroying { get; private set; }

/// <summary>
/// A <see cref="List{T}"/> of the currently attached <see cref="AttachableBehaviour"/>s.
/// </summary>
Expand All @@ -32,6 +34,7 @@ public class AttachableNode : NetworkBehaviour
/// <inheritdoc/>
protected override void OnNetworkPreSpawn(ref NetworkManager networkManager)
{
IsDestroying = false;
m_AttachedBehaviours.Clear();
base.OnNetworkPreSpawn(ref networkManager);
}
Expand Down Expand Up @@ -71,20 +74,28 @@ public override void OnNetworkPreDespawn()
{
for (int i = m_AttachedBehaviours.Count - 1; i >= 0; i--)
{
if (!m_AttachedBehaviours[i])
var attachable = m_AttachedBehaviours[i];
if (!attachable)
{
continue;
}
// If we don't have authority but should detach on despawn,
// then proceed to detach.
if (!m_AttachedBehaviours[i].HasAuthority)
if (!attachable.HasAuthority)
{
m_AttachedBehaviours[i].ForceDetach();
attachable.ForceDetach();
}
else
{
// Detach the normal way with authority
m_AttachedBehaviours[i].Detach();
if (attachable.IsSpawned)
{
// Detach the normal way with authority
attachable.Detach();
}
else if (!attachable.IsDestroying)
{
attachable.ForceDetach();
}
Comment thread
NoelStephensUnity marked this conversation as resolved.
Outdated
}
}
}
Expand All @@ -93,6 +104,7 @@ public override void OnNetworkPreDespawn()

internal override void InternalOnDestroy()
{
IsDestroying = true;
// Notify any attached behaviours that this node is being destroyed.
for (int i = m_AttachedBehaviours.Count - 1; i >= 0; i--)
{
Expand Down
6 changes: 6 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1688,8 +1688,14 @@ public static void NetworkHide(List<NetworkObject> networkObjects, ulong clientI
}
}

/// <summary>
/// Invoked when the NetworkObject is destroyed.
/// </summary>
internal event Action OnDestroying;

private void OnDestroy()
{
OnDestroying?.Invoke();
Comment thread
NoelStephensUnity marked this conversation as resolved.
Outdated
var networkManager = NetworkManager;
// If no NetworkManager is assigned, then just exit early
if (!networkManager)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ protected override void OnServerAndClientsCreated()
attachableNetworkObject.SetOwnershipStatus(NetworkObject.OwnershipStatus.Transferable);

// The target prefab that the source prefab will attach
// will be parented under the target prefab.
// to will be parented under the target prefab.
m_TargetNodePrefabA = CreateNetworkObjectPrefab("TargetA");
m_TargetNodePrefabB = CreateNetworkObjectPrefab("TargetB");
var sourceChild = new GameObject("SourceChild");
Expand Down Expand Up @@ -676,10 +676,13 @@ internal class TestAttachable : AttachableBehaviour
public GameObject DefaultParent => m_DefaultParent;
public AttachState State => m_AttachState;

public bool DestroyWithScene;

public override void OnNetworkSpawn()
{
AttachStateChange += OnAttachStateChangeEvent;
name = $"{name}-{NetworkManager.LocalClientId}";
NetworkObject.DestroyWithScene = DestroyWithScene;
base.OnNetworkSpawn();
}

Expand Down Expand Up @@ -780,9 +783,16 @@ public bool CheckForState(bool checkAttached, bool checkEvent)
/// </summary>
internal class TestNode : AttachableNode
{
public bool DestroyWithScene;
public bool OnAttachedInvoked { get; private set; }
public bool OnDetachedInvoked { get; private set; }

public override void OnNetworkSpawn()
{
NetworkObject.DestroyWithScene = DestroyWithScene;
base.OnNetworkSpawn();
}

public bool IsAttached(AttachableBehaviour attachableBehaviour)
{
return m_AttachedBehaviours.Contains(attachableBehaviour);
Expand Down
Loading