diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 4c5e9ae6af..b6de451821 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,6 +14,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where a `NetworkBehaviour` component's `OnNetworkDespawn` was not being invoked on the host-server side for an in-scene placed `NetworkObject` when a scene was unloaded (during a scene transition) and the `NetworkBehaviour` component was positioned/ordered before the `NetworkObject` component. (#2685) - Fixed issue where `SpawnWithObservers` was not being honored when `NetworkConfig.EnableSceneManagement` was disabled. (#2682) - Fixed issue where `NetworkAnimator` was not internally tracking changes to layer weights which prevented proper layer weight synchronization back to the original layer weight value. (#2674) - Fixed "writing past the end of the buffer" error when calling ResetDirty() on managed network variables that are larger than 256 bytes when serialized. (#2670) diff --git a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs index 0697449883..84af9377dd 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs @@ -417,6 +417,48 @@ public static void CheckForNetworkObject(GameObject gameObject, bool networkObje } } } + + if (networkObject != null) + { + OrderNetworkObject(networkObject); + } + } + + // Assures the NetworkObject precedes any NetworkBehaviour on the same GameObject as the NetworkObject + private static void OrderNetworkObject(NetworkObject networkObject) + { + var monoBehaviours = networkObject.gameObject.GetComponents(); + var networkObjectIndex = 0; + var firstNetworkBehaviourIndex = -1; + for (int i = 0; i < monoBehaviours.Length; i++) + { + if (monoBehaviours[i] == networkObject) + { + networkObjectIndex = i; + break; + } + + var networkBehaviour = monoBehaviours[i] as NetworkBehaviour; + if (networkBehaviour != null) + { + // Get the index of the first NetworkBehaviour Component + if (firstNetworkBehaviourIndex == -1) + { + firstNetworkBehaviourIndex = i; + } + } + } + + if (firstNetworkBehaviourIndex != -1 && networkObjectIndex > firstNetworkBehaviourIndex) + { + var positionsToMove = networkObjectIndex - firstNetworkBehaviourIndex; + for (int i = 0; i < positionsToMove; i++) + { + UnityEditorInternal.ComponentUtility.MoveComponentUp(networkObject); + } + + EditorUtility.SetDirty(networkObject.gameObject); + } } } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs index cb14dbf79f..dd0d930ea0 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkObjectTests.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using NUnit.Framework; +using Unity.Netcode.Editor; using UnityEngine; using UnityEngine.TestTools; @@ -64,9 +65,95 @@ public void GetBehaviourIndexOne() Object.DestroyImmediate(gameObject); } + /// + /// Verifies that a NetworkObject component that is positioned after a NetworkBehaviour component will + /// be migrated to a component index value that is before the lowest NetworkBehaviour component index value. + /// (The lowest NetworkBehaviour component's index value will also change when this happens) + /// + [Test] + public void NetworkObjectComponentOrder() + { + var gameObject = new GameObject(nameof(GetBehaviourIndexOne)); + // Add the Networkbehaviour first + var networkBehaviour = gameObject.AddComponent(); + // Add an empty MonoBehaviour inbetween the NetworkBehaviour and NetworkObject + gameObject.AddComponent(); + // Add the NetworkObject + var networkObject = gameObject.AddComponent(); + var componentIndices = GetIndices(gameObject); + + // Verify the NetworkObject procedes the NetworkBehaviour + Assert.True(componentIndices.NetworkObjectIndex > componentIndices.NetworkBehaviourIndex, $"[Initial Setup] NetworkObject index ({componentIndices.NetworkObjectIndex}) is not greater than the NetworkBehaviour index ({componentIndices.NetworkBehaviourIndex})!"); + + // Force-Invoke the CheckForNetworkObject method in order to verify the NetworkObject is moved + NetworkBehaviourEditor.CheckForNetworkObject(gameObject); + var adjustedIndices = GetIndices(gameObject); + + Assert.True(ValidateComponentIndices(componentIndices, GetIndices(gameObject)), "NetworkObject did not get migrated below the NetworkBehaviour!"); + + // Cleanup + Object.DestroyImmediate(gameObject); + } + + private bool ValidateComponentIndices(ComponentIndices previous, ComponentIndices current) + { + if (previous.NetworkObjectIndex != current.NetworkObjectIndex && previous.NetworkBehaviourIndex != current.NetworkBehaviourIndex) + { + if (current.NetworkObjectIndex < previous.NetworkObjectIndex && current.NetworkObjectIndex < current.NetworkBehaviourIndex) + { + return true; + } + } + return false; + } + + private ComponentIndices GetIndices(GameObject gameObject) + { + // Get the index/order values for the added NetworkBehaviour and NetworkObject + var components = gameObject.GetComponents(); + var componentIndices = new ComponentIndices() + { + NetworkObjectIndex = -1, + NetworkBehaviourIndex = -1 + }; + for (int i = 0; i < components.Length; i++) + { + if (componentIndices.NetworkObjectIndex != -1 && componentIndices.NetworkBehaviourIndex != -1) + { + break; + } + var component = components[i]; + var networkObjectComponent = component as NetworkObject; + if (networkObjectComponent != null) + { + componentIndices.NetworkObjectIndex = i; + continue; + } + var networkBehaviourComponent = component as EmptyNetworkBehaviour; + if (networkBehaviourComponent != null) + { + componentIndices.NetworkBehaviourIndex = i; + continue; + } + } + + return componentIndices; + } + + private struct ComponentIndices + { + public int NetworkObjectIndex; + public int NetworkBehaviourIndex; + } + public class EmptyNetworkBehaviour : NetworkBehaviour { } + + public class EmptyMonoBehaviour : MonoBehaviour + { + + } } }