From 030b0771c6fc2ed3a619ec5d93b568769819fd07 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 9 Oct 2024 17:01:43 -0500 Subject: [PATCH 1/6] fix Provide an extended IContactEventHandlerWithInfo that allows users to prioritize which object is being collided with as well as being able to determine if the instance should return non-rigidbody contact events. --- .../RigidbodyContactEventManager.cs | 110 +++++++++++++++++- 1 file changed, 104 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index 9471a94fb5..c8eed07328 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -6,6 +6,12 @@ namespace Unity.Netcode.Components { + public struct ContactEventHandlerInfo + { + public bool ProvideNonRigidBodyContactEvents; + public bool HasContactEventPriority; + } + public interface IContactEventHandler { Rigidbody GetRigidbody(); @@ -13,6 +19,11 @@ public interface IContactEventHandler void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default); } + public interface IContactEventHandlerWithInfo : IContactEventHandler + { + ContactEventHandlerInfo GetContactEventHandlerInfo(); + } + [AddComponentMenu("Netcode/Rigidbody Contact Event Manager")] public class RigidbodyContactEventManager : MonoBehaviour { @@ -34,6 +45,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() { @@ -64,6 +76,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 +116,95 @@ 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(); + } + } + + 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) + { + if (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); } } } From abe3287ffaef1c83c86a5d8c5778857893f9242b Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 9 Oct 2024 20:42:09 -0500 Subject: [PATCH 2/6] update Adding changelog entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index a9af9e9072..06104a6a56 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. + - `ContactEventHandlerInfo.ProvideNonRigidBodyContactEvents`: When set to true, non-`Rigidbody` collisions with the registered `Rigidbody` will generate contact event notifications. + - `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. - 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) From 43fbc5fb0001b993f11304b4b0cb0746cd99a105 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Wed, 9 Oct 2024 20:55:09 -0500 Subject: [PATCH 3/6] update Adding associated PR to the entry --- com.unity.netcode.gameobjects/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 06104a6a56..791b8de14d 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,9 +10,9 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added -- Added `IContactEventHandlerWithInfo` that derives from `IContactEventHandler` that can be updated per frame to provide `ContactEventHandlerInfo` information to the `RigidbodyContactEventManager` when processing collisions. - - `ContactEventHandlerInfo.ProvideNonRigidBodyContactEvents`: When set to true, non-`Rigidbody` collisions with the registered `Rigidbody` will generate contact event notifications. - - `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. +- 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) From e8b8a455dc385d93d59ab45122e01d1bfdac70eb Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 10 Oct 2024 14:38:29 -0500 Subject: [PATCH 4/6] style Adding XML API documentation. --- .../RigidbodyContactEventManager.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index c8eed07328..9409b234e8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -6,24 +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 { @@ -61,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(); From 6d4b9d7cf534920ce12639b2453f8348c19cca7a Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 10 Oct 2024 18:35:14 -0500 Subject: [PATCH 5/6] fix Assuring the default non-info contact event handler sets priority to the non-kinematic bodies. --- .../RigidbodyContactEventManager.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index 9409b234e8..d0808d2886 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -179,6 +179,12 @@ private void ProcessCollisions() { 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; @@ -235,17 +241,14 @@ private void ProcessCollisions() } } - if (preferredContactHandler == null) + if (preferredContactHandler == null && otherContactHandler != null) { - if (otherContactHandler != null) - { - preferredContactHandler = otherContactHandler; - preferredContactHandlerNonRigidbody = otherContactHandlerNonRigidbody; - preferredRigidbody = otherRigidbody; - otherContactHandler = null; - otherContactHandlerNonRigidbody = false; - otherRigidbody = null; - } + preferredContactHandler = otherContactHandler; + preferredContactHandlerNonRigidbody = otherContactHandlerNonRigidbody; + preferredRigidbody = otherRigidbody; + otherContactHandler = null; + otherContactHandlerNonRigidbody = false; + otherRigidbody = null; } if (preferredContactHandler == null || (preferredContactHandler != null && otherContactHandler == null && !preferredContactHandlerNonRigidbody)) From 4ee539d457e3ef9e489880267f61c1d03cfe8fb6 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Thu, 10 Oct 2024 18:36:06 -0500 Subject: [PATCH 6/6] test Added validation test for RigidbodyContactEventManager --- .../Runtime/Physics/NetworkRigidbodyTest.cs | 383 ++++++++++++++++++ 1 file changed, 383 insertions(+) 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