Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Fixed

- Fixed exception being thrown when a `GameObject` with an associated `NetworkTransform` is disabled. (#3243)
- Fixed `NetworkObject.DeferDespawn` to respect the `DestroyGameObject` parameter. (#3219)
- Changed the `NetworkTimeSystem.Sync` method to use half RTT to calculate the desired local time offset as opposed to the full RTT. (#3212)
- Fixed issue where a spawned `NetworkObject` that was registered with a prefab handler and owned by a client would invoke destroy more than once on the host-server side if the client disconnected while the `NetworkObject` was still spawned. (#3200)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,21 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int
if (isSpawnedLocally)
{
networkObject = networkManager.SpawnManager.SpawnedObjects[networkObjectId];
if (networkObject.ChildNetworkBehaviours.Count <= networkBehaviourId || networkObject.ChildNetworkBehaviours[networkBehaviourId] == null)
{
Debug.LogError($"[{nameof(NetworkTransformMessage)}][Invalid] Targeted {nameof(NetworkTransform)}, {nameof(NetworkBehaviour.NetworkBehaviourId)} ({networkBehaviourId}), does not exist! Make sure you are not spawning {nameof(NetworkObject)}s with disabled {nameof(GameObject)}s that have {nameof(NetworkBehaviour)} components on them.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice wordsmithing...

return false;
}

// Get the target NetworkTransform
NetworkTransform = networkObject.ChildNetworkBehaviours[networkBehaviourId] as NetworkTransform;
var transform = networkObject.ChildNetworkBehaviours[networkBehaviourId] as NetworkTransform;
if (transform == null)
{
Debug.LogError($"[{nameof(NetworkTransformMessage)}][Invalid] Targeted {nameof(NetworkTransform)}, {nameof(NetworkBehaviour.NetworkBehaviourId)} ({networkBehaviourId}), does not exist! Make sure you are not spawning {nameof(NetworkObject)}s with disabled {nameof(GameObject)}s that have {nameof(NetworkBehaviour)} components on them.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return false;
}

NetworkTransform = transform;
isServerAuthoritative = NetworkTransform.IsServerAuthoritative();
ownerAuthoritativeServerSide = !isServerAuthoritative && networkManager.IsServer;

Expand All @@ -81,8 +94,8 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int
}
else
{
// Deserialize the state
reader.ReadNetworkSerializableInPlace(ref State);
Debug.LogError($"[{nameof(NetworkTransformMessage)}][Invalid] Target NetworkObject does not exist!");
return false;
}

unsafe
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System.Collections;
using Unity.Netcode.Components;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;


namespace Unity.Netcode.RuntimeTests
{
internal class NetworkTransformErrorTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 1;

private GameObject m_AuthorityPrefab;
private GameObject m_NonAuthorityPrefab;

private HostAndClientPrefabHandler m_HostAndClientPrefabHandler;

public class EmptyNetworkBehaviour : NetworkBehaviour { }

/// <summary>
/// PrefabHandler that tracks and separates the client GameObject from the host GameObject.
/// Allows independent management of client and host game world while still instantiating NetworkObjects as expected.
/// </summary>
private class HostAndClientPrefabHandler : INetworkPrefabInstanceHandler
{
/// <summary>
/// The registered prefab is the prefab the networking stack is instantiated with.
/// Registering the prefab simulates the prefab that exists on the authority.
/// </summary>
private readonly GameObject m_RegisteredPrefab;

/// <summary>
/// Mocks the registered prefab changing on the non-authority after registration.
/// Allows testing situations mismatched GameObject state between the authority and non-authority.
/// </summary>
private readonly GameObject m_InstantiatedPrefab;

public HostAndClientPrefabHandler(GameObject authorityPrefab, GameObject nonAuthorityPrefab)
{
m_RegisteredPrefab = authorityPrefab;
m_InstantiatedPrefab = nonAuthorityPrefab;
}

/// <summary>
/// Returns the prefab that will mock the instantiated prefab not matching the registered prefab
/// </summary>
public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation)
{
return Object.Instantiate(m_InstantiatedPrefab).GetComponent<NetworkObject>();
}

public void Destroy(NetworkObject networkObject)
{
Object.Destroy(networkObject.gameObject);
}

public void Register(NetworkManager networkManager)
{
// Register the version that will be spawned by the authority (i.e. Host)
networkManager.PrefabHandler.AddHandler(m_RegisteredPrefab, this);
}
}

/// <summary>
/// Creates a GameObject and sets the transform parent to the given transform
/// Adds a component of the given type to the GameObject
/// </summary>
private static void AddChildToNetworkObject<T>(Transform transform) where T : Component
{
var gameObj = new GameObject();
gameObj.transform.parent = transform;
gameObj.AddComponent<T>();
}

protected override void OnServerAndClientsCreated()
{
// Create a prefab that has many child NetworkBehaviours
m_AuthorityPrefab = CreateNetworkObjectPrefab("AuthorityPrefab");
AddChildToNetworkObject<EmptyNetworkBehaviour>(m_AuthorityPrefab.transform);
AddChildToNetworkObject<EmptyNetworkBehaviour>(m_AuthorityPrefab.transform);
AddChildToNetworkObject<NetworkTransform>(m_AuthorityPrefab.transform);

// Create a second prefab with only one NetworkBehaviour
// This simulates the GameObjects on the other NetworkBehaviours being disabled
m_NonAuthorityPrefab = CreateNetworkObjectPrefab("NonAuthorityPrefab");
AddChildToNetworkObject<NetworkTransform>(m_NonAuthorityPrefab.transform);

// Create and register a prefab handler
// The prefab handler will behave as if the GameObjects have been disabled on the non-authority client
m_HostAndClientPrefabHandler = new HostAndClientPrefabHandler(m_AuthorityPrefab, m_NonAuthorityPrefab);
m_HostAndClientPrefabHandler.Register(m_ServerNetworkManager);
foreach (var client in m_ClientNetworkManagers)
{
m_HostAndClientPrefabHandler.Register(client);
}

base.OnServerAndClientsCreated();
}


[UnityTest]
public IEnumerator DisabledGameObjectErrorTest()
{
var instance = SpawnObject(m_AuthorityPrefab, m_ServerNetworkManager);
var networkObjectInstance = instance.GetComponent<NetworkObject>();

yield return WaitForConditionOrTimeOut(() => ObjectSpawnedOnAllClients(networkObjectInstance.NetworkObjectId));
AssertOnTimeout("Timed out waiting for object to spawn!");

LogAssert.Expect(LogType.Error, "[Netcode] NetworkBehaviour index 3 was out of bounds for NonAuthorityPrefab(Clone). NetworkBehaviours must be the same, and in the same order, between server and client.");
LogAssert.Expect(LogType.Error, "[NetworkTransformMessage][Invalid] Targeted NetworkTransform, NetworkBehaviourId (3), does not exist! Make sure you are not spawning NetworkObjects with disabled GameObjects that have NetworkBehaviour components on them.");

yield return new WaitForSeconds(0.3f);
}

private bool ObjectSpawnedOnAllClients(ulong networkObjectId)
{
foreach (var client in m_ClientNetworkManagers)
{
if (!client.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId))
{
return false;
}
}
return true;
}
}

}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.