Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: NetworkObject component should always precede NetworkBehaviour components [MTT-7216] #2685

1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
42 changes: 42 additions & 0 deletions com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MonoBehaviour>();
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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.RegularExpressions;
using NUnit.Framework;
using Unity.Netcode.Editor;
using UnityEngine;
using UnityEngine.TestTools;

Expand Down Expand Up @@ -64,9 +65,95 @@ public void GetBehaviourIndexOne()
Object.DestroyImmediate(gameObject);
}

/// <summary>
/// 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)
/// </summary>
[Test]
public void NetworkObjectComponentOrder()
{
var gameObject = new GameObject(nameof(GetBehaviourIndexOne));
// Add the Networkbehaviour first
var networkBehaviour = gameObject.AddComponent<EmptyNetworkBehaviour>();
// Add an empty MonoBehaviour inbetween the NetworkBehaviour and NetworkObject
gameObject.AddComponent<EmptyMonoBehaviour>();
// Add the NetworkObject
var networkObject = gameObject.AddComponent<NetworkObject>();
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<MonoBehaviour>();
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
{

}
}
}
Loading