diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index 2ed4484e52..32fea4c828 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -9,7 +9,10 @@ Additional documentation and release notes are available at [Multiplayer Documen
[Unreleased]
### Added
-
+
+- 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)
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs
index 9471a94fb5..d0808d2886 100644
--- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs
@@ -6,13 +6,70 @@
namespace Unity.Netcode.Components
{
+ ///
+ /// Information a returns to via
+ /// if the registers itself with as opposed to .
+ ///
+ public struct ContactEventHandlerInfo
+ {
+ ///
+ /// When set to true, the will include non-Rigidbody based contact events.
+ /// When the invokes the it will return null in place
+ /// of the collidingBody parameter if the contact event occurred with a collider that is not registered with the .
+ ///
+ public bool ProvideNonRigidBodyContactEvents;
+ ///
+ /// When set to true, the will prioritize invoking
+ /// if it is the 2nd colliding body in the contact pair being processed. With distributed authority, setting this value to true when a is owned by the local client
+ /// will assure is only invoked on the authoritative side.
+ ///
+ public bool HasContactEventPriority;
+ }
+
+ ///
+ /// Default implementation required to register a with a instance.
+ ///
+ ///
+ /// Recommended to implement this method on a component
+ ///
public interface IContactEventHandler
{
+ ///
+ /// Should return a .
+ ///
Rigidbody GetRigidbody();
+ ///
+ /// Invoked by the instance.
+ ///
+ /// A unique contact event identifier.
+ /// The average normal of the collision between two colliders.
+ /// If not null, this will be a registered that was part of the collision contact event.
+ /// The world space location of the contact event.
+ /// Will be set if this is a collision stay contact event (i.e. it is not the first contact event and continually has contact)
+ /// The average normal of the collision stay contact over time.
void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default);
}
+ ///
+ /// This is an extended version of and can be used to register a with a instance.
+ /// This provides additional information to the for each set of contact events it is processing.
+ ///
+ public interface IContactEventHandlerWithInfo : IContactEventHandler
+ {
+ ///
+ /// Invoked by for each set of contact events it is processing (prior to processing).
+ ///
+ ///
+ ContactEventHandlerInfo GetContactEventHandlerInfo();
+ }
+
+ ///
+ /// Add this component to an in-scene placed GameObject to provide faster collision event processing between instances and optionally static colliders.
+ ///
+ ///
+ ///
+ ///
[AddComponentMenu("Netcode/Rigidbody Contact Event Manager")]
public class RigidbodyContactEventManager : MonoBehaviour
{
@@ -34,6 +91,7 @@ private struct JobResultStruct
private readonly Dictionary m_RigidbodyMapping = new Dictionary();
private readonly Dictionary m_HandlerMapping = new Dictionary();
+ private readonly Dictionary m_HandlerInfo = new Dictionary();
private void OnEnable()
{
@@ -49,6 +107,15 @@ private void OnEnable()
Instance = this;
}
+ ///
+ /// Any implementation can register a to be handled by this instance.
+ ///
+ ///
+ /// You should enable for each associated with the being registered.
+ /// You can enable this during run time or within the editor's inspector view.
+ ///
+ /// or
+ /// true to register and false to remove from being registered
public void RegisterHandler(IContactEventHandler contactEventHandler, bool register = true)
{
var rigidbody = contactEventHandler.GetRigidbody();
@@ -64,6 +131,22 @@ public void RegisterHandler(IContactEventHandler contactEventHandler, bool regis
{
m_HandlerMapping.Add(instanceId, contactEventHandler);
}
+
+ if (!m_HandlerInfo.ContainsKey(instanceId))
+ {
+ var handlerInfo = new ContactEventHandlerInfo()
+ {
+ HasContactEventPriority = true,
+ ProvideNonRigidBodyContactEvents = false,
+ };
+ var handlerWithInfo = contactEventHandler as IContactEventHandlerWithInfo;
+
+ if (handlerWithInfo != null)
+ {
+ handlerInfo = handlerWithInfo.GetContactEventHandlerInfo();
+ }
+ m_HandlerInfo.Add(instanceId, handlerInfo);
+ }
}
else
{
@@ -88,25 +171,98 @@ private void OnDisable()
private void ProcessCollisions()
{
+ foreach (var contactEventHandler in m_HandlerMapping)
+ {
+ var handlerWithInfo = contactEventHandler.Value as IContactEventHandlerWithInfo;
+
+ if (handlerWithInfo != null)
+ {
+ m_HandlerInfo[contactEventHandler.Key] = handlerWithInfo.GetContactEventHandlerInfo();
+ }
+ else
+ {
+ var info = m_HandlerInfo[contactEventHandler.Key];
+ info.HasContactEventPriority = !m_RigidbodyMapping[contactEventHandler.Key].isKinematic;
+ m_HandlerInfo[contactEventHandler.Key] = info;
+ }
+ }
+
+ ContactEventHandlerInfo contactEventHandlerInfo0;
+ ContactEventHandlerInfo contactEventHandlerInfo1;
+
// Process all collisions
for (int i = 0; i < m_Count; i++)
{
var thisInstanceID = m_ResultsArray[i].ThisInstanceID;
var otherInstanceID = m_ResultsArray[i].OtherInstanceID;
- var rb0Valid = thisInstanceID != 0 && m_RigidbodyMapping.ContainsKey(thisInstanceID);
- var rb1Valid = otherInstanceID != 0 && m_RigidbodyMapping.ContainsKey(otherInstanceID);
- // Only notify registered rigid bodies.
- if (!rb0Valid || !rb1Valid || !m_HandlerMapping.ContainsKey(thisInstanceID))
+ var contactHandler0 = (IContactEventHandler)null;
+ var contactHandler1 = (IContactEventHandler)null;
+ var preferredContactHandler = (IContactEventHandler)null;
+ var preferredContactHandlerNonRigidbody = false;
+ var preferredRigidbody = (Rigidbody)null;
+ var otherContactHandler = (IContactEventHandler)null;
+ var otherRigidbody = (Rigidbody)null;
+
+ var otherContactHandlerNonRigidbody = false;
+
+ if (m_RigidbodyMapping.ContainsKey(thisInstanceID))
+ {
+ contactHandler0 = m_HandlerMapping[thisInstanceID];
+ contactEventHandlerInfo0 = m_HandlerInfo[thisInstanceID];
+ if (contactEventHandlerInfo0.HasContactEventPriority)
+ {
+ preferredContactHandler = contactHandler0;
+ preferredContactHandlerNonRigidbody = contactEventHandlerInfo0.ProvideNonRigidBodyContactEvents;
+ preferredRigidbody = m_RigidbodyMapping[thisInstanceID];
+ }
+ else
+ {
+ otherContactHandler = contactHandler0;
+ otherContactHandlerNonRigidbody = contactEventHandlerInfo0.ProvideNonRigidBodyContactEvents;
+ otherRigidbody = m_RigidbodyMapping[thisInstanceID];
+ }
+ }
+
+ if (m_RigidbodyMapping.ContainsKey(otherInstanceID))
+ {
+ contactHandler1 = m_HandlerMapping[otherInstanceID];
+ contactEventHandlerInfo1 = m_HandlerInfo[otherInstanceID];
+ if (contactEventHandlerInfo1.HasContactEventPriority && preferredContactHandler == null)
+ {
+ preferredContactHandler = contactHandler1;
+ preferredContactHandlerNonRigidbody = contactEventHandlerInfo1.ProvideNonRigidBodyContactEvents;
+ preferredRigidbody = m_RigidbodyMapping[otherInstanceID];
+ }
+ else
+ {
+ otherContactHandler = contactHandler1;
+ otherContactHandlerNonRigidbody = contactEventHandlerInfo1.ProvideNonRigidBodyContactEvents;
+ otherRigidbody = m_RigidbodyMapping[otherInstanceID];
+ }
+ }
+
+ if (preferredContactHandler == null && otherContactHandler != null)
+ {
+ preferredContactHandler = otherContactHandler;
+ preferredContactHandlerNonRigidbody = otherContactHandlerNonRigidbody;
+ preferredRigidbody = otherRigidbody;
+ otherContactHandler = null;
+ otherContactHandlerNonRigidbody = false;
+ otherRigidbody = null;
+ }
+
+ if (preferredContactHandler == null || (preferredContactHandler != null && otherContactHandler == null && !preferredContactHandlerNonRigidbody))
{
continue;
}
+
if (m_ResultsArray[i].HasCollisionStay)
{
- m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal);
+ preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal);
}
else
{
- m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint);
+ preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint);
}
}
}
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs
index 9ec32bc57f..601a1baccb 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs
@@ -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 @@ public IEnumerator TestRigidbodyKinematicEnableDisable()
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 m_ContactEvents = new List();
+
+ protected NetworkVariable m_EnableNonRigidbodyContacts = new NetworkVariable(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();
+
+ 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();
+ helper.AuthorityMode = NetworkTransform.AuthorityModes.Owner;
+ }
+ else
+ {
+ var helperWithInfo = m_PlayerPrefab.AddComponent();
+ helperWithInfo.AuthorityMode = NetworkTransform.AuthorityModes.Owner;
+ }
+
+ var rigidbody = m_PlayerPrefab.AddComponent();
+ rigidbody.useGravity = false;
+ rigidbody.isKinematic = true;
+ rigidbody.mass = 5.0f;
+ rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
+ var sphereCollider = m_PlayerPrefab.AddComponent();
+ sphereCollider.radius = 0.5f;
+ sphereCollider.providesContacts = true;
+
+ var networkRigidbody = m_PlayerPrefab.AddComponent();
+ networkRigidbody.UseRigidBodyForMotion = true;
+ networkRigidbody.AutoUpdateKinematicState = false;
+
+ m_RigidbodyContactEventManager = new GameObject();
+ m_RigidbodyContactEventManager.AddComponent();
+ }
+
+
+
+ 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() :
+ m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent();
+ var clientPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent() :
+ m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent();
+
+ // 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() :
+ m_ServerNetworkManager.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent();
+ otherPlayer.RegisterForContactEvents(false);
+ otherPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent() :
+ m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent();
+ 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