com.unity.netcode.gameobjects@2.1.1
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.1.1] - 2024-10-18 ### Added - Added ability to edit the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` within the inspector view. (#3097) - Added `IContactEventHandlerWithInfo` that derives from `IContactEventHandler` that can be updated per frame to provide `ContactEventHandlerInfo` information to the `RigidbodyContactEventManager` when processing collisions. (#3094) - `ContactEventHandlerInfo.ProvideNonRigidBodyContactEvents`: When set to true, non-`Rigidbody` collisions with the registered `Rigidbody` will generate contact event notifications. (#3094) - `ContactEventHandlerInfo.HasContactEventPriority`: When set to true, the `Rigidbody` will be prioritized as the instance that generates the event if the `Rigidbody` colliding does not have priority. (#3094) - Added a static `NetworkManager.OnInstantiated` event notification to be able to track when a new `NetworkManager` instance has been instantiated. (#3088) - Added a static `NetworkManager.OnDestroying` event notification to be able to track when an existing `NetworkManager` instance is being destroyed. (#3088) ### Fixed - Fixed issue where `NetworkPrefabProcessor` would not mark the prefab list as dirty and prevent saving the `DefaultNetworkPrefabs` asset when only imports or only deletes were detected.(#3103) - Fixed an issue where nested `NetworkTransform` components in owner authoritative mode cleared their initial settings on the server, causing improper synchronization. (#3099) - Fixed issue with service not getting synchronized with in-scene placed `NetworkObject` instances when a session owner starts a `SceneEventType.Load` event. (#3096) - Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092) - Fixed an issue where newly synchronizing clients would always receive current `NetworkVariable` values, potentially causing issues with collections if there were pending updates. Now, pending state updates serialize previous values to avoid duplicates on new clients. (#3081) - Fixed issue where changing ownership would mark every `NetworkVariable` dirty. Now, it will only mark any `NetworkVariable` with owner read permissions as dirty and will send/flush any pending updates to all clients prior to sending the change in ownership message. (#3081) - Fixed an issue where transferring ownership of `NetworkVariable` collections didn't update the new owner’s previous value, causing the last added value to be detected as a change during additions or removals. (#3081) - Fixed issue where a client (or server) with no write permissions for a `NetworkVariable` using a standard .NET collection type could still modify the collection which could cause various issues depending upon the modification and collection type. (#3081) - Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078) - Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored when spawning the player prefab. (#3077) - Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#3075) ### Changed - Changed `NetworkConfig.AutoSpawnPlayerPrefabClientSide` is no longer automatically set when starting `NetworkManager`. (#3097) - Updated `NetworkVariableDeltaMessage` so the server now forwards delta state updates from clients immediately, instead of waiting until the end of the frame or the next network tick. (#3081)
This commit is contained in:
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using Unity.Netcode.TestHelpers.Runtime;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Unity.Netcode.RuntimeTests
|
||||
@@ -12,9 +13,10 @@ namespace Unity.Netcode.RuntimeTests
|
||||
[TestFixture(PlayerCreation.PrefabHash)]
|
||||
[TestFixture(PlayerCreation.NoPlayer)]
|
||||
[TestFixture(PlayerCreation.FailValidation)]
|
||||
internal class ConnectionApprovalTests : NetcodeIntegrationTest
|
||||
internal class ConnectionApprovalTests : IntegrationTestWithApproximation
|
||||
{
|
||||
private const string k_InvalidToken = "Invalid validation token!";
|
||||
|
||||
public enum PlayerCreation
|
||||
{
|
||||
Prefab,
|
||||
@@ -24,6 +26,8 @@ namespace Unity.Netcode.RuntimeTests
|
||||
}
|
||||
private PlayerCreation m_PlayerCreation;
|
||||
private bool m_ClientDisconnectReasonValidated;
|
||||
private Vector3 m_ExpectedPosition;
|
||||
private Quaternion m_ExpectedRotation;
|
||||
|
||||
private Dictionary<ulong, bool> m_Validated = new Dictionary<ulong, bool>();
|
||||
|
||||
@@ -43,6 +47,12 @@ namespace Unity.Netcode.RuntimeTests
|
||||
|
||||
protected override void OnServerAndClientsCreated()
|
||||
{
|
||||
if (m_PlayerCreation == PlayerCreation.Prefab || m_PlayerCreation == PlayerCreation.PrefabHash)
|
||||
{
|
||||
m_ExpectedPosition = GetRandomVector3(-10.0f, 10.0f);
|
||||
m_ExpectedRotation = Quaternion.Euler(GetRandomVector3(-359.98f, 359.98f));
|
||||
}
|
||||
|
||||
m_ClientDisconnectReasonValidated = false;
|
||||
m_BypassConnectionTimeout = m_PlayerCreation == PlayerCreation.FailValidation;
|
||||
m_Validated.Clear();
|
||||
@@ -104,11 +114,36 @@ namespace Unity.Netcode.RuntimeTests
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ValidatePlayersPositionRotation()
|
||||
{
|
||||
foreach (var playerEntries in m_PlayerNetworkObjects)
|
||||
{
|
||||
foreach (var player in playerEntries.Value)
|
||||
{
|
||||
if (!Approximately(player.Value.transform.position, m_ExpectedPosition))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!Approximately(player.Value.transform.rotation, m_ExpectedRotation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ConnectionApproval()
|
||||
{
|
||||
yield return WaitForConditionOrTimeOut(ClientAndHostValidated);
|
||||
AssertOnTimeout("Timed out waiting for all clients to be approved!");
|
||||
|
||||
if (m_PlayerCreation == PlayerCreation.Prefab || m_PlayerCreation == PlayerCreation.PrefabHash)
|
||||
{
|
||||
yield return WaitForConditionOrTimeOut(ValidatePlayersPositionRotation);
|
||||
AssertOnTimeout("Not all player prefabs spawned in the correct position and/or rotation!");
|
||||
}
|
||||
}
|
||||
|
||||
private void NetworkManagerObject_ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
|
||||
@@ -127,8 +162,8 @@ namespace Unity.Netcode.RuntimeTests
|
||||
}
|
||||
|
||||
response.CreatePlayerObject = ShouldCheckForSpawnedPlayers();
|
||||
response.Position = null;
|
||||
response.Rotation = null;
|
||||
response.Position = m_ExpectedPosition;
|
||||
response.Rotation = m_ExpectedRotation;
|
||||
response.PlayerPrefabHash = m_PlayerCreation == PlayerCreation.PrefabHash ? m_PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace Unity.Netcode.RuntimeTests
|
||||
public IEnumerator ValidateApprovalTimeout()
|
||||
{
|
||||
// Just delay for a second
|
||||
yield return new WaitForSeconds(1);
|
||||
yield return new WaitForSeconds(k_TestTimeoutPeriod * 0.25f);
|
||||
|
||||
// Verify we haven't received the time out message yet
|
||||
NetcodeLogAssert.LogWasNotReceived(LogType.Log, m_ExpectedLogMessage);
|
||||
|
||||
@@ -670,7 +670,7 @@ namespace Unity.Netcode.RuntimeTests
|
||||
|
||||
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
||||
|
||||
WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
|
||||
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
||||
|
||||
foreach (var client in m_ClientNetworkManagers)
|
||||
{
|
||||
@@ -678,9 +678,9 @@ namespace Unity.Netcode.RuntimeTests
|
||||
Assert.IsTrue(manager.DeferMessageCalled);
|
||||
Assert.IsFalse(manager.ProcessTriggersCalled);
|
||||
|
||||
Assert.AreEqual(4, manager.DeferredMessageCountTotal());
|
||||
Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
|
||||
Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
|
||||
Assert.AreEqual(3, manager.DeferredMessageCountTotal());
|
||||
Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
|
||||
Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
|
||||
Assert.AreEqual(0, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab));
|
||||
AddPrefabsToClient(client);
|
||||
}
|
||||
@@ -812,7 +812,7 @@ namespace Unity.Netcode.RuntimeTests
|
||||
|
||||
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
||||
|
||||
WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
|
||||
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
||||
|
||||
// Validate messages are deferred and pending
|
||||
foreach (var client in m_ClientNetworkManagers)
|
||||
@@ -821,10 +821,10 @@ namespace Unity.Netcode.RuntimeTests
|
||||
Assert.IsTrue(manager.DeferMessageCalled);
|
||||
Assert.IsFalse(manager.ProcessTriggersCalled);
|
||||
|
||||
Assert.AreEqual(5, manager.DeferredMessageCountTotal());
|
||||
Assert.AreEqual(4, manager.DeferredMessageCountTotal());
|
||||
|
||||
Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
|
||||
Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
|
||||
Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
|
||||
Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
|
||||
Assert.AreEqual(1, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab));
|
||||
Assert.AreEqual(1, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, serverObject.GetComponent<NetworkObject>().GlobalObjectIdHash));
|
||||
AddPrefabsToClient(client);
|
||||
|
||||
@@ -180,6 +180,9 @@ namespace Unity.Netcode.RuntimeTests
|
||||
Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!");
|
||||
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ClientNetworkManagers[0]), $"Could not find the client {nameof(NetworkManager)} disconnect event entry!");
|
||||
Assert.IsTrue(m_DisconnectedEvent[m_ClientNetworkManagers[0]].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the client {nameof(NetworkManager)} disconnect event entry!");
|
||||
Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClientsIds.Count}!");
|
||||
Assert.IsTrue(m_ServerNetworkManager.ConnectedClients.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClients.Count}!");
|
||||
Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsList.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClientsList.Count}!");
|
||||
}
|
||||
|
||||
if (m_OwnerPersistence == OwnerPersistence.DestroyWithOwner)
|
||||
|
||||
@@ -59,13 +59,13 @@ namespace Unity.Netcode.RuntimeTests
|
||||
|
||||
public void Changed(int before, int after)
|
||||
{
|
||||
VerboseDebug($"Value changed from {before} to {after} on {NetworkManager.LocalClientId}");
|
||||
VerboseDebug($"[Client-{NetworkManager.LocalClientId}][{name}][MyNetworkVariable] Value changed from {before} to {after}");
|
||||
ValueOnClient[NetworkManager.LocalClientId] = after;
|
||||
}
|
||||
public void ListChanged(NetworkListEvent<int> listEvent)
|
||||
{
|
||||
Debug.Log($"ListEvent received: type {listEvent.Type}, index {listEvent.Index}, value {listEvent.Value}");
|
||||
Debug.Assert(ExpectedSize == MyNetworkList.Count);
|
||||
VerboseDebug($"[Client-{NetworkManager.LocalClientId}][{name}][MyNetworkList] ListEvent received: type {listEvent.Type}, index {listEvent.Index}, value {listEvent.Value}");
|
||||
Debug.Assert(ExpectedSize == MyNetworkList.Count, $"[{name}] List change failure! Expected Count: {ExpectedSize} Actual Count:{MyNetworkList.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,10 +185,13 @@ namespace Unity.Netcode.RuntimeTests
|
||||
var otherClient = m_ServerNetworkManager.ConnectedClientsList[2];
|
||||
m_NetSpawnedObject = SpawnObject(m_TestNetworkPrefab, m_ClientNetworkManagers[1]).GetComponent<NetworkObject>();
|
||||
|
||||
yield return RefreshGameObects(4);
|
||||
yield return RefreshGameObects(NumberOfClients);
|
||||
|
||||
// === Check spawn occurred
|
||||
yield return WaitForSpawnCount(NumberOfClients + 1);
|
||||
|
||||
AssertOnTimeout($"Timed out waiting for all clients to spawn {m_NetSpawnedObject.name}");
|
||||
|
||||
Debug.Assert(HiddenVariableObject.SpawnCount == NumberOfClients + 1);
|
||||
VerboseDebug("Objects spawned");
|
||||
|
||||
@@ -205,7 +208,6 @@ namespace Unity.Netcode.RuntimeTests
|
||||
// ==== Hide our object to a different client
|
||||
HiddenVariableObject.ExpectedSize = 2;
|
||||
m_NetSpawnedObject.NetworkHide(otherClient.ClientId);
|
||||
|
||||
currentValueSet = 3;
|
||||
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = currentValueSet;
|
||||
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkList.Add(currentValueSet);
|
||||
@@ -222,7 +224,7 @@ namespace Unity.Netcode.RuntimeTests
|
||||
VerboseDebug("Object spawned");
|
||||
|
||||
// ==== We need a refresh for the newly re-spawned object
|
||||
yield return RefreshGameObects(4);
|
||||
yield return RefreshGameObects(NumberOfClients);
|
||||
|
||||
currentValueSet = 4;
|
||||
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = currentValueSet;
|
||||
|
||||
@@ -13,6 +13,60 @@ namespace Unity.Netcode.RuntimeTests
|
||||
private NetworkManager m_ClientManager;
|
||||
private NetworkManager m_ServerManager;
|
||||
|
||||
private NetworkManager m_NetworkManagerInstantiated;
|
||||
private bool m_Instantiated;
|
||||
private bool m_Destroyed;
|
||||
|
||||
/// <summary>
|
||||
/// Validates the <see cref="NetworkManager.OnInstantiated"/> and <see cref="NetworkManager.OnDestroying"/> event notifications
|
||||
/// </summary>
|
||||
[UnityTest]
|
||||
public IEnumerator InstantiatedAndDestroyingNotifications()
|
||||
{
|
||||
NetworkManager.OnInstantiated += NetworkManager_OnInstantiated;
|
||||
NetworkManager.OnDestroying += NetworkManager_OnDestroying;
|
||||
var waitPeriod = new WaitForSeconds(0.01f);
|
||||
var prefab = new GameObject("InstantiateDestroy");
|
||||
var networkManagerPrefab = prefab.AddComponent<NetworkManager>();
|
||||
|
||||
Assert.IsTrue(m_Instantiated, $"{nameof(NetworkManager)} prefab did not get instantiated event notification!");
|
||||
Assert.IsTrue(m_NetworkManagerInstantiated == networkManagerPrefab, $"{nameof(NetworkManager)} prefab parameter did not match!");
|
||||
|
||||
m_Instantiated = false;
|
||||
m_NetworkManagerInstantiated = null;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var instance = Object.Instantiate(prefab);
|
||||
var networkManager = instance.GetComponent<NetworkManager>();
|
||||
Assert.IsTrue(m_Instantiated, $"{nameof(NetworkManager)} instance-{i} did not get instantiated event notification!");
|
||||
Assert.IsTrue(m_NetworkManagerInstantiated == networkManager, $"{nameof(NetworkManager)} instance-{i} parameter did not match!");
|
||||
Object.DestroyImmediate(instance);
|
||||
Assert.IsTrue(m_Destroyed, $"{nameof(NetworkManager)} instance-{i} did not get destroying event notification!");
|
||||
m_Instantiated = false;
|
||||
m_NetworkManagerInstantiated = null;
|
||||
m_Destroyed = false;
|
||||
}
|
||||
m_NetworkManagerInstantiated = networkManagerPrefab;
|
||||
Object.Destroy(prefab);
|
||||
yield return null;
|
||||
Assert.IsTrue(m_Destroyed, $"{nameof(NetworkManager)} prefab did not get destroying event notification!");
|
||||
NetworkManager.OnInstantiated -= NetworkManager_OnInstantiated;
|
||||
NetworkManager.OnDestroying -= NetworkManager_OnDestroying;
|
||||
}
|
||||
|
||||
private void NetworkManager_OnInstantiated(NetworkManager networkManager)
|
||||
{
|
||||
m_Instantiated = true;
|
||||
m_NetworkManagerInstantiated = networkManager;
|
||||
}
|
||||
|
||||
private void NetworkManager_OnDestroying(NetworkManager networkManager)
|
||||
{
|
||||
m_Destroyed = true;
|
||||
Assert.True(m_NetworkManagerInstantiated == networkManager, $"Destroying {nameof(NetworkManager)} and current instance is not a match for the one passed into the event!");
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator OnServerStoppedCalledWhenServerStops()
|
||||
{
|
||||
|
||||
@@ -265,6 +265,8 @@ namespace Unity.Netcode.RuntimeTests
|
||||
// After the 1st client has been given ownership to the object, this will be used to make sure each previous owner properly received the remove ownership message
|
||||
var previousClientComponent = (NetworkObjectOwnershipComponent)null;
|
||||
|
||||
var networkManagersDAMode = new List<NetworkManager>();
|
||||
|
||||
for (int clientIndex = 0; clientIndex < NumberOfClients; clientIndex++)
|
||||
{
|
||||
clientObject = clientObjects[clientIndex];
|
||||
@@ -322,6 +324,21 @@ namespace Unity.Netcode.RuntimeTests
|
||||
// In distributed authority mode, the current owner just rolls the ownership back over to the DAHost client (i.e. host mocking CMB Service)
|
||||
if (m_DistributedAuthority)
|
||||
{
|
||||
// In distributed authority, we have to clear out the NetworkManager instances as this changes relative to authority.
|
||||
networkManagersDAMode.Clear();
|
||||
foreach (var clientNetworkManager in m_ClientNetworkManagers)
|
||||
{
|
||||
if (clientNetworkManager.LocalClientId == clientObject.OwnerClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
networkManagersDAMode.Add(clientNetworkManager);
|
||||
}
|
||||
|
||||
if (!UseCMBService() && clientObject.OwnerClientId != m_ServerNetworkManager.LocalClientId)
|
||||
{
|
||||
networkManagersDAMode.Add(m_ServerNetworkManager);
|
||||
}
|
||||
clientObject.ChangeOwnership(NetworkManager.ServerClientId);
|
||||
}
|
||||
else
|
||||
@@ -330,7 +347,18 @@ namespace Unity.Netcode.RuntimeTests
|
||||
}
|
||||
}
|
||||
|
||||
yield return WaitForConditionOrTimeOut(ownershipMessageHooks);
|
||||
if (m_DistributedAuthority)
|
||||
{
|
||||
// We use an alternate method (other than message hooks) to verify each client received the ownership message since message hooks becomes problematic when you need
|
||||
// to make dynamic changes to your targets.
|
||||
yield return WaitForConditionOrTimeOut(() => OwnershipChangedOnAllTargetedClients(networkManagersDAMode, clientObject.NetworkObjectId, NetworkManager.ServerClientId));
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return WaitForConditionOrTimeOut(ownershipMessageHooks);
|
||||
}
|
||||
|
||||
|
||||
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for all clients to receive the {nameof(ChangeOwnershipMessage)} message (back to server).");
|
||||
|
||||
Assert.That(serverComponent.OnGainedOwnershipFired);
|
||||
@@ -351,6 +379,22 @@ namespace Unity.Netcode.RuntimeTests
|
||||
serverComponent.ResetFlags();
|
||||
}
|
||||
|
||||
private bool OwnershipChangedOnAllTargetedClients(List<NetworkManager> networkManagers, ulong networkObjectId, ulong expectedOwner)
|
||||
{
|
||||
foreach (var networkManager in networkManagers)
|
||||
{
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (networkManager.SpawnManager.SpawnedObjects[networkObjectId].OwnerClientId != expectedOwner)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private const int k_NumberOfSpawnedObjects = 5;
|
||||
|
||||
private bool AllClientsHaveCorrectObjectCount()
|
||||
|
||||
@@ -12,8 +12,6 @@ namespace Unity.Netcode.RuntimeTests
|
||||
internal class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest
|
||||
{
|
||||
protected override int NumberOfClients => 1;
|
||||
// "many" in this case means enough to exceed a ushort_max message size written in the header
|
||||
// 1500 is not a magic number except that it's big enough to trigger a failure
|
||||
private const int k_SpawnedObjects = 1500;
|
||||
|
||||
private NetworkPrefab m_PrefabToSpawn;
|
||||
@@ -52,19 +50,23 @@ namespace Unity.Netcode.RuntimeTests
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
// When this test fails it does so without an exception and will wait the default ~6 minutes
|
||||
[Timeout(10000)]
|
||||
public IEnumerator WhenManyObjectsAreSpawnedAtOnce_AllAreReceived()
|
||||
{
|
||||
var timeStarted = Time.realtimeSinceStartup;
|
||||
for (int x = 0; x < k_SpawnedObjects; x++)
|
||||
{
|
||||
NetworkObject serverObject = Object.Instantiate(m_PrefabToSpawn.Prefab).GetComponent<NetworkObject>();
|
||||
serverObject.NetworkManagerOwner = m_ServerNetworkManager;
|
||||
serverObject.Spawn();
|
||||
}
|
||||
|
||||
var timeSpawned = Time.realtimeSinceStartup - timeStarted;
|
||||
// Provide plenty of time to spawn all 1500 objects in case the CI VM is running slow
|
||||
var timeoutHelper = new TimeoutHelper(30);
|
||||
// ensure all objects are replicated
|
||||
yield return WaitForConditionOrTimeOut(() => SpawnObjecTrackingComponent.SpawnedObjects == k_SpawnedObjects);
|
||||
AssertOnTimeout($"Timed out waiting for the client to spawn {k_SpawnedObjects} objects!");
|
||||
yield return WaitForConditionOrTimeOut(() => SpawnObjecTrackingComponent.SpawnedObjects == k_SpawnedObjects, timeoutHelper);
|
||||
|
||||
AssertOnTimeout($"Timed out waiting for the client to spawn {k_SpawnedObjects} objects! Time to spawn: {timeSpawned} | Time to timeout: {timeStarted - Time.realtimeSinceStartup}", timeoutHelper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using Unity.Netcode.Components;
|
||||
using Unity.Netcode.TestHelpers.Runtime;
|
||||
@@ -539,5 +540,279 @@ namespace Unity.Netcode.RuntimeTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture(HostOrServer.DAHost, NetworkTransform.AuthorityModes.Owner)] // Validate the NetworkTransform owner authoritative mode fix using distributed authority
|
||||
[TestFixture(HostOrServer.Host, NetworkTransform.AuthorityModes.Server)] // Validate we have not impacted NetworkTransform server authoritative mode
|
||||
[TestFixture(HostOrServer.Host, NetworkTransform.AuthorityModes.Owner)] // Validate the NetworkTransform owner authoritative mode fix using client-server
|
||||
internal class NestedNetworkTransformTests : IntegrationTestWithApproximation
|
||||
{
|
||||
private const int k_NestedChildren = 5;
|
||||
protected override int NumberOfClients => 2;
|
||||
|
||||
private GameObject m_SpawnObject;
|
||||
|
||||
private NetworkTransform.AuthorityModes m_AuthorityMode;
|
||||
|
||||
private StringBuilder m_ErrorLog = new StringBuilder();
|
||||
|
||||
private List<NetworkManager> m_NetworkManagers = new List<NetworkManager>();
|
||||
private List<GameObject> m_SpawnedObjects = new List<GameObject>();
|
||||
|
||||
public NestedNetworkTransformTests(HostOrServer hostOrServer, NetworkTransform.AuthorityModes authorityMode) : base(hostOrServer)
|
||||
{
|
||||
m_AuthorityMode = authorityMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a player prefab with several nested NetworkTransforms
|
||||
/// </summary>
|
||||
protected override void OnCreatePlayerPrefab()
|
||||
{
|
||||
var networkTransform = m_PlayerPrefab.AddComponent<NetworkTransform>();
|
||||
networkTransform.AuthorityMode = m_AuthorityMode;
|
||||
var parent = m_PlayerPrefab;
|
||||
// Add several nested NetworkTransforms
|
||||
for (int i = 0; i < k_NestedChildren; i++)
|
||||
{
|
||||
var nestedChild = new GameObject();
|
||||
nestedChild.transform.parent = parent.transform;
|
||||
var nestedNetworkTransform = nestedChild.AddComponent<NetworkTransform>();
|
||||
nestedNetworkTransform.AuthorityMode = m_AuthorityMode;
|
||||
nestedNetworkTransform.InLocalSpace = true;
|
||||
parent = nestedChild;
|
||||
}
|
||||
base.OnCreatePlayerPrefab();
|
||||
}
|
||||
|
||||
private void RandomizeObjectTransformPositions(GameObject gameObject)
|
||||
{
|
||||
var networkObject = gameObject.GetComponent<NetworkObject>();
|
||||
Assert.True(networkObject.ChildNetworkBehaviours.Count > 0);
|
||||
|
||||
foreach (var networkTransform in networkObject.NetworkTransforms)
|
||||
{
|
||||
networkTransform.gameObject.transform.position = GetRandomVector3(-15.0f, 15.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomizes each player's position when validating distributed authority
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private GameObject FetchLocalPlayerPrefabToSpawn()
|
||||
{
|
||||
RandomizeObjectTransformPositions(m_PlayerPrefab);
|
||||
return m_PlayerPrefab;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomizes the player position when validating client-server
|
||||
/// </summary>
|
||||
/// <param name="connectionApprovalRequest"></param>
|
||||
/// <param name="connectionApprovalResponse"></param>
|
||||
private void ConnectionApprovalHandler(NetworkManager.ConnectionApprovalRequest connectionApprovalRequest, NetworkManager.ConnectionApprovalResponse connectionApprovalResponse)
|
||||
{
|
||||
connectionApprovalResponse.Approved = true;
|
||||
connectionApprovalResponse.CreatePlayerObject = true;
|
||||
RandomizeObjectTransformPositions(m_PlayerPrefab);
|
||||
connectionApprovalResponse.Position = GetRandomVector3(-15.0f, 15.0f);
|
||||
}
|
||||
|
||||
protected override void OnServerAndClientsCreated()
|
||||
{
|
||||
// Create a prefab to spawn with each NetworkManager as the owner
|
||||
m_SpawnObject = CreateNetworkObjectPrefab("SpawnObj");
|
||||
var networkTransform = m_SpawnObject.AddComponent<NetworkTransform>();
|
||||
networkTransform.AuthorityMode = m_AuthorityMode;
|
||||
var parent = m_SpawnObject;
|
||||
// Add several nested NetworkTransforms
|
||||
for (int i = 0; i < k_NestedChildren; i++)
|
||||
{
|
||||
var nestedChild = new GameObject();
|
||||
nestedChild.transform.parent = parent.transform;
|
||||
var nestedNetworkTransform = nestedChild.AddComponent<NetworkTransform>();
|
||||
nestedNetworkTransform.AuthorityMode = m_AuthorityMode;
|
||||
nestedNetworkTransform.InLocalSpace = true;
|
||||
parent = nestedChild;
|
||||
}
|
||||
|
||||
if (m_DistributedAuthority)
|
||||
{
|
||||
if (!UseCMBService())
|
||||
{
|
||||
m_ServerNetworkManager.OnFetchLocalPlayerPrefabToSpawn = FetchLocalPlayerPrefabToSpawn;
|
||||
}
|
||||
|
||||
foreach (var client in m_ClientNetworkManagers)
|
||||
{
|
||||
client.OnFetchLocalPlayerPrefabToSpawn = FetchLocalPlayerPrefabToSpawn;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ServerNetworkManager.NetworkConfig.ConnectionApproval = true;
|
||||
m_ServerNetworkManager.ConnectionApprovalCallback += ConnectionApprovalHandler;
|
||||
foreach (var client in m_ClientNetworkManagers)
|
||||
{
|
||||
client.NetworkConfig.ConnectionApproval = true;
|
||||
}
|
||||
}
|
||||
|
||||
base.OnServerAndClientsCreated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the transform positions of two NetworkObject instances
|
||||
/// </summary>
|
||||
/// <param name="current">the local instance (source of truth)</param>
|
||||
/// <param name="testing">the remote instance</param>
|
||||
/// <returns></returns>
|
||||
private bool ValidateTransforms(NetworkObject current, NetworkObject testing)
|
||||
{
|
||||
if (current.ChildNetworkBehaviours.Count == 0 || testing.ChildNetworkBehaviours.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < current.NetworkTransforms.Count - 1; i++)
|
||||
{
|
||||
var transformA = current.NetworkTransforms[i].transform;
|
||||
var transformB = testing.NetworkTransforms[i].transform;
|
||||
if (!Approximately(transformA.position, transformB.position))
|
||||
{
|
||||
m_ErrorLog.AppendLine($"TransformA Position {transformA.position} != TransformB Position {transformB.position}");
|
||||
return false;
|
||||
}
|
||||
if (!Approximately(transformA.localPosition, transformB.localPosition))
|
||||
{
|
||||
m_ErrorLog.AppendLine($"TransformA Local Position {transformA.position} != TransformB Local Position {transformB.position}");
|
||||
return false;
|
||||
}
|
||||
if (transformA.parent != null)
|
||||
{
|
||||
if (current.NetworkTransforms[i].InLocalSpace != testing.NetworkTransforms[i].InLocalSpace)
|
||||
{
|
||||
m_ErrorLog.AppendLine($"NetworkTransform-{current.OwnerClientId}-{current.NetworkTransforms[i].NetworkBehaviourId} InLocalSpace ({current.NetworkTransforms[i].InLocalSpace}) is different from the remote instance version on Client-{testing.NetworkManager.LocalClientId}!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates all player instances spawned with the correct positions including all nested NetworkTransforms
|
||||
/// When running in server authority mode we are validating this fix did not impact that.
|
||||
/// </summary>
|
||||
private bool AllClientInstancesSynchronized()
|
||||
{
|
||||
m_ErrorLog.Clear();
|
||||
|
||||
foreach (var current in m_NetworkManagers)
|
||||
{
|
||||
var currentPlayer = current.LocalClient.PlayerObject;
|
||||
var currentNetworkObjectId = currentPlayer.NetworkObjectId;
|
||||
foreach (var testing in m_NetworkManagers)
|
||||
{
|
||||
if (currentPlayer == testing.LocalClient.PlayerObject)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!testing.SpawnManager.SpawnedObjects.ContainsKey(currentNetworkObjectId))
|
||||
{
|
||||
m_ErrorLog.AppendLine($"Failed to find Client-{currentPlayer.OwnerClientId}'s player instance on Client-{testing.LocalClientId}!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var remoteInstance = testing.SpawnManager.SpawnedObjects[currentNetworkObjectId];
|
||||
if (!ValidateTransforms(currentPlayer, remoteInstance))
|
||||
{
|
||||
m_ErrorLog.AppendLine($"Failed to validate Client-{currentPlayer.OwnerClientId} against its remote instance on Client-{testing.LocalClientId}!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that dynamically spawning works the same.
|
||||
/// When running in server authority mode we are validating this fix did not impact that.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool AllSpawnedObjectsSynchronized()
|
||||
{
|
||||
m_ErrorLog.Clear();
|
||||
|
||||
foreach (var current in m_SpawnedObjects)
|
||||
{
|
||||
var currentNetworkObject = current.GetComponent<NetworkObject>();
|
||||
var currentNetworkObjectId = currentNetworkObject.NetworkObjectId;
|
||||
foreach (var testing in m_NetworkManagers)
|
||||
{
|
||||
if (currentNetworkObject.OwnerClientId == testing.LocalClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!testing.SpawnManager.SpawnedObjects.ContainsKey(currentNetworkObjectId))
|
||||
{
|
||||
m_ErrorLog.AppendLine($"Failed to find Client-{currentNetworkObject.OwnerClientId}'s player instance on Client-{testing.LocalClientId}!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var remoteInstance = testing.SpawnManager.SpawnedObjects[currentNetworkObjectId];
|
||||
if (!ValidateTransforms(currentNetworkObject, remoteInstance))
|
||||
{
|
||||
m_ErrorLog.AppendLine($"Failed to validate Client-{currentNetworkObject.OwnerClientId} against its remote instance on Client-{testing.LocalClientId}!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that spawning player and dynamically spawned prefab instances with nested NetworkTransforms
|
||||
/// synchronizes properly in both client-server and distributed authority when using owner authoritative mode.
|
||||
/// </summary>
|
||||
[UnityTest]
|
||||
public IEnumerator NestedNetworkTransformSpawnPositionTest()
|
||||
{
|
||||
if (!m_DistributedAuthority || (m_DistributedAuthority && !UseCMBService()))
|
||||
{
|
||||
m_NetworkManagers.Add(m_ServerNetworkManager);
|
||||
}
|
||||
m_NetworkManagers.AddRange(m_ClientNetworkManagers);
|
||||
|
||||
yield return WaitForConditionOrTimeOut(AllClientInstancesSynchronized);
|
||||
AssertOnTimeout($"Failed to synchronize all client instances!\n{m_ErrorLog}");
|
||||
|
||||
foreach (var networkManager in m_NetworkManagers)
|
||||
{
|
||||
// Randomize the position
|
||||
RandomizeObjectTransformPositions(m_SpawnObject);
|
||||
|
||||
// Create an instance owned by the specified networkmanager
|
||||
m_SpawnedObjects.Add(SpawnObject(m_SpawnObject, networkManager));
|
||||
}
|
||||
// Randomize the position once more just to assure we are instantiating remote instances
|
||||
// with a completely different position
|
||||
RandomizeObjectTransformPositions(m_SpawnObject);
|
||||
yield return WaitForConditionOrTimeOut(AllSpawnedObjectsSynchronized);
|
||||
AssertOnTimeout($"Failed to synchronize all spawned NetworkObject instances!\n{m_ErrorLog}");
|
||||
m_SpawnedObjects.Clear();
|
||||
m_NetworkManagers.Clear();
|
||||
}
|
||||
|
||||
protected override IEnumerator OnTearDown()
|
||||
{
|
||||
// In case there was a failure, go ahead and clear these lists out for any pending TextFixture passes
|
||||
m_SpawnedObjects.Clear();
|
||||
m_NetworkManagers.Clear();
|
||||
return base.OnTearDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
#if COM_UNITY_MODULES_PHYSICS
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using Unity.Netcode.Components;
|
||||
using Unity.Netcode.TestHelpers.Runtime;
|
||||
@@ -108,5 +110,386 @@ namespace Unity.Netcode.RuntimeTests
|
||||
Assert.IsTrue(clientPlayerInstance == null, $"[Client-Side] Player {nameof(NetworkObject)} is not null!");
|
||||
}
|
||||
}
|
||||
|
||||
internal class ContactEventTransformHelperWithInfo : ContactEventTransformHelper, IContactEventHandlerWithInfo
|
||||
{
|
||||
public ContactEventHandlerInfo GetContactEventHandlerInfo()
|
||||
{
|
||||
var contactEventHandlerInfo = new ContactEventHandlerInfo()
|
||||
{
|
||||
HasContactEventPriority = IsOwner,
|
||||
ProvideNonRigidBodyContactEvents = m_EnableNonRigidbodyContacts.Value,
|
||||
};
|
||||
return contactEventHandlerInfo;
|
||||
}
|
||||
|
||||
protected override void OnRegisterForContactEvents(bool isRegistering)
|
||||
{
|
||||
RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class ContactEventTransformHelper : NetworkTransform, IContactEventHandler
|
||||
{
|
||||
public static Vector3 SessionOwnerSpawnPoint;
|
||||
public static Vector3 ClientSpawnPoint;
|
||||
public static bool VerboseDebug;
|
||||
public enum HelperStates
|
||||
{
|
||||
None,
|
||||
MoveForward,
|
||||
}
|
||||
|
||||
private HelperStates m_HelperState;
|
||||
|
||||
public void SetHelperState(HelperStates state)
|
||||
{
|
||||
m_HelperState = state;
|
||||
if (!m_NetworkRigidbody.IsKinematic())
|
||||
{
|
||||
m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero;
|
||||
m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero;
|
||||
}
|
||||
m_NetworkRigidbody.Rigidbody.isKinematic = m_HelperState == HelperStates.None;
|
||||
if (!m_NetworkRigidbody.IsKinematic())
|
||||
{
|
||||
m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero;
|
||||
m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected struct ContactEventInfo
|
||||
{
|
||||
public ulong EventId;
|
||||
public Vector3 AveragedCollisionNormal;
|
||||
public Rigidbody CollidingBody;
|
||||
public Vector3 ContactPoint;
|
||||
}
|
||||
|
||||
protected List<ContactEventInfo> m_ContactEvents = new List<ContactEventInfo>();
|
||||
|
||||
protected NetworkVariable<bool> m_EnableNonRigidbodyContacts = new NetworkVariable<bool>(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
|
||||
|
||||
protected NetworkRigidbody m_NetworkRigidbody;
|
||||
public ContactEventTransformHelper Target;
|
||||
|
||||
public bool HasContactEvents()
|
||||
{
|
||||
return m_ContactEvents.Count > 0;
|
||||
}
|
||||
|
||||
public Rigidbody GetRigidbody()
|
||||
{
|
||||
return m_NetworkRigidbody.Rigidbody;
|
||||
}
|
||||
|
||||
public bool HadContactWith(ContactEventTransformHelper otherObject)
|
||||
{
|
||||
if (otherObject == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var contactEvent in m_ContactEvents)
|
||||
{
|
||||
if (contactEvent.CollidingBody == otherObject.m_NetworkRigidbody.Rigidbody)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual void CheckToStopMoving()
|
||||
{
|
||||
SetHelperState(HadContactWith(Target) ? HelperStates.None : HelperStates.MoveForward);
|
||||
}
|
||||
|
||||
public void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default)
|
||||
{
|
||||
if (Target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (collidingBody != null)
|
||||
{
|
||||
Log($">>>>>>> contact event with {collidingBody.name}!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($">>>>>>> contact event with non-rigidbody!");
|
||||
}
|
||||
|
||||
m_ContactEvents.Add(new ContactEventInfo()
|
||||
{
|
||||
EventId = eventId,
|
||||
AveragedCollisionNormal = averagedCollisionNormal,
|
||||
CollidingBody = collidingBody,
|
||||
ContactPoint = contactPoint,
|
||||
});
|
||||
CheckToStopMoving();
|
||||
}
|
||||
|
||||
private void SetInitialPositionClientServer()
|
||||
{
|
||||
if (IsServer)
|
||||
{
|
||||
if (!NetworkManager.DistributedAuthorityMode && !IsLocalPlayer)
|
||||
{
|
||||
transform.position = ClientSpawnPoint;
|
||||
m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.position = SessionOwnerSpawnPoint;
|
||||
m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.position = ClientSpawnPoint;
|
||||
m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetInitialPositionDistributedAuthority()
|
||||
{
|
||||
if (HasAuthority)
|
||||
{
|
||||
if (IsSessionOwner)
|
||||
{
|
||||
transform.position = SessionOwnerSpawnPoint;
|
||||
m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.position = ClientSpawnPoint;
|
||||
m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
m_NetworkRigidbody = GetComponent<NetworkRigidbody>();
|
||||
|
||||
m_NetworkRigidbody.Rigidbody.maxLinearVelocity = 15;
|
||||
m_NetworkRigidbody.Rigidbody.maxAngularVelocity = 10;
|
||||
|
||||
if (NetworkManager.DistributedAuthorityMode)
|
||||
{
|
||||
SetInitialPositionDistributedAuthority();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetInitialPositionClientServer();
|
||||
}
|
||||
if (IsLocalPlayer)
|
||||
{
|
||||
RegisterForContactEvents(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_NetworkRigidbody.Rigidbody.detectCollisions = false;
|
||||
}
|
||||
base.OnNetworkSpawn();
|
||||
}
|
||||
|
||||
protected virtual void OnRegisterForContactEvents(bool isRegistering)
|
||||
{
|
||||
RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering);
|
||||
}
|
||||
|
||||
public void RegisterForContactEvents(bool isRegistering)
|
||||
{
|
||||
OnRegisterForContactEvents(isRegistering);
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (!IsSpawned || !IsOwner || m_HelperState != HelperStates.MoveForward)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var distance = Vector3.Distance(Target.transform.position, transform.position);
|
||||
var moveAmount = Mathf.Max(1.2f, distance);
|
||||
// Head towards our target
|
||||
var dir = (Target.transform.position - transform.position).normalized;
|
||||
var deltaMove = dir * moveAmount * Time.fixedDeltaTime;
|
||||
m_NetworkRigidbody.Rigidbody.MovePosition(m_NetworkRigidbody.Rigidbody.position + deltaMove);
|
||||
|
||||
|
||||
Log($" Loc: {transform.position} | Dest: {Target.transform.position} | Dist: {distance} | MoveDelta: {deltaMove}");
|
||||
}
|
||||
|
||||
protected void Log(string msg)
|
||||
{
|
||||
if (VerboseDebug)
|
||||
{
|
||||
Debug.Log($"Client-{OwnerClientId} {msg}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture(HostOrServer.Host, ContactEventTypes.Default)]
|
||||
[TestFixture(HostOrServer.DAHost, ContactEventTypes.Default)]
|
||||
[TestFixture(HostOrServer.Host, ContactEventTypes.WithInfo)]
|
||||
[TestFixture(HostOrServer.DAHost, ContactEventTypes.WithInfo)]
|
||||
internal class RigidbodyContactEventManagerTests : IntegrationTestWithApproximation
|
||||
{
|
||||
protected override int NumberOfClients => 1;
|
||||
|
||||
|
||||
private GameObject m_RigidbodyContactEventManager;
|
||||
|
||||
public enum ContactEventTypes
|
||||
{
|
||||
Default,
|
||||
WithInfo
|
||||
}
|
||||
|
||||
private ContactEventTypes m_ContactEventType;
|
||||
private StringBuilder m_ErrorLogger = new StringBuilder();
|
||||
|
||||
public RigidbodyContactEventManagerTests(HostOrServer hostOrServer, ContactEventTypes contactEventType) : base(hostOrServer)
|
||||
{
|
||||
m_ContactEventType = contactEventType;
|
||||
}
|
||||
|
||||
protected override void OnCreatePlayerPrefab()
|
||||
{
|
||||
ContactEventTransformHelper.SessionOwnerSpawnPoint = GetRandomVector3(-4, -3);
|
||||
ContactEventTransformHelper.ClientSpawnPoint = GetRandomVector3(3, 4);
|
||||
if (m_ContactEventType == ContactEventTypes.Default)
|
||||
{
|
||||
var helper = m_PlayerPrefab.AddComponent<ContactEventTransformHelper>();
|
||||
helper.AuthorityMode = NetworkTransform.AuthorityModes.Owner;
|
||||
}
|
||||
else
|
||||
{
|
||||
var helperWithInfo = m_PlayerPrefab.AddComponent<ContactEventTransformHelperWithInfo>();
|
||||
helperWithInfo.AuthorityMode = NetworkTransform.AuthorityModes.Owner;
|
||||
}
|
||||
|
||||
var rigidbody = m_PlayerPrefab.AddComponent<Rigidbody>();
|
||||
rigidbody.useGravity = false;
|
||||
rigidbody.isKinematic = true;
|
||||
rigidbody.mass = 5.0f;
|
||||
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
|
||||
var sphereCollider = m_PlayerPrefab.AddComponent<SphereCollider>();
|
||||
sphereCollider.radius = 0.5f;
|
||||
sphereCollider.providesContacts = true;
|
||||
|
||||
var networkRigidbody = m_PlayerPrefab.AddComponent<NetworkRigidbody>();
|
||||
networkRigidbody.UseRigidBodyForMotion = true;
|
||||
networkRigidbody.AutoUpdateKinematicState = false;
|
||||
|
||||
m_RigidbodyContactEventManager = new GameObject();
|
||||
m_RigidbodyContactEventManager.AddComponent<RigidbodyContactEventManager>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private bool PlayersSpawnedInRightLocation()
|
||||
{
|
||||
var position = m_ServerNetworkManager.LocalClient.PlayerObject.transform.position;
|
||||
if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, position))
|
||||
{
|
||||
m_ErrorLogger.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!");
|
||||
return false;
|
||||
}
|
||||
|
||||
position = m_ClientNetworkManagers[0].LocalClient.PlayerObject.transform.position;
|
||||
if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position))
|
||||
{
|
||||
m_ErrorLogger.AppendLine($"Client-{m_ClientNetworkManagers[0].LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!");
|
||||
return false;
|
||||
}
|
||||
var playerObject = (NetworkObject)null;
|
||||
if (!m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_ClientNetworkManagers[0].LocalClient.PlayerObject.NetworkObjectId))
|
||||
{
|
||||
m_ErrorLogger.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} cannot find a local spawned instance of Client-{m_ClientNetworkManagers[0].LocalClientId}'s player object!");
|
||||
return false;
|
||||
}
|
||||
playerObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_ClientNetworkManagers[0].LocalClient.PlayerObject.NetworkObjectId];
|
||||
position = playerObject.transform.position;
|
||||
|
||||
if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position))
|
||||
{
|
||||
m_ErrorLogger.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId))
|
||||
{
|
||||
m_ErrorLogger.AppendLine($"Client-{m_ClientNetworkManagers[0].LocalClientId} cannot find a local spawned instance of Client-{m_ServerNetworkManager.LocalClientId}'s player object!");
|
||||
return false;
|
||||
}
|
||||
playerObject = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId];
|
||||
position = playerObject.transform.position;
|
||||
if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, playerObject.transform.position))
|
||||
{
|
||||
m_ErrorLogger.AppendLine($"Client-{m_ClientNetworkManagers[0].LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TestContactEvents()
|
||||
{
|
||||
ContactEventTransformHelper.VerboseDebug = m_EnableVerboseDebug;
|
||||
|
||||
m_PlayerPrefab.SetActive(false);
|
||||
m_ErrorLogger.Clear();
|
||||
// Validate all instances are spawned in the right location
|
||||
yield return WaitForConditionOrTimeOut(PlayersSpawnedInRightLocation);
|
||||
AssertOnTimeout($"Timed out waiting for all player instances to spawn in the corect location:\n {m_ErrorLogger}");
|
||||
m_ErrorLogger.Clear();
|
||||
|
||||
var sessionOwnerPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent<ContactEventTransformHelper>() :
|
||||
m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent<ContactEventTransformHelperWithInfo>();
|
||||
var clientPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent<ContactEventTransformHelper>() :
|
||||
m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent<ContactEventTransformHelperWithInfo>();
|
||||
|
||||
// Get both players to point towards each other
|
||||
sessionOwnerPlayer.Target = clientPlayer;
|
||||
clientPlayer.Target = sessionOwnerPlayer;
|
||||
|
||||
sessionOwnerPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward);
|
||||
clientPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward);
|
||||
|
||||
|
||||
yield return WaitForConditionOrTimeOut(() => sessionOwnerPlayer.HadContactWith(clientPlayer) || clientPlayer.HadContactWith(sessionOwnerPlayer));
|
||||
AssertOnTimeout("Timed out waiting for a player to collide with another player!");
|
||||
|
||||
clientPlayer.RegisterForContactEvents(false);
|
||||
sessionOwnerPlayer.RegisterForContactEvents(false);
|
||||
var otherPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ServerNetworkManager.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent<ContactEventTransformHelper>() :
|
||||
m_ServerNetworkManager.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent<ContactEventTransformHelperWithInfo>();
|
||||
otherPlayer.RegisterForContactEvents(false);
|
||||
otherPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent<ContactEventTransformHelper>() :
|
||||
m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent<ContactEventTransformHelperWithInfo>();
|
||||
otherPlayer.RegisterForContactEvents(false);
|
||||
|
||||
Object.Destroy(m_RigidbodyContactEventManager);
|
||||
m_RigidbodyContactEventManager = null;
|
||||
}
|
||||
|
||||
protected override IEnumerator OnTearDown()
|
||||
{
|
||||
// In case of a test failure
|
||||
if (m_RigidbodyContactEventManager)
|
||||
{
|
||||
Object.Destroy(m_RigidbodyContactEventManager);
|
||||
m_RigidbodyContactEventManager = null;
|
||||
}
|
||||
|
||||
return base.OnTearDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // COM_UNITY_MODULES_PHYSICS
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests
|
||||
[TestFixture(HostOrServer.Server)]
|
||||
internal class PlayerObjectTests : NetcodeIntegrationTest
|
||||
{
|
||||
protected override int NumberOfClients => 1;
|
||||
protected override int NumberOfClients => 2;
|
||||
|
||||
protected GameObject m_NewPlayerToSpawn;
|
||||
|
||||
@@ -52,4 +52,136 @@ namespace Unity.Netcode.RuntimeTests
|
||||
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side player object to change!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that when auto-player spawning but SpawnWithObservers is disabled,
|
||||
/// the player instantiated is only spawned on the authority side.
|
||||
/// </summary>
|
||||
[TestFixture(HostOrServer.DAHost)]
|
||||
[TestFixture(HostOrServer.Host)]
|
||||
[TestFixture(HostOrServer.Server)]
|
||||
internal class PlayerSpawnNoObserversTest : NetcodeIntegrationTest
|
||||
{
|
||||
protected override int NumberOfClients => 2;
|
||||
|
||||
public PlayerSpawnNoObserversTest(HostOrServer hostOrServer) : base(hostOrServer) { }
|
||||
|
||||
protected override bool ShouldCheckForSpawnedPlayers()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnCreatePlayerPrefab()
|
||||
{
|
||||
var playerNetworkObject = m_PlayerPrefab.GetComponent<NetworkObject>();
|
||||
playerNetworkObject.SpawnWithObservers = false;
|
||||
base.OnCreatePlayerPrefab();
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator SpawnWithNoObservers()
|
||||
{
|
||||
yield return s_DefaultWaitForTick;
|
||||
|
||||
if (!m_DistributedAuthority)
|
||||
{
|
||||
// Make sure clients did not spawn their player object on any of the clients including the owner.
|
||||
foreach (var client in m_ClientNetworkManagers)
|
||||
{
|
||||
foreach (var playerObject in m_ServerNetworkManager.SpawnManager.PlayerObjects)
|
||||
{
|
||||
Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObject.NetworkObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{playerObject.NetworkObjectId}!");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For distributed authority, we want to make sure the player object is only spawned on the authority side and all non-authority instances did not spawn it.
|
||||
var playerObjectId = m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId;
|
||||
foreach (var client in m_ClientNetworkManagers)
|
||||
{
|
||||
Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{m_ServerNetworkManager.LocalClientId}!");
|
||||
}
|
||||
|
||||
foreach (var clientPlayer in m_ClientNetworkManagers)
|
||||
{
|
||||
playerObjectId = clientPlayer.LocalClient.PlayerObject.NetworkObjectId;
|
||||
Assert.IsFalse(m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{m_ServerNetworkManager.LocalClientId} spawned player object for Client-{clientPlayer.LocalClientId}!");
|
||||
foreach (var client in m_ClientNetworkManagers)
|
||||
{
|
||||
if (clientPlayer == client)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{clientPlayer.LocalClientId}!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This test validates the player position and rotation is correct
|
||||
/// relative to the prefab's initial settings if no changes are applied.
|
||||
/// </summary>
|
||||
[TestFixture(HostOrServer.DAHost)]
|
||||
[TestFixture(HostOrServer.Host)]
|
||||
[TestFixture(HostOrServer.Server)]
|
||||
internal class PlayerSpawnPositionTests : IntegrationTestWithApproximation
|
||||
{
|
||||
protected override int NumberOfClients => 2;
|
||||
|
||||
public PlayerSpawnPositionTests(HostOrServer hostOrServer) : base(hostOrServer) { }
|
||||
|
||||
private Vector3 m_PlayerPosition;
|
||||
private Quaternion m_PlayerRotation;
|
||||
|
||||
protected override void OnCreatePlayerPrefab()
|
||||
{
|
||||
var playerNetworkObject = m_PlayerPrefab.GetComponent<NetworkObject>();
|
||||
m_PlayerPosition = GetRandomVector3(-10.0f, 10.0f);
|
||||
m_PlayerRotation = Quaternion.Euler(GetRandomVector3(-180.0f, 180.0f));
|
||||
playerNetworkObject.transform.position = m_PlayerPosition;
|
||||
playerNetworkObject.transform.rotation = m_PlayerRotation;
|
||||
base.OnCreatePlayerPrefab();
|
||||
}
|
||||
|
||||
private void PlayerTransformMatches(NetworkObject player)
|
||||
{
|
||||
var position = player.transform.position;
|
||||
var rotation = player.transform.rotation;
|
||||
Assert.True(Approximately(m_PlayerPosition, position), $"Client-{player.OwnerClientId} position {position} does not match the prefab position {m_PlayerPosition}!");
|
||||
Assert.True(Approximately(m_PlayerRotation, rotation), $"Client-{player.OwnerClientId} rotation {rotation.eulerAngles} does not match the prefab rotation {m_PlayerRotation.eulerAngles}!");
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator PlayerSpawnPosition()
|
||||
{
|
||||
if (m_ServerNetworkManager.IsHost)
|
||||
{
|
||||
PlayerTransformMatches(m_ServerNetworkManager.LocalClient.PlayerObject);
|
||||
|
||||
foreach (var client in m_ClientNetworkManagers)
|
||||
{
|
||||
yield return WaitForConditionOrTimeOut(() => client.SpawnManager.SpawnedObjects.ContainsKey(m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId));
|
||||
AssertOnTimeout($"Client-{client.LocalClientId} does not contain a player prefab instance for client-{m_ServerNetworkManager.LocalClientId}!");
|
||||
PlayerTransformMatches(client.SpawnManager.SpawnedObjects[m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var client in m_ClientNetworkManagers)
|
||||
{
|
||||
yield return WaitForConditionOrTimeOut(() => m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(client.LocalClient.PlayerObject.NetworkObjectId));
|
||||
AssertOnTimeout($"Client-{m_ServerNetworkManager.LocalClientId} does not contain a player prefab instance for client-{client.LocalClientId}!");
|
||||
PlayerTransformMatches(m_ServerNetworkManager.SpawnManager.SpawnedObjects[client.LocalClient.PlayerObject.NetworkObjectId]);
|
||||
foreach (var subClient in m_ClientNetworkManagers)
|
||||
{
|
||||
yield return WaitForConditionOrTimeOut(() => subClient.SpawnManager.SpawnedObjects.ContainsKey(client.LocalClient.PlayerObject.NetworkObjectId));
|
||||
AssertOnTimeout($"Client-{subClient.LocalClientId} does not contain a player prefab instance for client-{client.LocalClientId}!");
|
||||
PlayerTransformMatches(subClient.SpawnManager.SpawnedObjects[client.LocalClient.PlayerObject.NetworkObjectId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user